• Android平台下OpenCV移植与使用---基于C/C++


      在《Android Studio增加NDK代码编译支持--Mac环境》和《Mac平台下Opencv开发环境搭建》两篇文章中,介绍了如何使用NDK环境和Opencv环境搭建与测试使用,现在,在PC端对图像处理算法测试没问题后,该在移动端进行功能移植了,ios平台的很简单,直接把类库拷进工程就行了,android的稍微麻烦点,这里就以android平台为例说明移植步骤。

      为了更好的模块移植,这里使用Android源码的make文件写法:*.mk,Android源码是一个很大的工程,它的编译采用一个大的mk文件,通过脚本文件的配置来自定义编译的,在build/core/下面的Android.mk文件就是总的编译文件入口:

      

       这里写的opencv安卓模块也使用mk文件写法来编译so库。这里新建了一个测试工程,可以在GitHub上download或fork来查看源码: https://github.com/linjk/TestOpenCV

        下面开始移植步骤:

       1. 新建测试工程OpenCVTest:

        

        2. 拷贝下载的opencv的android平台的开发包,这里下载3.1.0版本的:

         

          这里把sdk目录下的native目录拷贝到工程根目录,这个目录下是c/c++语法的,java目录是已封装好的一些java接口,按需选择吧,为了更好的算法移植而不用每次改写,这里选择native库,复制后工程结构如下:

          

        3. 新建jni目录,用于编写本地c++代码:

          在src目录单击右键,按下图操作:

          

          结果如下:

          

        4. 编写java类的本地接口声明,用于给java层调用:

          这里声明一个很简单的opencv本地方法,用于把一副图像编程灰度图像,当然,这个效果用安卓的图像矩阵来处理就行了,但是,复杂一点的功能,如边缘检测、身份证识别就要借助opencv来弄了,这里仅做功能测试:

          

        5. 生成本地方法桥接头文件:

          命令行进入src/main/java路径,然后执行命令: javah -jni cn.linjk.jniBridge.OpenCVUtils, -jni参数后面参数格式是:包名+类名,结果如下:

            

            我们把这个文件移动到jni目录下,并新建一个同名的cpp类cn_linjk_jniBridge_OpenCVUtils.cpp:

        6. 由于之前使用了android studio生成的jni目录,因此,编译上可能会和使用mk文件编译生成so库不一样,这里取消它的路径属性:

        在app/build.gradle文件的android块下增加这个配置:

    sourceSets{
            main{
                jni.srcDirs = []
            }
        }
    

         可以发现,jni目录由蓝色变成了黄色:

        

        7. 编写编译规则文件,指定ndk路径:

          7.1 指定ndk路径:

          

          7.2 在jni目录下新建两个mk编译文件,内容分别如下:

          

          Android.mk文件内容如下:

    LOCAL_PATH := $(call my-dir)
    
    include $(CLEAR_VARS)
    
    OpenCV_INSTALL_MODULES := on
    OpenCV_CAMERA_MODULES := off
    
    OPENCV_LIB_TYPE := SHARED
    
    ifeq ("$(wildcard $(OPENCV_MK_PATH))","")
    include ../../../../native/jni/OpenCV.mk
    else
    include $(OPENCV_MK_PATH)
    endif
    
    LOCAL_MODULE := testopencv
    
    LOCAL_SRC_FILES := cn_linjk_jniBridge_OpenCVUtils.cpp
    
    LOCAL_LDLIBS +=  -lm -llog -landroid
    
    include $(BUILD_SHARED_LIBRARY)
    

         LOCAL_MODULE声明的是模块名称,必须与在OpenCVUtils声明的加载库名一样。

        Application.mk文件内容如下:

    APP_STL := gnustl_static
    APP_CPPFLAGS := -frtti -fexceptions
    APP_ABI := arm64-v8a armeabi armeabi-v7a mips mips64
    

         APP_ABI声明了声明针对那些CPU架构的so库。

        7.3 在app/build.gradle声明一个task,用于编译生成so库。

          7.3.1 编辑jni的cpp文件,内容如下,为测试能否调用库,这里先在函数打印cv版本:

    //
    // Created by LinJK on 21/11/2016.
    //
    
    #include "cn_linjk_jniBridge_OpenCVUtils.h"
    
    #include <opencv2/opencv.hpp>
    
    #include <Android/log.h>
    #include <Android/asset_manager.h>
    #include <Android/asset_manager_jni.h>
    
    #define TAG "cn.linjk.opencvtest.jni"
    #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__)
    #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__)
    #define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__)
    #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__)
    #define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__)
    
    extern "C" {
    
    JNIEXPORT void JNICALL Java_cn_linjk_jniBridge_OpenCVUtils_img2gray
        (JNIEnv *env , jclass objClass, jstring imgFilePath) {
    
            LOGI("OpenCV version: %s", CV_VERSION);
    
        }
    
    }
    

         7.3.2 增加生成so库的task:

          

        7.3.3 执行任务,生成so库:

          执行命令"gradle cv_ndkBuild",结果如下:

          

          把对应得so库复制到app/libs目录下对应cpu架构目录下:

          

        8. 在MainActivity调用看能否输出opencv库版本:

          MainActivity.java内容:

    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            OpenCVUtils.img2gray("");
        }
    }
    

         运行后,发现出现如下错误:

        

        这是我们指定生成动态链接库,因此,还需要libopencv_java3.so这个库,复制到我们的libs下,再次运行,结果如下:

        

        输出正确,可以继续下一步了。

        附:

        也可以使用命令“arm-none-eabi-readelf -d libtestopencv.so”查看其需要的链接库,命令执行结果如下:

        

        .so文件是ELF(Excutable and Linking Formar)格式的缩写,最初由UNIX系统实验室发布,它是应用程序二进制接口的一部分,ELF文件以节(section)的方式组织在一起,“节”描述了文件的各项信息,例如代码、数据、符号表、重定位表、全局偏移表等。

      9. 编写图像处理类:

      9.1 这里使用照相机获取输入图像,代码看github的源码就行,这里主要看看c++最终代码:

        cn_linjk_jniBridge_OpenCVUtils.cpp

    //
    // Created by LinJK on 21/11/2016.
    //
    
    #include "cn_linjk_jniBridge_OpenCVUtils.h"
    
    #include <opencv2/opencv.hpp>
    #include <string>
    #include <iostream>
    
    #include <Android/log.h>
    #include <Android/asset_manager.h>
    #include <Android/asset_manager_jni.h>
    
    #define TAG "cn.linjk.opencvtest.jni"
    #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__)
    #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__)
    #define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__)
    #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__)
    #define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__)
    
    using namespace std;
    using namespace cv;
    
    class ImageUtils{
    public:
        void imageToGray(Mat inputImg, string outFilePath);
    };
    
    void ImageUtils::imageToGray(Mat inputImg, string outFilePath) {
        Mat gray;
    
        Mat input = inputImg.clone();
    
        cvtColor(input, gray, COLOR_BGR2GRAY);
    
        imwrite(outFilePath,  gray);
    }
    
    extern "C" {
    
    JNIEXPORT void JNICALL Java_cn_linjk_jniBridge_OpenCVUtils_img2gray
        (JNIEnv *env , jclass objClass, jstring imgFilePath) {
    
            LOGI("OpenCV version: %s", CV_VERSION);
    
            char buf[128];
            const char *str = env->GetStringUTFChars(imgFilePath, 0);
            LOGD("图像路径: %s", str);
    
            Mat img = imread(str);
            if (!img.data) {
                LOGE("-----CV: 读取相片数据出错");
            }
            else {
                LOGD("-----CV: 读取相片数据成功");
                ImageUtils().imageToGray(img, str);
            }
        }
    
    }
    

           MainActivity.java方法内容如下:

    package cn.linjk.opencvtest;
    
    import android.content.Intent;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.net.Uri;
    import android.os.Environment;
    import android.provider.MediaStore;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.view.Display;
    import android.view.View;
    import android.widget.Button;
    import android.widget.ImageView;
    
    import java.io.File;
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.io.IOException;
    
    import cn.linjk.jniBridge.OpenCVUtils;
    
    public class MainActivity extends AppCompatActivity {
    
        private Button btnOpenCamera;
        private ImageView ivImgOutput;
        private Button btnImgGray;
    
        private String imageFilePath;
    
        private static final int CAMERA_RESULT = 1112;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            btnOpenCamera = (Button)findViewById(R.id.btn_open_camera);
            btnOpenCamera.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View pView) {
                    openCamera();
                }
            });
    
            ivImgOutput = (ImageView)findViewById(R.id.img_output);
    
            btnImgGray   = (Button)findViewById(R.id.img_gray);
            btnImgGray.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View pView) {
                    OpenCVUtils.img2gray(imageFilePath);
                    //
                    BitmapFactory.Options bmpFactoryOptions = new BitmapFactory.Options();
                    bmpFactoryOptions.inJustDecodeBounds = true;
                    Bitmap bmp = BitmapFactory.decodeFile(imageFilePath, bmpFactoryOptions);
    
                    bmpFactoryOptions.inSampleSize = calculateInSampleSize(bmpFactoryOptions, 1280, 800);
    
                    bmpFactoryOptions.inJustDecodeBounds = false;
    
                    bmp = BitmapFactory.decodeFile(imageFilePath, bmpFactoryOptions);
    
                    ivImgOutput.setImageBitmap(bmp);
                }
            });
        }
    
        @Override
        protected void onActivityResult(int requestCode, int resultCode, Intent data) {
            super.onActivityResult(requestCode, resultCode, data);
    
            if (resultCode == RESULT_OK) {
                BitmapFactory.Options bmpFactoryOptions = new BitmapFactory.Options();
                bmpFactoryOptions.inJustDecodeBounds = true;
                Bitmap bmp = BitmapFactory.decodeFile(imageFilePath, bmpFactoryOptions);
    
                bmpFactoryOptions.inSampleSize = calculateInSampleSize(bmpFactoryOptions, 1280, 800);
    
                bmpFactoryOptions.inJustDecodeBounds = false;
    
                bmp = BitmapFactory.decodeFile(imageFilePath, bmpFactoryOptions);
    
                ivImgOutput.setImageBitmap(bmp);
    
                saveBitmap(bmp);
            }
        }
    
        private void openCamera() {
            imageFilePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/testImage.jpg";
            File imageFile = new File(imageFilePath);
            Uri imageFileUri = Uri.fromFile(imageFile);
    
            Intent i = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            i.putExtra(MediaStore.EXTRA_OUTPUT, imageFileUri);
            startActivityForResult(i, CAMERA_RESULT);
        }
    
        private int calculateInSampleSize(BitmapFactory.Options options,
                                          int reqWidth, int reqHeight) {
    
            final int height = options.outHeight;
            final int width = options.outWidth;
            int inSampleSize = 1;
    
            if (height > reqHeight || width > reqWidth) {
    
                final int heightRatio = Math.round((float) height
                        / (float) reqHeight);
                final int widthRatio = Math.round((float) width / (float) reqWidth);
    
                inSampleSize = heightRatio < widthRatio ? widthRatio : heightRatio;
            }
    
            return inSampleSize;
        }
    
        private void saveBitmap(Bitmap bm) {
            File f = new File(imageFilePath);
            if (f.exists()) {
                f.delete();
            }
            try {
                FileOutputStream out = new FileOutputStream(f);
                bm.compress(Bitmap.CompressFormat.PNG, 90, out);
                out.flush();
                out.close();
            }
            catch (FileNotFoundException e) {
                e.printStackTrace();
            }
            catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    

           运行结果如下:

          

         这里,opencv在android平台的移植和简单功能测试已经完成了,后面更多精彩opencv算法就可以继续实现啦~~

          详细代码移步我的GitHub查阅即可:

          https://github.com/linjk/TestOpenCV 

  • 相关阅读:
    第二周作业修改+
    第三周作业
    第二周作业修改
    第三次作业
    第二次作业
    获奖感想
    最后的作业
    14周作业
    第七周作业
    第六周作业
  • 原文地址:https://www.cnblogs.com/linjk/p/6063936.html
Copyright © 2020-2023  润新知