吐槽

公司没招到安卓程序员,让我和老大两个搞过安卓的来做研发,新买的两个开发板之间一人一个,要把正在运行的C#项目改成安卓项目,我C#技能树没点啊!完全没头绪,但是现在就我是新来的,就我闲着,老大还干着维护的活,so,开搞吧。

参考博客

查官网是不可能的,这辈子都不可能,英文又不好,算法也不会,只能抄抄博客勉强维持生活的样子:

https://blog.csdn.net/weixin_43215867/article/details/88543922

没错,就这一篇,因为要求用android studio开发,而且我有升级强迫症,其他找的大多是eclipse或者Android.mk,android studio 3以后推荐用CMakeList.txt了,这东西我也花了一天时间摸索大概搞懂。
 

工具版本

这里不得不再次吐槽,我按上面的博客装好opencv后,问算法的大佬,结果大佬说他用的是旧版本3.3.0的,得重新下载又花了一个小时,重新配,还好配好了之后只要替换就行了。
    Android studio 版本:3.4
    OpenCV 版本:3.3.0

接入步骤

第一步:建工程,没按上面博客的建jniLib,直接把opencv\sdk\native\libs里所有东西扔到建工程时自带的libs目录下了

第二步:编辑java对接jni的接口文件

public class OpenCVUtil {
    static {

        System.loadLibrary("opencv_java3");
        System.loadLibrary("opencv-lib");
    }

    public native void Canny(Object bitmap);



    public  native int YUV2RGB(byte[] data,int width,int height,String path);
}

 

第三步:在app下新建一个CMakeList.txt,内容如下:

# 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)


set(OpenCV_DIR F:/opencv/opencv-3.4.5-android-sdk/OpenCV-android-sdk/sdk/native/jni)  #OpenCV jni根目录
find_package(OpenCV REQUIRED)

include_directories(${CMAKE_SOURCE_DIR}/src/main/jni)  #opencv的include目录

add_library(libopencv_java3 SHARED IMPORTED)
set_target_properties(libopencv_java3 PROPERTIES IMPORTED_LOCATION
        ${CMAKE_SOURCE_DIR}/libs/${ANDROID_ABI}/libopencv_java3.so)  #OpenCV 在libs下的so包


set(CMAKE_VERBOSE_MAKEFILE on)

set(libs ${PROJECT_SOURCE_DIR}/libs)
include_directories(${PROJECT_SOURCE_DIR}/src/main/jni)

