前言
前面的文章《Android通过OpenCV和TesserartOCR实时进行识别》我们已经搭好一个利用NDK方式实时处理摄像头数据的程序了,今天我们就在看看OpenCV中通过级联方式实时进行人脸检测。
视频效果
特别说明
本章我把OpenCV版本改为了4.1,原因是用Opencv3.4.6版本时,在做编译运行后报错
在网上找了好多资料,如在build.gradle中改
都无法解决这个问题,所以我换了OpenCV4.1后完全无问题了。等我再研究研究找到解决OpenCV3.4.6的问题后,会专门写一章来说明。
代码演示
为了减少前面环境搭建,我们直接用《Android通过OpenCV和TesserartOCR实时进行识别》项目,在这个基础上直接实现我们的人脸检测。
haarcascade_frontalface_alt2.xml
级联检测的数据文件,这个文件是OpenCV已经训练好的数据,我们直接拿来就可以用,文件在OpenCV的源码下的data\haarcascades文件夹下,网上也可以找到这个文件的下载链接。
找到文件后我们需要把这个文件存放到Android项目的资源文件下,在res下新建一个raw的类型
然后把我们的haarcascade_frontalface_alt2.xml拷贝到raw下面
然后在MainActivity下面定义一个File类型,写一个将训练文件复制到Android本地的方法,便于后面NDK调动训练文件时可以直接从路径中加载。
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的文件
facedetector.h
头文件中写入加载训练文件(loadcascade)和人脸检测(detectorface)两个方法。
facedetector.cpp
CPP文件中写两个方法的实现,首先定义了一个CascadeClassifier
加载训练文件,这里单独列出加载训练文件是因为我们打开摄像头时就先加载过来,后面直接进行检测即可,如果每一帧都要重新加载,被影响速度。
人脸检测方法
CPP的全部代码
//
// Created by 36574 on 2019-07-11.
//
#include "facedetector.h"
CascadeClassifier cascadeClassifier;
void facedetector::loadcascade(char *filepath) {
cascadeClassifier.load(filepath);
}
//人脸检测
vector<Mat> facedetector::detectorface(Mat &src) {
//用于存放识别到的图像
std::vector<Mat>output;
std::vector<Rect> 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的类里面加入一个加载训练文件的方法
然后通过ALT+ENTER会在native-lib.cpp生成对应的方法
native-lib.cpp
我们在这里直接调用facedetector类中的loadcascade即可。
然后在native-lib.cpp中原来的getCameraframebitbmp方法后屏蔽掉我们原来的检测,改为调用facedetector类中的detectorface,如下:
完整的native-lib.cpp
#include <jni.h>
#include <string>
#include <android/log.h>
#include <android/bitmap.h>
#include <opencv2/opencv.hpp>
#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, "<init>", "()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<cv::Mat> outdsts=testcv::getrectdetector(src);
std::vector<cv::Mat> 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<Bitmap>列表中
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<char *>(filepath));
env->ReleaseStringUTFChars(filepath_, filepath);
}
最后我们在MainActivity开户摄像头前加入加载训练文件的过程即可。
视频中的截图
-END-