Android 에 서 는 dlib+opencv 를 사용 하여 동적 얼굴 검사 기능 을 구현 합 니 다.

18658 단어 android얼굴 검사
개술
안 드 로 이 드 카메라 미리 보기 기능 을 완성 한 후에 이 를 바탕 으로 dlib 와 opencv 라 이브 러 리 를 사용 하여 얼굴 검 측 에 관 한 demo 를 만 들 었 습 니 다.이 demo 는 카메라 미리 보기 과정 에서 사람의 얼굴 을 실시 간 으로 감지 하고 검 측 된 사람의 얼굴 을 직사각형 상자 로 묘사 했다.구체 적 인 실현 원 리 는 다음 과 같다.
2 층 View,1 층 TextureView 를 사용 하여 미리 보기 에 사용 합 니 다.프로그램 은 TextureView 에서 미리 보기 프레임 데 이 터 를 가 져 온 다음 dlib 라 이브 러 리 를 호출 하여 프레임 데 이 터 를 처리 하고 마지막 으로 검 측 결 과 를 맨 위 에 있 는 SurfaceView 에 그립 니 다.
2 항목 설정
프로젝트 에 dlib 와 opencv 라 이브 러 리 를 사 용 했 기 때문에 설정 이 필요 합 니 다.주로 다음 과 같은 몇 가지 측면 과 관련된다.
2.1 C++지원
프로젝트 생 성 과정 에서 Include C+Support,C++11,Exceptions Support(-fexceptions)와 Runtime Type Information Support(-ftti)를 순서대로 선택 합 니 다.마지막 으로 생 성 된 build.gradle 파일 은 다음 과 같 습 니 다.

defaultConfig {
 applicationId "com.example.lightweh.facedetection"
 minSdkVersion 23
 targetSdkVersion 28
 versionCode 1
 versionName "1.0"
 testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
 externalNativeBuild {
 cmake {
  arguments "-DCMAKE_BUILD_TYPE=Release"
  cppFlags "-std=c++11 -frtti -fexceptions"
 }
 }
}
그 중에서 arguments 인 자 는 나중에 추 가 된 것 으로 주로 CMake 의 컴 파일 모드 를 Release 로 지정 합 니 다.Debug 모드 에서 dlib 라 이브 러 리 에서 관련 알고리즘 의 운행 속도 가 매우 느 리 기 때 문 입 니 다.초기 에 C++코드 를 디 버 깅 해 야 한다 면 arguments 매개 변 수 를 먼저 설명 할 수 있 습 니 다.
2.2 dlib 와 opencv 다운로드
dlib 홈 페이지최신 버 전의 원본 코드 를 다운로드 하고 압축 을 푼 후 폴 더 의 dlib 디 렉 터 리 를 Android Studio 프로젝트 의 cpp 디 렉 터 리 에 복사 합 니 다.
sourceforge 다운로드최신 opencv-android 라 이브 러 리 에서 압축 을 풀 고 폴 더 의 native 디 렉 터 리 를 Android Studio 프로젝트 의 cpp 디 렉 터 리 에 복사 하여 opencv 로 이름 을 바 꿉 니 다.
2.3 CMakeLists 설정
CMakeLists 파일 에 dlib 의 cmake 파일 을 먼저 포함 하고 opencv 의 include 폴 더 를 추가 하고 opencv 의 so 라 이브 러 리 를 도입 하 는 동시에 jnicommon 디 렉 터 리 의 파일 및 얼굴 검사 관련 파일 을 native-lib 라 이브 러 리 에 추가 하고 마지막 으로 링크 합 니 다.

#   native  
set(NATIVE_DIR ${CMAKE_SOURCE_DIR}/src/main/cpp)
#   dlib
include(${NATIVE_DIR}/dlib/cmake)
#   opencv include   
include_directories(${NATIVE_DIR}/opencv/jni/include)
#   opencv so 
add_library(
 libopencv_java3
 SHARED
 IMPORTED)
set_target_properties(
 libopencv_java3
 PROPERTIES
 IMPORTED_LOCATION
 ${NATIVE_DIR}/opencv/libs/${ANDROID_ABI}/libopencv_java3.so)
