Android打开相机进行人脸识别,使用虹软人脸识别引擎

摘要:
最后一张效果图,画质很差,很好看。功能描述:人脸识别使用虹软的FreeSDK,包括人脸跟踪、人脸检测、人脸识别、年龄和性别检测。本演示仅使用FT和FR,并在FaceCameraHelper中封装了相机和面部跟踪和识别功能。

上一张效果图,渣画质,能看就好

Android打开相机进行人脸识别,使用虹软人脸识别引擎第1张


功能说明:

人脸识别使用的是虹软的FreeSDK,包含人脸追踪,人脸检测,人脸识别,年龄、性别检测功能,其中本demo只使用了FT和FR(人脸追踪和人脸识别),封装了开启相机和人脸追踪、识别功能在FaceCameraHelper中。

实现逻辑:

打开相机,监听预览数据回调进行人脸追踪,且为每个检测到的人脸都分配一个trackID(上下帧位置变化不大的人脸框可认为是同一个人脸,具体实现的逻辑可见代码),同时,为了人脸搜索,为每个trackID都分配一个状态(识别中,识别失败,识别通过)、姓名,识别通过则在人脸框上显示姓名,否则只显示trackID(本demo没配服务端,只做了模拟操作)。流程说明见下图。

Android打开相机进行人脸识别,使用虹软人脸识别引擎第2张


FaceCameraHelper包含的接口:

public interface FaceTrackListener {

/**
* 回传相机预览数据和人脸框位置
*
* @param nv21 相机预览数据
* @param ftFaceList 待处理的人脸列表
* @param trackIdList 人脸追踪ID列表
*/
void onPreviewData(byte[] nv21, List<AFT_FSDKFace> ftFaceList, List<Integer> trackIdList);


/**
* 当出现异常时执行
*
* @param e 异常信息
*/
void onFail(Exception e);


/**
* 当相机打开时执行
*
* @param camera 相机实例
*/
void onCameraOpened(Camera camera);

/**
* 根据自己的需要可以删除部分人脸,比如指定区域、留下最大人脸等
*
* @param ftFaceList 人脸列表
* @param trackIdList 人脸追踪ID列表
*/
void adjustFaceRectList(List<AFT_FSDKFace> ftFaceList, List<Integer> trackIdList);

/**
* 请求人脸特征后的回调
*
* @param frFace 人脸特征数据
* @param requestId 请求码
*/
void onFaceFeatureInfoGet(@Nullable AFR_FSDKFace frFace, Integer requestId);
}

