安卓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

四、实现效果



相关推荐
淡海水7 小时前
【AI模型】常见问题与解决方案
人工智能·深度学习·机器学习
β添砖java8 小时前
深度学习(13)PyTorch神经网络基础
人工智能·深度学习
victory04319 小时前
论文设计和撰写1
人工智能·深度学习·机器学习
张人玉10 小时前
机器视觉VsionPro——多目标检测高级用法动态
目标检测·计算机视觉·机器视觉·vsionpro
沪漂阿龙11 小时前
OpenAI Agents SDK 深度解析(三):执行层——Agent 的“幕后指挥部”
人工智能·深度学习
@大迁世界11 小时前
14个你现在必须关闭的 iOS 26 设置,不然手机很快被它榨干
macos·ios·智能手机·objective-c·cocoa
数智工坊11 小时前
【SAM-DETR论文阅读】:基于语义对齐匹配的DETR极速收敛检测框架
网络·论文阅读·人工智能·深度学习·transformer
童园管理札记11 小时前
【续】数字时代:学前教育的新改革
经验分享·深度学习·职场和发展·微信公众平台
AI医影跨模态组学13 小时前
如何将纵向CT影像组学特征与局部晚期胃癌化疗时空异质性及耐药演化建立关联,并进一步解释其与化疗响应、淋巴结转移及生存预后的机制联系
人工智能·深度学习·论文·医学·医学影像·影像组学
有为少年14 小时前
从概率估计到“LLM 训练是有损压缩”
人工智能·线性代数·机器学习·计算机视觉·矩阵