4-ndk学习之opencv(1)

2020-04-01  本文已影响0人  ftd黑马

首先解释下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}
)

至此,我们便成功的集成到了项目中。

这里有一个坑,我花了两天时间踩的,就是说,我第一次下载的ndk是r18b的版本,编译是没有问题的,一运行就提示 image.png ,查阅资料翻看源码,得知需要在gradle配置, image.png

但是配置上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);
}

如果有不对的地方,希望大家在评论区多多指正,共同学习,谢谢大家。

上一篇下一篇

猜你喜欢

热点阅读