Android接入OpenCv實現人臉識別

Android接入OpenCv實現人臉識別

導入OpenCv

獲取資源

下載地址:https://opencv.org/releases/
文件目錄:
opencv-4.1.0-android-sdk/OpenCV-android-sdk/sdk/native
	--- jni/include 頭文件
	--- libs 動態庫

Cmake

cmake_minimum_required(VERSION 3.4.1)
add_library(
        native-lib
        SHARED
        native-lib.cpp)

#導入頭文件
include_directories(include)

#導入庫文件
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_SOURCE_DIR}/libs/${ANDROID_ABI}")

find_library(
        log-lib
        log)

target_link_libraries(
        native-lib
        opencv_java4
        android
        ${log-lib})

build.gradle

android{
	...
	sourceSets {
        main {
            jniLibs.srcDirs = ['src/main/cpp/libs']
        }
    }
}

人臉識別

獲取人臉識別模型

OpenCV-android-sdk/sdk/etc/lbpcascades/lbpcascade_frontalface.xml

將文件放在assets文件下,然後拷貝到應用中

public static void copyAssets(Context context, String path) {
	File model = new File(path);
	File file = new File(context.getFilesDir(), model.getName());
	if (file.exists()) {
		file.delete();
	}
	try {
		FileOutputStream fos = new FileOutputStream(file);
		InputStream inputStream = context.getAssets().open(path);
		int len;
		byte[] b = new byte[2048];
		while ((len = inputStream.read(b)) != -1) {
			fos.write(b, 0, len);
		}
		fos.close();
		inputStream.close();
	} catch (Exception e) {
		e.printStackTrace();
	}
}

打開攝像頭


// 權限
<uses-permission android:name="android.permission.CAMERA"/>

// 動態申請 
...

// 開啓攝像頭 CameraHelper.java
public class CameraHelper implements Camera.PreviewCallback {

    public static final int WIDTH = 640;
    public static final int HEIGHT = 480;
    private int mCameraId;
    private Camera mCamera;
    private byte[] buffer;
    private Camera.PreviewCallback mPreviewCallback;

    public CameraHelper(int cameraId) {
        mCameraId = cameraId;
    }

    public void switchCamera() {
        if (mCameraId == Camera.CameraInfo.CAMERA_FACING_BACK) {
            mCameraId = Camera.CameraInfo.CAMERA_FACING_FRONT;
        } else {
            mCameraId = Camera.CameraInfo.CAMERA_FACING_BACK;
        }
        stopPreview();
        startPreview();
    }

    public int getCameraId() {
        return mCameraId;
    }

    public void stopPreview() {
        if (mCamera != null) {
            //預覽數據回調接口
            mCamera.setPreviewCallback(null);
            //停止預覽
            mCamera.stopPreview();
            //釋放攝像頭
            mCamera.release();
            mCamera = null;
        }
    }

    public void startPreview() {
        try {
            //獲得camera對象
            mCamera = Camera.open(mCameraId);
            //配置camera的屬性
            Camera.Parameters parameters = mCamera.getParameters();
            //設置預覽數據格式爲nv21
            parameters.setPreviewFormat(ImageFormat.NV21);
            //這是攝像頭寬、高
            parameters.setPreviewSize(WIDTH, HEIGHT);
            // 設置攝像頭 圖像傳感器的角度、方向
            setFocusMode(parameters);
            mCamera.setParameters(parameters);
            buffer = new byte[WIDTH * HEIGHT * 3 / 2];
            //數據緩存區
            mCamera.addCallbackBuffer(buffer);
            mCamera.setPreviewCallbackWithBuffer(this);
            //設置預覽畫面
            SurfaceTexture surfaceTexture = new SurfaceTexture(11);
            mCamera.setPreviewTexture(surfaceTexture);
            mCamera.startPreview();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }


    public void setPreviewCallback(Camera.PreviewCallback previewCallback) {
        mPreviewCallback = previewCallback;
    }


    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {
        mPreviewCallback.onPreviewFrame(data, camera);
        camera.addCallbackBuffer(buffer);
    }

    /**
     * 自動對焦
     * @param parameters
     */
    private void setFocusMode(Camera.Parameters parameters) {
        List<String> focusModes = parameters.getSupportedFocusModes();
        if (focusModes != null
                && focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
        }
    }
}

SurfaceView 顯示


// activity_main.xml
<?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"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <SurfaceView
        android:id="@+id/surfaceView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
    <LinearLayout
        android:layout_width="match_parent"
        android:orientation="horizontal"
        android:layout_height="wrap_content">
        <Button
            android:text="切換攝像頭"
            android:onClick="switchCamera"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </LinearLayout>
</RelativeLayout>

// MainActivity.java
public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback, Camera.PreviewCallback{
    private OpenCvJni openCvJni;
    private CameraHelper cameraHelper;
    int cameraId = Camera.CameraInfo.CAMERA_FACING_FRONT;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        openCvJni = new OpenCvJni();
        SurfaceView surfaceView = findViewById(R.id.surfaceView);
        surfaceView.getHolder().addCallback(this);
        cameraHelper = new CameraHelper(cameraId);
        cameraHelper.setPreviewCallback(this);
        Utils.copyAssets(this, "lbpcascade_frontalface.xml");
    }


