Android NDK OpenCV级联方式实时进行人脸检测

前言

前面的文章《Android通过OpenCV和TesserartOCR实时进行识别》我们已经搭好一个利用NDK方式实时处理摄像头数据的程序了,今天我们就在看看OpenCV中通过级联方式实时进行人脸检测。

视频效果

特别说明

本章我把OpenCV版本改为了4.1,原因是用Opencv3.4.6版本时,在做编译运行后报错

Android NDK OpenCV级联方式实时进行人脸检测_第1张图片

在网上找了好多资料,如在build.gradle中改

Android NDK OpenCV级联方式实时进行人脸检测_第2张图片

都无法解决这个问题,所以我换了OpenCV4.1后完全无问题了。等我再研究研究找到解决OpenCV3.4.6的问题后,会专门写一章来说明。

代码演示

为了减少前面环境搭建,我们直接用《Android通过OpenCV和TesserartOCR实时进行识别》项目,在这个基础上直接实现我们的人脸检测。

haarcascade_frontalface_alt2.xml

级联检测的数据文件,这个文件是OpenCV已经训练好的数据,我们直接拿来就可以用,文件在OpenCV的源码下的data\haarcascades文件夹下,网上也可以找到这个文件的下载链接。

Android NDK OpenCV级联方式实时进行人脸检测_第3张图片

找到文件后我们需要把这个文件存放到Android项目的资源文件下,在res下新建一个raw的类型

Android NDK OpenCV级联方式实时进行人脸检测_第4张图片

Android NDK OpenCV级联方式实时进行人脸检测_第5张图片

然后把我们的haarcascade_frontalface_alt2.xml拷贝到raw下面

Android NDK OpenCV级联方式实时进行人脸检测_第6张图片

然后在MainActivity下面定义一个File类型,写一个将训练文件复制到Android本地的方法,便于后面NDK调动训练文件时可以直接从路径中加载。