#  jni_common        ,  SRC_LIST 
AUX_SOURCE_DIRECTORY(${NATIVE_DIR}/jni_common SRC_LIST)
add_library( # Sets the name of the library.
 native-lib
 # Sets the library as a shared library.
 SHARED
 # Provides a relative path to your source file(s).
 ${SRC_LIST}
 src/main/cpp/face_detector.h
 src/main/cpp/face_detector.cpp
 src/main/cpp/native-lib.cpp)
find_library( # Sets the name of the path variable.
 log-lib
 # Specifies the name of the NDK library that
 # you want CMake to locate.
 log)
target_link_libraries( # Specifies the target library.
 native-lib
 dlib
 libopencv_java3
 jnigraphics
 # Links the target library to the log library
 # included in the NDK.
 ${log-lib})
#   release    
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -s -O3 -Wall")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -s -O3 -Wall")
C++코드 에 헤더 파일 인'android/bitmap.h'를 사 용 했 기 때문에 링크 할 때 jnigraphics 라 이브 러 리 를 추가 해 야 합 니 다.
3 JNI 관련 자바 클래스 정의
3.1 VisionDetRet 클래스
VisionDetRet 류 의 관련 대상 은 주로 C++와 자바 간 의 데이터 전달 을 책임 집 니 다.

public final class VisionDetRet {
 private int mLeft;
 private int mTop;
 private int mRight;
 private int mBottom;
 VisionDetRet() {}
 public VisionDetRet(int l, int t, int r, int b) {
 mLeft = l;
 mTop = t;
 mRight = r;
 mBottom = b;
 }
 public int getLeft() {
 return mLeft;
 }
 public int getTop() {
 return mTop;
 }
 public int getRight() {
 return mRight;
 }
 public int getBottom() {
 return mBottom;
 }
}
3.2 FaceDet 클래스
Facedet 클래스 는 JNI 함수 호출 클래스 로 C++가 필요 한 native 방법 을 정의 합 니 다.

public class FaceDet {
 private static final String TAG = "FaceDet";
 // accessed by native methods
 @SuppressWarnings("unused")
 private long mNativeFaceDetContext;
 static {
 try {
  //    native   
  System.loadLibrary("native-lib");
  jniNativeClassInit();
  Log.d(TAG, "jniNativeClassInit success");
 } catch (UnsatisfiedLinkError e) {
  Log.e(TAG, "library not found");
 }
 }
 public FaceDet() {
 jniInit();
 }
 @Nullable
 @WorkerThread
 public List<VisionDetRet> detect(@NonNull Bitmap bitmap) {
 VisionDetRet[] detRets = jniBitmapDet(bitmap);
 return Arrays.asList(detRets);
 }
 @Override
 protected void finalize() throws Throwable {
 super.finalize();
 release();
 }
 public void release() {
 jniDeInit();
 }
 @Keep
 private native static void jniNativeClassInit();
 @Keep
 private synchronized native int jniInit();
 @Keep
 private synchronized native int jniDeInit();
 @Keep
 private synchronized native VisionDetRet[] jniBitmapDet(Bitmap bitmap);
}
4 Native 방법의 실현
4.1 VisionDetRet 클래스 에 대응 하 는 C++클래스 정의