    @Override
    protected void onResume() {
        super.onResume();
        String path = new File(Environment.getExternalStorageDirectory(),
                "lbpcascade_frontalface.xml").getAbsolutePath();
        cameraHelper.startPreview();
        openCvJni.init(path);
    }


    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {
        openCvJni.postData(data, CameraHelper.WIDTH, CameraHelper.HEIGHT, cameraId);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {

    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        openCvJni.setSurface(holder.getSurface());
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {

    }

    public void switchCamera(View view) {
        cameraHelper.switchCamera();
        cameraId = cameraHelper.getCameraId();
    }
}

溝通android和OpenCv之Java層

// OpenCvJni.java
public class OpenCvJni {
    static {
        System.loadLibrary("native-lib");
    }
    /**
     * 初始化native層相關邏輯
     * @param path 人臉識別模型路徑
     */
    public native void init(String path) ;

    /**
     * 攝像頭採集的數據發送到native層
     * @param data 每一幀數據(NV21)
     * @param width 攝像頭採集圖片的寬
     * @param height 攝像頭採集圖片的高
     * @param cameraId 攝像頭ID(前置、後置)
     */
    public native void postData(byte[] data, int width, int height, int cameraId);

    /**
     * 發送Surface到native,用於把數據後的圖像數據直接顯示到Surface上
     * @param surface
     */
    public native void setSurface(Surface surface);
}

溝通android和OpenCv之C++層

// native-lib.cpp
#include <jni.h>
#include <string>
#include "opencv2/opencv.hpp"
#include <android/native_window_jni.h>

ANativeWindow *window = 0;
using namespace cv;

DetectionBasedTracker *tracker = 0;

class CascadeDetectorAdapter : public DetectionBasedTracker::IDetector {
public:
    CascadeDetectorAdapter(cv::Ptr<cv::CascadeClassifier> detector) :
            IDetector(),
            Detector(detector) {
    }

    void detect(const cv::Mat &Image, std::vector<cv::Rect> &objects) {
        Detector->detectMultiScale(Image, objects, scaleFactor, minNeighbours, 0, minObjSize,
                                   maxObjSize);
    }

    virtual ~CascadeDetectorAdapter() {
    }

private:
    CascadeDetectorAdapter();
    cv::Ptr<cv::CascadeClassifier> Detector;
};

extern "C"
JNIEXPORT void JNICALL
Java_com_barray_opencvlib_OpenCvJni_init(JNIEnv *env, jobject thiz, jstring path_) {
    const char *path = env->GetStringUTFChars(path_, 0);

    //創建檢測器  Ptr是智能指針,不需要關心釋放
    Ptr<CascadeClassifier> mainClassifier = makePtr<CascadeClassifier>(path);
    Ptr<CascadeDetectorAdapter> mainDetector = makePtr<CascadeDetectorAdapter>(mainClassifier);

    //創建跟蹤器
    Ptr<CascadeClassifier> trackClassifier = makePtr<CascadeClassifier>(path);
    Ptr<CascadeDetectorAdapter> trackingDetector = makePtr<CascadeDetectorAdapter>(trackClassifier);

    //開始識別,結果在CascadeDetectorAdapter中返回
    DetectionBasedTracker::Parameters DetectorParams;
    tracker = new DetectionBasedTracker(mainDetector, trackingDetector, DetectorParams);
    tracker->run();
    env->ReleaseStringUTFChars(path_, path);
}


extern "C"
JNIEXPORT void JNICALL
Java_com_barray_opencvlib_OpenCvJni_postData(JNIEnv *env, jobject thiz, jbyteArray data_, jint width,
                                             jint height, jint cameraId) {
    jbyte *data = env->GetByteArrayElements(data_, NULL);

    // 數據的行數也就是數據高度,因爲數據類型是NV21,所以爲Y+U+V的高度, 也就是height + height/4 + height/4
    Mat src(height*3/2, width, CV_8UC1, data);

    // 轉RGB
    cvtColor(src, src, COLOR_YUV2RGBA_NV21);
    if (cameraId == 1) {// 前置攝像頭
        //逆時針旋轉90度
        rotate(src, src, ROTATE_90_COUNTERCLOCKWISE);
        //1:水平翻轉   0:垂直翻轉
        flip(src, src, 1);
    } else {
        //順時針旋轉90度
        rotate(src, src, ROTATE_90_CLOCKWISE);

    }
    Mat gray;
    //灰度化
    cvtColor(src, gray, COLOR_RGBA2GRAY);
    //二值化
    equalizeHist(gray, gray);

    std::vector<Rect> faces;
    //檢測圖片
    tracker->process(gray);
    //獲取CascadeDetectorAdapter中的檢測結果
    tracker->getObjects(faces);
    //畫出矩形
    for (Rect face : faces) {
        rectangle(src, face, Scalar(255, 0, 0));
    }

    //顯示到serface
    if (window) {
        ANativeWindow_setBuffersGeometry(window, src.cols, src.rows, WINDOW_FORMAT_RGBA_8888);
        ANativeWindow_Buffer window_buffer;
        do {
            //lock失敗 直接brek出去
            if (ANativeWindow_lock(window, &window_buffer, 0)) {
                ANativeWindow_release(window);
                window = 0;
                break;
            }

            uint8_t *dst_data = static_cast<uint8_t *>(window_buffer.bits);
            //stride : 一行多少個數據
            //(RGBA) * 4
            int dst_linesize = window_buffer.stride * 4;

            //一行一行拷貝,src.data是圖片的RGBA數據,要拷貝到dst_data中,也就是window的緩衝區裏
            for (int i = 0; i < window_buffer.height; ++i) {
                memcpy(dst_data + i * dst_linesize, src.data + i * src.cols * 4, dst_linesize);
            }
            //提交刷新
            ANativeWindow_unlockAndPost(window);
        } while (0);
    }
    src.release();
    gray.release();
    env->ReleaseByteArrayElements(data_, data, 0);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_barray_opencvlib_OpenCvJni_setSurface(JNIEnv *env, jobject thiz, jobject surface) {
    if (window) {
        ANativeWindow_release(window);
        window = 0;
    }
    window = ANativeWindow_fromSurface(env, surface);
}

源碼下載地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章