Android基於OpenCV通過JNI識別並顯示人臉位置

Android基於OpenCV通過JNI識別並顯示人臉位置

OpenCV介紹地址:https://docs.opencv.org/2.4/doc/tutorials/introduction/android_binary_package/O4A_SDK.html
Android OpenCV Java Demo地址: https://github.com/kongqw/OpenCVForAndroid,其中人臉比對在Master分支
本文基於JNI實現,源碼地址:Gitee:OpenCVJniFaceDetect

設計思路

取Camera API 中onPreviewFrame 回調的YUV數據送到JNI,
進一步用OpenCV的API識別並畫出人臉區域,再通過ANativeWindow顯示到surface。

代碼設計說明

效果如下

代碼結構如下

其中
app\src\main\cpp\include 來自 opencv-3.4.3-android-sdk\sdk\native\jni\include
app\src\main\assets\lbpcascade_frontalface.xml 來自 opencv-3.4.3-android-sdk\sdk\etc\lbpcascades
app\src\main\jniLibs\ 來自 opencv-3.4.3-android-sdk\sdk\native\libs

JNI識別人臉並畫區域代碼如下

    // nv21的數據
    jbyte *data = env->GetByteArrayElements(data_, NULL);
    //mat  data->Mat
    //1、高 2、寬
    Mat src(h + h / 2, w, CV_8UC1, data);
    //顏色格式的轉換 nv21->RGBA
    //將 nv21的yuv數據轉成了rgba
    cvtColor(src, src, COLOR_YUV2RGBA_NV21);
    // 可以將Mat的數據寫到存儲卡,正在寫的過程 退出了,導致文件丟失數據
    //imwrite("/sdcard/src.jpg",src);
    if (cameraId == 1) {
        //前置攝像頭,需要逆時針旋轉90度
        rotate(src, src, ROTATE_90_COUNTERCLOCKWISE);
        //水平翻轉 鏡像
        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;
    //定位人臉 N個
    tracker->process(gray);
    tracker->getObjects(faces);
    for (Rect face : faces) {
        //畫矩形 分別指定 bgra
        rectangle(src, face, Scalar(255, 0, 0));
    }

通過ANativeWindow顯示RGBA數據到surface代碼如下

        ANativeWindow_setBuffersGeometry(window, src.cols, src.rows, WINDOW_FORMAT_RGBA_8888);
        ANativeWindow_Buffer buffer;
        do {
            //lock失敗 直接brek出去
            if (ANativeWindow_lock(window, &buffer, 0)) {
                ANativeWindow_release(window);
                window = 0;
                break;
            }
            //src.data : rgba的數據
            //把src.data 拷貝到 buffer.bits 裏去
            // 一行一行的拷貝
            //一行需要多少像素 * 4(RGBA),當stride>width時直接memcpy會顯示異常
            //memcpy(buffer.bits, src.data, buffer.stride * buffer.height * 4);
            
            UpdateFrameBuffer(&buffer, src.data);

            //提交刷新
            ANativeWindow_unlockAndPost(window);
        } while (0);

將RGA數據填充到ANativeWindow_Buffer代碼如下

參考自Github:ndk-samples:webp_view.cpp#UpdateFrameBuffer

*
 * UpdateFrameBuffer():
 *     Internal function to perform bits copying onto current frame buffer
 *     src:
 *        - if nullptr, blank it
 *        - otherwise,  copy to given buf
 *     assumption:
 *         src and bug MUST be in the same geometry format & layout
 */
void UpdateFrameBuffer(ANativeWindow_Buffer *buf, uint8_t *src) {
    // src is either null: to blank the screen
    //     or holding exact pixels with the same fmt [stride is the SAME]
    uint8_t *dst = reinterpret_cast<uint8_t *> (buf->bits);
    uint32_t bpp;
    switch (buf->format) {
        case WINDOW_FORMAT_RGB_565:
            bpp = 2;
            break;
        case WINDOW_FORMAT_RGBA_8888:
        case WINDOW_FORMAT_RGBX_8888:
            bpp = 4;
            break;
        default:
            assert(0);
            return;
    }
    uint32_t stride, width;
    stride = buf->stride * bpp;
    width = buf->width * bpp;
    if (src) {
        for (auto height = 0; height < buf->height; ++height) {
            memcpy(dst, src, width);
            dst += stride, src += width;
        }
    } else {
        for (auto height = 0; height < buf->height; ++height) {
            memset(dst, 0, width);
            dst += stride;
        }
    }
}

注意問題說明

  • OpenCV 需要依賴 gnustl_static,參考自opencv-3.4.3-android-sdk\samples\face-detection\jni\Application.mk, NDK r18b中 移除了gnustl_static,注意選擇NDK17及以下版本
  • 目前Camera預覽數據是640X480,在驍龍820手機設備上單幀需要10ms左右,加大尺寸效率會降低
  • OpenCV提供的模型對側臉、臉部明暗相差大等情況的識別效果不是特別好
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章