4-ndk學(xué)習(xí)之opencv(1)

首先解釋下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)目中。

這里有一個坑,我花了兩天時間踩的,就是說,我第一次下載的ndk是r18b的版本,編譯是沒有問題的,一運(yùn)行就提示
image.png
,查閱資料翻看源碼,得知需要在gradle配置,
image.png

但是配置上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í),謝謝大家。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容