무지개 얼굴 인식 - Android Camera 실시간 얼굴 추적 액자

13391 단어 얼굴 인식android
홍소안면인식 안드로이드 SDK를 사용하는 과정에서 미리볼 때 보통 안면틀을 그려야 하지만 PC플랫폼 카메라 애플리케이션과 달리 안드로이드플랫폼 카메라에서 앱 개발을 하려면 카메라 전·후면 전환, 기기 가로·세로 화면 전환 등을 고려해야 하기 때문에 안면인식 프로그램 개발 과정에서 안면틀 그리기 적합 실현이 어렵다.이 문제에 대해 본고는 다음과 같은 내용을 통해 해결 방법을 소개할 것이다.
  • 카메라 원시 프레임 데이터와 미리보기 영상 화면의 관계
  • 뷰에 얼굴 프레임 그리기 프로세스
  • 구체적인 장면 어댑터 방안 소개
  • 다양한 장면의 상황을 처리하고 적합 함수 실현
  • 뷰에 적합한 얼굴 프레임 그리기
  • 다음과 같은 Rect 지침이 사용됩니다.
    변수 이름
    속뜻
    originalRect
    얼굴 검출 회전하는 얼굴 테두리
    scaledRect
    originalRect를 기반으로 축소된 얼굴 상자
    drawRect
    최종 그리기에 필요한 얼굴 상자
    1. 카메라의 원시 프레임 데이터와 미리보기 영상 화면의 관계
    안드로이드 장치는 일반적으로 휴대용 장치로 카메라가 장치에 집적되어 있고 장치의 회전도 카메라의 회전을 초래하기 때문에 영상도 회전할 수 있다. 이 문제를 해결하고 사용자가 정상적인 영상을 볼 수 있도록 안드로이드는 카메라 미리보기 데이터를 컨트롤러에 그릴 때 회전 각도와 관련된 API를 설정한다.개발자는Activity의 디스플레이 방향에 따라 회전 각도를 설정할 수 있다. 이 내용은 다음과 같다. 안드로이드는Camera2를 사용하여 미리보기 데이터를 가져와 미리보기된 YUV 데이터를 NV21로 변환하고 Bitmap으로 변환하여 컨트롤러에 표시하며 이 Bitmap을 카메라 미리보기 효과의 Bitmap으로 변환하여 컨트롤러에 표시한다.원시 데이터와 미리 보기 화면의 관계를 이해하기 편리하다
    2. 얼굴 테두리를 View에 그리는 절차
    총체적 절차
  • 1단계, 확대/축소
  • 2단계, 회전
  • 이미지 데이터와 미리 보기 화면의 회전 각도 관계에 따라 대응하는 회전 방안을 선택해야 한다
  • 후면 카메라(미러링 미리보기)
  • 후면 카메라, 후면 카메라 0도 회전, 후면 카메라 90도 회전, 후면 카메라 180도 회전, 270도 회전
  • 전면 카메라(미리보기로 미러링됨)
  • 전면 카메라, 회전 0도 전면 카메라, 회전 90도 전면 카메라, 회전 180도 전면 카메라, 회전 270도
    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)
  • 두 번째 단계: 회전은 사이즈 수정이 끝난 후에 우리는 얼굴 테두리를 회전하면 목표의 얼굴 테두리를 얻을 수 있다. 그 중에서 회전하는 과정은 다음과 같다.
  • 원시 데이터와 미리보기 화면의 회전 각도 가져오기(이상 90도)
  • 회전 각도에 따라 얼굴 테두리를 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


  • 최종적으로 회전 각도가 90도일 때 그리는 데 필요한 drawRect4. 다양한 장면의 상황을 처리하고 적합 함수를 실현한다.
    위의 분석을 통해 프레임에 필요한 드로잉 매개변수는 다음과 같습니다. 여기서 구조 함수의 마지막 두 매개변수는 특수 장면의 수동 교정에 추가로 추가됩니다.
  • previewWidth &previewHeight 미리보기 폭이 높고 얼굴 추적하는 얼굴 상자는 이 사이즈에 근거한 것이다
  • canvas Width & canvas Height가 그려진 컨트롤러의 넓이, 즉 비친 목표 크기
  • cameraDisplayOrientation 미리보기 데이터와 원본 데이터의 회전 각도
  • cameraId 카메라 ID, 시스템은 앞면 카메라에 대해 기본 렌즈 처리를 하고 뒷면 카메라는 없다
  • isMirror 미리보기 화면이 수평으로 표시되는지 여부. 예를 들어 우리가 수동으로 다시 미리보기 화면을 설정하면 최종 결과도 거울로 처리해야 한다
  • mirrorHorizontal은 호환되는 일부 장치에 사용되며 조정된 상자의 수평을 다시 미러링
  • mirrorVertical은 호환되는 일부 장치에 사용되며 조정된 상자를 수직으로 다시 미러링
  • /**
     *            ,           
     *
     * @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에 그리기
  • 사용자 정의 View 1개
  • /**
     *            
     */
    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는 [무지개 얼굴 인식 오픈 플랫폼]에서 다운로드할 수 있습니다.

    좋은 웹페이지 즐겨찾기