android 人脸识别边框_虹软人脸识别 - Android Camera实时人脸追踪画框适配
在使用虹軟人臉識別Android SDK的過程中 ,預(yù)覽時一般都需要繪制人臉框,但是和PC平臺相機應(yīng)用不同,在Android平臺相機進行應(yīng)用開發(fā)還需要考慮前后置相機切換、設(shè)備橫豎屏切換等情況,因此在人臉識別項目開發(fā)過程中,人臉框繪制適配的實現(xiàn)比較困難。針對該問題,本文將通過以下內(nèi)容介紹解決方法:
相機原始幀數(shù)據(jù)和預(yù)覽成像畫面的關(guān)系
人臉框繪制到View上的流程
具體場景適配方案介紹
處理多種場景的情況,實現(xiàn)適配函數(shù)
將適配好的人臉框繪制到View上
以下用到的Rect說明:
變量名
含義
originalRect
人臉檢測回傳的人臉框
scaledRect
基于originalRect縮放后的人臉框
drawRect
最終繪制所需的人臉框
一、相機原始幀數(shù)據(jù)和預(yù)覽成像畫面的關(guān)系
Android設(shè)備一般為手持設(shè)備,相機集成在設(shè)備上,設(shè)備的旋轉(zhuǎn)也會導(dǎo)致相機的旋轉(zhuǎn),因此成像也會發(fā)生旋轉(zhuǎn),為了解決這一問題,讓用戶能夠看到正常的成像,Android提供了相機預(yù)覽數(shù)據(jù)繪制到控件時,設(shè)置旋轉(zhuǎn)角度的相關(guān)API,開發(fā)者可根據(jù)Activity的顯示方向設(shè)置不同的旋轉(zhuǎn)角度,這塊內(nèi)容在以下文章中有介紹:
Android使用Camera2獲取預(yù)覽數(shù)據(jù)
將預(yù)覽的YUV數(shù)據(jù)轉(zhuǎn)換為NV21,再轉(zhuǎn)換為Bitmap并顯示到控件上,同時也將該Bitmap轉(zhuǎn)換為相機預(yù)覽效果的Bitmap顯示到控件上,便于了解原始數(shù)據(jù)和預(yù)覽畫面的關(guān)系
二、人臉框繪制到View上的流程
總體流程
第一步,縮放
第二步,旋轉(zhuǎn)
需要根據(jù)圖像數(shù)據(jù)和預(yù)覽畫面的旋轉(zhuǎn)角度關(guān)系,選擇對應(yīng)的旋轉(zhuǎn)方案
后置攝像頭(預(yù)覽不鏡像)
后置攝像頭,旋轉(zhuǎn)0度
后置攝像頭,旋轉(zhuǎn)90度
后置攝像頭,旋轉(zhuǎn)180度
后置攝像頭,旋轉(zhuǎn)270度
前置攝像頭(預(yù)覽會鏡像)
前置攝像頭,旋轉(zhuǎn)0度
前置攝像頭,旋轉(zhuǎn)90度
前置攝像頭,旋轉(zhuǎn)180度
前置攝像頭,旋轉(zhuǎn)270度
三、具體場景下的適配方案介紹
以如下場景為例,介紹人臉框適配方案:
屏幕分辨率
相機預(yù)覽尺寸
相機ID
屏幕朝向
原始數(shù)據(jù)
預(yù)覽效果
1080x1920
1280x720
后置相機
豎屏
原始數(shù)據(jù)
預(yù)覽效果
可以看到,在豎屏情況下,原始數(shù)據(jù)順時針旋轉(zhuǎn)90度并縮放才能達到預(yù)覽畫面的效果,既然圖像數(shù)據(jù)旋轉(zhuǎn)并縮放了,那人臉框也要隨著圖像旋轉(zhuǎn)并縮放。我們可以先旋轉(zhuǎn)再縮放,也可以先縮放在旋轉(zhuǎn),這里以先縮放再旋轉(zhuǎn)為例介紹適配的步驟。
第一步,縮放
第二步,旋轉(zhuǎn)
第一步:縮放
假設(shè)人臉檢測結(jié)果的位置信息是originalRect:(left, top, right, bottom)(相對于1280x720的圖像的位置),我們將其放大為相對于1920x1080的圖像的位置:
scaledRect:(originalRect.left * 1.5, originalRect.top * 1.5, originalRect.right * 1.5, originalRect.bottom * 1.5)
第二步:旋轉(zhuǎn)
在尺寸修改完成后,我們再將人臉框旋轉(zhuǎn)即可得到目標人臉框,其中旋轉(zhuǎn)的過程如下:
獲取原始數(shù)據(jù)和預(yù)覽畫面的旋轉(zhuǎn)角度(以上情況為90度)
根據(jù)旋轉(zhuǎn)角度將人臉框調(diào)整為View需要的人臉框,對于繪制所需的人臉框,我們分析下計算方式:
drawRect.left
繪制所需的Rect的left的值也就是scaledRect的下邊界到圖像下邊界的距離,也就是1080 - scaledRect.bottom
drawRect.top
繪制所需的Rect的top的值也就是scaledRect的左邊界到圖像左邊界的距離,也就是scaledRect.left
drawRect.right
繪制所需的Rect的right的值也就是scaledRect的上邊界到圖像下邊界的距離,也就是1080 - scaledRect.top
drawRect.bottom
繪制所需的Rect的bottom的值也就是scaledRect的右邊界到圖像上邊界的距離,也就是scaledRect.right
最終得出了旋轉(zhuǎn)角度為90度時繪制所需的drawRect
四、處理多種場景的情況,實現(xiàn)適配函數(shù)
通過以上分析,可得出畫框時需要用到的繪制參數(shù)如下,其中構(gòu)造函數(shù)的最后兩個參數(shù)是額外添加的,用于特殊場景的手動矯正:
previewWidth & previewHeight
預(yù)覽寬高,人臉追蹤的人臉框是基于這個尺寸的
canvasWidth & canvasHeight
被繪制的控件的寬高,也就是映射后的目標尺寸
cameraDisplayOrientation
預(yù)覽數(shù)據(jù)和源數(shù)據(jù)的旋轉(zhuǎn)角度
cameraId
相機ID,系統(tǒng)對于前置相機是有做默認鏡像處理的,而后置相機則沒有
isMirror
預(yù)覽畫面是否水平鏡像顯示,例如我們?nèi)绻謩釉O(shè)置了再次鏡像預(yù)覽畫面,則需要將最終結(jié)果也鏡像處理
mirrorHorizontal
為兼容部分設(shè)備使用,將調(diào)整后的框水平再次鏡像
mirrorVertical
為兼容部分設(shè)備使用,將調(diào)整后的框垂直再次鏡像
/**
* 創(chuàng)建一個繪制輔助類對象,并且設(shè)置繪制相關(guān)的參數(shù)
*
* @param previewWidth 預(yù)覽寬度
* @param previewHeight 預(yù)覽高度
* @param canvasWidth 繪制控件的寬度
* @param canvasHeight 繪制控件的高度
* @param cameraDisplayOrientation 旋轉(zhuǎn)角度
* @param cameraId 相機ID
* @param isMirror 是否水平鏡像顯示(若相機是手動鏡像顯示的,設(shè)為true,用于糾正)
* @param mirrorHorizontal 為兼容部分設(shè)備使用,水平再次鏡像
* @param mirrorVertical 為兼容部分設(shè)備使用,垂直再次鏡像
*/
public DrawHelper(int previewWidth, int previewHeight, int canvasWidth,
int canvasHeight, int cameraDisplayOrientation, int cameraId,
boolean isMirror, boolean mirrorHorizontal, boolean mirrorVertical) {
this.previewWidth = previewWidth;
this.previewHeight = previewHeight;
this.canvasWidth = canvasWidth;
this.canvasHeight = canvasHeight;
this.cameraDisplayOrientation = cameraDisplayOrientation;
this.cameraId = cameraId;
this.isMirror = isMirror;
this.mirrorHorizontal = mirrorHorizontal;
this.mirrorVertical = mirrorVertical;
}
人臉框映射的具體實現(xiàn)
/**
* 調(diào)整人臉框用來繪制
*
* @param ftRect FT人臉框
* @return 調(diào)整后的需要被繪制到View上的rect
*/
public Rect adjustRect(Rect ftRect) {
// 預(yù)覽寬高
int previewWidth = this.previewWidth;
int previewHeight = this.previewHeight;
// 畫布的寬高,也就是View的寬高
int canvasWidth = this.canvasWidth;
int canvasHeight = this.canvasHeight;
// 相機預(yù)覽顯示旋轉(zhuǎn)角度
int cameraDisplayOrientation = this.cameraDisplayOrientation;
// 相機Id,前置相機在顯示時會默認鏡像
int cameraId = this.cameraId;
// 是否預(yù)覽鏡像
boolean isMirror = this.isMirror;
// 針對于一些特殊場景做額外的人臉框鏡像操作,
// 比如cameraId為CAMERA_FACING_FRONT的相機打開后沒鏡像、
// 或cameraId為CAMERA_FACING_BACK的相機打開后鏡像
boolean mirrorHorizontal = this.mirrorHorizontal;
boolean mirrorVertical = this.mirrorVertical;
if (ftRect == null) {
return null;
}
Rect rect = new Rect(ftRect);
float horizontalRatio;
float verticalRatio;
// cameraDisplayOrientation 為0或180,也就是landscape或reverse-landscape時
// 或
// cameraDisplayOrientation 為90或270,也就是portrait或reverse-portrait時
// 分別計算水平縮放比和垂直縮放比
if (cameraDisplayOrientation % 180 == 0) {
horizontalRatio = (float) canvasWidth / (float) previewWidth;
verticalRatio = (float) canvasHeight / (float) previewHeight;
} else {
horizontalRatio = (float) canvasHeight / (float) previewWidth;
verticalRatio = (float) canvasWidth / (float) previewHeight;
}
rect.left *= horizontalRatio;
rect.right *= horizontalRatio;
rect.top *= verticalRatio;
rect.bottom *= verticalRatio;
Rect newRect = new Rect();
// 關(guān)鍵部分,根據(jù)旋轉(zhuǎn)角度以及相機ID對人臉框進行旋轉(zhuǎn)和鏡像處理
switch (cameraDisplayOrientation) {
case 0:
if (cameraId == 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 (cameraId == 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 (cameraId == 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 (cameraId == 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;
}
/**
* isMirror mirrorHorizontal finalIsMirrorHorizontal
* true true false
* false false false
* true false true
* false true true
*
* XOR
*/
if (isMirror ^ mirrorHorizontal) {
int left = newRect.left;
int right = newRect.right;
newRect.left = canvasWidth - right;
newRect.right = canvasWidth - left;
}
if (mirrorVertical) {
int top = newRect.top;
int bottom = newRect.bottom;
newRect.top = canvasHeight - bottom;
newRect.bottom = canvasHeight - top;
}
return newRect;
}
五、將適配好的人臉框繪制到View上
實現(xiàn)一個自定義View
/**
* 用于顯示人臉信息的控件
*/
public class FaceRectView extends View {
private static final String TAG = "FaceRectView";
private CopyOnWriteArrayList drawInfoList = new CopyOnWriteArrayList<>();
private Paint paint;
public FaceRectView(Context context) {
this(context, null);
}
public FaceRectView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
paint = new Paint();
}
// 主要的繪制操作
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (drawInfoList != null && drawInfoList.size() > 0) {
for (int i = 0; i < drawInfoList.size(); i++) {
DrawHelper.drawFaceRect(canvas, drawInfoList.get(i), 4, paint);
}
}
}
// 清空畫面中的人臉
public void clearFaceInfo() {
drawInfoList.clear();
postInvalidate();
}
public void addFaceInfo(DrawInfo faceInfo) {
drawInfoList.add(faceInfo);
postInvalidate();
}
public void addFaceInfo(List faceInfoList) {
drawInfoList.addAll(faceInfoList);
postInvalidate();
}
}
繪制的具體操作,畫人臉框
/**
* 繪制數(shù)據(jù)信息到view上,若 {@link DrawInfo#getName()} 不為null則繪制 {@link DrawInfo#getName()}
*
* @param canvas 需要被繪制的view的canvas
* @param drawInfo 繪制信息
* @param faceRectThickness 人臉框厚度
* @param paint 畫筆
*/
public static void drawFaceRect(Canvas canvas, DrawInfo drawInfo, int faceRectThickness, Paint paint) {
if (canvas == null || drawInfo == null) {
return;
}
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(faceRectThickness);
paint.setColor(drawInfo.getColor());
paint.setAntiAlias(true);
Path mPath = new Path();
//左上
Rect rect = drawInfo.getRect();
mPath.moveTo(rect.left, rect.top + rect.height() / 4);
mPath.lineTo(rect.left, rect.top);
mPath.lineTo(rect.left + rect.width() / 4, rect.top);
//右上
mPath.moveTo(rect.right - rect.width() / 4, rect.top);
mPath.lineTo(rect.right, rect.top);
mPath.lineTo(rect.right, rect.top + rect.height() / 4);
//右下
mPath.moveTo(rect.right, rect.bottom - rect.height() / 4);
mPath.lineTo(rect.right, rect.bottom);
mPath.lineTo(rect.right - rect.width() / 4, rect.bottom);
//左下
mPath.moveTo(rect.left + rect.width() / 4, rect.bottom);
mPath.lineTo(rect.left, rect.bottom);
mPath.lineTo(rect.left, rect.bottom - rect.height() / 4);
canvas.drawPath(mPath, paint);
// 其中需要注意的是,canvas.drawText函數(shù)傳入的位置,x是水平方向的起點,
// 而 y是 BaseLine,文字會在 BaseLine的上方繪制
if (drawInfo.getName() == null) {
paint.setStyle(Paint.Style.FILL_AND_STROKE);
paint.setTextSize(rect.width() / 8);
String str = (drawInfo.getSex() == GenderInfo.MALE ? "MALE" : (drawInfo.getSex() == GenderInfo.FEMALE ? "FEMALE" : "UNKNOWN"))
+ ","
+ (drawInfo.getAge() == AgeInfo.UNKNOWN_AGE ? "UNKNWON" : drawInfo.getAge())
+ ","
+ (drawInfo.getLiveness() == LivenessInfo.ALIVE ? "ALIVE" : (drawInfo.getLiveness() == LivenessInfo.NOT_ALIVE ? "NOT_ALIVE" : "UNKNOWN"));
canvas.drawText(str, rect.left, rect.top - 10, paint);
} else {
paint.setStyle(Paint.Style.FILL_AND_STROKE);
paint.setTextSize(rect.width() / 8);
canvas.drawText(drawInfo.getName(), rect.left, rect.top - 10, paint);
}
}
溫馨提示:
本來自己研究了較長時間,后來發(fā)現(xiàn)虹軟人臉識別Android Demo中早已給出該適配方案,上述代碼也源于官方Demo,通過研讀Demo,發(fā)現(xiàn)其中還提供了很多其他在接入虹軟人臉識別SDK時可能用到的優(yōu)化策略,如:
1. 通過異步人臉特征提取實現(xiàn)多人臉識別
2. 使用faceId優(yōu)化識別邏輯
3. 識別時的畫框適配方案
4. 打開雙攝進行紅外活體檢測
Android Demo可在[虹軟人臉識別開放平臺]下載
總結(jié)
以上是生活随笔為你收集整理的android 人脸识别边框_虹软人脸识别 - Android Camera实时人脸追踪画框适配的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [Leedcode][JAVA][第72
- 下一篇: Android 开源框架Universa