美文网首页
4-ndk学习之opencv(1)

4-ndk学习之opencv(1)

作者: ftd黑马 | 来源:发表于2020-04-01 17:06 被阅读0次

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

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

    相关文章

      网友评论

          本文标题:4-ndk学习之opencv(1)

          本文链接:https://www.haomeiwen.com/subject/rhbwuhtx.html