人工智能时代的到来,相信你们已耳濡目染,虹软免费、离线开放的人脸识别 SDK,正推进着全行业进入刷脸时代,为了方便开发者接入,虹软提供了多种语言,多种平台的人脸识别SDK的支持,使用场景普遍。产品主要功能有:人脸检测、追踪、特征提取、特征比对、属性检测,活体检测,图像质量检测等,此外,虹软提供的是基于本地算法特征的离线识别SDK,提供全平台的离线支持。html
现现在,人脸查找及跟踪这例Demo很是火,以前个人大学室友也曾用python调opencv库函数来实现过相似的功能,包括不少比赛,也会在此基础上构造赛题,而虹软也正是提供了这方面的技术支持。所以做为初学者的我,也想尝试基于虹软的SDK来写我的脸查找及跟踪的样例,并写此文章进行记录,向广大初学开发者做分享。java
此Demo采用Maven做为项目管理工具,并基于Windows x64,Java 8,SDK是基于虹软人脸识别SDK3.0python
SDK依赖Jar包 可从虹软官网获取 点击“免费获取” ”登陆“后 选择 “具体平台/版本/语言”进行获取git
pom.xml 依赖包括github
<dependencies> <dependency> <groupId>org.bytedeco</groupId> <artifactId>javacv-platform</artifactId> <version>1.5.2</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.8</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-access</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>com.arcsoft.face</groupId> <artifactId>arcsoft-sdk-face</artifactId> <version>1.0.0</version> <scope>system</scope> <systemPath>${basedir}/libs/arcsoft-sdk-face-3.0.0.0.jar</systemPath> </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>26.0-jre</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.8.1</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.6.0</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.6</version> </dependency> </dependencies>
此类继承BasePooledObjectFactory
抽象类,为FaceEngine
对象池算法
1)成员变量说明apache
//人脸识别引擎库路径 private String libPath; //SDK的APP_ID private String appId; //SDK的SDK_KEY private String sdkKey; //引擎配置类 private EngineConfiguration engineConfiguration;
其中 人脸识别引擎库,APP_ID,SDK_KEY 可经过虹软官网”开发者中心“ 进行 “登陆”后 在“个人应用“中进行获取canvas
2)create()方法数组
public FaceEngine create() throws Exception { FaceEngine faceEngine = new FaceEngine(libPath); int activeCode = faceEngine.activeOnline(appId, sdkKey); log.info("faceEngineActiveCode:" + activeCode); int initCode = faceEngine.init(engineConfiguration); log.info("faceEngineInitCode:" + initCode); return faceEngine; }
FaceEngine
人脸识别引擎此方法,经过libPath(SDK引擎库的路径)实例化FaceEngine
对象,再根据APP_ID,SDK_KEY调用activeOnline()
方法激活引擎(联网状态下)安全
成功激活引擎后,根据EngineConfiguration
引擎配置类 调用init()
方法初始化引擎
3)wrap()方法
public PooledObject<FaceEngine> wrap(FaceEngine faceEngine) { return new DefaultPooledObject<>(faceEngine); }
FaceEngine
人脸识别引擎PooledObject
包装类此方法,经过PooledObject
包装器对象 将faceEngine
进行包装,便于维护引擎的状态
4)destroyObject()方法
public void destroyObject(PooledObject<FaceEngine> p) throws Exception { FaceEngine faceEngine = p.getObject(); int result = faceEngine.unInit(); super.destroyObject(p); }
PooledObject
包装类此方法,从PooledObject
包装器对象中获取faceEngine
引擎,随后卸载引擎
1)成员变量说明
//VIDEO模式人脸检测引擎,用于预览帧人脸追踪 private FaceEngine ftEngine; //人脸注册引擎 private FaceEngine regEngine; //用于人脸识别的引擎池 private GenericObjectPool<FaceEngine> frEnginePool; //存放视频中识别到的人脸信息(faceId为key,FaceResult为Value) (FaceId用来标记一张人脸,从进入画面到离开画面这个值不变,可使用FaceId判断用户) //ConcurrentHashMap:在多线程运行状况下,增/删faceResultRegistry中的键值对时,保证其线程安全 //volatile关键字:在多线程运行状况下,增/删faceResultRegistry中的键值对后,保证其线程之间的可见性 private volatile ConcurrentHashMap<Integer, FaceResult> faceResultRegistry = new ConcurrentHashMap<>(); //线程池 private ExecutorService frService = Executors.newFixedThreadPool(20); //存放 注册照与注册照人脸特征值 的映射 public ConcurrentHashMap<String, byte[]> faceFeatureRegistry = new ConcurrentHashMap<>(); //记录上次清理过期人脸时间 private long lastClearTime = System.currentTimeMillis(); //封装 视频中检测到的人脸与注册照人脸 比对结果 @Data public class FaceResult { private boolean flag = false; private String name; private float score; } //封装 视频中检测到的人脸信息 @Data public class FacePreviewInfo { private FaceInfo faceInfo; private int age; private boolean liveness; }
2)init()方法
public void initEngine(String libPath,String appId,String sdkKey) { //引擎配置 ftEngine = new FaceEngine(libPath); int activeCode = ftEngine.activeOnline(appId, sdkKey); EngineConfiguration ftEngineCfg = new EngineConfiguration(); ftEngineCfg.setDetectMode(DetectMode.ASF_DETECT_MODE_VIDEO); ftEngineCfg.setFunctionConfiguration(FunctionConfiguration.builder().supportFaceDetect(true).build()); int ftInitCode = ftEngine.init(ftEngineCfg); //引擎配置 regEngine = new FaceEngine(libPath); EngineConfiguration regEngineCfg = new EngineConfiguration(); regEngineCfg.setDetectMode(DetectMode.ASF_DETECT_MODE_IMAGE); regEngineCfg.setFunctionConfiguration(FunctionConfiguration.builder().supportFaceDetect(true).supportFaceRecognition(true).build()); int regInitCode = regEngine.init(regEngineCfg); GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig(); poolConfig.setMaxIdle(5); poolConfig.setMaxTotal(5); poolConfig.setMinIdle(5); poolConfig.setLifo(false); EngineConfiguration frEngineCfg = new EngineConfiguration(); frEngineCfg.setFunctionConfiguration(FunctionConfiguration.builder().supportFaceRecognition(true).build()); frEnginePool = new GenericObjectPool(new FaceEngineFactory(libPath, appId, sdkKey, frEngineCfg), poolConfig);//底层库算法对象池 if (!(activeCode == ErrorInfo.MOK.getValue() || activeCode == ErrorInfo.MERR_ASF_ALREADY_ACTIVATED.getValue())) { log.error("activeCode: " + activeCode); throw new RuntimeException("activeCode: " + activeCode); } if (ftInitCode != ErrorInfo.MOK.getValue()) { log.error("ftInitEngine: " + ftInitCode); throw new RuntimeException("ftInitEngine: " + ftInitCode); } if (regInitCode != ErrorInfo.MOK.getValue()) { log.error("regInitEngine: " + regInitCode); throw new RuntimeException("regInitEngine: " + regInitCode); } }
此方法,根据传入的libPath,APP_ID,SDK_KEY去初始化用于人脸检测跟踪引擎(VIDEO模式,开启人脸检测功能) 以及用于人脸注册的引擎(IMAGE模式,开启人脸识别功能),而后再去实例化人脸识别引擎池,设置引擎池对应属性后,实例化EngineConfiguration
对象(开启人脸识别功能),最后经过FaceEngineFactory
的构造方法去初始化引擎并获取对象池。
3)registerFace()方法 注册人脸
public void registerFace(String imagePath) { log.info("正在注册人脸"); int count = 0; if (regEngine != null) { File file = new File(imagePath); File[] files = file.listFiles(); for (File file1 : files) { ImageInfo imageInfo = ImageFactory.getRGBData(file1); if (imageInfo != null) { List<FaceInfo> faceInfoList = new ArrayList<>(); int code = regEngine.detectFaces(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList); if (code == 0 && faceInfoList.size() > 0) { FaceFeature faceFeature = new FaceFeature(); int resCode = regEngine.extractFaceFeature(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(),imageInfo.getImageFormat(), faceInfoList.get(0), faceFeature); if (resCode == 0) { int lastIndexOf = file1.getName().lastIndexOf("."); String name = file1.getName().substring(0, lastIndexOf); faceFeatureRegistry.put(name, faceFeature.getFeatureData()); log.info("成功注册人脸:" + name); count++; } } } } log.info("人脸注册完成,共注册:" + count + "张人脸"); } else { throw new RuntimeException("注册失败,引擎未初始化或初始化失败"); } }
此方法,将参数目录下的每一个文件解析为ImageInfo
类型的RGB图像信息数据,再调用FaceEngineService
对象的detectFaces()
方法检测并获取人脸信息数据(其所需参数有 图像数据 ,图像宽度(4的倍数),图片高度,图像的颜色格式,存放检测到的人脸信息List)。成功检测到人脸后,再经过extractFaceFeature()
方法提取人脸特征值(其所需参数有图像数据,图像宽度(4的倍数),图像高度,图像的颜色格式,人脸信息,存放提取到的人脸特征信息)。成功获取到人脸特征值后,将图片文件名和人脸特征值以key-value的形式存放于ConcurrentHashMap
中。
4)detectFaces()方法 人脸检测
public List<FacePreviewInfo> detectFaces(ImageInfo imageInfo) { if (ftEngine != null) { List<FaceInfo> faceInfoList = new ArrayList<>(); int code = ftEngine.detectFaces(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList); List<FacePreviewInfo> previewInfoList = new LinkedList<>(); for (FaceInfo faceInfo : faceInfoList) { FacePreviewInfo facePreviewInfo = new FacePreviewInfo(); facePreviewInfo.setFaceInfo(faceInfo); previewInfoList.add(facePreviewInfo); } clearFaceResultRegistry(faceInfoList); return previewInfoList; } return null; }
ImageInfo
图像信息FacePreviewInfo
列表信息此方法,根据传入的ImageInfo
类型的RGB图像信息数据,调用ftEngine引擎的detectFaces()
方法 获取人脸信息,遍历获取到的人脸信息列表设置于FacePreviewInfo
类型对象中,随后将faceInfoList
列表 传入clearFaceResultRegistry()
方法,清理过期的人脸,并返回FacePreviewInfo
列表。
5)clearFaceResultRegistry()方法 清理过期人脸
private void clearFaceResultRegistry(List<FaceInfo> faceInfoList) { if (System.currentTimeMillis() - lastClearTime > 5000) { Iterator<Integer> iterator = faceResultRegistry.keySet().iterator(); for (; iterator.hasNext(); ) { Integer next = iterator.next(); boolean flag = false; for (FaceInfo faceInfo : faceInfoList) { if (next.equals(faceInfo.getFaceId())) { flag = true; } } if (!flag) { iterator.remove(); } } } }
FacePreviewInfo
列表信息此方法,若当前时间距离上次清理过期人脸已有5s(用户可根据须要自行设置),则遍历faceResultRegistry
的key,判断faceResultRegistry
与faceInfoList
(即以前识别到的与新识别到的人脸)是否存在相同FaceId
的人脸,是,则删除faceResultRegistry
中此过期的人脸信息。
6)getFaceResult() 方法
public FaceResult getFaceResult(FaceInfo faceInfo, ImageInfo imageInfo) { FaceResult faceResult = faceResultRegistry.get(faceInfo.getFaceId()); if (faceResult == null) { faceResult = new FaceResult(); faceResultRegistry.put(faceInfo.getFaceId(), faceResult); frService.submit(new FaceInfoRunnable(faceInfo, imageInfo, faceResult)); } else if (faceResult.isFlag()) { return faceResult; } return null; }
ImageInfo
图像信息FaceResult
结果此方法,先尝试根据faceId
从faceResultRegistry
中获取 FaceResult
(即以前是否比对过相同人脸),若不存在则实例化一个faceResult
并将其以faceId
为Key,faceResult
为value 存放到faceResultRegistry
中,同时新建一个FaceInfoRunnable
线程并将faceInfo
, imageInfo
, faceResult
三者传入线程中 运行;若存在,则判断 faceResult
的flag是否为true(便是否可从注册照找到类似人脸), 若为true 直接返回便可 。
7)FaceInfoRunnable
此类为一个实现 Runnable 接口的线程实现类
成员变量说明
//传入 视频中识别到的人脸信息 private FaceInfo faceInfo; //传入 ImageInfo图像信息 private ImageInfo imageInfo; //人脸比对结果封装 private FaceResult faceResult;
run()方法
@Override public void run() { FaceEngine frEngine = null; try { frEngine = frEnginePool.borrowObject(); if (frEngine != null) { FaceFeature faceFeature = new FaceFeature(); int resCode = frEngine.extractFaceFeature(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfo, faceFeature); if (resCode == 0) { float score = 0.0F; Iterator<Map.Entry<String, byte[]>> iterator = faceFeatureRegistry.entrySet().iterator(); for (; iterator.hasNext(); ) { Map.Entry<String, byte[]> next = iterator.next(); FaceFeature faceFeatureTarget = new FaceFeature(); faceFeatureTarget.setFeatureData(next.getValue()); FaceSimilar faceSimilar = new FaceSimilar(); frEngine.compareFaceFeature(faceFeatureTarget, faceFeature, faceSimilar); if (faceSimilar.getScore() > score) { score = faceSimilar.getScore(); faceResult.setName(next.getKey()); } } log.info("类似度:" + score); if (score >= 0.8f) { faceResult.setScore(score); faceResult.setFlag(true); faceResultRegistry.put(faceInfo.getFaceId(), faceResult); } else { faceResultRegistry.remove(faceInfo.getFaceId()); } } } } catch (Exception e) { } finally { if (frEngine != null) { frEnginePool.returnObject(frEngine); } } }
run()
方法,根据成员变量FaceInfo
,ImageInfo
调用frEngine的extractFaceFeature()
方法获取人脸特征值。成功获取特征值后,遍历faceFeatureRegistry
(注册照人脸)中的特征值,结合刚获取到的特征值经过compareFaceFeature()
方法比对 俩人脸类似度(其所需参数有人脸特征值1,人脸特征值2,比对模型,存放比对类似值结果),并以类似度最高的注册照命名faceResult的Name
,最终,若类似度大于等于0.8(用户可根据须要自行设置) 则将类似度值设置于faceResult
对象并将其flag设为true(即注册照中找到类似人脸),并以faceId
为key 再次put到faceResultRegistry
中,不然remove此faceId
的faceResul
t,最后释放引擎。
此类是视频播放类
1)成员变量说明
//视频帧抓取器 private FFmpegFrameGrabber fFmpegFrameGrabber; //视频播放监听器 private VideoListener videoListener; //管理定时任务(true表示其关联的线程设为守护线程) private Timer timer = new Timer(true);
2)start()方法
public void start() { try { fFmpegFrameGrabber.setPixelFormat(AV_PIX_FMT_BGR24 ); fFmpegFrameGrabber.start(); videoListener.onStart(); } catch (FrameGrabber.Exception e) { videoListener.onError(e); } final int[] lengthInVideoFrames = {fFmpegFrameGrabber.getLengthInVideoFrames()}; OpenCVFrameConverter.ToIplImage converter = new OpenCVFrameConverter.ToIplImage();//转换器 TimerTask task = new TimerTask() { @Override public void run() { try { Frame grab = fFmpegFrameGrabber.grabImage(); lengthInVideoFrames[0]--; if (grab != null) { IplImage iplImage = converter.convert(grab); if (iplImage != null) { videoListener.onPreview(iplImage); } } if (lengthInVideoFrames[0] <= 0) { stop(); } } catch (Exception e) { videoListener.onError(e); } } }; timer.schedule(task, 0, 40); }
此方法,用于开始播放视频,首先为帧抓取器设置 要转换成的图像数据格式,随后启动帧抓取器和视频播放监听器。lengthInVideoFrames
数组中存放视频的帧数,而converter
变量为帧与图片之间的转换器。经过TimerTask
生成一个线程,在线程run()
方法中去抓取视频中的每一帧并将其转换为图像,获取到的图像交给videoListener
的onPreview()
回调方法进行处理,若帧数已处理完则中止运行。另外,TimerTask
线程将由timer
进行管理,每40毫秒执行一次。
此类为包含main()
方法的主类,右击 执行 Run ‘MainApplication.main()’ 便可运行此Demo
main() 主方法
//虹软引擎库存放路径 String libPath = "D:\\arcsoft_lib"; //sdk的APP_ID String appId = "9iSfMeAhjA52N**************iW1aKes2TpSrd"; //sdk的SDK_KEY String sdkKey = "BuRTH3hGs91m**************dxEgyP9xu6fiFG7G"; //视频文件路径(从视频中查找并跟踪人脸) String videoPath="D:\icon-man.mp4"; //须要识别人的注册照目录路径 String imagePath="D:\\photo";
Loader.load(opencv_imgproc.class); Loader.load(CvPoint.class); Loader.load(CvFont.class); CanvasFrame canvas = new CanvasFrame("预览"); canvas.setDefaultCloseOperation(EXIT_ON_CLOSE); VideoPlayer videoPlayer = new VideoPlayer(videoPath);
首先,加载opencv_imgproc
,CvPoint
,CvFont
等程序所需类,并实例化预览窗口(设置程序退出即窗口关闭)
同时向VideoPlayer
的构造方法传入视频路径(为FFmpegFrameGrabber
成员变量 指定具体视频) 实例化VideoPlayer
FaceRecognize faceRecognize = new FaceRecognize(); faceRecognize.initEngine(libPath,appId,sdkKey); faceRecognize.registerFace(imagePath); OpenCVFrameConverter.ToIplImage converter = new OpenCVFrameConverter.ToIplImage();
以后,实例化FaceRecognize
并根据LibPath,APP_ID,SDK_KEY调用initEngine()
方法初始化FaceRecognize
各引擎,同时实例化转换器
videoPlayer.setListener(new VideoListener() { CvScalar color = cvScalar(0, 0, 255, 0); // blue [green] [red] CvFont cvFont = cvFont(opencv_imgproc.FONT_HERSHEY_DUPLEX); @Override public void onStart() { } @Override public void onPreview(IplImage iplImage) { ImageInfo imageInfo = new ImageInfo(); imageInfo.setWidth(iplImage.width()); imageInfo.setHeight(iplImage.height()); imageInfo.setImageFormat(ImageFormat.CP_PAF_BGR24); byte[] imageData = new byte[iplImage.imageSize()]; iplImage.imageData().get(imageData); imageInfo.setImageData(imageData); List<FaceRecognize.FacePreviewInfo> previewInfoList = faceRecognize.detectFaces(imageInfo); for (FaceRecognize.FacePreviewInfo facePreviewInfo : previewInfoList) { int x = facePreviewInfo.getFaceInfo().getRect().getLeft(); int y = facePreviewInfo.getFaceInfo().getRect().getTop(); int xMax = facePreviewInfo.getFaceInfo().getRect().getRight(); int yMax = facePreviewInfo.getFaceInfo().getRect().getBottom(); CvPoint pt1 = cvPoint(x, y); CvPoint pt2 = cvPoint(xMax, yMax); opencv_imgproc.cvRectangle(iplImage, pt1, pt2, color, 1, 4, 0); FaceRecognize.FaceResult faceResult = faceRecognize.getFaceResult(facePreviewInfo.getFaceInfo(), imageInfo); if (faceResult != null) { try { CvPoint pt3 = cvPoint(x, y - 2); opencv_imgproc.cvPutText(iplImage, faceResult.getName(), pt3, cvFont, color); } catch (Exception e) { e.printStackTrace(); } } } Frame frame = converter.convert(iplImage); canvas.showImage(frame); } @Override public void onCancel() { } @Override public void onError(Exception e) { } }); videoPlayer.start();
上述代码 ,首先为VideoPlayer
设置监听器,再启动VideoPlayer
。而监听器VideoListener
的onPreview()
方法中,先将传入的IplImage
类型图像信息数据(即由帧抓取器获取到的图片)设置到ImageInfo类型对象中,以后调用faceRecognize
的detectFaces()
方法获取人脸信息。成功获取到人脸后,根据人脸信息肯定人脸方位坐标,调用opencv_imgproc绘制方形矩阵。再根据识别到的人脸信息和图像信息数据 调用faceRecognize
的getFaceResult()
方法获取FaceResult
(即注册照中是否拥有与视频中类似的人脸)。若FaceResult
不为空,则调用 opencv_imgproc
将对应图片文件名写于人脸框上方,最后将图片转化为视频帧做用于canvas上进行展现。
如有想一块儿学习虹软SDK,感觉人脸识别奥秘的同窗,可经过点击此连接获取Demo源码
了解更多人脸识别产品相关内容请到虹软视觉开放平台哦