安卓NDK视觉开发——手机拍照文档边缘检测实现方法与库封装

一、项目创建

创建NDK项目有两种方式,一种从新创建整个项目,一个在创建好的项目添加NDK接口。

1.创建NDK项目

创建 一个Native C++项目:

选择包名、API版本与算法交互的语言:

选择C++版本:

创建完之后,可以在项目中看到一个jni或者cpp的目录,目录包含一个CMakeLists.txt文件一个xxx.cpp文件:

2.添加NDK项目

在main目录添加一个目录,可命名为cpp或者jni都行:

把创建好的目录转化为JNI交互目录:

转化成功之后,目录下包含一个CMakeLists.txt文件一个xxx.cpp文件:

3.添加NDK依赖

选择使用的NDK版本:

选择CMake版本:

把下载好的NDK添加到配置文件:

4.测试与使用

添加类Java交互类:

在java交互类里面接口与jni交互的API:

java 复制代码
package com.example.docscan;
public class scanlib
{
    public native String stringFromJNI();
    // Used to load the 'docscan' library on application startup.
    static {
        System.loadLibrary("docscan");
    }

}

在xxx.cpp里面实现函数功能:

cpp 复制代码
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_docscan_scanlib_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

在MainActivity类里面调用函数:

java 复制代码
public class MainActivity extends AppCompatActivity {


    private ScanLib scan_lib = new ScanLib();
    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        // Example of a call to a native method
        TextView tv = binding.sampleText;
        tv.setText(scan_lib.stringFromJNI());
    }
}

二、添加依赖库

1.OpenCV

OpenCV是图像处理的基础,完整的包有上百M的大小,基于apk包大小的考虑,要对OpenCV做剪枝,之后重新编译成SDK,复制到jni(cpp)目录下:

2.NCNN

NCNN是深度学习算法模型的推理加速库,可以基于CPU或NPU进行推理,对应市场常用机型,选择使用NCNN版本并添加jni(cpp)目录下:

3.算法代码

把算法实现代码添加jni(cpp)目录下:

3. 源码编译

在CMakeLists.txt文件中添加这两个库与算法代码:

bash 复制代码
project(ScanJiaLib)
cmake_minimum_required(VERSION 3.4.1)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fopenmp")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fopenmp")

if(DEFINED ANDROID_NDK_MAJOR AND ${ANDROID_NDK_MAJOR} GREATER 20)
    set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -static-openmp")
endif()

## opencv 库
set(OpenCV_DIR "${CMAKE_SOURCE_DIR}/sdk/native/jni")
find_package(OpenCV REQUIRED)

if (OpenCV_FOUND)
    message(STATUS "OpenCV_LIBS: ${OpenCV_LIBS}")
    message(STATUS "OpenCV_INCLUDE_DIRS: ${OpenCV_INCLUDE_DIRS}")
else ()
    message(FATAL_ERROR "opencv Not Found!")
endif (OpenCV_FOUND)

#ncnn库
set(ncnn_DIR ${CMAKE_SOURCE_DIR}/ncnn-20221128-android-vulkan/${ANDROID_ABI}/lib/cmake/ncnn)
find_package(ncnn REQUIRED)
set_target_properties(
        ncnn PROPERTIES
        INTERFACE_COMPILE_OPTIONS "-frtti;-fexceptions"
        # ncnn.cmake 里面是关的,把它重新打开防止跟opencv2冲突,如果是重新编译ncnn的请自己尝试要开还是关
)

#算法代码
add_library(ScanJia-jni SHARED ScanJia_jni.cpp BitmapUtils.cpp DocumentEdge.cpp)

target_link_libraries(ScanJia-jni ${OnnxRuntime_LIBS} ncnn ${OpenCV_LIBS} jnigraphics)

4.封装成so包

在CMakeLists.txt里面添加封装库保存目录和要封装的cpp文件,重新编译:

bash 复制代码
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/libs/${ANDROID_ABI})
add_library(DocScan SHARED BitmapUtils.cpp DocumentEdge.cpp ScanJia_jni.cpp)

编译完成之后,在jni(cpp)目录生成封装好的so包,生成完成之后,注释掉上面的语句:

5.调用so包

在CMakeLists.txt里面添加so库目录:

bash 复制代码
add_library(DocScan SHARED)
set_target_properties(DocScan
        PROPERTIES IMPORTED_LOCATION
        ${PROJECT_SOURCE_DIR}/libs/${ANDROID_ABI}/libDocScan.so)

在java交互类里面添加so包名:

java 复制代码
    static
    {
        System.loadLibrary("DocScan");
    }

在build.gradle里面添加要调用的库:

bash 复制代码
 ndk {
 	moduleName "DocScan"
    abiFilters "armeabi-v7a", "arm64-v8a"
   	}

三、 API文档

1.Java交互类

在交互Java交互类ScanJiaSim.java中添加调用接口:

