opencv android studio native C++ 配置及开发

开始之前需要的

  1. 在开始之前,你需要下载好Android Studio, 且在AS的SDK Tool中下载好NDK和Cmake;
  2. 在opencv的官网下载opencv android包,本文使用的是opencv 4.6.0 android版。opencv官网:Releases - OpenCV 

本文总体的思路本文将按Native C++编程的3个层面,native C++层,jni层,managed层这3个层面的顺序去完成该demo。native层是C++的具体方法,jni层提供了接口功能的实现,通常借助native层的方法来实现,从而让上层能调用jni层的函数实现对应的功能, jni层代码写在cpp文件中。Managed层也就是应用层,这里需要对jni层的函数进行声明才能与jni层的函数对应起来,并在此对函数进行调用,通常是JAVA或者Kotlin代码。3层的关系如下图所示。

opencv android studio native C++ 配置及开发_第1张图片

正式开始-创建项目

新建project, 选择native c++, 点击next,如下所示,

输入有意义的项目名字,并选择语言为java或者kotlin, 本文选择kotlin,点击next,

这一页C++标准选择可以默认,点击finish,就完成了创建。

 

添加C++源文件

在安卓视图下,app下有个cpp文件夹,在该文件夹下有默认的两个文件,CMakeLists.txt和native-lib.cpp。当然,我们可以在native-lib.cpp中写所有的C++代码和JNI层代码,但是为了让C++代码看起来更有组织和方便理解,我们只在native-lib.cpp文件中写JNI层代码,其他的图像处理过程写在别的文件中。下面添加两个文件。

右击cpp文件夹,选择New -> C/C++ Header File, 命名为opencv-utils。

右击cpp文件夹,选择New -> C/C++ Source File, 命名为opencv-utils。

添加完上述两个文件后,在安卓视图下cpp文件夹下的文件应该看起来如下图。此时这两个文件内容是空的。

配置Opencv的构建(即配置CMakeLists.txt)

打开CMakeLists.txt, 并在文件的上面添加下面的代码,添加在“cmake_minimum_required”或“project”的下面,注意将openCV_DIR替换成自己下载的opencv库的路径。见下图,

# opencv

set(OpenCV_STATIC ON)

set(OpenCV_DIR /Users/xxx/Downloads/OpenCV-android-sdk/sdk/native/jni)

find_package(OpenCV REQUIRED)

 

 将源文件opencv-utils.cpp添加到add_library中,这样才能编译到库中,

 为了在native code中操作Bitmap, 在“target_link_libraries”之前增加如下的代码,

# jnigraphics lib from NDK is used for Bitmap manipulation in native code

find_library(jnigraphics-lib jnigraphics)

 

最后,还要把opencv和jnigraphics库包含进链接进程中,在“target_link_libraries”里增加下面代码:

${OpenCV_LIBS}
${jnigraphics-lib}

 

Sync & Build

1.首先进行同步,点击 File -> Sync project with Gradle Files 

2.接着进行build, 点击build -> Make Project, 如果成功了,则祝贺。

如果失败,可以参考:(我这里是成功的,因此下面的方法没有验证过)

接下来开始用OpenCV

利用OpenCV实现图像的翻转和模糊两个函数

1. 在头文件opencv-utils.h中添加以下代码:

#pragma once
#include 
using namespace cv;
void myFlip(Mat& src);
void myBlur(Mat& src, float sigma);

 

这里opencv2头文件会报错,先不用管,只要构建完这个项目之后就能自动解决报错。

2. 在源文件opencv-utils.cpp中写上函数的实现代码:

#include "opencv-utils.h"
#include 

void myFlip(Mat& src) {
    flip(src, src, 0);
}

void myBlur(Mat& src, float sigma) {
    GaussianBlur(src, src, Size(), sigma);
}

 

暴露native 方法给manage

1. 首先在native-lib.cpp文件中暴露上述两个函数的接口:(注意将函数名中包名到类名这部分替换成自己的)

