무지개 얼굴 인식 - Android Camera 실시간 얼굴 추적 액자
변수 이름
속뜻
originalRect
얼굴 검출 회전하는 얼굴 테두리
scaledRect
originalRect를 기반으로 축소된 얼굴 상자
drawRect
최종 그리기에 필요한 얼굴 상자
1. 카메라의 원시 프레임 데이터와 미리보기 영상 화면의 관계
안드로이드 장치는 일반적으로 휴대용 장치로 카메라가 장치에 집적되어 있고 장치의 회전도 카메라의 회전을 초래하기 때문에 영상도 회전할 수 있다. 이 문제를 해결하고 사용자가 정상적인 영상을 볼 수 있도록 안드로이드는 카메라 미리보기 데이터를 컨트롤러에 그릴 때 회전 각도와 관련된 API를 설정한다.개발자는Activity의 디스플레이 방향에 따라 회전 각도를 설정할 수 있다. 이 내용은 다음과 같다. 안드로이드는Camera2를 사용하여 미리보기 데이터를 가져와 미리보기된 YUV 데이터를 NV21로 변환하고 Bitmap으로 변환하여 컨트롤러에 표시하며 이 Bitmap을 카메라 미리보기 효과의 Bitmap으로 변환하여 컨트롤러에 표시한다.원시 데이터와 미리 보기 화면의 관계를 이해하기 편리하다
2. 얼굴 테두리를 View에 그리는 절차
총체적 절차
3. 구체적인 장면에서의 적합한 방안 소개
다음과 같은 장면을 예로 들면 소개인의 얼굴 테두리 배합 방안이 적절하다.
화면 해상도
카메라 미리 보기 치수
카메라 ID
화면 방향
원시 데이터
효과 미리 보기
1080x1920
1280x720
후면 카메라
세로 병풍
원시 데이터
효과 미리 보기
이를 통해 알 수 있듯이 세로 화면의 경우 원시 데이터가 시계 방향으로 90도 회전하고 축소해야만 미리 보기 화면의 효과를 얻을 수 있다. 이미지 데이터가 회전하고 축소된 이상 얼굴 상자도 이미지에 따라 회전하고 축소해야 한다.우리는 먼저 회전하고 축소할 수도 있고, 먼저 축소해서 회전할 수도 있다. 여기서 축소하고 회전하는 것을 예로 삼아 적당한 절차를 소개한다.
1단계, 줌 2단계, 회전
originalRect:(left, top, right, bottom)
(1280x720의 이미지에 대한 위치)로 가정하고 1920x1080의 이미지에 대한 위치로 확대한다: scaledRect:(originalRect.left * 1.5, originalRect.top * 1.5, originalRect.right * 1.5, originalRect.bottom * 1.5)
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
최종적으로 회전 각도가 90도일 때 그리는 데 필요한
drawRect
4. 다양한 장면의 상황을 처리하고 적합 함수를 실현한다.위의 분석을 통해 프레임에 필요한 드로잉 매개변수는 다음과 같습니다. 여기서 구조 함수의 마지막 두 매개변수는 특수 장면의 수동 교정에 추가로 추가됩니다.
/**
* ,
*
* @param previewWidth
* @param previewHeight
* @param canvasWidth
* @param canvasHeight
* @param cameraDisplayOrientation
* @param cameraId ID
* @param isMirror ( , true, )
* @param mirrorHorizontal ,
* @param mirrorVertical ,
*/
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;
}
얼굴 틀의 구체적인 실현
/**
*
*
* @param ftRect FT
* @return View rect
*/
public Rect adjustRect(Rect ftRect) {
//
int previewWidth = this.previewWidth;
int previewHeight = this.previewHeight;
// , View
int canvasWidth = this.canvasWidth;
int canvasHeight = this.canvasHeight;
//
int cameraDisplayOrientation = this.cameraDisplayOrientation;
// Id,
int cameraId = this.cameraId;
//
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();
// , ID
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;
}
5. 잘 어울리는 얼굴 테두리를 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();
}
}
/**
* 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 ,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);
}
}
훈훈한 힌트: 원래 자신이 비교적 긴 시간을 연구한 후에 홍소안면인식 안드로이드 데모에서 이미 이 적당한 방안을 제시한 것을 발견했다. 상기 코드도 정부 데모에서 유래한 것이다. 데모를 연구한 결과 홍소안면인식 SDK에 접속할 때 사용할 수 있는 최적화 전략도 많이 제공했다. 예를 들어 다음과 같다.비동기적인 얼굴 특징 추출을 통해 다중 얼굴 식별을 실현한다.faceId를 사용하여 식별 논리 최적화 3.식별할 때의 액자 배합 방안 4.더블 촬영을 열어 적외선 활체 검사를 진행하다
Android Demo는 [무지개 얼굴 인식 오픈 플랫폼]에서 다운로드할 수 있습니다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Amazon Rekognition으로 얼굴 인식하기(AWS CLI에서 간편하게)AWS Rekognition으로 얼굴 인식 시스템을 구축할 때 어떤 느낌으로 움직이는지 살펴보았습니다. 본격적인 시스템을 구축하기 전에 시도한 단계를 남겨 둡니다. macOS Catalina aws-cli 2.1.1...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.