#include <jni.h>
#define CLASSNAME_VISION_DET_RET "com/lightweh/dlib/VisionDetRet"
#define CONSTSIG_VISION_DET_RET "()V"
#define CLASSNAME_FACE_DET "com/lightweh/dlib/FaceDet"
class JNI_VisionDetRet {
public:
 JNI_VisionDetRet(JNIEnv *env) {
 //   VisionDetRet   
 jclass detRetClass = env->FindClass(CLASSNAME_VISION_DET_RET);
 //   VisionDetRet     
 jID_left = env->GetFieldID(detRetClass, "mLeft", "I");
 jID_top = env->GetFieldID(detRetClass, "mTop", "I");
 jID_right = env->GetFieldID(detRetClass, "mRight", "I");
 jID_bottom = env->GetFieldID(detRetClass, "mBottom", "I");
 }
 void setRect(JNIEnv *env, jobject &jDetRet, const int &left, const int &top,
   const int &right, const int &bottom) {
 //   VisionDetRet   jDetRet      
 env->SetIntField(jDetRet, jID_left, left);
 env->SetIntField(jDetRet, jID_top, top);
 env->SetIntField(jDetRet, jID_right, right);
 env->SetIntField(jDetRet, jID_bottom, bottom);
 }
 //   VisionDetRet   
 static jobject createJObject(JNIEnv *env) {
 jclass detRetClass = env->FindClass(CLASSNAME_VISION_DET_RET);
 jmethodID mid =
  env->GetMethodID(detRetClass, "<init>", CONSTSIG_VISION_DET_RET);
 return env->NewObject(detRetClass, mid);
 }
 //   VisionDetRet     
 static jobjectArray createJObjectArray(JNIEnv *env, const int &size) {
 jclass detRetClass = env->FindClass(CLASSNAME_VISION_DET_RET);
 return (jobjectArray) env->NewObjectArray(size, detRetClass, NULL);
 }
private:
 jfieldID jID_left;
 jfieldID jID_top;
 jfieldID jID_right;
 jfieldID jID_bottom;
};
4.2 얼굴 검사 클래스 정의
얼굴 검출 알고리즘 은 크기 와 위치 가 다른 창 으로 그림 에서 미 끄 러 진 다음 창 에 얼굴 이 있 는 지 여 부 를 판단 해 야 합 니 다.본 고 는 dlib 에서 HOG(histogram of oriented gradient)방법 으로 사람의 얼굴 을 검 측 하 는데 그 검 측 효 과 는 opencv 보다 좋다.dlib 역시 CNN 방법 을 제공 하여 얼굴 검 사 를 하 는데 효과 가 HOG 보다 좋 지만 GPU 를 사용 하여 속 도 를 내야 한다.그렇지 않 으 면 프로그램 운행 이 매우 느 릴 것 이다.
 

class FaceDetector {
private:
 dlib::frontal_face_detector face_detector;
 std::vector<dlib::rectangle> det_rects;
public:
 FaceDetector();
 //         
 int Detect(const cv::Mat &image);
 //       
 std::vector<dlib::rectangle> getDetResultRects();
};
FaceDetector::FaceDetector() {
 //        
 face_detector = dlib::get_frontal_face_detector();
}
int FaceDetector::Detect(const cv::Mat &image) {
 if (image.empty())
 return 0;
 if (image.channels() == 1) {
 cv::cvtColor(image, image, CV_GRAY2BGR);
 }
 dlib::cv_image<dlib::bgr_pixel> dlib_image(image);
 det_rects.clear();
 //              
 det_rects = face_detector(dlib_image);
 return det_rects.size();
}
std::vector<dlib::rectangle> FaceDetector::getDetResultRects() {
 return det_rects;
}
4.3 native 방법 실현