extern "C" JNIEXPORT void JNICALL
Java_com_demo_opencv_1basic_MainActivity_flip(JNIEnv* env, jobject p_this, jobject bitmapIn, jobject bitmapOut) {
    Mat src;
    bitmapToMat(env, bitmapIn, src, false);
    // NOTE bitmapToMat returns Mat in RGBA format, if needed convert to BGRA using cvtColor

    myFlip(src);

    // NOTE matToBitmap expects Mat in GRAY/RGB(A) format, if needed convert using cvtColor
    matToBitmap(env, src, bitmapOut, false);
}

extern "C" JNIEXPORT void JNICALL
Java_com_demo_opencv_1basic_MainActivity_blur(JNIEnv* env, jobject p_this, jobject bitmapIn, jobject bitmapOut, jfloat sigma) {
    Mat src;
    bitmapToMat(env, bitmapIn, src, false);
    myBlur(src, sigma);
    matToBitmap(env, src, bitmapOut, false);
}

 

2. 在JNI层需要实现将Android的Bitmap转换成OpenCV的Mat格式,增加下面两个方法:

void bitmapToMat(JNIEnv *env, jobject bitmap, Mat& dst, jboolean needUnPremultiplyAlpha)
{
    AndroidBitmapInfo  info;
    void*              pixels = 0;

    try {
        CV_Assert( AndroidBitmap_getInfo(env, bitmap, &info) >= 0 );
        CV_Assert( info.format == ANDROID_BITMAP_FORMAT_RGBA_8888 ||
                   info.format == ANDROID_BITMAP_FORMAT_RGB_565 );
        CV_Assert( AndroidBitmap_lockPixels(env, bitmap, &pixels) >= 0 );
        CV_Assert( pixels );
        dst.create(info.height, info.width, CV_8UC4);
        if( info.format == ANDROID_BITMAP_FORMAT_RGBA_8888 )
        {
            Mat tmp(info.height, info.width, CV_8UC4, pixels);
            if(needUnPremultiplyAlpha) cvtColor(tmp, dst, COLOR_mRGBA2RGBA);
            else tmp.copyTo(dst);
        } else {
            // info.format == ANDROID_BITMAP_FORMAT_RGB_565
            Mat tmp(info.height, info.width, CV_8UC2, pixels);
            cvtColor(tmp, dst, COLOR_BGR5652RGBA);
        }
        AndroidBitmap_unlockPixels(env, bitmap);
        return;
    } catch(const cv::Exception& e) {
        AndroidBitmap_unlockPixels(env, bitmap);
        jclass je = env->FindClass("java/lang/Exception");
        env->ThrowNew(je, e.what());
        return;
    } catch (...) {
        AndroidBitmap_unlockPixels(env, bitmap);
        jclass je = env->FindClass("java/lang/Exception");
        env->ThrowNew(je, "Unknown exception in JNI code {nBitmapToMat}");
        return;
    }
}

void matToBitmap(JNIEnv* env, Mat src, jobject bitmap, jboolean needPremultiplyAlpha)
{
    AndroidBitmapInfo  info;
    void*              pixels = 0;

    try {
        CV_Assert( AndroidBitmap_getInfo(env, bitmap, &info) >= 0 );
        CV_Assert( info.format == ANDROID_BITMAP_FORMAT_RGBA_8888 ||
                   info.format == ANDROID_BITMAP_FORMAT_RGB_565 );
        CV_Assert( src.dims == 2 && info.height == (uint32_t)src.rows && info.width == (uint32_t)src.cols );
        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 )
        {
            Mat tmp(info.height, info.width, CV_8UC4, pixels);
            if(src.type() == CV_8UC1)
            {
                cvtColor(src, tmp, COLOR_GRAY2RGBA);
            } else if(src.type() == CV_8UC3){
                cvtColor(src, tmp, COLOR_RGB2RGBA);
            } else if(src.type() == CV_8UC4){
                if(needPremultiplyAlpha) cvtColor(src, tmp, COLOR_RGBA2mRGBA);
                else src.copyTo(tmp);
            }
        } else {
            // info.format == ANDROID_BITMAP_FORMAT_RGB_565
            Mat tmp(info.height, info.width, CV_8UC2, pixels);
            if(src.type() == CV_8UC1)
            {
                cvtColor(src, tmp, COLOR_GRAY2BGR565);
            } else if(src.type() == CV_8UC3){
                cvtColor(src, tmp, COLOR_RGB2BGR565);
            } else if(src.type() == CV_8UC4){
                cvtColor(src, tmp, COLOR_RGBA2BGR565);
            }
        }
        AndroidBitmap_unlockPixels(env, bitmap);
        return;
    } catch(const cv::Exception& e) {
        AndroidBitmap_unlockPixels(env, bitmap);
        jclass je = env->FindClass("java/lang/Exception");
        env->ThrowNew(je, e.what());
        return;
    } catch (...) {
        AndroidBitmap_unlockPixels(env, bitmap);
        jclass je = env->FindClass("java/lang/Exception");
        env->ThrowNew(je, "Unknown exception in JNI code {nMatToBitmap}");
        return;
    }
}

 