java 复制代码
	//初始化算法类,boolean useGPU------是否启用gpu加速
    public native boolean init(AssetManager mgr,boolean useGPU);

    //通用文档边缘检测,Bitmap bitmap------传入图像,返回PointI是检测到的四个点
    public  native PointI edgeDetector(Bitmap bitmap);

    //书本边缘检测,Bitmap bitmap------传入图像,返回PointI是检测到的四个点
    public native PointI bookEdgeDetect(Bitmap bitmap);

    //边缘校正,Bitmap bitmap------传入图像,返回校正后的图像,如果校正的点没有手动更新,则使用边缘检测到的点进行校正
    public  native Bitmap reveseEdge(Bitmap bitmap);

    //接收手动更新过的边缘点,如果手动更新过边缘点,则调用这个函数把更新的点发回校正函数使用
    public native int sendPoint(int x1,int y1,int x2,int y2,int x3,int y3,int x4,int y4);

2.JNI文件

在jni文件ScanJia_jni.cpp中实现交互类定义的接口:

cpp 复制代码
extern "C" JNIEXPORT jboolean JNICALL Java_com_dashu_scanjia_ScanJiaSim_init(JNIEnv* env, 
jobject thiz, jobject assetManager,jboolean cpu_gpu);

extern "C" JNIEXPORT jobject JNICALL Java_com_dashu_scanjia_ScanJiaSim_edgeDetector(JNIEnv *env,jobject thiz, jobject b_image);

extern "C" JNIEXPORT jobject JNICALLJava_com_dashu_scanjia_ScanJiaSim_bookEdgeDetect(JNIEnv *env,jobject thiz, jobject b_image);

extern "C" JNIEXPORT jobject JNICALL Java_com_dashu_scanjia_ScanJiaSim_reveseEdge(JNIEnv *env,jobject, jobject image);

extern "C" JNIEXPORT int JNICALL Java_com_dashu_scanjia_ScanJiaSim_sendPoint(JNIEnv *env, jobject instance,int x1,int 
y1,int x2,int y2,int x3,int y3,int x4,int y4)

3.算法代码实现

在cpp算法代码中实现接口:

cpp 复制代码
		 /// 读取模型
        /// \param mgr 
        /// \param edge_model_parma -边缘模型路径
        /// \param edge_model_bin -边缘模型路径
        /// \param mid_model_parma  - 书本中线模型路径
        /// \param mid_model_bin  - 书本中线模型路径
        /// \param use_gpu -是否启用GPU推理
        /// \return 
		int read_model(AAssetManager* mgr,
                       std::string edge_model_parma = "ED210113FP16.param",
					   std::string edge_model_bin = "ED210113FP16.bin",
					   std::string mid_model_parma = "M20210325F.param",
					   std::string mid_model_bin = "M20210325F.bin",
					   bool use_gpu = true);
        /// 边缘检测
        /// \param cv_src -原图像
        /// \param points_out -检测到的点集
        /// \param is_book -是否是书本
        /// \return 
		int detect(cv::Mat cv_src, std::vector<cv::Point>& points_out, bool is_book);

        /// 图像校正
        /// \param cv_src -原图像
        /// \param cv_dst -结果图像
        /// \param in_points -校正点集
        /// \return 
		int revise_image(cv::Mat& cv_src, cv::Mat& cv_dst, std::vector<cv::Point>& in_points);

4.调用接口

在MainActivity.java类中调用接口:

java 复制代码
//实例化接口类
private ScanJiaSim scan_jia_sim = new ScanJiaSim();

//初始化类,根据匹配的机型选择是否启用GPU,启用状态只是参考,最终是否能启用是基于底层是否能检测到GPU
boolean ret_init = scan_jia_sim.init(getAssets(),use_gpu);

//调用边缘检测
ScanJiaSim.PointI point = scan_jia_sim.edgeDetector(b_image);

//调用书本边缘检测
ScanJiaSim.PointI point = scan_jia_sim.bookEdgeDetect(b_image);

//图像校正,如果手动更新过边缘点,则要先调用upPoint()函数
Bitmap bitmap = scan_jia_sim.reveseEdge(b_image);

//手动更新过边缘点,则在校正之前把边缘点传入
private void upPoint(Point p1, Point p2, Point p3, Point p4) throws IOException

四、实现效果



相关推荐
程序猿阿伟27 分钟前
《鸿蒙微内核与人工智能算法协同,开启智能系统新时代》
人工智能·计算机视觉
神经美学_茂森1 小时前
【通俗理解】AI的两次寒冬:从感知机困局到深度学习前夜
人工智能·深度学习
Chatopera 研发团队1 小时前
Deep learning for NLP around 2017
人工智能·深度学习·自然语言处理
董董灿是个攻城狮4 小时前
009:传统计算机视觉之边缘检测
人工智能·计算机视觉·cnn
Quz4 小时前
OpenCV在现代社会中的应用
人工智能·opencv·计算机视觉
代码小鬼才4 小时前
关于量子神经网络的思考
人工智能·深度学习·神经网络
我爱一根柴哈5 小时前
人工智能机器学习从入门到高级学习资料(含资料下载地址)
人工智能·深度学习·机器学习
最好Tony5 小时前
深度学习blog-隐马尔可夫模型
人工智能·深度学习
B站计算机毕业设计超人6 小时前
计算机毕业设计Python+Spark中药推荐系统 中药识别系统 中药数据分析 中药大数据 中药可视化 中药爬虫 中药大数据 大数据毕业设计 大
大数据·python·深度学习·机器学习·课程设计·数据可视化·推荐算法
红色的山茶花6 小时前
YOLOv10-1.1部分代码阅读笔记-instance.py
笔记·深度学习·yolo