Android NDK OpenCV级联方式实时进行人脸检测_第7张图片

    private File mCascadeFile;


    private void copyCascadeFile() {
        try {
            // load cascade file from application resources
            InputStream is = getResources().openRawResource(R.raw.haarcascade_frontalface_alt2);
            File cascadeDir = getDir("cascade", Context.MODE_PRIVATE);
            mCascadeFile = new File(cascadeDir, "lbpcascade_frontalface2.xml");
            if(mCascadeFile.exists()) return;
            FileOutputStream os = new FileOutputStream(mCascadeFile);


            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = is.read(buffer)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
            is.close();
            os.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


然后在CPP下面新建一个facedetector的文件

Android NDK OpenCV级联方式实时进行人脸检测_第8张图片

facedetector.h

头文件中写入加载训练文件(loadcascade)和人脸检测(detectorface)两个方法。

Android NDK OpenCV级联方式实时进行人脸检测_第9张图片

facedetector.cpp

CPP文件中写两个方法的实现,首先定义了一个CascadeClassifier

加载训练文件,这里单独列出加载训练文件是因为我们打开摄像头时就先加载过来,后面直接进行检测即可,如果每一帧都要重新加载,被影响速度。

Android NDK OpenCV级联方式实时进行人脸检测_第10张图片

人脸检测方法

  1. 转为灰度图

  2. 直方图均衡化

  3. 多尺度检测detectMultiScale

  4. 在源图上绘制检测的矩形

Android NDK OpenCV级联方式实时进行人脸检测_第11张图片

CPP的全部代码

//
// Created by 36574 on 2019-07-11.
//


#include "facedetector.h"


CascadeClassifier cascadeClassifier;


void facedetector::loadcascade(char *filepath) {
    cascadeClassifier.load(filepath);
}


//人脸检测
vector facedetector::detectorface(Mat &src) {
    //用于存放识别到的图像
    std::vectoroutput;


    std::vector faces;
    Mat gray;


    //灰度图
    cvtColor(src, gray, COLOR_BGRA2GRAY);
    //直方图均衡化
    equalizeHist(gray, gray);


    //多尺度人脸检测
    cascadeClassifier.detectMultiScale(gray, faces, 2, 3, 0);
    //在源图上画出人脸
    for (int i = 0; i < faces.size(); i++) {
        rectangle(src, faces[i], Scalar(255, 0, 255), 2);
    }


    return output;
}

VaccaeOpenCVJNI

我们在OpenCVJNI的类里面加入一个加载训练文件的方法

Android NDK OpenCV级联方式实时进行人脸检测_第12张图片

然后通过ALT+ENTER会在native-lib.cpp生成对应的方法

native-lib.cpp

Android NDK OpenCV级联方式实时进行人脸检测_第13张图片

我们在这里直接调用facedetector类中的loadcascade即可。

然后在native-lib.cpp中原来的getCameraframebitbmp方法后屏蔽掉我们原来的检测,改为调用facedetector类中的detectorface,如下:

Android NDK OpenCV级联方式实时进行人脸检测_第14张图片

完整的native-lib.cpp

#include 
#include 
#include 
#include 
#include 
#include "testcv.h"
#include "facedetector.h"


#define LOG_TAG "System.out"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)


//将Mat转换为bitmap
jobject mat2bitmap(JNIEnv *env, cv::Mat &src, bool needPremultiplyAlpha, jobject bitmap_config) {
    jclass java_bitmap_class = (jclass) env->FindClass("android/graphics/Bitmap");
    jmethodID mid = env->GetStaticMethodID(java_bitmap_class, "createBitmap",
                                           "(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");
    jobject bitmap = env->CallStaticObjectMethod(java_bitmap_class,
                                                 mid, src.size().width, src.size().height,
                                                 bitmap_config);
    AndroidBitmapInfo info;
    void *pixels = 0;


    try {
        CV_Assert(AndroidBitmap_getInfo(env, bitmap, &info) >= 0);
        CV_Assert(src.type() == CV_8UC1 || src.type() == CV_8UC3 || src.type() == CV_8UC4);
        CV_Assert(AndroidBitmap_lockPixels(env, bitmap, &pixels) >= 0);
        CV_Assert(pixels);


        if (info.format == ANDROID_BITMAP_FORMAT_RGBA_8888) {
            cv::Mat tmp(info.height, info.width, CV_8UC4, pixels);
            if (src.type() == CV_8UC1) {
                cvtColor(src, tmp, cv::COLOR_GRAY2RGBA);
            } else if (src.type() == CV_8UC3) {
                cvtColor(src, tmp, cv::COLOR_RGB2BGRA);
            } else if (src.type() == CV_8UC4) {
                if (needPremultiplyAlpha) {
                    cvtColor(src, tmp, cv::COLOR_RGBA2mRGBA);
                } else {
                    src.copyTo(tmp);
                }
            }
        } else {
            // info.format == ANDROID_BITMAP_FORMAT_RGB_565
            cv::Mat tmp(info.height, info.width, CV_8UC2, pixels);
            if (src.type() == CV_8UC1) {
                cvtColor(src, tmp, cv::COLOR_GRAY2BGR565);
            } else if (src.type() == CV_8UC3) {
                cvtColor(src, tmp, cv::COLOR_RGB2BGR565);
            } else if (src.type() == CV_8UC4) {
                cvtColor(src, tmp, cv::COLOR_RGBA2BGR565);
            }
        }
        AndroidBitmap_unlockPixels(env, bitmap);
        return bitmap;
    } catch (cv::Exception e) {
        AndroidBitmap_unlockPixels(env, bitmap);
        jclass je = env->FindClass("org/opencv/core/CvException");
        if (!je) je = env->FindClass("java/lang/Exception");
        env->ThrowNew(je, e.what());
        return bitmap;
    } catch (...) {
        AndroidBitmap_unlockPixels(env, bitmap);
        jclass je = env->FindClass("java/lang/Exception");
        env->ThrowNew(je, "Unknown exception in JNI code {nMatToBitmap}");
        return bitmap;
    }
}






extern "C"
JNIEXPORT jobject JNICALL
Java_dem_vac_tesseractocr_VaccaeOpenCVJNI_getCameraframebitbmp(JNIEnv *env, jclass type,
                                                               jobject bmp, jstring text_) {
    const char *text = env->GetStringUTFChars(text_, 0);


    AndroidBitmapInfo bitmapInfo;
    void *pixelscolor;
    int ret;


    //获取图像信息,如果返回值小于0就是执行失败
    if ((ret = AndroidBitmap_getInfo(env, bmp, &bitmapInfo)) < 0) {
        LOGI("AndroidBitmap_getInfo failed! error-%d", ret);
        return NULL;
    }


    //判断图像类型是不是RGBA_8888类型
    if (bitmapInfo.format != ANDROID_BITMAP_FORMAT_RGBA_8888) {
        LOGI("BitmapInfoFormat error");
        return NULL;
    }


    //获取图像像素值
    if ((ret = AndroidBitmap_lockPixels(env, bmp, &pixelscolor)) < 0) {
        LOGI("AndroidBitmap_lockPixels() failed ! error=%d", ret);
        return NULL;
    }


    //获取ArrayList类引用
    jclass list_jcls = env->FindClass("java/util/ArrayList");
    if (list_jcls == NULL) {
        LOGI("ArrayList没找到相关类!");
        return 0;
    }


    //获取ArrayList构造函数id
    jmethodID list_init = env->GetMethodID(list_jcls, "", "()V");
    //创建一个ArrayList对象
    jobject list_obj = env->NewObject(list_jcls, list_init, "");




    //获取ArrayList对象的add()的methodID
    jmethodID list_add = env->GetMethodID(list_jcls, "add", "(Ljava/lang/Object;)Z");


    //生成源图像
    cv::Mat src(bitmapInfo.height, bitmapInfo.width, CV_8UC4, pixelscolor);


    //图像处理
//    std::vector outdsts=testcv::getrectdetector(src);
    std::vector outdsts = facedetector::detectorface(src);


    //获取原图片的参数
    jclass java_bitmap_class = (jclass) env->FindClass("android/graphics/Bitmap");
    jmethodID mid = env->GetMethodID(java_bitmap_class, "getConfig",
                                     "()Landroid/graphics/Bitmap$Config;");
    jobject bitmap_config = env->CallObjectMethod(bmp, mid);
    //将SRC转换为图片
    jobject _bitmap = mat2bitmap(env, src, false, bitmap_config);


    env->CallBooleanMethod(list_obj, list_add, _bitmap);


    //判断有截出的图像后加入到返回的List列表中
    if(outdsts.size()>0) {
        for (int i = 0; i < outdsts.size(); i++) {
            jobject dstbmp = mat2bitmap(env, outdsts[i], false, bitmap_config);
            env->CallBooleanMethod(list_obj, list_add, dstbmp);
        }
    }


    AndroidBitmap_unlockPixels(env, bmp);




    return list_obj;
}


extern "C"
JNIEXPORT void JNICALL
Java_dem_vac_tesseractocr_VaccaeOpenCVJNI_loadcascade(JNIEnv *env, jclass type, jstring filepath_) {
    const char *filepath = env->GetStringUTFChars(filepath_, 0);


    // TODO
    facedetector::loadcascade(const_cast(filepath));


    env->ReleaseStringUTFChars(filepath_, filepath);
}

最后我们在MainActivity开户摄像头前加入加载训练文件的过程即可。

Android NDK OpenCV级联方式实时进行人脸检测_第15张图片

视频中的截图

Android NDK OpenCV级联方式实时进行人脸检测_第16张图片

-END-

Vaccae的往期经典


OpenCV

《C++ OpenCV案例实战---卡号获取》

《C++ OpenCV案例实战---卡片截取(附代码)》

《C++ OpenCV透视变换---切换手机正面图片》

《C++ OpenCV实战---获取数量》

《C++ OpenCV实战---利用颜色分割获取数量》

《OpenCV4Android NDK方式进行Canny边缘检测》

《OpenCV4Android NDK方式TesserartOCR实时进行识别》


Android

《Android利用SurfaceView结合科大讯飞修改语音实别UI》

《Android关于语音识别的功能实现分析(一)---结构化思维》

《Android关于语音识别的功能实现分析(二)---语义解析》

《Android根据类生成签名字符串》

《Android碎片化布局fragment的实战应用》

《Android中RecyclerView嵌套RecyclerView》

《Android里用AsyncTask后的接口回调》


.Net C#

《C#自定义特性(Attribute)讲解与实际应用》

《C#根据类生成签名字符串(附DEMO下载地址)》

《C++创建动态库C#调用》

《C#与三菱PLC(型号FX2N)串口通讯类》


数据库及其它

《Oracel存储过程写报表实战》

《Delphi轮播视频和图片程序(用于双屏显示程序)》

《SQL随机增加销售数据的脚本编写(附脚本下载地址)》

《SQL Server中With As的介绍与应用(三)--递归的实战应用》

《Oracle通过ODBC连接SQL Server数据库》

《Oracle利用row_number()over()方式解决插入数据时重复键的问题》


长按下方二维码关注微卡智享

你可能感兴趣的:(java,python,android,计算机视觉,人工智能)