JNI_VisionDetRet *g_pJNI_VisionDetRet;
JavaVM *g_javaVM = NULL;
//              
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
 g_javaVM = vm;
 JNIEnv *env;
 vm->GetEnv((void **) &env, JNI_VERSION_1_6);
 //     g_pJNI_VisionDetRet
 g_pJNI_VisionDetRet = new JNI_VisionDetRet(env);
 return JNI_VERSION_1_6;
}
//            
void JNI_OnUnload(JavaVM *vm, void *reserved) {
 g_javaVM = NULL;
 delete g_pJNI_VisionDetRet;
}
namespace {
#define JAVA_NULL 0
 using DetPtr = FaceDetector *;
 //               ,  Jave    C++    (    )
 class JNI_FaceDet {
 public:
 JNI_FaceDet(JNIEnv *env) {
  jclass clazz = env->FindClass(CLASSNAME_FACE_DET);
  mNativeContext = env->GetFieldID(clazz, "mNativeFaceDetContext", "J");
  env->DeleteLocalRef(clazz);
 }
 DetPtr getDetectorPtrFromJava(JNIEnv *env, jobject thiz) {
  DetPtr const p = (DetPtr) env->GetLongField(thiz, mNativeContext);
  return p;
 }
 void setDetectorPtrToJava(JNIEnv *env, jobject thiz, jlong ptr) {
  env->SetLongField(thiz, mNativeContext, ptr);
 }
 jfieldID mNativeContext;
 };
 // Protect getting/setting and creating/deleting pointer between java/native
 std::mutex gLock;
 std::shared_ptr<JNI_FaceDet> getJNI_FaceDet(JNIEnv *env) {
 static std::once_flag sOnceInitflag;
 static std::shared_ptr<JNI_FaceDet> sJNI_FaceDet;
 std::call_once(sOnceInitflag, [env]() {
  sJNI_FaceDet = std::make_shared<JNI_FaceDet>(env);
 });
 return sJNI_FaceDet;
 }
 //  java        c++    
 DetPtr const getDetPtr(JNIEnv *env, jobject thiz) {
 std::lock_guard<std::mutex> lock(gLock);
 return getJNI_FaceDet(env)->getDetectorPtrFromJava(env, thiz);
 }
 // The function to set a pointer to java and delete it if newPtr is empty
 // C++  new  ,     long    java    
 void setDetPtr(JNIEnv *env, jobject thiz, DetPtr newPtr) {
 std::lock_guard<std::mutex> lock(gLock);
 DetPtr oldPtr = getJNI_FaceDet(env)->getDetectorPtrFromJava(env, thiz);
 if (oldPtr != JAVA_NULL) {
  delete oldPtr;
 }
 getJNI_FaceDet(env)->setDetectorPtrToJava(env, thiz, (jlong) newPtr);
 }
} // end unnamespace
#ifdef __cplusplus
extern "C" {
#endif
#define DLIB_FACE_JNI_METHOD(METHOD_NAME) Java_com_lightweh_dlib_FaceDet_##METHOD_NAME
void JNIEXPORT
DLIB_FACE_JNI_METHOD(jniNativeClassInit)(JNIEnv *env, jclass _this) {}
//            
jobjectArray getRecResult(JNIEnv *env, DetPtr faceDetector, const int &size) {
 //                 jobjectArray
 jobjectArray jDetRetArray = JNI_VisionDetRet::createJObjectArray(env, size);
 for (int i = 0; i < size; i++) {
 //                    ,      
 jobject jDetRet = JNI_VisionDetRet::createJObject(env);
 env->SetObjectArrayElement(jDetRetArray, i, jDetRet);
 dlib::rectangle rect = faceDetector->getDetResultRects()[i];
 //              jobject    
 g_pJNI_VisionDetRet->setRect(env, jDetRet, rect.left(), rect.top(),
     rect.right(), rect.bottom());
 }
 return jDetRetArray;
}
JNIEXPORT jobjectArray JNICALL
DLIB_FACE_JNI_METHOD(jniBitmapDet)(JNIEnv *env, jobject thiz, jobject bitmap) {
 cv::Mat rgbaMat;
 cv::Mat bgrMat;
 jniutils::ConvertBitmapToRGBAMat(env, bitmap, rgbaMat, true);
 cv::cvtColor(rgbaMat, bgrMat, cv::COLOR_RGBA2BGR);
 //          
 DetPtr mDetPtr = getDetPtr(env, thiz);
 //         ,         
 jint size = mDetPtr->Detect(bgrMat);
 //       
 return getRecResult(env, mDetPtr, size);
}
jint JNIEXPORT JNICALL
DLIB_FACE_JNI_METHOD(jniInit)(JNIEnv *env, jobject thiz) {
 DetPtr mDetPtr = new FaceDetector();
 //          
 setDetPtr(env, thiz, mDetPtr);
 return JNI_OK;
}
jint JNIEXPORT JNICALL
DLIB_FACE_JNI_METHOD(jniDeInit)(JNIEnv *env, jobject thiz) {
 //    0
 setDetPtr(env, thiz, JAVA_NULL);
 return JNI_OK;
}
#ifdef __cplusplus
}
#endif
5 자바 단 호출 얼굴 검출 알고리즘
얼굴 검 사 를 시작 하기 전에 카메라 AutoFitTextureView 에 사용자 정의 Bounding BoxView 를 덮어 서 검 측 된 얼굴 사각형 상 자 를 그 려 야 합 니 다.이 View 의 구체 적 인 실현 은 다음 과 같 습 니 다.

