这是Adrain大神的形状识别系列教程的最后一个,完结撒花
这个项目我历尽千辛万苦,终于在查阅各种资料后能够成功转换成Android代码,实属不易啊。不过也确实是因为自己对OpenCV都不怎么了解,才导致了虽然看懂Adrain大神的博客后,但写成Android版本却频频出现bug。最近这两天也开始看Adrain大神的教程,也开始了解了OpenCV的一些基础,希望以后能够越来越顺利的在Andoid利用OpenCV。
话不多说,开始记录下最后一个教程的项目。以下,是本次项目要用到的图片。
首先,需要构建一个类ColorLabeler来标识颜色。以下是初始化一些数据:
1 | //定义一个颜色名称数组 |
在以上初始化数据的代码中,会发现有一个新东西–LAB。据Adrain大神说,他的这篇教程是基于计算已知颜色和所给图像区域的平均值的欧式距离来进行标识,而之所以选择LAB而不是HSV或RGB,是因为LAB在欧氏距离中有意义。所以,我们需要把已知颜色的RGB转换成LAB。
在创建rgbMat的时候耗费我巨多时间,因为我一直都对RGB在Mat中的保存毫不清楚,虽然原博客是用三个一行三列的数组去存,但是在JAVA中却不能。首先需要再了解一下Mat,上次说过Mat是一个矩阵指针,但更重要的它的类型。Mat具有多种数据深度,如下(摘自:https://blog.csdn.net/yang_xian521/article/details/7107786)
• CV_8U - 8-bit unsigned integers ( 0..255 )
• CV_8S - 8-bit signed integers ( -128..127 )
• CV_16U - 16-bit unsigned integers ( 0..65535 )
• CV_16S - 16-bit signed integers ( -32768..32767 )
• CV_32S - 32-bit signed integers ( -2147483648..2147483647 )
• CV_32F - 32-bit floating-point numbers ( -FLT_MAX..FLT_MAX, INF, NAN )
• CV_64F - 64-bit floating-point numbers ( -DBL_MAX..DBL_MAX, INF, NAN )
然后,Mat更厉害的是,它还涉及一个叫做channel,即通道。以前我们接触的二维数组,都是一行一列存一个数据,但是在Mat中,有几个通道,一行一列中就可以存储几个数组,而我们如果使用Mat来存RGB明显是需要三通道来存,且看以下图片(摘自:https://blog.csdn.net/xiahouzuoxin/article/details/38298165):
因此,在这里我选择了CV_8UC3这种类型的Mat来存放RGB。
还有一个坑,我看到网上说Scarla存放的顺序其实是BGR,所以我一开始也是按这个顺序去定颜色,结果居然蓝色给我说成了红色,红色又说是蓝色。我才意识到,在JAVA这里,Scarla还是按照RGB的顺序,简直欲哭无泪~
OK,继续往下看。
1 | public String label(Mat image, MatOfPoint contour){ |
先定义一个函数label,我们需要识别图像image和轮廓contour作为参数。以上代码解决的是,如何计算欧式距离。首先看到drawContours那里,当我们执行到那里的时候会发现,其实是图上的一个形状被画出来,而且还是实心白色的。(PS:Adrain大神的代码基本都是把前景变白色,背景为黑色来进行操作。),画出来的效果如下:
这样,在计算的时候,就只计算蒙版了的形状,即白色形状,简化了计算量。此外,还使用图像腐蚀,用图像腐蚀,更有利于形状的分割。在erode的时候遇到了问题,由于原本的python代码只是简单的mask = cv2.erode(mask, None, iterations=2),而JAVA代码要求的参数比较多,是这样public static void erode(Mat src, Mat dst, Mat kernel, Point anchor, int iterations)。最后在opencv的library中找到解释,发现kernel和anchor其实有默认值,所以这里只需把默认值代入就行:
• element – structuring element used for erosion; if element=Mat() , a 3 x 3 rectangular structuring element is used.
• anchor – position of the anchor within the element; default value (-1, -1) means that the anchor is at the element center.
接着要算图片区域的平均值,这里需要注意的是mean这个函数很坑。一开始看着教程我传入了两个参数,但一直报错,说mask.empty()||mask.type==0,于是我一直以为是我传入的mask的值是空值所以报错,但是我测试的时候发现我传进去的mask不为空啊,而且类型也不是0。在opencv的library中指出:C++: Scalar mean(InputArray src, InputArray mask=noArray()),但由于它后面对mask的解释是“可选择”对象,所以我一直忽略了mask=noArray()这个东西,后来不断查资料改代码后才发现,之前报错的所说的东西不是指我哪里错,而是在提示我mask应该是要按照提示那样的。于是我最后把mask的类型改成0,才终于成功(开心到泪流满面ㄒoㄒ)。激动完毕,继续:
1 | //计算平均值跟各个颜色的lab的欧式距离 |
接下来就是计算欧式距离啦。把之前算出来的平均值,与红色、绿色、蓝色的lab值做欧式距离计算,选出距离最小的那个lab作为该形状的颜色。ok,颜色标识这部分代码总算是完成了,接下来要做的就是跟以前一样啦。
1 | //读入图片 |
以上代码就是传入图片,然后把每个图形的颜色写出来,具体就不再解释啦。最终效果图如下:
补上Adrain大神的教程:https://www.pyimagesearch.com/2016/02/15/determining-object-color-with-opencv/
于是终于,我学完了Adrain大神的这三个教程,简直感动。Adrain大神真的很好人,不仅推荐了书籍,而且他还有一个17天教程,所谓教程其实就是他把他写过的教程文章每天给我推送一篇。虽然全英很痛苦,但好在自己有那么一点底子,看起来也不算很累,也幸好python代码不算很难看,我也才能运用在Android上。希望经过17天后,我能大概了解OpenCV,以及提升英语能力2333。最后补上这三个教程我写的Android完整代码:https://github.com/SecretLin/shape-detection-with-OpenCV/tree/master
代码可能写得不好,希望懂行的大佬多多指教。