其实一般情况下都是halcon转emguCV了,反过来做的应该很少吧?所以本章我也是只说怎么把halcon算子转换成emguCV代码。其实halcon跟emguCV都有那么多代码,很多我也在学习中呢,所以本章只有一节,把我整理的一些学习笔记写进来与大家一起分享。
先聊几个稍微简单的吧!
(1) Halcon里面的read_image(),其实就相当于emguCV里面的带地址参数的实例化image或者Mat,或者也还有一种方法,就是:
public static Mat Imread(string filename, LoadImageType loadType):读取一个图片。
(2) Halcon里面的打开相机open_framegrabber()相当于emguCV里面的实例化Capture类;
(3) Halcon里面的保存图片write_image()相当于:
public static bool Imwrite(string filename, IInputArray image, params int[] parameters):保存图片。
(4) Halcon里面的grab_image()就相当于emguCV的grab()了等等。
注意:这里说的"相当于",不是意思完全一样的意思,只是主要功能大致相同。
下面我们再主讲几个算子之间的转换。
第一个:图像格式的转换(trans_from_rgb()),如下图(4-5-1),是把一个BGR的图像转换成HSV空间。
如上图(4-5-1),我用方法的形式写了这个转换。首先,申明并实例化一个Hsv格式的image,大小等于需要转换的图像的尺寸。然后是关键代码:
public static void CvtColor(IInputArray src, IOutputArray dst, ColorConversion code, int dstCn = 0):一开始我看F12里面的解释以为这个代码只能进行颜色控件之间的转换(Bgr转换到Hsv),结果在ColorConversion的时候,发现Hsv转换到Bgr,Bgr转成灰度图也是这个算子!所以这个算子相当于也对应了trans_from_rgb(),trans_to_rgb(),rgb1_to_gray()。学一个抵三个了。这波不亏。那参数就好说了:第一个参数是输入的图像;第二个参数是输出的图像;第三个参数是要转换成哪个颜色空间(Bgr到Hsv,Hsv到Bgr,Bgr到Gray)全部在这里选择,其实还有其它类型的转换,在此不做扩展了。第四个参数是输出图片的通道数,默认为0,就是取决于转换的方式,不用填的。
可是转换出来后,它是一个Hsv空间的Image,需要单独得到三个通道的图片,还需要把他们分离开。这就相当于halcon里面的decompose3()了。这儿用到一个分离的方法:
public static void Split(IInputArray src, IOutputArray mv):把多通道的输入图像分离成多个单通道的图像。两个参数第一个是输入,第二个就是输出了。这儿我声明并实例化了一个VectorOfMat类型的变量,用来作为输出,然后分别拿三个Mat类型的图片来让它赋值。(遇到这种新类型,千万不要懵逼,因为我们有TIPS 1)。最后这三个Mat图片就是转换的3个单通道的H、S、V图片了。见下图(4-5-2),从左到右为RGB图,H,S,V图片:
第二个:阈值筛选(threshold())。上一节已经详细介绍了阈值筛选的7种类型,但是它的思路跟halcon还不是完全一样,比如你要筛选灰度值在50到120之间的部分,该怎么做呢?如下图(4-5-3):
这是针对halcon里面的region理论选择的方法,就是说只记录这个地方的位置信息,不记录这个地方的像素灰度值信息。所以我的ThresholdType后面选的是Binary和BinaryInv。其实binary就是二进制的意思,非黑即白,先对这个单通道图片进行阈值筛选出灰度值大于minValue的部分,赋值为255;然后再对单通道图片阈值筛选出灰度值小于maxValue的部分,赋值为255,这样你就获得了两张图片,两张图片的高亮部分分别代表了原始图片灰度值[minValue,255]和[0,maxValue]的部分,这两个部分再求交集就是[minValue,maxValue]的地方了。那么求交集怎么求呢?不再是halcon里面的intersection了。而是:
public static void BitwiseAnd(IInputArray src1, IInputArray src2, IOutputArray dst, IInputArray mask = null):这个算子就是"与"操作了。每个像素每个像素的进行。(这是"与"操作,那么"或"操作,"非"操作呢?详见本节TIPS 2)
经过这一步,一般的halcon里面的threshold就可以实现啦,如果还要实现一些其它的阈值方法,就靠同学们自己去摸索了。老规矩,看看这个方法实现的结果吧(4-5-4)(我的minValue和maxValue参数分别设置为50,150):
第三个:筛选区域(select_shape())。这儿我只讲最常用的面积筛选了哈。代码见下图:
其实意思是有点变化了。这个是找到一个一个的轮廓,然后根据轮廓的面积进行的筛选,注意不是那个区域的面积,是轮廓。这个方法的参数我得说一下,第一个参数就是输入的原始图片了,但是这个图片不是任何Mat都可以的,必须是binary的,或者说必须是二值图。如果不是怎么办?在线等,很着急?转呗,我就是这么弄的,直接用上面的第二个阈值筛选就可以得到二值图了;第二个参数是输出的轮廓,它是二维数组的点的集合,这个在本节TIPS 1 已经有说;第三个、第四个参数是轮廓面积的最小最大阈值了。那么,怎么实现的呢?首先:
public static void FindContours(IInputOutputArray image, IOutputArray contours, IOutputArray hierarchy, RetrType mode, ChainApproxMethod method, Point offset = default(Point)):顾名思义,找到轮廓。第一个算子输入的图片;第二个参数输出的轮廓;第三个参数是可选的输出向量;包含关于图像拓扑的信息,我一般都是null;第四个参数是找轮廓的类型,这可以有四个选项,请自己查看啦;第五个参数是计算轮廓里面用到的近似的方法,我喜欢选简单的,不让cpu太辛苦。第六个参数直接默认。
找到轮廓后还不够,还要根据面积筛选。筛选前得知道每个轮廓的面积,这就用到一个方法:
public static double ContourArea(IInputArray contour, bool oriented = false);计算轮廓面积。里面第一个参数是输入的轮廓,第二个参数是什么方向?默认为false吧。那我算出来的面积在哪输出啊?好吧,我调皮了,这是一个带返回值的方法,返回的double类型就是面积了。
整个函数的逻辑就是先找到所有的轮廓,然后用一个for循环依次求出每个轮廓的面积,再把符合面积的轮廓一个一个push到输出result_contour里面。这儿有一个细节,对于VectorOfVectorOfPoint类型的轮廓,里面的成员就是VectorOfPoint类型的,而vectorOfPoint类型的又相当于一个数组,它里面的成员又是Point类型的。
说了这么多,还是看看效果吧,下图(4-5-6)左一是一个彩色图片,中间的是转成的binary图片,最后是找到的轮廓。
学完这么多是不是觉得有点别扭?特别是像我这种用惯了halcon的,刚转emguCV的时候特别的难受啊,各种数据类型之间的变换都特别的别扭。渐渐我发现了原因:首先:halcon喜欢基于region去做很多事,而emguCV则喜欢基于图片像素本身。这就意味着每次操作都是对图片的。而图片的类型也挺多的。其次,就是你执行一步后想看看什么效果不是很方便,不像halcon,全部在变量窗口里面,随时查看。最后,就是没有halcon那么多辅助功能啦,比如特征检测,前趋函数,后继函数,还有自动的彩色显示等等。(说到这儿我倒是想到一个小TIPS 3,差点忘记了)。总的来说,作为免费的,已经不错了~
第四个:裁剪图片(crop_part()),讲到这儿才发现这个算子我好像没讲过,那好吧:
crop_part( : ImagePart : , , , : ):就是裁剪图片。后面四个参数决定了一个矩形区域,裁剪掉图片矩形区域以外的部分。第一个参数是要裁剪的图像;第二个参数是裁剪以后的输出图像;第三个第四个参数是矩形的左上角点的坐标;第五个第六个参数是矩形的宽和高。
其实这个算子对应的emguCV算子超级简单,就是Mat类的实例化,非要写成一个方法的调用,那就如下图(4-5-7):
在halcon的F1帮助里面,搜索"crop",发现好几个裁剪的算子,还有一个算子:
crop_domain_rel( : ImagePart : , , , : ):也是裁剪图片,只是形式稍有不同。第一个参数就是待裁剪的输入图片;第二个参数就是裁剪后的输出图片;第三个参数是从顶部往下裁剪掉多少行像素;第四个参数是从左往右裁剪掉多少列像素;第五个参数是从下往上裁剪掉多少行像素;第六个参数是从右往左裁剪掉多少列像素。这个感觉有点像剪刀,对图像四个边各给上一剪刀。经过简单的数学变换,这也是可以变成上面的那个算子的。但是如果还用上面那种算法,我就没必要扯这么多了嘛。这儿我再教大家一招,如下图(4-5-8):
这个算法,是从像素的角度去裁剪图片,或者说把要保留的部分重新赋值给一个新的图片。所以新图片的尺寸就是裁剪后的尺寸了,也就是上图中(4-5-8)的size1,是原图片宽度减去左右裁剪掉的列数,和原图片高度减去上下裁剪掉的行数。然后对图片进行实例化,赋予尺寸。然后两个for循环的嵌套,给这个新图片ImageCroped三个通道的每个像素赋值。怎么赋值呢?这儿有一个对应关系,假设原图片left减掉了5列,top减掉了6行,那么原图片的(5,6)位置的像素值就等于新图片(0,0)位置的像素值,对不?理解了这一点,再看上图中我的嵌套循环就很好理解了。其中data[]里面的三个数分别代表像素的横纵坐标和通道,都是从0开始的。
讲了这么多,不能只是看看而已哦亲们,一定要自己亲自操作下,才能转换成自己的知识,反正都是最后一节了,要辛苦也是最后一次了,再坚持一下咯!
还有就是,halcon算子跟emguCV的算法都是上千个的,我这儿只是列举了其中的很少一部分,非常常用的一部分。其实还有很多重要的算子,要考我们自己去学习了。毕竟,这只是一本入门的教材嘛,一年级的数学书怎么会教你乘法口诀,但它依然是你将来学会微积分,泰勒公式,高斯定理,傅里叶函数最下面的那块垫脚石。
最后,一起复习下这一节学到的方法:
1)public static Mat Imread(string filename, LoadImageType loadType):
2)public static bool Imwrite(string filename, IInputArray image, params int[] parameters);
3)public static void CvtColor(IInputArray src, IOutputArray dst, ColorConversion code, int dstCn = 0);
4)public static void BitwiseAnd(IInputArray src1, IInputArray src2, IOutputArray dst, IInputArray mask = null):
5)public static void FindContours(IInputOutputArray image, IOutputArray contours, IOutputArray hierarchy, RetrType mode, ChainApproxMethod method, Point offset = default(Point)):
6)public static double ContourArea(IInputArray contour, bool oriented = false);
7)crop_part(: ImagePart : , , , : ):
8)crop_domain_rel(Image:: , , , : ):
1) VectorOfMat具体什么意思,我也说不上来,矢量图片?查了一些资料没找到很好的解释。我自己的理解就是数组,Mat是一个图像,那VectorOfMat就是n个Mat,不过是有序排列的。那同理:
VectorOfPoint就是一组点的有序排列了,VectorOfVectorOfpoint就是n个二维排列的点了。一法通,万法通,所有的VectorOf的都好理解很多了,以后应该还会再遇到它的。
2) 其实你自己F12也能找到了,毕竟打渔方法都交给你们啦!
如上图(4-5-7)从上到下,分别为与,非,或,异或。
3) 这个是关于imageBox控件的一个使用小技巧,就是当程序运行时,可以在imageBox控件上右键来做一些操作。比如保存图片,读取图片,缩放,以及最后一个property,可以看到很多图像的特征,如下图:图中红框框按钮还可以看到图像的灰度直方图,看不清数字是可以放大看的哈。