如上图所示,并添加上必要的头文件。

Managed层调用Native层代码

1. 首先在页面布局上,增加ImageView, Button和SeekBar,分别用来显示图片,点击按钮,调节滑动条。页面布局的调整在安卓视图的app -> res -> layout下的activity_main.xml中。

调整好的页面如下图所示:

2. 在MainActivity中,声明JNI层的函数:

external fun blur(bitmapIn: Bitmap, bitmapOut: Bitmap, sigma: Float)
external fun flip(bitmapIn: Bitmap, bitmapOut: Bitmap)

在创建Native C++文件时默认在MainActivity中导入了库,如果没有的话,就在MainActivity.kt加上下面的代码,

companion object {
        // Used to load the 'opencv_basic' library on application startup.
        init {
            System.loadLibrary("opencv_basic")
        }
    }

MainActivity.kt的整个内容如下:

 

在MainActivity中处理Android Bitmap

1. 创建2个bitmaps, 分别叫做srcBitmap和dstBitmap,并将dstBitmap显示在屏幕上,

class MainActivity : AppCompatActivity() {
    var srcBitmap: Bitmap? = null
    var dstBitmap: Bitmap? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        srcBitmap = BitmapFactory.decodeResource(this.resources, R.drawable.mountain)

        // Create and display dstBitmap in image view, we will keep updating
        // dstBitmap and the changes will be displayed on screen
        dstBitmap = srcBitmap!!.copy(srcBitmap!!.config, true)
        imageView.setImageBitmap(dstBitmap)
         ...

 

2. 设置对seekbar的监听,每当用户滑动滑杆的时候,读取滑杆的Sigma值作为模糊函数的参数。

a. 在MainActivity的onCreat函数上,加上sldSigma.setOnSeekBarChangeListener(this)

点击this并选择第2个让MainActivity实现接口,并将3个接口函数都选上,如下图。

 

b.  写上滑杆的响应函数:

fun doBlur() {
        // The SeekBar range is 0-100 convert it to 0.1-10
        val sigma = max(0.1F, sldSigma.progress / 10F)

        // This is the actual call to the blur method inside native-lib.cpp
        this.blur(srcBitmap!!, dstBitmap!!, sigma)
}

    override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
        this.doBlur()
    }

    override fun onStartTrackingTouch(seekBar: SeekBar?) {}

    override fun onStopTrackingTouch(seekBar: SeekBar?) {}

 

3. 设置对Button的监听,每当用户点击按钮,即调用对应的响应函数。

a. 在MainActivity中设置Button的响应函数,如下 

fun btnFlip_click(view: View) {
        this.flip(srcBitmap!!, dstBitmap!!)
}

b. 将Button与其响应函数进行关联,当点击该Button时,即调用该响应。在布局文件activity_main.xml中点击btnFlip,并在Common Attributes的onClick中选择btnFlip_click。

额外补充

如果出现unresolved reference: imageView,只需要在app下的build.gradle里增加

id 'kotlin-android'和id 'kotlin-android-extensions',如下所示,

plugins {
    id 'com.android.application'
    // id 'org.jetbrains.kotlin.android'
    id 'kotlin-android'
    id 'kotlin-android-extensions'
}

 

然后点击Sync now,最后点击imageView,会出现红色import提示,点击import即可。 

最终结果

1. 原始图片

2. 图片翻转

 

3. 图片模糊

 

 

参考资料:

OpenCV in Android native using C++

THE END !

你可能感兴趣的:(opencv,android,studio,c++)