public class BoundingBoxView extends SurfaceView implements SurfaceHolder.Callback {
 protected SurfaceHolder mSurfaceHolder;
 private Paint mPaint;
 private boolean mIsCreated;
 public BoundingBoxView(Context context, AttributeSet attrs) {
  super(context, attrs);
  mSurfaceHolder = getHolder();
  mSurfaceHolder.addCallback(this);
  mSurfaceHolder.setFormat(PixelFormat.TRANSPARENT);
  setZOrderOnTop(true);
  mPaint = new Paint();
  mPaint.setAntiAlias(true);
  mPaint.setColor(Color.RED);
  mPaint.setStrokeWidth(5f);
  mPaint.setStyle(Paint.Style.STROKE);
 }
 @Override
 public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) {
 }
 @Override
 public void surfaceCreated(SurfaceHolder surfaceHolder) {
  mIsCreated = true;
 }
 @Override
 public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
  mIsCreated = false;
 }
 public void setResults(List<VisionDetRet> detRets)
 {
  if (!mIsCreated) {
   return;
  }
  Canvas canvas = mSurfaceHolder.lockCanvas();
  //         。
  canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
  canvas.drawColor(Color.TRANSPARENT);
  for (VisionDetRet detRet : detRets) {
   Rect rect = new Rect(detRet.getLeft(), detRet.getTop(), detRet.getRight(), detRet.getBottom());
   canvas.drawRect(rect, mPaint);
  }
  mSurfaceHolder.unlockCanvasAndPost(canvas);
 }
}
또한 레이아웃 파일 에 해당 하 는 BoundingBoxView 층 을 추가 하여 AutoFitTextureView 와 완전히 일치 하도록 해 야 합 니 다.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:tools="http://schemas.android.com/tools"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 tools:context=".CameraFragment">
 <com.lightweh.facedetection.AutoFitTextureView
  android:id="@+id/textureView"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:layout_centerVertical="true"
  android:layout_centerHorizontal="true" />
 <com.lightweh.facedetection.BoundingBoxView
  android:id="@+id/boundingBoxView"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:layout_alignLeft="@+id/textureView"
  android:layout_alignTop="@+id/textureView"
  android:layout_alignRight="@+id/textureView"
  android:layout_alignBottom="@+id/textureView" />
</RelativeLayout>
Bounding BoxView 추가 완료 후 Camera Fragment 에 해당 하 는 얼굴 검사 코드 를 추가 할 수 있 습 니 다.

private class detectAsync extends AsyncTask<Bitmap, Void, List<VisionDetRet>> {
 @Override
 protected void onPreExecute() {
  mIsDetecting = true;
  super.onPreExecute();
 }
 protected List<VisionDetRet> doInBackground(Bitmap... bp) {
  List<VisionDetRet> results;
  //       
  results = mFaceDet.detect(bp[0]);
  return results;
 }
 protected void onPostExecute(List<VisionDetRet> results) {
  //            
  mBoundingBoxView.setResults(results);
  mIsDetecting = false;
 }
}
그 다음 에 onResume 과 onPause 함수 에서 얼굴 검사 대상 의 초기 화 와 방출 을 완성 합 니 다.

@Override
public void onResume() {
 super.onResume();
 startBackgroundThread();
 mFaceDet = new FaceDet();
 if (mTextureView.isAvailable()) {
  openCamera(mTextureView.getWidth(), mTextureView.getHeight());
 } else {
  mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
 }
}
@Override
public void onPause() {
 closeCamera();
 stopBackgroundThread();
 if (mFaceDet != null) {
  mFaceDet.release();
 }
 super.onPause();
}
마지막 으로 TextureView 의 리 셋 함수 onSurfaceTextureUpdated 에서 호출 을 마 쳤 습 니 다.

@Override
public void onSurfaceTextureUpdated(SurfaceTexture texture) {
 if (!mIsDetecting) {
  Bitmap bp = mTextureView.getBitmap();
  //              
  bp = Bitmap.createBitmap(bp, 0, 0, bp.getWidth(), bp.getHeight(), mTextureView.getTransform(null), true );

  new detectAsync().execute(bp);
 }
}
6 테스트 결과
테스트 결과 960 x720 의 bitmap 이미 지 는 화 웨 이 휴대 전화(Android 6.0,8 핵 1.2GHz,2G 메모리)에서 한 번 의 검 사 를 수행 하 는 데 약 800~850 ms 가 걸 렸 다.데모 실행 효 과 는 다음 과 같 습 니 다.

7 데모 소스 코드
Github: https://github.com/lightweh/FaceDetection
총결산
위 에서 말 한 것 은 소 편 이 소개 한 안 드 로 이 드 에서 dlib+opencv 를 사용 하여 동적 인 얼굴 검 측 기능 을 실현 하 는 것 입 니 다.여러분 에 게 도움 이 되 기 를 바 랍 니 다.궁금 한 점 이 있 으 시 면 메 시 지 를 남 겨 주세요.소 편 은 제때에 답 해 드 리 겠 습 니 다.여기 서도 저희 사이트 에 대한 여러분 의 지지 에 감 사 드 립 니 다!

좋은 웹페이지 즐겨찾기