```
FT人脸框绘制并回调数据:

  

@Override
public void onPreviewFrame(byte[] nv21, Camera camera) {
if (faceTrackListener != null) {
ftFaceList.clear();
int ftCode = ftEngine.AFT_FSDK_FaceFeatureDetect(nv21, previewSize.width, previewSize.height, AFT_FSDKEngine.CP_PAF_NV21, ftFaceList).getCode();
if (ftCode != 0) {
faceTrackListener.onFail(new Exception("ft failed,code is " + ftCode));
}
refreshTrackId(ftFaceList);
faceTrackListener.adjustFaceRectList(ftFaceList, currentTrackIdList);
if (surfaceViewRect != null) {
Canvas canvas = surfaceViewRect.getHolder().lockCanvas();
if (canvas == null) {
faceTrackListener.onFail(new Exception("can not get canvas of surfaceViewRect"));
return;
}
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
if (ftFaceList.size() > 0) {
for (int i = 0; i < ftFaceList.size(); i++) {
Rect adjustedRect = TrackUtil.adjustRect(new Rect(ftFaceList.get(i).getRect()), previewSize.width, previewSize.height, surfaceWidth, surfaceHeight, cameraOrientation, mCameraId);
TrackUtil.drawFaceRect(canvas, adjustedRect, faceRectColor, faceRectThickness, currentTrackIdList.get(i), nameMap.get(currentTrackIdList.get(i)));
}
}
surfaceViewRect.getHolder().unlockCanvasAndPost(canvas);
}

faceTrackListener.onPreviewData(nv21, ftFaceList, currentTrackIdList);
}
}

  


大多数设备相机预览数据图像的朝向在横屏时为0度。其他情况按逆时针依次增加90度,因此人脸框的绘制需要做同步转化。CameraID为0时,也就是后置摄像头情况,相机预览数据的显示为原画面,而CameraID为1时,也就是前置摄像头情况,相机的预览画面显示为镜像画面,适配的代码:

/**
     * @param rect          FT人脸框
     * @param previewWidth  相机预览的宽度
     * @param previewHeight 相机预览高度
     * @param canvasWidth   画布的宽度
     * @param canvasHeight  画布的高度
     * @param cameraOri     相机预览方向
     * @param mCameraId     相机ID
     * @return
     */
    static Rect adjustRect(Rect rect, int previewWidth, int previewHeight, int canvasWidth, int canvasHeight, int cameraOri, int mCameraId) {
        if (rect == null) {
            return null;
        }
        if (canvasWidth < canvasHeight) {
            int t = previewHeight;
            previewHeight = previewWidth;
            previewWidth = t;
        }
 
        float horizontalRatio;
        float verticalRatio;
        if (cameraOri == 0 || cameraOri == 180) {
            horizontalRatio = (float) canvasWidth / (float) previewWidth;
            verticalRatio = (float) canvasHeight / (float) previewHeight;
        } else {
            horizontalRatio = (float) canvasHeight / (float) previewHeight;
            verticalRatio = (float) canvasWidth / (float) previewWidth;
        }
        rect.left *= horizontalRatio;
        rect.right *= horizontalRatio;
        rect.top *= verticalRatio;
        rect.bottom *= verticalRatio;
 
        Rect newRect = new Rect();
 
        switch (cameraOri) {
            case 0:
                if (mCameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) {
                    newRect.left = canvasWidth - rect.right;
                    newRect.right = canvasWidth - rect.left;
                } else {
                    newRect.left = rect.left;
                    newRect.right = rect.right;
                }
                newRect.top = rect.top;
                newRect.bottom = rect.bottom;
                break;
            case 90:
                newRect.right = canvasWidth - rect.top;
                newRect.left = canvasWidth - rect.bottom;
                if (mCameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) {
                    newRect.top = canvasHeight - rect.right;
                    newRect.bottom = canvasHeight - rect.left;
                } else {
                    newRect.top = rect.left;
                    newRect.bottom = rect.right;
                }
                break;
            case 180:
                newRect.top = canvasHeight - rect.bottom;
                newRect.bottom = canvasHeight - rect.top;
                if (mCameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) {
                    newRect.left = rect.left;
                    newRect.right = rect.right;
                } else {
                    newRect.left = canvasWidth - rect.right;
                    newRect.right = canvasWidth - rect.left;
                }
                break;
            case 270:
                newRect.left = rect.top;
                newRect.right = rect.bottom;
                if (mCameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) {
                    newRect.top = rect.left;
                    newRect.bottom = rect.right;
                } else {
                    newRect.top = canvasHeight - rect.right;
                    newRect.bottom = canvasHeight - rect.left;
                }
                break;
            default:
                break;
        }
        return newRect;
    }

  


由于FR引擎不支持多线程调用,因此只能串行执行,若需要更高效的实现,可创建多个FREngine实例进行任务分配。

FR线程队列:

private LinkedBlockingQueue<FaceRecognizeRunnable> faceRecognizeRunnables = new LinkedBlockingQueue<FaceRecognizeRunnable>(MAX_FRTHREAD_COUNT);

  

FR线程:

public class FaceRecognizeRunnable implements Runnable {
private Rect faceRect;
private int width;
private int height;
private int format;
private int ori;
private Integer requestId;
private byte[]nv21Data;
public FaceRecognizeRunnable(byte[]nv21Data,Rect faceRect, int width, int height, int format, int ori, Integer requestId) {
if (nv21Data==null) {
return;
}
this.nv21Data = new byte[nv21Data.length];
System.arraycopy(nv21Data,0,this.nv21Data,0,nv21Data.length);
this.faceRect = new Rect(faceRect);
this.width = width;
this.height = height;
this.format = format;
this.ori = ori;
this.requestId = requestId;
}

@Override
public void run() {
if (faceTrackListener!=null && nv21Data!=null) {
if (frEngine != null) {
AFR_FSDKFace frFace = new AFR_FSDKFace();
int frCode = frEngine.AFR_FSDK_ExtractFRFeature(nv21Data, width, height, format, faceRect, ori, frFace).getCode();
if (frCode == 0) {
faceTrackListener.onFaceFeatureInfoGet(frFace, requestId);
} else {
faceTrackListener.onFaceFeatureInfoGet(null, requestId);
faceTrackListener.onFail(new Exception("fr failed errorCode is " + frCode));
}
nv21Data = null;
}else {
faceTrackListener.onFaceFeatureInfoGet(null, requestId);
faceTrackListener.onFail(new Exception("fr failed ,frEngine is null" ));
}
if (faceRecognizeRunnables.size()>0){
executor.execute(faceRecognizeRunnables.poll());
}
}
}
}

  


上下帧是否为相同人脸的判断(trackID刷新):

/**
* 刷新trackId
*
* @param ftFaceList 传入的人脸列表
*/
public void refreshTrackId(List<AFT_FSDKFace> ftFaceList) {
currentTrackIdList.clear();
//每项预先填充-1
for (int i = 0; i < ftFaceList.size(); i++) {
currentTrackIdList.add(-1);
}
//前一次无人脸现在有人脸,填充新增TrackId
if (formerTrackIdList.size() == 0) {
for (int i = 0; i < ftFaceList.size(); i++) {
currentTrackIdList.set(i, ++currentTrackId);
}
} else {
//前后都有人脸,对于每一个人脸框
for (int i = 0; i < ftFaceList.size(); i++) {
//遍历上一次人脸框
for (int j = 0; j < formerFaceRectList.size(); j++) {
//若是同一张人脸
if (TrackUtil.isSameFace(SIMILARITY_RECT, formerFaceRectList.get(j), ftFaceList.get(i).getRect())) {
//记录ID
currentTrackIdList.set(i, formerTrackIdList.get(j));
break;
}
}
}
}
//上一次人脸框不存在此人脸
for (int i = 0; i < currentTrackIdList.size(); i++) {
if (currentTrackIdList.get(i) == -1) {
currentTrackIdList.set(i, ++currentTrackId);
}
}
formerTrackIdList.clear();
formerFaceRectList.clear();
for (int i = 0; i < ftFaceList.size(); i++) {
formerFaceRectList.add(new Rect(ftFaceList.get(i).getRect()));
formerTrackIdList.add(currentTrackIdList.get(i));
}
}

  

项目地址:https://github.com/wangshengyang1996/FaceTrackDemo

若有不当的地方望指出。

免责声明:文章转载自《Android打开相机进行人脸识别,使用虹软人脸识别引擎》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇chrome表单自动填充导致input文本框背景变成偏黄色问题解决高德地图实现地址检索获取结果列表和坐标下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

SVG动画

动画原理 SVG动画,就是元素的属性值关于时间的变化。 如下图来说,元素的某个属性值的起始值(from)到结束值(to)在一个时间段(duration)根据时间函数(timing-function)计算出每一帧(frame)的插值(interpolation)作为变换的行为。 PS:SVG动画是帧动画,在SVG里也就是每秒设置多少个value值。 SVG...

svg.js教程及使用手册详解(一)

做毕设的时候,因为要使用到画图和自定义动画,所以接触到了SVG。网上关于SVG和Canvas的对比很多,具体的辨析这里就不赘言。网上关于SVG的所谓教程基本上都是SVG本身,但是却没有一个针对svg.js这样的集成的SVG库进行具体讲解的教程,做这件事的,本文应该是第一篇,也是给那些需要动态的使用SVG的兄弟们一点帮助。 简介: SVG.js是一个轻量级的...

使用 AFNetworking 进行 XML 和 JSON 数据请求

(1)XML 数据请求 使用 AFNetworking 中的 AFHTTPRequestOperation 和 AFXMLParserResponseSerializer,另外结合第三方框架 XMLDictionary 进行数据转换 使用 XMLDictionary 的好处:有效避免自行实现 NSXMLParserDelegate 委托代理协议方法来进行繁...

android应用程序中获取view的位置

android应用程序中获取view的位置_雨枫技术教程网  我们重点在获取view的y坐标,你懂的... 依次介绍以下四个方法:   1.getLocationInWindow   int[] position = new int[2];  textview.getLocationInWindow(position);  System.out.pr...

cocos creator基础-cc.Node(三)坐标空间转换

cc.Vec2 1: cc.Vec2 二维向量坐标, 表结构{x: 120, y: 120}; cc.v2(x, y) 创建一个二维向量 cc.p() 创建一个二维向量(这个2.0被废弃了,用cc.v2替代) 2: cc.pSub: 向量相减 cc.pSub(v1, v2) 被废弃,用v1.sub(v2)替代 3: cc.pAdd: 向量相加; cc....

Unity3d—GUI能量条

1、打开Unity编辑器。2、在脚本文件夹中添加C#脚本,我的是添加了skill_01这个脚本。(要自己设置文件夹,方便管理,不然文件添乱不方便管理) 3、注意,脚本的名字一旦确定就不要去改动,因为一个脚本的名字就是一个类名,改动会容易出现程序的不正确。4、编写对应的脚本代码1 usingSystem.Collections; 2 usingSystem....