Categories
程式開發

OpenCV在Android上的應用



{“ type”:“ doc”,“ content”:[{“type”:”heading”,”attrs”:{“align”:null,”level”:1},”content”:[{“type”:”text”,”text”:”一. OpenCV 介绍”}]},{“ type”:“ paragraph”,“ attrs”:{“ indent”:0,“ number”:0,“ align”:null,“ origin”:null}},{“ type”:“ blockquote” ,“內容”:[{“type”:”paragraph”,”content”:[{“type”:”text”,”text”:”OpenCV是一个基于BSD许可(开源)发行的跨平台计算机视觉库,可以运行在Linux、Windows、Android和Mac OS操作系统上。它轻量级而且高效——由一系列 C 函数和少量 C++ 类构成,同时提供了Python、Ruby、MATLAB等语言的接口,实现了图像处理和计算机视觉方面的很多通用算法。”}]}]},{“ type”:“ paragraph”,“ attrs”:{“ indent”:0,“ number”:0,“ align”:null,“ origin”:null}},{“ type”:“ paragraph” ,“ attrs”:{“ indent”:0,“ number”:0,“ align”:null,“ origin”:null},“ content”:[{“type”:”text”,”text”:”在移动端上使用 OpenCV 可以完成一系列图像处理的工作。”}]},{“ type”:“ paragraph”,“ attrs”:{“ indent”:0,“ number”:0,“ align”:null,“ origin”:null}},{“ type”:“ heading” ,“ attrs”:{“ align”:null,“ level”:1},“ content”:[{“type”:”text”,”text”:”二. OpenCV 在 Android 上的配置”}]},{“ type”:“ paragraph”,“ attrs”:{“ indent”:0,“ number”:0,“ align”:null,“ origin”:null}},{“ type”:“ paragraph” ,“ attrs”:{“ indent”:0,“ number”:0,“ align”:null,“ origin”:null},“ content”:[{“type”:”text”,”text”:”我在项目中使用的 OpenCV 版本是 4.x。”}]},{“ type”:“ paragraph”,“ attrs”:{“ indent”:0,“ number”:0,“ align”:null,“ origin”:null}},{“ type”:“ paragraph” ,“ attrs”:{“ indent”:0,“ number”:0,“ align”:null,“ origin”:null},“ content”:[{“type”:”text”,”text”:”在 Android Studio 中创建一个 Library,将官网下载的 OpenCV 导入后,就可以直接调用 OpenCV 中 Java 类的方法。”}]},{“ type”:“ paragraph”,“ attrs”:{“ indent”:0,“ number”:0,“ align”:null,“ origin”:null}},{“ type”:“ paragraph” ,“ attrs”:{“ indent”:0,“ number”:0,“ align”:null,“ origin”:null},“ content”:[{“type”:”text”,”text”:”如果想调用 C++ 的类,也可以使用 CMake 创建环境,然后通过 include 文件放入指定路径。”}]},{“ type”:“ paragraph”,“ attrs”:{“ indent”:0,“ number”:0,“ align”:null,“ origin”:null}},{“ type”:“ paragraph” ,“ attrs”:{“ indent”:0,“ number”:0,“ align”:null,“ origin”:null},“ content”:[{“type”:”text”,”text”:”下面是项目中使用的 CMakeLists.txt”}]},{“ type”:“ paragraph”,“ attrs”:{“ indent”:0,“ number”:0,“ align”:null,“ origin”:null}},{“ type”:“ codeblock” ,“ attrs”:{“ lang”:“ cmake”},“ content”:[{“type”:”text”,”text”:”cmakeminimumrequired(VERSION 3.6.0)nnincludedirectories(n ${CMAKESOURCEDIR}/src/main/cpp/includen)nnaddlibrary(libopencvjava4 SHARED IMPORTED)nsettargetproperties(n libopencvjava4n PROPERTIES IMPORTEDLOCATIONn ${CMAKESOURCEDIR}/src/main/jniLibs/libs/${ANDROIDABI}/libopencvjava4.so)nnaddlibrary(libc++shared SHARED IMPORTED)nsettargetproperties(n libc++sharedn PROPERTIES IMPORTEDLOCATIONn ${CMAKESOURCEDIR}/src/main/jniLibs/libs/${ANDROIDABI}/libc++shared.so)nnnaddlibrary(n detectnn SHAREDnn src/main/cpp/detect-lib.cppn src/main/cpp/detect-phone.cppn)nnnfindlibrary(n log-libn logn)nntargetlinklibraries(n detect libopencvjava4 libc++_shared jnigraphicsn ${log-lib}n)”}]},{“ type”:“ paragraph”,“ attrs”:{“ indent”:0,“ number”:0,“ align”:null,“ origin”:null}},{“ type”:“ paragraph” ,“ attrs”:{“ indent”:0,“ number”:0,“ align”:null,“ origin”:null},“ content”:[{“type”:”text”,”text”:”其中,detect-lib.cpp 和 detect-phone.cpp 是我创建的 C++ 类。打成 so 文件时,会包含这2个类。”}]},{“ type”:“ paragraph”,“ attrs”:{“ indent”:0,“ number”:0,“ align”:null,“ origin”:null}},{“ type”:“ heading” ,“ attrs”:{“ align”:null,“ level”:1},“ content”:[{“type”:”text”,”text”:”三. 例子两则”}]},{“ type”:“ paragraph”,“ attrs”:{“ indent”:0,“ number”:0,“ align”:null,“ origin”:null}},{“ type”:“ heading” ,“ attrs”:{“ align”:null,“ level”:2},“ content”:[{“type”:”text”,”text”:”3.1 作为二维码识别的兜底方案”}]},{“ type”:“ paragraph”,“ attrs”:{“ indent”:0,“ number”:0,“ align”:null,“ origin”:null}},{“ type”:“ paragraph” ,“ attrs”:{“ indent”:0,“ number”:0,“ align”:null,“ origin”:null},“ content”:[{“type”:”text”,”text”:”在 Android 原生开发中,二维码识别有老牌的 zxing 等开源库。为何还要使用 OpenCV 呢?”}]},{“ type”:“ paragraph”,“ attrs”:{“ indent”:0,“ number”:0,“ align”:null,“ origin”:null}},{“ type”:“ paragraph” ,“ attrs”:{“ indent”:0,“ number”:0,“ align”:null,“ origin”:null},“ content”:[{“type”:”text”,”text”:”因为 OpenCV 有自己的优势,借助它可以定位到二维码的位置,一般识别不到二维码的内容大多是因为找不到它的位置。要是能够找到位置,就可以快速识别二维码的内容。”}]},{“ type”:“ paragraph”,“ attrs”:{“ indent”:0,“ number”:0,“ align”:null,“ origin”:null}},{“ type”:“ paragraph” ,“ attrs”:{“ indent”:0,“ number”:0,“ align”:null,“ origin”:null},“ content”:[{“type”:”text”,”text”:”这样一来,识别二维码时需要先拍一张照,从图像中找出二维码的位置。当然,还可以对图像进行预处理,以便能够更好地找到二维码的位置。”}]},{“ type”:“ paragraph”,“ attrs”:{“ indent”:0,“ number”:0,“ align”:null,“ origin”:null}},{“ type”:“ paragraph” ,“ attrs”:{“ indent”:0,“ number”:0,“ align”:null,“ origin”:null},“ content”:[{“type”:”text”,”text”:”下面的代码,展示了在应用层拍完照之后,将图片的路径传到 jni 层将其转换成对应的 Mat 对象,再转换成灰度图像,然后找出二维码的位置,要是能够找到的话就识别出二维码的内容。”}]},{“ type”:“ paragraph”,“ attrs”:{“ indent”:0,“ number”:0,“ align”:null,“ origin”:null}},{“ type”:“ codeblock” ,“ attrs”:{“ lang”:“ cpp”},“ content”:[{“type”:”text”,”text”:”extern “C”nJNIEXPORT jstring JNICALLnJavacomxxxsdkutilsDetectUtilsqrDetect(JNIEnv env, jclass jc,jstring filePath) {nn const char filepathstr = env->GetStringUTFChars(filePath, 0);n string path = filepathstr;n Mat src = imread(path);nn Mat gray, qrcoderoi;n cvtColor(src, gray, COLORBGR2GRAY);n QRCodeDetector qrcodedetector;n vector pts;n string detectinfo;n bool detresult = qrcodedetector.detect(gray, pts);n if (detresult) {n detectinfo = qrcodedetector.decode(gray, pts, qrcoderoi);n return env->NewStringUTF(detectinfo.cstr());n } else {n detectinfo = “”;n return env->NewStringUTF(detectinfo.c_str());n }n}”}]},{“ type”:“ paragraph”,“ attrs”:{“ indent”:0,“ number”:0,“ align”:null,“ origin”:null}},{“ type”:“ paragraph” ,“ attrs”:{“ indent”:0,“ number”:0,“ align”:null,“ origin”:null},“ content”:[{“type”:”text”,”text”:”对应的 Java 代码,方便应用层调用 jni 层的 qrDetect()”}]},{“ type”:“ paragraph”,“ attrs”:{“ indent”:0,“ number”:0,“ align”:null,“ origin”:null}},{“ type”:“ codeblock” ,“ attrs”:{“ lang”:“ java”},“ content”:[{“type”:”text”,”text”:”public class DetectUtils {nn static {n System.loadLibrary(“detect”);n }nn /*n 识别二维码n @param filePathn @returnn */n public static native String qrDetect(String filePath);nn ……n}”}]},{“ type”:“ paragraph”,“ attrs”:{“ indent”:0,“ number”:0,“ align”:null,“ origin”:null}},{“ type”:“ paragraph” ,“ attrs”:{“ indent”:0,“ number”:0,“ align”:null,“ origin”:null},“ content”:[{“type”:”text”,”text”:”最后是应用层的调用”}]},{“ type”:“ paragraph”,“ attrs”:{“ indent”:0,“ number”:0,“ align”:null,“ origin”:null}},{“ type”:“ codeblock” ,“ attrs”:{“ lang”:“ kotlin”},“ content”:[{“type”:”text”,”text”:”// 使用 OpenCV 进行二维码识别nval result = DetectUtils.qrDetect(filePath)nL.d(“opencvs识别二维码: $result”)”}]},{“ type”:“ paragraph”,“ attrs”:{“ indent”:0,“ number”:0,“ align”:null,“ origin”:null}},{“ type”:“ heading” ,“ attrs”:{“ align”:null,“ level”:2},“ content”:[{“type”:”text”,”text”:”3.2 比对图像的差异”}]},{“ type”:“ paragraph”,“ attrs”:{“ indent”:0,“ number”:0,“ align”:null,“ origin”:null}},{“ type”:“ paragraph” ,“ attrs”:{“ indent”:0,“ number”:0,“ align”:null,“ origin”:null},“ content”:[{“type”:”text”,”text”:”在我们的实际开发中遇到一个应用场景:需要判断我们的手机回收机里面是否存放了物体。(手机回收机是一个触摸屏设备,可以通过 Android 系统来操作内部的硬件设备。)”}]},{“ type”:“ paragraph”,“ attrs”:{“ indent”:0,“ number”:0,“ align”:null,“ origin”:null}},{“ type”:“ paragraph” ,“ attrs”:{“ indent”:0,“ number”:0,“ align”:null,“ origin”:null},“ content”:[{“type”:”text”,”text”:”我们事先拍一张回收机内没有物体的图作为基准图像,等到需要判断是否存在物体时再拍一张图片。两幅图片对比看比例,比列超过阈值则认为回收机内存在着物体。”}]},{“ type”:“ paragraph”,“ attrs”:{“ indent”:0,“ number”:0,“ align”:null,“ origin”:null}},{“ type”:“ paragraph” ,“ attrs”:{“ indent”:0,“ number”:0,“ align”:null,“ origin”:null},“ content”:[{“type”:”text”,”text”:”下面的代码,展示了在应用层拍完照之后,跟基准图片进行比对,并返回结果。”}]},{“ type”:“ paragraph”,“ attrs”:{“ indent”:0,“ number”:0,“ align”:null,“ origin”:null}},{“ type”:“ codeblock” ,“ attrs”:{“ lang”:“ cpp”},“ content”:[{“type”:”text”,”text”:”extern “C”nJNIEXPORT jboolean JNICALLnJavacomxxxsdkutilsDetectUtilscheckPhoneInMTA(JNIEnv env, jclass jc,jstring baseImgPath,jstring filePath) {nn jboolean tRet = false;n const char filepathstr = env->GetStringUTFChars(filePath, 0);n string path = filepathstr;n Mat src = imread(path);nn const char *baseimgpathstr = env->GetStringUTFChars(baseImgPath, 0);n string basePath = baseimgpathstr;n Mat baseImg = imread(basePath);nn int result = checkPhoneInBox(baseImg,src,40,0.1);nn LOGI(“checkPhoneInBox result = %d”,result);n if (result == 0) {n tRet = true;n }nn return tRet;n}”}]},{“ type”:“ paragraph”,“ attrs”:{“ indent”:0,“ number”:0,“ align”:null,“ origin”:null}},{“ type”:“ paragraph” ,“ attrs”:{“ indent”:0,“ number”:0,“ align”:null,“ origin”:null},“ content”:[{“type”:”text”,”text”:”两张图片真正的比对是在 checkPhoneInBox() 中完成的。其中,maxFilter() 是为了处理彩色的情况,然后使用高斯滤波进行降噪处理,再进行二值化处理,最后判断灰度差异区域占总图像的比列是否超过预先设定的阈值。”}]},{“ type”:“ paragraph”,“ attrs”:{“ indent”:0,“ number”:0,“ align”:null,“ origin”:null}},{“ type”:“ codeblock” ,“ attrs”:{“ lang”:“ cpp”},“ content”:[{“type”:”text”,”text”:”int checkPhoneInBox(cv::Mat baseImg, cv::Mat snapImg, int diffThresh, double threshRatio) {nn cv::Mat baseMaxImg, snapMaxImg,baseGausImg, snapGausImg;n if (baseImg.empty()|| snapImg.empty())n {n return -1;n }nn try {n maxFilter(baseImg, baseMaxImg);n maxFilter(snapImg, snapMaxImg);n } catch (…) {n return -1;n }nn cv::GaussianBlur(baseMaxImg, baseGausImg, cv::Size(5, 5),0);n cv::GaussianBlur(snapMaxImg, snapGausImg, cv::Size(5, 5),0);nn cv::Mat diff,diffBin;n cv::Mat noMax;n cv::absdiff(baseGausImg, snapGausImg, diff);n cv::threshold(diff, diffBin, diffThresh, 255, cv::THRESHBINARY);nn float ratio = (float)cv::countNonZero(diffBin) / (long)diffBin.total();nn LOGI(“ratio = %f,%d,%ld”,ratio,cv::countNonZero(diffBin),(long)diffBin.total());nn if (ratio > threshRatio)n {n return 0;n }n elsen {n return 1;n }n}nnint maxFilter(cv::Mat baseImg, cv::Mat &maxImg)n{n if (baseImg.channels() <3)n {n maxImg = baseImg.clone();n }n elsen {n maxImg.create(baseImg.size(), CV8UC1);n for (int r=0;r