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);
}

源码下载地址

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