美文网首页
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