#add_library(libopencv_java3 SHARED IMPORTED )
#set_target_properties(libopencv_java3 PROPERTIES
#        IMPORTED_LOCATION "${libs}/${ANDROID_ABI}/libopencv_java3.so)
#add_library(libcpufeatures STATIC IMPORTED )
#set_target_properties(libcpufeatures PROPERTIES
#        IMPORTED_LOCATION ${libs}/${ANDROID_ABI}/libcpufeatures.a)
#
#add_library(libIlmImf STATIC IMPORTED )
#set_target_properties(libIlmImf PROPERTIES
#        IMPORTED_LOCATION ${libs}/${ANDROID_ABI}/libIlmImf.a)
#
#add_library(liblibjasper STATIC IMPORTED )
#set_target_properties(liblibjasper PROPERTIES
#        IMPORTED_LOCATION ${libs}/${ANDROID_ABI}/liblibjasper.a)
#
#add_library(liblibjpeg STATIC IMPORTED )
#set_target_properties(liblibjpeg PROPERTIES
#        IMPORTED_LOCATION ${libs}/${ANDROID_ABI}/liblibjpeg.a)
#
#add_library(liblibpng STATIC IMPORTED )
#set_target_properties(liblibpng PROPERTIES
#        IMPORTED_LOCATION ${libs}/${ANDROID_ABI}/liblibpng.a)
#
#add_library(liblibprotobuf STATIC IMPORTED )
#set_target_properties(liblibprotobuf PROPERTIES
#        IMPORTED_LOCATION ${libs}/${ANDROID_ABI}/liblibprotobuf.a)
#
#add_library(liblibtiff STATIC IMPORTED )
#set_target_properties(liblibtiff PROPERTIES
#        IMPORTED_LOCATION ${libs}/${ANDROID_ABI}/liblibtiff.a)
#
#add_library(liblibwebp STATIC IMPORTED )
#set_target_properties(liblibwebp PROPERTIES
#        IMPORTED_LOCATION ${libs}/${ANDROID_ABI}/liblibwebp.a)
#
#add_library(libopencv_calib3d STATIC IMPORTED )
#set_target_properties(libopencv_calib3d PROPERTIES
#        IMPORTED_LOCATION ${libs}/${ANDROID_ABI}/libopencv_calib3d.a)
#
#add_library(libopencv_core STATIC IMPORTED )
#set_target_properties(libopencv_core PROPERTIES
#        IMPORTED_LOCATION ${libs}/${ANDROID_ABI}/libopencv_core.a)
#
#add_library(libopencv_dnn STATIC IMPORTED )
#set_target_properties(libopencv_dnn PROPERTIES
#        IMPORTED_LOCATION ${libs}/${ANDROID_ABI}/libopencv_dnn.a)
#
#add_library(libopencv_features2d STATIC IMPORTED )
#set_target_properties(libopencv_features2d PROPERTIES
#        IMPORTED_LOCATION ${libs}/${ANDROID_ABI}/libopencv_features2d.a)
#
#add_library(libopencv_flann STATIC IMPORTED )
#set_target_properties(libopencv_flann PROPERTIES
#        IMPORTED_LOCATION ${libs}/${ANDROID_ABI}/libopencv_flann.a)
#
#add_library(libopencv_highgui STATIC IMPORTED )
#set_target_properties(libopencv_highgui PROPERTIES
#        IMPORTED_LOCATION ${libs}/${ANDROID_ABI}/libopencv_highgui.a)
#
#add_library(libopencv_imgcodecs STATIC IMPORTED )
#set_target_properties(libopencv_imgcodecs PROPERTIES
#        IMPORTED_LOCATION ${libs}/${ANDROID_ABI}/libopencv_imgcodecs.a)
#
#add_library(libopencv_imgproc STATIC IMPORTED )
#set_target_properties(libopencv_imgproc PROPERTIES
#        IMPORTED_LOCATION ${libs}/${ANDROID_ABI}/libopencv_imgproc.a)
#
#add_library(libopencv_ml STATIC IMPORTED )
#set_target_properties(libopencv_ml PROPERTIES
#        IMPORTED_LOCATION ${libs}/${ANDROID_ABI}/libopencv_ml.a)
#
#add_library(libopencv_objdetect STATIC IMPORTED )
#set_target_properties(libopencv_objdetect PROPERTIES
#        IMPORTED_LOCATION ${libs}/${ANDROID_ABI}/libopencv_objdetect.a)
#
#add_library(libopencv_photo STATIC IMPORTED )
#set_target_properties(libopencv_photo PROPERTIES
#        IMPORTED_LOCATION ${libs}/${ANDROID_ABI}/libopencv_photo.a)
#
#add_library(libopencv_shape STATIC IMPORTED )
#set_target_properties(libopencv_shape PROPERTIES
#        IMPORTED_LOCATION ${libs}/${ANDROID_ABI}/libopencv_shape.a)
#
#add_library(libopencv_stitching STATIC IMPORTED )
#set_target_properties(libopencv_stitching PROPERTIES
#        IMPORTED_LOCATION ${libs}/${ANDROID_ABI}/libopencv_stitching.a)
#
#add_library(libopencv_superres STATIC IMPORTED )
#set_target_properties(libopencv_superres PROPERTIES
#        IMPORTED_LOCATION ${libs}/${ANDROID_ABI}/libopencv_superres.a)
#
#add_library(libopencv_video STATIC IMPORTED )
#set_target_properties(libopencv_video PROPERTIES
#        IMPORTED_LOCATION ${libs}/${ANDROID_ABI}/libopencv_video.a)
#
#add_library(libopencv_videoio STATIC IMPORTED )
#set_target_properties(libopencv_videoio PROPERTIES
#        IMPORTED_LOCATION ${libs}/${ANDROID_ABI}/libopencv_videoio.a)
#
#add_library(libopencv_videostab STATIC IMPORTED )
#set_target_properties(libopencv_videostab PROPERTIES
#        IMPORTED_LOCATION ${libs}/${ANDROID_ABI}/libopencv_videostab.a)
#
#add_library(libtbb STATIC IMPORTED )
#set_target_properties(libtbb PROPERTIES
#        IMPORTED_LOCATION ${libs}/${ANDROID_ABI}/libtbb.a)
#
#add_library(libtegra_hal STATIC IMPORTED )
#set_target_properties(libtegra_hal PROPERTIES
#        IMPORTED_LOCATION ${libs}/${ANDROID_ABI}/libtegra_hal.a)


