OpenCV 的集成

 

OpenCV

https://opencv.org/

OpenCV 源码

https://github.com/opencv/opencv/releases

  • 下载 opencv-3.4.4-android-sdk.zip

拷贝头文件和 so 到 Android 工程

  • E:\OpenCV\opencv-3.4.4-android-sdk\sdk\native\libs\armeabi
  • E:\OpenCV\opencv-3.4.4-android-sdk\sdk\native\jni\include

OpenCV 目录结构

Gradle 的配置

在当前的 Module 下

  • 指定 CPU 的架构类型
  • 添加 ndk 文件的查找目录
android {
    ......
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
    defaultConfig {
        multiDexEnabled true
        ndk {
            // 只生成 armeabi 的 CPU 架构的 .so
            abiFilters "armeabi"
        }
    }
    sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
            // 添加 ndk 文件查找路径
            jniLibs.srcDirs = ['src/main/jniLibs']
        }
    }
}

CMake 的配置

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.4.1)

# 判断编译器类型,如果是gcc编译器,则在编译选项中加入c++11支持
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11")
if (CMAKE_COMPILER_IS_GNUCXX)
    set(CMAKE_CXX_FLAGS "-std=c++11 ${CMAKE_CXX_FLAGS}")
    message(STATUS "optional:-std=c++11")
endif (CMAKE_COMPILER_IS_GNUCXX)

# CPP 中需要引入的头文件, 以这个配置的目录为基准, 配置后才可以使用相对路径
include_directories(src/main/jniLibs/include)

# OpenCV 配置目录
set(distribution_DIR ${CMAKE_SOURCE_DIR}/../../../../src/main/jniLibs)

# 添加 opencv 的 so 库
add_library(
        opencv_java3
        SHARED
        IMPORTED
)
set_target_properties(
        opencv_java3
        PROPERTIES IMPORTED_LOCATION
        ../../../../src/main/jniLibs/armeabi/libopencv_java3.so
)

# 添加我们自己写的 cpp
add_library(
        opencv-lib
        SHARED
        src/main/cpp/opencv-lib.cpp
        src/main/cpp/impl/opencv_util.cpp
        src/main/cpp/impl/card_ocr.cpp
        src/main/cpp/impl/face_detection.cpp
)

# 链接的库
target_link_libraries(
        opencv-lib
        opencv_java3
        jnigraphics
        ${log-lib}
)

find_library(
        log-lib
        log
)

编写一个工具类

于处理 OpenCV 的 Mat 与 Android 的 Bitmap 之间的相互转化

  • Bitmap 中的色彩空间与 Mat 的对应关系
    • RGBA_8888 对应 CV_8UC4
    • RGBA_565 对应 CV_8UC2
    • 灰度图 对应 CV_8UC1
  • 头文件(opencv_util.h)
//
// Created by Frank on 2018/6/5.
// OpenCV 工具类的头文件
//
#ifndef OPENCV_UTILS_H_
#define OPENCV_UTILS_H_

#include <jni.h>
#include <android/bitmap.h>
#include "opencv2/opencv.hpp"

using namespace cv;
using namespace std;

class OpenCVUtil {

public:

    OpenCVUtil() {}

    ~OpenCVUtil() {}

    /**
     * Bitmap 转为 Mat
     *
     * @param bitmap 需要操作的 bitmap
     * @param destMat 用于存储的 mat
     */
    static void bitmapToMat(JNIEnv *env, jobject &bitmap, Mat &destMat);

    /**
     * Mat 转为 Bitmap
     *
     * @param mat 需要操作的 mat
     * @param destBitmap  被作用的 bitmap
     */
    static void matToBitmap(JNIEnv *env, Mat &mat, jobject &destBitmap);

    /**
     * 在 bitmap 上标记矩形框
     */
    static void markRectOnBitmap(JNIEnv *env, jobject &bitmap, vector<Rect> &rects);

    /**
     * 抛出 Java 异常
     */
    static void throwRuntimeException(JNIEnv *env, const char *message);
};

#endif
  • 实现代码(opencv_util.cpp)
//
// Created by Frank on 2018/6/5.
//

#include "../header/opencv_util.h"

/**
 * bitmap 与 mat 之间的转换
 */
