Android 에 서 는 dlib+opencv 를 사용 하여 동적 얼굴 검사 기능 을 구현 합 니 다.
안 드 로 이 드 카메라 미리 보기 기능 을 완성 한 후에 이 를 바탕 으로 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 를 사용 하여 동적 인 얼굴 검 측 기능 을 실현 하 는 것 입 니 다.여러분 에 게 도움 이 되 기 를 바 랍 니 다.궁금 한 점 이 있 으 시 면 메 시 지 를 남 겨 주세요.소 편 은 제때에 답 해 드 리 겠 습 니 다.여기 서도 저희 사이트 에 대한 여러분 의 지지 에 감 사 드 립 니 다!
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Kotlin의 기초 - 2부지난 글에서는 Kotlin이 무엇인지, Kotlin의 특징, Kotlin에서 변수 및 데이터 유형을 선언하는 방법과 같은 Kotlin의 기본 개념에 대해 배웠습니다. 유형 변환은 데이터 변수의 한 유형을 다른 데이터...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.