#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11 -fexceptions -frtti")
#支持-std=gnu++11
set(CMAKE_VERBOSE_MAKEFILE on)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11 -Wall -DGLM_FORCE_SIZE_T_LENGTH")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DGLM_FORCE_RADIANS")

#include_directories(D:/Projects/Android/CLMAndroid/OpenCV-android-sdk/sdk/native/jni/include )
#set(OpenCV_DIR D:/Projects/Android/CLMAndroid/OpenCV-android-sdk/sdk/native/jni)
#find_package(OpenCV REQUIRED)
#target_link_libraries(${OpenCV_LIBS})

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

add_library( # Sets the name of the library.
        opencv-lib

        # Sets the library as a shared library.
        SHARED

        # Provides a relative path to your source file(s).
        src/main/jni/com_my_opencv_OpenCVUtil.cpp)

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library( # Sets the name of the path variable.
        log-lib

        # Specifies the name of the NDK library that
        # you want CMake to locate.
        log)

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
        opencv-lib android log GLESv2 EGL dl

        jnigraphics

#        libcpufeatures
#        libIlmImf
#        liblibjasper
#        liblibjpeg
#        liblibpng
#        liblibprotobuf
#        liblibtiff
#        liblibwebp
        libopencv_java3
#        libopencv_calib3d
#        libopencv_core
#        libopencv_dnn
#        libopencv_features2d
#        libopencv_flann
#        libopencv_highgui
#        libopencv_imgcodecs
#        libopencv_imgproc
#        libopencv_ml
#        libopencv_objdetect
#        libopencv_photo
#        libopencv_shape
#        libopencv_stitching
#        libopencv_superres
#        libopencv_video
#        libopencv_videoio
#        libopencv_videostab
#        libtbb
#        libtegra_hal
        ${OpenCV_LIBS}


        # Links the target library to the log library
        # included in the NDK.
        ${log-lib})

看见那么多注释没?全是踩的坑,以为opencv啥组件没加,然后一个个试的,结果根本不是这个原因导致编译不过,是NDK版本太新的问题。

第四步:编写你的jni CPP,网上说用javah从java里生成对应的c++的h头文件,但是实际测试下来不用这个也可以的,只要你知道jni里cpp的命名规则,inclue了jni.h就行,比如我这里的工程是

com_my_opencv_OpenCVUtil.cpp

com_my_opencv是包名,点换成了下划线,OpenCVUtil是java类名,具体内容直接是上面博客里的,我自己加了个YUV2RGB的方法:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_my_opencv_OpenCVUtil */
#include <cstdlib>
#include <opencv/cv.h>
#include <opencv/highgui.h>
#include <opencv/ml.h>
#include <string>
#include <vector>
#include <opencv2/opencv.hpp>
#include <android/bitmap.h>
#include <android/log.h>
#include <opencv2/core/core.hpp>
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>

#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/ml/ml.hpp>
#include <opencv2/objdetect/objdetect.hpp>
#include <vector>
using namespace cv;
using namespace std;

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

    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("org/opencv/core/CvException");
//        if (!je) je = env->FindClass("java/lang/Exception");
        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 BitmapToMat(JNIEnv *env, jobject& bitmap, Mat& mat) {
    BitmapToMat2(env, bitmap, mat, false);
}

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

    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("org/opencv/core/CvException");
//        if (!je) je = env->FindClass("java/lang/Exception");
        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;
    }
}

void MatToBitmap(JNIEnv *env, Mat& mat, jobject& bitmap) {
    MatToBitmap2(env, mat, bitmap, false);
}

extern "C"
JNIEXPORT void JNICALL Java_com_my_opencv_OpenCVUtil_Canny
  (JNIEnv *env, jobject instance,jobject bitmap){
  // TODO

      Mat mat_1;
      //bitmap转化成mat
      BitmapToMat(env, bitmap, mat_1);
      //将原始图转化为灰度图
      cvtColor(mat_1, mat_1, COLOR_BGR2GRAY);
      //先使用3*3内核来降噪
      blur(mat_1, mat_1, Size(3, 3));
      //运行canny算子
      Canny(mat_1, mat_1, 20, 50, 3);
      MatToBitmap(env, mat_1, bitmap);
}