void OpenCVUtil::bitmapToMat(JNIEnv *env, jobject &bitmap, Mat &destMat) {
    // 锁定画布
    void *pixels = 0;
    AndroidBitmap_lockPixels(env, bitmap, &pixels);

    // 获取 bitmap 的信息
    AndroidBitmapInfo info;
    AndroidBitmap_getInfo(env, bitmap, &info);

    // 创建 Mat
    // RGBA_888 <---->  CV_8UC4
    // RGB565   <---->  CV_8UC2
    // GRAY     <---->  CV_8UC1
    destMat.create(info.height, info.width, CV_8UC4);// 创建一个 8UC4 的 Mat, 用来接收 bitmap
    if (info.format == ANDROID_BITMAP_FORMAT_RGBA_8888) {// rgba_888 对应 openCV 的 CV_8UC4
        Mat temp(info.height, info.width, CV_8UC4, pixels);
        temp.copyTo(destMat);
    } else if (info.format == ANDROID_BITMAP_FORMAT_RGB_565) {
        Mat temp(info.height, info.width, CV_8UC2, pixels);
        temp.copyTo(destMat, COLOR_BGR5652RGBA);
    } else {
        // TODO: Bitmap 的色彩模式为其他情况, 另行处理
    }

    // 解锁画布
    AndroidBitmap_unlockPixels(env, bitmap);
}

/**
 * mat 与 bitmap 之间的转换
 */
void OpenCVUtil::matToBitmap(JNIEnv *env, Mat &mat, jobject &destBitmap) {
    // 锁定画布
    void *pixels;
    AndroidBitmap_lockPixels(env, destBitmap, &pixels);
    // 获取 bitmap 的信息
    AndroidBitmapInfo info;
    AndroidBitmap_getInfo(env, destBitmap, &info);
    // 创建 Mat
    // RGBA_888 <---->  CV_8UC4
    // RGB565   <---->  CV_8UC2
    // GRAY     <---->  CV_8UC1
    if (info.format == ANDROID_BITMAP_FORMAT_RGBA_8888) {// rgba_888 对应 openCV 的 CV_8UC4
        Mat temp(info.height, info.width, CV_8UC4, pixels);
        if (mat.type() == CV_8UC4) {
            mat.copyTo(temp);
        } else if (mat.type() == CV_8UC2) {
            cvtColor(mat, temp, COLOR_BGR5652RGBA);
        } else if (mat.type() == CV_8UC1) {
            cvtColor(mat, temp, COLOR_GRAY2RGBA);
        } else {
            // TODO: Mat 为其他情况, 另行处理
        }
    } else if (info.format == ANDROID_BITMAP_FORMAT_RGB_565) {
        Mat temp(info.height, info.width, CV_8UC2, pixels);
        if (mat.type() == CV_8UC2) {
            mat.copyTo(temp);
        } else if (mat.type() == CV_8UC4) {
            cvtColor(mat, temp, COLOR_RGBA2BGR565);
        } else if (mat.type() == CV_8UC1) {
            cvtColor(mat, temp, COLOR_GRAY2BGR565);
        } else {
            // TODO: Mat 为其他情况, 另行处理
        }
    } else {
        // TODO: Bitmap 的色彩模式为其他情况, 另行处理
    }
    // 解锁画布
    AndroidBitmap_unlockPixels(env, destBitmap);
}

/**
 * 标记 bitmap 上的矩形框
 */
void OpenCVUtil::markRectOnBitmap(JNIEnv *env, jobject &bitmap, vector<Rect> &rects) {
    // 将 bitmap 转为 mat
    Mat mat;
    bitmapToMat(env, bitmap, mat);
    int i = 0;
    for (; i < rects.size(); i++) {
        // 在原始矩阵上将人脸绘制出来
        rectangle(mat, rects[i], Scalar(255, 155, 155), 4);
    }
    // 将 origin_mat 应用到 bitmap 上去
    matToBitmap(env, mat, bitmap);
}

/**
 * 抛出 runtime 异常
 */
void OpenCVUtil::throwRuntimeException(JNIEnv *env, const char *message) {
    if (message == NULL) return;
    jclass je = env->FindClass("java/lang/Exception");
    env->ThrowNew(je, message);
}