ndk实例总结:opencv图像处理

ndk实例总结系列

ndk实例总结:jni实例
ndk实例总结:opencv图像处理
ndk实例总结:安卓Camera与usbCamera原始图像处理
ndk实例总结补充:使用V4L2采集usb图像分析
ndk实例总结:使用fmpeg播放rtsp流

前言

本篇博客总结下在jni中使用opencv进行图像处理的使用实例

在Android中opencv的使用有两种方式,一种是使用opencv的Android版api,另一种是通过jni来使用opencv,本篇总结是第二种方式

依赖库编译

通过jni在android平台使用opencv官方有提供已编译完的完整动态库文件(libopencv_java3.so),也可以自行使用Android的ndk包来编译opencv源码

自行编译参考这篇博客:ubuntu16.04 编译 opencv for Android(更新版),可以选择不编译一些用不到的模块来减小动态库体积

项目构架

在这里插入图片描述
从上图中可以看到cpp文件夹内存放的opencv头文件、工具类、jni的native代码和CMakeLists文件

为了减少项目体积,jniLibs文件夹中只放了使用armv7构架编译的三个opencv模块动态库文件

动态库在CMakeLists文件中添加

cmake_minimum_required(VERSION 3.4.1)

#设置头文件目录
include_directories(${CMAKE_SOURCE_DIR}/include)

#设置jniLibs目录
set(jniLibs "${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}")

#opencv
add_library(libopencv_core SHARED IMPORTED)
set_target_properties(libopencv_core PROPERTIES
        IMPORTED_LOCATION "${jniLibs}/libopencv_core.so")

add_library(libopencv_imgcodecs SHARED IMPORTED)
set_target_properties(libopencv_imgcodecs PROPERTIES
        IMPORTED_LOCATION "${jniLibs}/libopencv_imgcodecs.so")

add_library(libopencv_imgproc SHARED IMPORTED)
set_target_properties(libopencv_imgproc PROPERTIES
        IMPORTED_LOCATION "${jniLibs}/libopencv_imgproc.so")

#jni library
add_library(image_process_lib
        SHARED
        image_process_jni.cpp
        )

find_library(log-lib log)

target_link_libraries(
        image_process_lib
        jnigraphics
        libopencv_core
        libopencv_imgcodecs
        libopencv_imgproc
        ${log-lib})

流程很简单,分别设置头文件目录jniLibs目录路径,然后添加自行编译的opencv动态库,最后添加到target link中就可以了

build.gradle

android {
    ...

    defaultConfig {
        ...
        externalNativeBuild {
            cmake {
                arguments "-DANDROID_ARM_NEON=TRUE", "-DCMAKE_BUILD_TYPE=Release"
                cppFlags "-std=c++11"
            }
        }
        ndk {
            abiFilters 'armeabi-v7a'
        }
    }

    ...

    externalNativeBuild {
        cmake {
            path "src/main/cpp/CMakeLists.txt"
            version "3.10.2"
        }
    }
}

在gradle中添加相应配置:NEON指令开启、使用release来编译、使用c++11标准库,只打包armeabi-v7a的动态库、设置CMakeLists文件的路径和指定cmake版本

通用方法

opencv中处理图像基本都是基于mat这个封装类,而android中的图像都是基于bitmap类,因此需要提供两个类互转的功能

bitmap转mat函数

#include "utils/jni_lib.hpp"

#include <android/bitmap.h>
#include <opencv2/opencv.hpp>
#include <opencv2/imgcodecs/imgcodecs_c.h>

