美文网首页
OpenCV之Android中的应用

OpenCV之Android中的应用

作者: 张小潇 | 来源:发表于2020-02-06 13:43 被阅读0次

    前言

    Android的开发与其他平台的开发显着不同。所以在开始编程Android之前,我们建议您确保您熟悉以下主题:

    1. Java编程语言是Android操作系统的主要开发技术。kotlin虽然越来越流行,但是对java是全兼容的。
    2. Java本机接口(JNI)是Java虚拟机中运行本机代码的技术。此外,您可以在JNI上找到Oracle文档
    3. Android Activity及其生命周期,这是一个必不可少的Android API类。
    4. OpenCV的开发一定要了解Android Camera的具体细节。

    一、准备工作

    1、环境

    MacOS 10.15.1
    Android studio 3.2
    Android NDK : android-ndk-r20
    Opencv4.2.0

    2、下载

    Opencv
    NDK

    二、集成OpenCV

    OpenCV不需要我们自己去交差编译生成动/静态库,解压后的文件已经包含了动态库。下载库、导入.h和动/静态库、配置CmakeList。详细步骤:

    1、AndroidStudio创建NDK项目:

    创建项目

    2、导入.h文件和.so动态库资源:

    资源导入

    3、CMakeLists.txt文件配置:

    CMakeLists.txt

    4、在app的build.gradle添加:

     sourceSets {
            main {
                jniLibs.srcDirs = ['src/main/jniLibs']
            }
        }
    

    注意:在NDKr18开始,去掉gnustl_static,如果你是使用NDKr18以下,使用Opencv3_SDK,Opencv3使用的是gnustl_static,Opencv4开始已经去掉了gnustl_static,使用Opencv3需要在build.gradle做相对应配置:

     externalNativeBuild {
                cmake {
                    cppFlags ""
                    abiFilters 'armeabi-v7a'
                    //因为opencv 需要依赖 gnustl_static
                    // r18b的ndk gnustl_static被移除了,所以别用r18b
                    arguments "-DANDROID_STL=gnustl_static"
                }
            }
    

    三、使用官方人脸识别模型写个Demo

    1、创建NDK项目,以下是目录结构:

    目录结构

    2、找到官方提供的人脸识别模型,复制到assets目录中:

    assets

    3、添加相对应帮助类:

    Utils:需要加载人脸识别模型到内置sd卡中
    CameraHelper:摄像头处理帮助类

    4、创建追踪适配器类DetectionBasedTracker_jni.h放在include目录下:

    #include <jni.h>
    #include <iostream>
    #include <opencv2/core.hpp>
    #include <opencv2/objdetect.hpp>
    
    using namespace std;
    using namespace cv;
    
    class CascadeDetectorAdapter: public DetectionBasedTracker::IDetector
    {
    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()
        {
        }
    
    private:
        CascadeDetectorAdapter();
        cv::Ptr<cv::CascadeClassifier> Detector;
    };
    

    5、创建JNI类

    package com.itzxx.facetrackingdemo;
    import android.view.Surface;
    
    public class OpencvNativeJni {
    
        // Used to load the 'native-lib' library on application startup.
        static {
            System.loadLibrary("native-lib");
        }
    
        /**
         * 初始化 追踪器
         * @param model sd卡路径
         */
        public native void init(String model);
    
        /**
         * 设置画布
         * ANativeWindow
         */
        public native void setSurface(Surface surface);
    
        /**
         * 处理摄像头数据,进行渲染
         */
        public native void postData(byte[] data, int w, int h, int cameraId);
    
        /**
         * 释放资源
         */
        public native void release();
    }
    

    6、初始化opencv的分类器和追踪器(两个追踪适配器)

    extern "C"
    JNIEXPORT void JNICALL
    Java_com_itzxx_facetrackingdemo_OpencvNativeJni_init(JNIEnv *env, jobject thiz, jstring surface) {
        // TODO: implement init()
        //sdk的模型路径
        const char *model = env->GetStringUTFChars(surface, 0);
        if (tracker) {
            tracker->stop();
            delete tracker;
            tracker = 0;
        }
        //智能指针
        Ptr<CascadeClassifier> classifier = makePtr<CascadeClassifier>(model);
        //创建一个跟踪适配器
        Ptr<CascadeDetectorAdapter> mainDetector = makePtr<CascadeDetectorAdapter>(classifier);
        //智能指针1
        Ptr<CascadeClassifier> classifier1 = makePtr<CascadeClassifier>(model);
        //创建一个跟踪适配器1
        Ptr<CascadeDetectorAdapter> trackingDetector = makePtr<CascadeDetectorAdapter>(classifier1);
        //拿去用的跟踪器
        DetectionBasedTracker::Parameters DetectorParams;
        tracker = new DetectionBasedTracker(mainDetector, trackingDetector, DetectorParams);
        //开启跟踪器
        tracker->run();
        env->ReleaseStringUTFChars(surface, model);
    }
    

    7、将原生的Surface转化为NDKNative版本的Surface:

    extern "C"
    JNIEXPORT void JNICALL
    Java_com_itzxx_facetrackingdemo_OpencvNativeJni_setSurface(JNIEnv *env, jobject thiz,
                                                            jobject surface) {
        // TODO: implement setSurface()
        if (window) {
            ANativeWindow_release(window);
            window = 0;
        }
        //#编译链接NDK/platforms/android-X/usr/lib/libandroid.so
        //target_link_libraries(XXX android )
        window = ANativeWindow_fromSurface(env, surface);
    }
    

    8、主要处理图片渲染逻辑代码,主要注意的点是摄像头的问题,前置摄像需要逆时针90度,后置摄像需要顺时针10度,还有就是拷贝数据的时候宽高被弄错

    extern "C"
    JNIEXPORT void JNICALL
    Java_com_itzxx_facetrackingdemo_OpencvNativeJni_postData(JNIEnv *env, jobject thiz, jbyteArray datas,
                                                          jint w, jint h, jint camera_id) {
        // TODO: implement postData()
        // nv21的数据
        jbyte *data = env->GetByteArrayElements(datas, NULL);
        //mat  data-》Mat
        //1、高 2、宽
        Mat src(h * 3 / 2, w, CV_8UC1, data);
        //颜色格式的转换 nv21->RGBA
        //将 nv21的yuv数据转成了rgba
        cvtColor(src, src, COLOR_YUV2RGBA_NV21);
        // 正在写的过程 退出了,导致文件丢失数据
        //imwrite("/sdcard/src.jpg",src);
        if (camera_id == 1) {
            //前置摄像头,需要逆时针旋转90度
            rotate(src, src, ROTATE_90_COUNTERCLOCKWISE);
            //水平翻转 镜像
            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);
        tracker->getObjects(faces);
        for (Rect face : faces) {
            //画矩形
            //分别指定 bgra
            rectangle(src, face, Scalar(255, 0, 0));
        }
        //显示
        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 里去
                // 一行一行的拷贝
                //memcpy(buffer.bits, src.data, buffer.stride * buffer.height * 4);
                uint8_t *dst_data = static_cast<uint8_t *>(buffer.bits);
                //stride : 一行多少个数据
                //(RGBA) * 4
                int dst_linesize = buffer.stride * 4;
                //一行一行拷贝,src.data是图片的RGBA数据,要拷贝到dst_data中,也就是window的缓冲区里
                for (int i = 0; i < buffer.height; ++i) {
                    memcpy(dst_data + i * dst_linesize, src.data + i * src.cols * 4, dst_linesize);
                }
                //提交刷新
                ANativeWindow_unlockAndPost(window);
            } while (0);
        }
        //释放Mat
        //内部采用的 引用计数
        src.release();
        gray.release();
        env->ReleaseByteArrayElements(datas, data, 0);
    }
    

    9、最后记得退出的时候释放一下资源

    extern "C"
    JNIEXPORT void JNICALL
    Java_com_itzxx_facetrackingdemo_OpencvNativeJni_release(JNIEnv *env, jobject thiz) {
        // TODO: implement release()
        if (tracker) {
            tracker->stop();
            delete tracker;
            tracker = 0;
        }
    }
    

    四、项目地址

    相关文章

      网友评论

          本文标题:OpenCV之Android中的应用

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