一、前言
人工智能时代的到来,相信大家已耳濡目染,虹软免费、离线开放的人脸识别 SDK,正推动着全行业进入刷脸时代,为了方便开发者接入,虹软提供了多种语言,多种平台的人脸识别SDK的支持,使用场景广泛。产品主要功能有:人脸检测、追踪、特征提取、特征比对、属性检测,活体检测,图像质量检测等,此外,虹软提供的是基于本地算法特征的离线识别SDK,提供全平台的离线支持。
现如今,人脸查找及跟踪这例Demo非常火,之前我的大学室友也曾用python调opencv库函数来实现过类似的功能,包括很多比赛,也会在此基础上构造赛题,而虹软也正是提供了这方面的技术支持。因此作为初学者的我,也想尝试基于虹软的SDK来写个人脸查找及跟踪的样例,并写此文章进行记录,向广大初学开发者作分享。
此Demo采用Maven作为项目管理工具,并基于Windows x64,Java 8,SDK是基于虹软人脸识别SDK3.0
二、项目结构
SDK依赖Jar包 可从虹软官网获取 点击“免费获取” ”登录“后 选择 “具体平台/版本/语言”进行获取
三、项目依赖
pom.xml 依赖包括
- Javacv,Lombok,Guava,Apache Common系列工具类,Logback日志相关依赖
- 虹软人脸识别SDK依赖Jar包
<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>
四、项目流程
五、效果展示
六、核心代码说明
1.FaceEngineFactory类 源码说明
此类继承BasePooledObjectFactory
抽象类,为FaceEngine
对象池
1)成员变量说明
//人脸识别引擎库路径
private String libPath;
//SDK的APP_ID
private String appId;
//SDK的SDK_KEY
private String sdkKey;
//引擎配置类
private EngineConfiguration engineConfiguration;
其中 人脸识别引擎库,APP_ID,SDK_KEY 可通过虹软官网”开发者中心“ 进行 “登录”后 在“我的应用“中进行获取
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
引擎,随后卸载引擎
2.FaceRecognize类 源码说明
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);
}
}
- 参数说明:人脸识别引擎库路径,APP_ID,SDK_KEY
- 返回结果:无
- 代码流程解读:
此方法,根据传入的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,最后释放引擎。
3.VideoPlayer类 源码说明
此类是视频播放类
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毫秒执行一次。
4.MainApplication类 源码说明
此类为包含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源码
了解更多人脸识别产品相关内容请到虹软视觉开放平台哦
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。