void bmp2mat(JNIEnv *env, jobject &srcBitmap, cv::Mat &dstMat, bool needPremultiplyAlpha) {
    void *srcPixels = 0;
    AndroidBitmapInfo srcBitmapInfo;
    try {
        CV_Assert(AndroidBitmap_getInfo(env, srcBitmap, &srcBitmapInfo) >= 0);
        CV_Assert(srcBitmapInfo.format == ANDROID_BITMAP_FORMAT_RGBA_8888 ||
                  srcBitmapInfo.format == ANDROID_BITMAP_FORMAT_RGB_565);
        CV_Assert(AndroidBitmap_lockPixels(env, srcBitmap, &srcPixels) >= 0);
        CV_Assert(srcPixels);
        uint32_t srcHeight = srcBitmapInfo.height;
        uint32_t srcWidth = srcBitmapInfo.width;
        dstMat.create(srcHeight, srcWidth, CV_8UC4);
        if (srcBitmapInfo.format == ANDROID_BITMAP_FORMAT_RGBA_8888) {
            LOGI("RGBA_8888");
            cv::Mat tmp(srcHeight, srcWidth, CV_8UC4, srcPixels);
            if (needPremultiplyAlpha) {
                cvtColor(tmp, dstMat, cv::COLOR_mRGBA2RGBA);
            } else {
                tmp.copyTo(dstMat);
            }
        } else {
            LOGI("RGB_565");
            cv::Mat tmp = cv::Mat(srcHeight, srcWidth, CV_8UC2, srcPixels);
            cvtColor(tmp, dstMat, cv::COLOR_BGR5652RGBA);
        }
        AndroidBitmap_unlockPixels(env, srcBitmap);
    } catch (cv::Exception &e) {
        AndroidBitmap_unlockPixels(env, srcBitmap);
        jclass je = env->FindClass("java/lang/Exception");
        env->ThrowNew(je, e.what());
    } catch (...) {
        AndroidBitmap_unlockPixels(env, srcBitmap);
        jclass je = env->FindClass("java/lang/Exception");
        env->ThrowNew(je, "unknown");
    }
}

主要就是从bitmap中获取图像的宽高,通过这个宽高创建mat对象,然后将bitmap内的像素存入一个四通道的mat中

mat转bitmap函数

void mat2bmp(JNIEnv *env, cv::Mat &src, const jobject &bitmap, bool needPremultiplyAlpha) {
    AndroidBitmapInfo info;
    void *pixels = 0;

    try {
        CV_Assert(AndroidBitmap_getInfo(env, bitmap, &info) >= 0);
        CV_Assert(src.type() == CV_8UC1 || src.type() == CV_8UC3 || src.type() == CV_8UC4);
        CV_Assert(AndroidBitmap_lockPixels(env, bitmap, &pixels) >= 0);
        CV_Assert(pixels);
        if (info.format == ANDROID_BITMAP_FORMAT_RGBA_8888) {
            LOGI("RGBA_8888");
            cv::Mat tmp(info.height, info.width, CV_8UC4, pixels);
            if (src.type() == CV_8UC1) {
                LOGI("CV_8UC1");
                cvtColor(src, tmp, CV_GRAY2RGBA);
            } else if (src.type() == CV_8UC3) {
                LOGI("CV_8UC3");
                cvtColor(src, tmp, CV_BGR2RGBA);
            } else if (src.type() == CV_8UC4) {
                LOGI("CV_8UC4");
                if (needPremultiplyAlpha) {
                    cvtColor(src, tmp, cv::COLOR_RGBA2mRGBA);
                } else {
                    src.copyTo(tmp);
                }
            }
        } else {
            LOGI("RGB_565");
            // info.format == ANDROID_BITMAP_FORMAT_RGB_565
            cv::Mat tmp(info.height, info.width, CV_8UC2, pixels);
            if (src.type() == CV_8UC1) {
                LOGI("CV_8UC1");
                cvtColor(src, tmp, CV_GRAY2BGR565);
            } else if (src.type() == CV_8UC3) {
                LOGI("CV_8UC3");
                cvtColor(src, tmp, CV_BGR2BGR565);
            } else if (src.type() == CV_8UC4) {
                LOGI("CV_8UC4");
                cvtColor(src, tmp, CV_RGBA2BGR565);
            }
        }
        AndroidBitmap_unlockPixels(env, bitmap);
    } catch (cv::Exception &e) {
        AndroidBitmap_unlockPixels(env, bitmap);
        jclass je = env->FindClass("org/opencv/core/CvException");
        if (!je) je = env->FindClass("java/lang/Exception");
        env->ThrowNew(je, e.what());
    } catch (...) {
        AndroidBitmap_unlockPixels(env, bitmap);
        jclass je = env->FindClass("java/lang/Exception");
        env->ThrowNew(je, "Unknown exception in JNI code {nMatToBitmap}");
    }
}

