首先解釋下opencv,它是一個基于BSD許可(開源)發(fā)行的跨平臺計算機(jī)視覺庫,可以運(yùn)行在Linux、Windows、Android和Mac OS操作系統(tǒng)上。普遍應(yīng)用于人臉識別,車牌識別等場景。
opencv集成
1.首先需要在官網(wǎng)下載opencv-sdk庫,下載地址為https://opencv.org/releases/,下載對應(yīng)的sdk包,我這里使用的是3.4.8版本。
因?yàn)槲疫@里主要是針對ndk進(jìn)行研究,所以拋棄java層,直接找到目錄OpenCV-android-sdk\sdk\native,其中l(wèi)ibs目錄下已經(jīng)有編譯好的so庫,jni目錄下的include目錄,里面是一些很基礎(chǔ)重要的頭文件,圖像識別的api基本都在這里,我們需要在as項(xiàng)目中關(guān)聯(lián)。
2.將so文件復(fù)制在libs目錄下,同時需要在gradle和cmake中配置
在app.gradle下配置:
在android{}中:
sourceSets {
main {
//jni庫的調(diào)用會到資源文件夾下libs里面找so文件
jniLibs.srcDirs = ['C:/Users/wenda/Desktop/OpenCV-android-sdk/sdk/native/libs']
}
}
在cmake中配置:
#用來設(shè)置編譯本地native library的時候需要的Cmake最小版本
#這個是創(chuàng)建AndroidStudio項(xiàng)目的時候自動生成,不需要太在意.
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")
#調(diào)用頭文件的具體路徑
include_directories(C:/Users/wenda/Desktop/OpenCV-android-sdk/sdk/native/jni/include)
add_library(libopencv_java3 SHARED IMPORTED)
#如果你引用了其他的so庫,關(guān)聯(lián)
set_target_properties(libopencv_java3 PROPERTIES IMPORTED_LOCATION "${opencvlibs}/${ANDROID_ABI}/libopencv_java3.so")
#加入cpp源文件
add_library(
native-lib #設(shè)置本地lib的name
SHARED # 動態(tài)庫 Linux .so Windows .dll
native-lib.cpp
)
#這里都是獲取的ndk系統(tǒng)自帶的so庫,這個的作用是用來讓我們加一些編譯本地NDK庫的時候所用的到一些依賴庫.
find_library(
log-lib#是這個庫的別名,在ndk,platform,ndk16,lib下的so庫
log#是我們調(diào)試的時候打印log的一個庫
)
message("當(dāng)前的log路徑在哪里=====================" ${log-lib})
#鏈接到so庫
target_link_libraries(
#這個的目的是用來關(guān)聯(lián)我們本地的庫跟第三方的庫.這里就是把native-lib庫和log庫關(guān)聯(lián)起來.
native-lib
libopencv_java3
android
${log-lib}
)
至此,我們便成功的集成到了項(xiàng)目中。