std::string jstring2str(JNIEnv* env, jstring jstr)
{
    char*   rtn   =   NULL;
    jclass   clsstring   =   env->FindClass("java/lang/String");
    jstring   strencode   =   env->NewStringUTF("GB2312");
    jmethodID   mid   =   env->GetMethodID(clsstring,   "getBytes",   "(Ljava/lang/String;)[B");
    jbyteArray   barr=   (jbyteArray)env->CallObjectMethod(jstr,mid,strencode);
    jsize   alen   =   env->GetArrayLength(barr);
    jbyte*   ba   =   env->GetByteArrayElements(barr,JNI_FALSE);
    if(alen   >   0)
    {
        rtn   =   (char*)malloc(alen+1);
        memcpy(rtn,ba,alen);
        rtn[alen]=0;
    }
    env->ReleaseByteArrayElements(barr,ba,0);
    env->DeleteLocalRef(clsstring);
    env->DeleteLocalRef(strencode);

    std::string stemp(rtn);
    free(rtn);
    return   stemp;
}

extern "C"
JNIEXPORT jint JNICALL Java_com_my_opencv_OpenCVUtil_YUV2RGB
        (JNIEnv *env, jobject instance,jbyteArray yuv,jint width,jint height,jstring path){
    jbyte * pBuf = (jbyte*)env->GetByteArrayElements(yuv, 0);

    Mat image(height + height/2,width,CV_8UC1,(unsigned char *)pBuf);
    Mat mBgr;
    cvtColor(image, mBgr, CV_YUV2BGR_I420);
    bool  result = imwrite(jstring2str(env,path),mBgr);

    env->ReleaseByteArrayElements(yuv, pBuf, 0);
    env->DeleteLocalRef(instance);
    env->DeleteLocalRef(path);
    if (result){
        return 0;
    }
        return -1;
}

第五步:编辑build.gradle文件

在defaultConfig节点下加入:

// 使用Cmake工具
        externalNativeBuild {
            cmake {
                cppFlags "-std=c++11","-frtti", "-fexceptions"
                abiFilters "armeabi"
            }
        }

 

在android节点下加入:

 // 配置CMakeLists.txt路径
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"  // 设置所要编写的c源码位置,以及编译后so文件的名字
        }
    }
    sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
        }
    }

完整代码如下:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.1"
    defaultConfig {
        applicationId "com.my.mycvapplication"
        minSdkVersion 21
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        // 使用Cmake工具
        externalNativeBuild {
            cmake {
                cppFlags "-std=c++11","-frtti", "-fexceptions"
                abiFilters "armeabi"
            }
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    // 配置CMakeLists.txt路径
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"  // 设置所要编写的c源码位置,以及编译后so文件的名字
        }
    }
    sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.2.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}

第六步:编译,生成so文件

第七步:新建空activity模块,将生成的so导入运行:

新建的模块里build.gradle里的android目录下添加:

    sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
        }
    }

引入上面的java接口类,注意包名不能改:

在activity.xml里拖一个imgview,和button,在MainActivity里调用jni:

                Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.testhand);
                new OpenCVUtil().Canny(bitmap);
                iv.setImageBitmap(bitmap);

启动,真机调试,都没问题。

大坑来临

调试也过了,没啥问题,把代码打包给算法大佬,大佬在cpp里加了几个方法,识别人体部位健康的一些代码,结果编译过不了,报

 error: undefined reference to 'cv::HOGDescriptor::compute(cv::_InputArray const&, std::__ndk1::vector<float, std::__ndk1::allocator<float> >&, cv::Size_<int>, cv::Size_<int>, std::__ndk1::vector<cv::Point_<int>, std::__ndk1::allocator<cv::Point_<int> > > const&) const'

这简直莫名其妙,卡了我三天,上面CMakeList.txt那么多注释就是找这个问题试验的,开始以为是opencv哪个模块没加进去的,一个个试了之后不行,然后又去stackoverflow里查,找到相似问题了但是没答案,还是死马当活马医的看一个博客说降低NDK版本,去下了个14b的版本,重新编译,过了。

写个博客以作记录。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