4-ndk学习之opencv(1)
首先解释下opencv,它是一个基于BSD许可(开源)发行的跨平台计算机视觉库,可以运行在Linux、Windows、Android和Mac OS操作系统上。普遍应用于人脸识别,车牌识别等场景。
opencv集成
1.首先需要在官网下载opencv-sdk库,下载地址为https://opencv.org/releases/,下载对应的sdk包,我这里使用的是3.4.8版本。
因为我这里主要是针对ndk进行研究,所以抛弃java层,直接找到目录OpenCV-android-sdk\sdk\native,其中libs目录下已经有编译好的so库,jni目录下的include目录,里面是一些很基础重要的头文件,图像识别的api基本都在这里,我们需要在as项目中关联。
2.将so文件复制在libs目录下,同时需要在gradle和cmake中配置
在app.gradle下配置:
在android{}中:
sourceSets {
main {
//jni库的调用会到资源文件夹下libs里面找so文件
jniLibs.srcDirs = ['C:/Users/wenda/Desktop/OpenCV-android-sdk/sdk/native/libs']
}
}
在cmake中配置:
#用来设置编译本地native library的时候需要的Cmake最小版本
#这个是创建AndroidStudio项目的时候自动生成,不需要太在意.
cmake_minimum_required(VERSION 3.4.1)
set(CMAKE_VERBOSE_MAKEFILE on)
#打印日志
message("aaaaaa")
#定义变量opencvlibs使后面的命令可以使用定位具体的库文件
set(opencvlibs "C:/Users/wenda/Desktop/OpenCV-android-sdk/sdk/native/libs")
#调用头文件的具体路径
include_directories(C:/Users/wenda/Desktop/OpenCV-android-sdk/sdk/native/jni/include)
add_library(libopencv_java3 SHARED IMPORTED)
#如果你引用了其他的so库,关联
set_target_properties(libopencv_java3 PROPERTIES IMPORTED_LOCATION "${opencvlibs}/${ANDROID_ABI}/libopencv_java3.so")
#加入cpp源文件
add_library(
native-lib #设置本地lib的name
SHARED # 动态库 Linux .so Windows .dll
native-lib.cpp
)
#这里都是获取的ndk系统自带的so库,这个的作用是用来让我们加一些编译本地NDK库的时候所用的到一些依赖库.
find_library(
log-lib#是这个库的别名,在ndk,platform,ndk16,lib下的so库
log#是我们调试的时候打印log的一个库
)
message("当前的log路径在哪里=====================" ${log-lib})
#链接到so库
target_link_libraries(
#这个的目的是用来关联我们本地的库跟第三方的库.这里就是把native-lib库和log库关联起来.
native-lib
libopencv_java3
android
${log-lib}
)
至此,我们便成功的集成到了项目中。
但是配置上argument以后还是报错,最终最终的坑居然是降ndk版本,换成r16b就可以了,不需要配置argument。谨记谨记。
opencv代码
首先我们需要将opencv的sdk提供的一个人脸集保存到项目中,assets目录下就可以,这个人脸集人脸集合越大,识别效果越好,所以实际开发中,我们需要采集到人脸将人脸保存下来,存到人脸集中(一般在服务器存),目录是在,OpenCV-android-sdk\sdk\etc\lbpcascades中的lbpcascade_frontalface.xml
知识点我自己有些时候看起来也有点吃力,例如什么卷积,特征向量,矩阵,全都还给大学老师了,重在撸码,先把人脸识别出来。
java层的就不贴了,就是一个camerahelper的工具,在FaceDetectionActivity的onresume方法定义一个init的native方法,在onstop中,定义一个release的native方法,在摄像头回调中,调用postData的native方法,在surfaceview的surfaceChanged方法中,调用setSurface的native方法,在native层刷新画布。
在FaceDetectionActivity中,关键方法如下:
/**
* 初始化opencv
*/
native void init(String model);
/**
* 设置画布
* @param surface 画布
*/
native void setSurface(Surface surface);
/**
* 处理摄像头的数据
* @param data 图片数组
* @param width 宽度
* @param height 高度
* @param cameraId 摄像头id 区分前后摄像头
*/
native void postData(byte[] data, int width, int height, int cameraId);
/**
* 释放跟踪器
*/
native void release();
在native-lib.cpp中:
#include <jni.h>
#include <string>
#include <opencv2/opencv.hpp>
#include <opencv2/imgcodecs.hpp>
#include <android/native_window_jni.h>
#include <android/log.h>
using namespace cv;
ANativeWindow *window = 0;
#define TAG "ftd"
// 定义info信息
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__)
class CascadeDetectorAdapter : public DetectionBasedTracker::IDetector {
private:
CascadeDetectorAdapter();
cv::Ptr<cv::CascadeClassifier> Detector;
public:
CascadeDetectorAdapter(cv::Ptr<cv::CascadeClassifier> detector) :
IDetector(),
Detector(detector) {
CV_Assert(detector);
}
void detect(const cv::Mat &Image, std::vector<cv::Rect> &objects) {
Detector->detectMultiScale(Image,objects,scaleFactor,minNeighbours,0,minObjSize,maxObjSize);
}
virtual ~CascadeDetectorAdapter() {
}
};
//追踪器
DetectionBasedTracker *tracker = 0;
extern "C"
JNIEXPORT void JNICALL
Java_com_example_myapplication_FaceDetectionActivity_init(JNIEnv *env, jobject thiz,
jstring _model) {
LOGI("_init :%s","==============bengin===================");
const char *model = env->GetStringUTFChars(_model, 0);
if(model == NULL){
return;
}
if (tracker) {
//防止内存泄漏
tracker->stop();
delete tracker;
tracker = 0;
}
//1.makePtr 创建CascadeClassifier
Ptr<CascadeClassifier> classifier = makePtr<CascadeClassifier>(model);
//创建一个跟踪适配器
Ptr<CascadeDetectorAdapter> mainDetector = makePtr<CascadeDetectorAdapter>(classifier);
Ptr<CascadeClassifier> classifier1 = makePtr<CascadeClassifier>(model);
//创建一个跟踪适配器
Ptr<CascadeDetectorAdapter> trackingDetector = makePtr<CascadeDetectorAdapter>(classifier1);
//拿去用的跟踪器
DetectionBasedTracker::Parameters DetectorParams;
//不断跟踪识别人脸
tracker = new DetectionBasedTracker(mainDetector, trackingDetector, DetectorParams);
//开启跟踪器
tracker->run();
env->ReleaseStringUTFChars(_model, model);
LOGI("_init :%s","============ok=====================");
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_myapplication_FaceDetectionActivity_release(JNIEnv *env, jobject instance) {
if (tracker) {
tracker->stop();
delete tracker;
tracker = 0;
}
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_myapplication_FaceDetectionActivity_setSurface(JNIEnv *env, jobject
instance,jobject surface) {
//设置画布进行刷新
if (window) {
ANativeWindow_release(window);
window = 0;
}
window = ANativeWindow_fromSurface(env, surface);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_myapplication_FaceDetectionActivity_postData(JNIEnv *env, jobject instance, jbyteArray data_,jint width, jint height, jint cameraId) {
//传过来的数据都是nv21的数据,而OpenCv是在Mat中处理的,
jbyte *data = env->GetByteArrayElements(data_, NULL);
//所以将data数据添加到Mat中
//1.高(nv21模型转换) 2.宽,3.
Mat src(height + height / 2, width, CV_8UC1, data);
//颜色格式转换 nv21 转成 RGBA
//将nv21的yuv数据转成rgba
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;
//定位人脸 N个
tracker->process(gray); //处理摄像头采集后的数据(Mat 灰度)
tracker->getObjects(faces); //
for(int i =0;i<faces.size();i++){
LOGI("the string is :%s","Rect");
rectangle(src, faces.at(i), Scalar(255, 0, 255));
//将截取到的人脸保存到sdcard
Mat m;
//把img中的人脸部位拷到m中
src(faces.at(i)).copyTo(m);
//把人脸从新定义为24*24的大小图片
resize(m, m, Size(24, 24));
//置灰
cvtColor(m, m, COLOR_BGR2GRAY);
char p[100];
sprintf(p, "mnt/sdcard/info/%d.jpg", i);
//把mat写出为jpg文件
//这里可以控制一下数量
imwrite(p, m);
LOGI("图片保存成功");
}
//显示
if (window) {
//设置windows的属性
// 因为旋转了 所以宽、高需要交换
//这里使用 cols 和rows 代表 宽、高 就不用关心上面是否旋转了
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 里去
//填充rgb数据给dst_data
uint8_t *dst_data = static_cast<uint8_t *>(buffer.bits);
//stride : 一行多少个数据 (RGBA) * 4
int dst_line_size = buffer.stride * 4;
//一行一行拷贝
for (int i = 0; i < buffer.height; ++i) {
//void *memcpy(void *dest, const void *src, size_t n);
//从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中
memcpy(dst_data + i * dst_line_size,
src.data + i * src.cols * 4, dst_line_size);
}
//提交刷新
ANativeWindow_unlockAndPost(window);
} while (0);
}
//释放Mat
//内部采用的 引用计数
src.release();
gray.release();
env->ReleaseByteArrayElements(data_, data, 0);
}
如果有不对的地方,希望大家在评论区多多指正,共同学习,谢谢大家。