但是配置上argument以后還是報錯,最終最終的坑居然是降ndk版本,換成r16b就可以了,不需要配置argument。謹(jǐn)記謹(jǐn)記。
opencv代碼
首先我們需要將opencv的sdk提供的一個人臉集保存到項(xiàng)目中,assets目錄下就可以,這個人臉集人臉集合越大,識別效果越好,所以實(shí)際開發(fā)中,我們需要采集到人臉將人臉保存下來,存到人臉集中(一般在服務(wù)器存),目錄是在,OpenCV-android-sdk\sdk\etc\lbpcascades中的lbpcascade_frontalface.xml
知識點(diǎn)我自己有些時候看起來也有點(diǎn)吃力,例如什么卷積,特征向量,矩陣,全都還給大學(xué)老師了,重在擼碼,先把人臉識別出來。
java層的就不貼了,就是一個camerahelper的工具,在FaceDetectionActivity的onresume方法定義一個init的native方法,在onstop中,定義一個release的native方法,在攝像頭回調(diào)中,調(diào)用postData的native方法,在surfaceview的surfaceChanged方法中,調(diào)用setSurface的native方法,在native層刷新畫布。
在FaceDetectionActivity中,關(guān)鍵方法如下:
/**
* 初始化opencv
*/
native void init(String model);
/**
* 設(shè)置畫布
* @param surface 畫布
*/
native void setSurface(Surface surface);
/**
* 處理攝像頭的數(shù)據(jù)
* @param data 圖片數(shù)組
* @param width 寬度
* @param height 高度
* @param cameraId 攝像頭id 區(qū)分前后攝像頭
*/
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) {
//防止內(nèi)存泄漏
tracker->stop();
delete tracker;
tracker = 0;
}
//1.makePtr 創(chuàng)建CascadeClassifier
Ptr<CascadeClassifier> classifier = makePtr<CascadeClassifier>(model);
//創(chuàng)建一個跟蹤適配器
Ptr<CascadeDetectorAdapter> mainDetector = makePtr<CascadeDetectorAdapter>(classifier);
Ptr<CascadeClassifier> classifier1 = makePtr<CascadeClassifier>(model);
//創(chuàng)建一個跟蹤適配器
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) {
//設(shè)置畫布進(jìn)行刷新
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) {
//傳過來的數(shù)據(jù)都是nv21的數(shù)據(jù),而OpenCv是在Mat中處理的,
jbyte *data = env->GetByteArrayElements(data_, NULL);
//所以將data數(shù)據(jù)添加到Mat中
//1.高(nv21模型轉(zhuǎn)換) 2.寬,3.
Mat src(height + height / 2, width, CV_8UC1, data);
//顏色格式轉(zhuǎn)換 nv21 轉(zhuǎn)成 RGBA
//將nv21的yuv數(shù)據(jù)轉(zhuǎn)成rgba
cvtColor(src, src, COLOR_YUV2RGBA_NV21);
//如果正在寫的過程中退出,導(dǎo)致文件丟失
if (cameraId == 1) {
//前置攝像頭,需要逆時針旋轉(zhuǎn)90度
rotate(src, src, ROTATE_90_COUNTERCLOCKWISE);
//水平翻轉(zhuǎn) 鏡像1水平 0為垂直
flip(src, src, 1);
} else {
//順時針旋轉(zhuǎn)90度
rotate(src, src, ROTATE_90_CLOCKWISE);
}
Mat gray;
//灰色
cvtColor(src, gray, COLOR_RGBA2GRAY);
//增強(qiáng)對比度 (直方圖均衡) (優(yōu)化代碼)
equalizeHist(gray, gray); //優(yōu)化
std::vector<Rect> faces;
//定位人臉 N個
tracker->process(gray); //處理攝像頭采集后的數(shù)據(jù)(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文件
//這里可以控制一下數(shù)量
imwrite(p, m);
LOGI("圖片保存成功");
}
//顯示
if (window) {
//設(shè)置windows的屬性
// 因?yàn)樾D(zhuǎn)了 所以寬、高需要交換
//這里使用 cols 和rows 代表 寬、高 就不用關(guān)心上面是否旋轉(zhuǎn)了
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的數(shù)據(jù)
//把src.data 一行一行的拷貝到 buffer.bits 里去
//填充rgb數(shù)據(jù)給dst_data
uint8_t *dst_data = static_cast<uint8_t *>(buffer.bits);
//stride : 一行多少個數(shù)據(jù) (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èi)存地址的起始位置開始拷貝n個字節(jié)到目標(biāo)dest所指的內(nèi)存地址的起始位置中
memcpy(dst_data + i * dst_line_size,
src.data + i * src.cols * 4, dst_line_size);
}
//提交刷新
ANativeWindow_unlockAndPost(window);
} while (0);
}
//釋放Mat
//內(nèi)部采用的 引用計數(shù)
src.release();
gray.release();
env->ReleaseByteArrayElements(data_, data, 0);
}
如果有不對的地方,希望大家在評論區(qū)多多指正,共同學(xué)習(xí),謝謝大家。