和bitmap转mat差不多,只是需要将mat从相应的颜色格式转换成bitmap的颜色格式(RGBA_8888或RGB_565)

创建Bitmap对象

jobject createBitmap(JNIEnv *env, cv::Mat &pngimage) {
    // Image Details
    int imgWidth = pngimage.cols;
    int imgHeight = pngimage.rows;
    int numPix = imgWidth * imgHeight;
    // Creaing Bitmap Config Class
    jclass bmpCfgCls = env->FindClass("android/graphics/Bitmap$Config");
    jmethodID bmpClsValueOfMid = env->GetStaticMethodID(bmpCfgCls, "valueOf",
                                                        "(Ljava/lang/String;)Landroid/graphics/Bitmap$Config;");
    jobject jBmpCfg = env->CallStaticObjectMethod(bmpCfgCls, bmpClsValueOfMid,
                                                  env->NewStringUTF("RGB_565" /*or*/ /*"ARGB_8888"*/));
    // Creating a Bitmap Class
    jclass bmpCls = env->FindClass("android/graphics/Bitmap");
    jmethodID createBitmapMid = env->GetStaticMethodID(bmpCls, "createBitmap",
                                                       "(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");
    return env->CallStaticObjectMethod(bmpCls, createBitmapMid, imgWidth, imgHeight, jBmpCfg);
}

使用jni来创建一个bitmap对象

用法实例

将png图片转换为bitmap的实例

jobject pngToBitmap(JNIEnv *env, jstring imagePath) {
    std::string path = jstring_to_string(env, imagePath);
    LOGI("imagePath: %s", path.c_str());
    cv::Mat pngMat = cv::imread(path);
    jobject bitmap = createBitmap(env, pngMat);
    mat2bmp(env, pngMat, bitmap, false);
    return bitmap;
}

使用imread函数从路径中读取图片生成mat对象,然后创建bitmap对象,最后将mat转为bitmap返回

将bitmap保存成本地png图片

void bitmapToPng(JNIEnv *env, jstring savePath, jobject srcBitmap) {
    std::string path = jstring_to_string(env, savePath);
    LOGI("imagePath: %s", path.c_str());
    cv::Mat pngMat;
    bmp2mat(env, srcBitmap, pngMat, false);
    std::vector<int> param;
    param.push_back(CV_IMWRITE_PNG_COMPRESSION);
    param.push_back(9);
    cv::cvtColor(pngMat, pngMat, CV_RGBA2BGR);
    cv::imwrite(path, pngMat, param);
    LOGI("bitmapToPng complete");
}

将bitmap转换成mat对象,然后设置png相关参数并转换成BGR格式,最后使用imwrite保存在本地

最后java端和java jni端的代码可以去看demo,这里就不贴了

ndk开发基础学习系列:

JNI和NDK编程(一)JNI的开发流程
JNI和NDK编程(二)NDK的开发流程
JNI和NDK编程(三)JNI的数据类型和类型签名
JNI和NDK编程(四)JNI调用Java方法的流程

完整demo:

https://github.com/GavinAndre/JNIDemo

发布了174 篇原创文章 · 获赞 119 · 访问量 55万+

猜你喜欢

转载自blog.csdn.net/lj402159806/article/details/86609106