第四节 第三个EmguCV程序:fin
还记得上一节开头说的那个例子么,经过今天这两天的念念不忘,被我摸索出来解决办法了!知道答案那一刻才知道我为了实现这一步走了多少弯路,答案原来so easy……好在念念不忘,必有回响。有结果终究是好的,在琢磨的过程中我还学了点新东西,不亏!
那么言归正传,这是一个什么例子呢?见下图:
有没有发现有个不小的突起在左边深色物体的边缘,本节的目的就是筛选出这个边缘!图片可以去我QQ群文件下载(强行打广告还是很不好的,所以还是老实点吧,图片下载方式详见本节TIPS 1)。面对这么一块大突起,颜色也跟主物体差不多,有什么好的思路呢?其实简单的形态学就可以了!好像从这一节开始,我们开始重点讲解一些emguCV图像处理方面的内容,好吧,且听我娓娓道来~
首先,先到解决方案那里再新建一个项目(Windows窗体应用程序)。添加完该添加的引用、选择项,然后在form里添加一个imageBox控件一个button控件,如下图(4-4-2):
我这人比较懒,按钮少一个是一个,所以读取图片直接放到了form_load()里面,同理,说过的知识点,我也懒得再重复,直接上代码吧(4-4-3):
Path里面就是那张图片在我电脑保存的位置,这个你得根据你的情况稍微变动变动。所以我一运行,imageBox1控件就已经显示照片了。接下来是"识别"按钮的代码了。粗鲁的先上图(4-4-4)吧:
代码就这么长啦,但是全篇知识点!思路是这样的:先选出图片右边白色区域,再用大的结构元素进行闭运算,把凹坑处填上,再与闭运算之前的白色区域做差,求出因为闭运算多出来的部分,再用小点的结构元素开运算去掉小的部分,最后得到的就是大的那个突起部分了,最后显示。也就是代码中我写的六步,我们一步一步来:
第一步:跟halcon类似又几乎不一样的threshold:
public static double Threshold(IInputArray src, IOutputArray dst, double threshold, double maxValue, ThresholdType thresholdType):首先,这个方法来自CvInvoke类,emguCV的几乎所有方法都来自这个类。这个方法的意思就是用来阈值的,但是它的功能复杂了很多。先说参数:第一个是要进行阈值筛选的图片,必须是单通道的;第二个是筛选后输出的图片,不是region,是图片!必须强调下;第三个参数是阈值的值(有点蒙?无妨);第四个参数是阈值的最大值,第五个参数你要阈值筛选的方法,对的,一个阈值筛选,emguCV整出了7种方法,多到令人发指啊。。。(欲知7种详情,请见本节TIPS 2),在此先说本例中选择的这种方法,它的意思保留灰度值大于threshold的像素不动,小于threshold的像素全部为0。我们就是选出右边白色的区域,也就是灰度值大于100的区域,灰度值小于100的像素直接被赋值为0了。如果你希望可以像halcon那样看到每一步之后的变量,可以加一句imageBox1.Image=matThreshold;然后再执行下看看。
第二步:就是闭运算这一大块白色的区域了。闭运算是有一个结构元素的嘛,所以先创建一个结构元,这里面用到一个方法:
public static Mat GetStructuringElement(ElementShape shape, Size ksize, Point anchor):这是一个构建并返回Mat类型的结构元的方法:第一个参数是结构元素的形状,第二个参数是它的尺寸,第三个元素是它的锚点,一般都选择(-1,-1)点,即默认的中心点。本例通过这个方法获得了一个半径为270的圆形结构元素。
然后进行闭运算,这个闭运算的算子又厉害了,它不但可以闭运算,还会…,算了我先只讲闭运算,(具体详情详见本节 TIPS 3):
public static void MorphologyEx(IInputArray src, IOutputArray dst, MorphOp operation, IInputArray kernel, Point anchor, int iterations, BorderType borderType, MCvScalar borderValue):这个方法确切来说是进行形态学变换的,但是主线路我们只讲闭运算:第一个参数是输入的图像,第二个参数是变换后输出的图像,第三个参数是你要形态学变换的方法,选择闭运算(close),第四个参数是你要用到的结构元素,本例中就是上面那个圆啦,第五个参数是锚点,选择默认的(-1,-1)就挺好,第六个参数是形态变换的次数,它可以重复多次,这一点与halcon不同;第七个参数是边界模式,我一般也是选择默认的default,第八个参数是边界的颜色,因为是单通道,所以这儿的颜色只有一个参数。闭运算之后,再拿输出的图像再跟输入图像做差,也就是接下来的
第三步:求差
public static void AbsDiff(IInputArray src1, IInputArray src2, IOutputArray dst):这个方法是求两个图像对应像素之间灰度值不一样的地方,这个地方用同一位置两个像素灰度值差值的绝对值表示出来,相当于halcon里面的sub_image()我感觉。不过这儿的灰度值做差是求绝对值的。到了这一步,图像是什么样子了呢?加一行imageBox1.Image=matDifference;就可以看到了,如下图(图4-4-5):
第四步:看这图片,心中自然就能想到该怎么做,那就是开运算!对的,跟第三步也就八九不离十了。先实例化一个开运算要用到的结构元素,这儿我又实例化了一个尺寸为5*5的矩形作为结构元素。然后开运算,还是一样的方法,只不过第三个参数改成了Open。开运算结束,就算是咱找出来这一片区域了吧,不过我们得把它框出来,所以用到
第五步:求边缘。要是halcon好像很多种方法可以实现,其实emguCV也差不了多少,这儿我们就用一招canny求边缘的方法。
public static void Canny(IInputArray image, IOutputArray edges, double threshold1, double threshold2, int apertureSize = 3, bool l2Gradient = false):后面两个参数一般不用管,第一个参数是输入的原始图像了,第二个参数是输出的边缘图像,第三个第四个参数就是最小最大阈值了,是canny算法里面的。怎么用,前面讲过的啦,你应该知道。经过这一步就求出边缘啦,看下图(4-4-6)。现在问题就是怎么把原图像和这个边缘同时显示出来了,也就是
第六步:要显示,必须要把这个轮廓画到原图上去,然后再显示出来。这儿我们用到了一个方法:
public static void Add(IInputArray src1, IInputArray src2, IOutputArray dst, IInputArray mask = null, DepthType dtype = DepthType.Default):这个方法的意思就是把两张图片的灰度值相加然后输出,类似于halcon里面的add_image()吧,那参数就很好理解了:第一个第二个参数就是输入的两幅图片,第三个参数就是输出的相加后的图片。后面的两个参数都不用填的。(这个加图片如果用的不过瘾,还有更厉害的,详见本节TIPS 3)经过这一步,这个白色的轮廓就画到原图中去了。我们再显示这张图片就大功告成了!
最后一起看结果吧!如下图(4-4-7)。
最后我们还是得复习下本节新学到的一些方法:
1)public static double Threshold(IInputArray src, IOutputArray dst, double threshold, double maxValue, ThresholdType thresholdType);
2)public static Mat GetStructuringElement(ElementShape shape, Size ksize, Point anchor);
3)public static void MorphologyEx(IInputArray src, IOutputArray dst, MorphOp operation, IInputArray kernel, Point anchor, int iterations, BorderType borderType, MCvScalar borderValue):
4)public static void AbsDiff(IInputArray src1,IInputArray src2, IOutputArray dst):
5)public static void Canny(IInputArray image, IOutputArray edges, double threshold1, double threshold2, int apertureSize = 3, bool l2Gradient = false):
6)public static void Add(IInputArray src1,IInputArray src2, IOutputArray dst, IInputArray mask = null, DepthType dtype = DepthType.Default):
总结:emguCV有点类似于halcon在C#的二次开发,也有一个大的类里面装了很多种方法(halcon里面叫算子),就是CvInvoke类。所以每次调用一个方法的时候,有输出变量的话,都得先申明并实例化那个变量。另外,很多方法你猜是有这个方法的,但是不知道有没有,可以对这个类F12,进入后ctrl+F,搜索你想到的这个方法的关键词就可以了。或者当你对某个方法的参数不记得怎么用的了,不太清晰某个方法是做什么的了,都可以到这里面查找,这个F12的作用就相当于halcon里面的F1帮助。这也是知识点啊兄弟们!
最后的最后,不得不无耻的告诉大家一下:本节的图片其实来自halcon的一个例子:fin.hdev,我是直接截屏保存的。当然这个解题思路也是copy例子里面的,但是运行速度真的慢了好多,比halcon。
本节TIPS:
1) 其实这个图片来自halcon例程:fin.hdev的第一个图片的截图。。。你去截图保存就可以了。
2)
开局我就说过,本书的目的是为了授之以渔,所以我不但要说这七种方法分别代表什么意思,而且我是怎么学来的我也要告诉你们:你用一个emguCV的方法的时候,先F12进入[从元数据]里面看看按照提示一点点弄懂这个方法是干啥的,每个参数的意思以及参数的类型,最后看看所有的可选项,比如上图中的ThresholdType(阈值类型),你在敲入"."之后会自动出来下面的7种阈值筛选的方法,每种方法都有解释,这儿它是用"?:"语法来写的,这个语法是先在问好的左边说一句话,问你对不对,对就返回冒号左边的数值,不对就返回冒号右边的数值。 比如第一个Binary,问号左边就是value>threshold(第三个参数:阈值的值),若对,就返回max_value,那该点的灰度值value就等于max_value(方法里面第四个参数)了,若不对,就返回冒号右边的,就是0 了。所以这个方法的作用就是把一张图片中灰度值大于threshold(第三个参数)的像素全部重新赋值为最大阈值(第四个参数),小于threshold的全部赋值为0,即(抹黑)。如下图(4-4-9),我的threshold是50,max_value是255:
灰度值大于50的地方都变成了255的灰度值,小于50的地方灰度值都变成了0。
理解通第一个,接下来的就很好理解了:
BinaryInv:value= value>threshold?0:max_value。跟Binary刚好相反,大于threshold的像素赋值为0,小于threshold的反而赋值max_value。如下图(4-4-10)(参数还是那么多):
Mask:是什么意思呢?我母鸡啊~鼠标选中它也不解释,我运行还报错。。算了不管它!
Otsu:这个是自动选择最优阈值,就是第三个参数,它自己给你算出来,所以你设置的多少就没用了。哈哈知道它是怎么算的吗?我知道,但我不想说!好啦,其实在halcon部分已经说过了……。先看效果(4-4-11)(我三、四两个参数都设置为255的):
ToZero:value=value>threshold? Value:0。这意思就好理解了,保留灰度值大于threshold的像素不动,小于threshold的像素全部为0,如下图(4-4-12)(为了明显点,第三个参数我改成了100):
ToZeroInv:value= value>threshold? 0:Value。这个又跟上面一个相反了,大于threshold的像素全部抹黑为0,小于threshold的全部保留。如下图(4-4-13)(参数没变):
Trunc:value= value>threshold? Threshold:Value。灰度值大于threshold的像素全部赋值为threshold,灰度值小于threshold的像素保持原状。如下图(4-4-14)(参数没变):
七种方法总算讲完了,感觉画了我好多的力气去讲一个算子实在是心累,划不来。以后这种知识点我不会再这么认真详细的讲了哈!已经教你们方法了,得自己学会学习嘛。
3) MorphologyEx()这个算子其实是形态学变换嘛,看完正文你也知道,但是它不但可以进行开运算(open)、闭运算(close),还可以进行腐蚀(erose)、膨胀(dilate)、梯度(gradient)、顶帽(tophat)、黑帽(blackhat),对的,就是这么多,简直厉害!开闭运算,腐蚀膨胀,相信你都用的很熟悉了,但是剩下三个可能就有点陌生了,我在网上搜到了这三个形态学变换的公式,方便大家理解:
Gradient(img)=dilate(img)-erose(img);
Tophat(img)=img-open(img);
Blackhat(img)=close(img)-img;
其实有点像是组合拳了。梯度感觉有点像轮廓的感觉,顶帽黑帽感觉有点像是找那些边缘的突起和凹坑。其实这个黑帽就是本节找那个疙瘩的思路,你发现没有?
4) public static void AddWeighted(IInputArray src1, double alpha, IInputArray src2, double beta, double gamma, IOutputArray dst, DepthType dtype = DepthType.Default);这个算子也是两个图片灰度值相加,只不过它加的有点任性,我用公式来表达吧:dst=src1*alpha+src2*beta+gamma;对的,就是说不但可以灰度相加,还可以乘以系数相加,最后还可以加一个补偿!