日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

android 人脸识别边框_虹软人脸识别 - Android Camera实时人脸追踪画框适配

發(fā)布時間:2023/12/10 Android 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 android 人脸识别边框_虹软人脸识别 - Android Camera实时人脸追踪画框适配 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

在使用虹軟人臉識別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)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。