第1节 第一个EmguCV程序 打开图片和相机
Halcon有自己专门的应用软件可以在那个里面开发,emguCV就没了,它直接在C#上开发就可以了。某种程度上,比halcon流程少一点,但是图像处理部分的步骤却多了很多,调试也会差一点。。。 回到正题,第一个程序我们需要学会三项技能(当然不是说hello emguCV……):1)用emguCV控件显示图片;2)用emguCV打开相机获取单个图片;3)用emguCV连续获取图片,即摄像。
首先,打开vs2017,新建一个C# Windows窗体应用程序,命名为firstEmguCVProgram。然后确认,添加引用,一共四个,你得浏览下,(细节操作前面都讲过,这儿我就简洁一点了),如下图(4-2-1):
![da41e7349ad35b632bce1342d520340a.png](https://i-blog.csdnimg.cn/blog_migrate/6a5044ae04a777ba119ba2b567cd0e06.jpeg)
图 4-2-1
最上面一行地址栏是这些库所在的位置了,我是默认安装路径的,选中的四个就是我们要添加的四个库了。然后在工具栏右键-->选择项-->浏览。这儿只要添加一个就可以了,如下图(4-2-2):
![dd5f5dbadc8bfad9704e9a3facac352d.png](https://i-blog.csdnimg.cn/blog_migrate/0f350fdf743b778a9d05da92d87a5013.jpeg)
图 4-2-2
还是我选中的这个,然后在工具栏里找到ImageBox控件和button控件,添加到form窗体里面,如下图(4-2-3):
![1a41a4f7afd823aeec449ce7feda5b09.png](https://i-blog.csdnimg.cn/blog_migrate/9cbabd2c44729b58c8187ad9d6caed84.jpeg)
图 4-2-3
双击form窗体,进入程序编写界面,在最上面添加引用(using),在form1初始化代码下面添加: CvInvoke.UseOpenCL = false;如下图(4-2-4):
![e972f039921f7d6717881de148eacac2.png](https://i-blog.csdnimg.cn/blog_migrate/3dab9fc5f0291511d5b755bc01e5bac6.jpeg)
图 4-2-4
然后声明并且实例化几个全局变量:2个抓拍图片的句柄,属于Capture类;2个Mat类的图片。对于这四个变量,我故意用了两种实例化的方式,就是为了说明两种方法都行。另外我连接了两个相机,如果你只有一个相机,就自己稍微改下代码,也是可以学习的,代码如下图(4-2-5)。
![c01a7731ae560360cee10b2f69e85ae5.png](https://i-blog.csdnimg.cn/blog_migrate/e22591c0b263f6776132868111e1e73c.jpeg)
图 4-2-5
部署完这些,我们先进入第一个按钮"打开照片"的代码编写:双击这个按钮,进入单击按钮事件代码的编写,代码如下(4-2-6):
![653945a7ff7a9dc09327690cc86983bd.png](https://i-blog.csdnimg.cn/blog_migrate/d0eec6f385874e51a99f9b139e13a95e.jpeg)
图 4-2-6
OpenFileDialog类不是第一次见了,就是用来干这个事的。。。If里面第一行我声明并实例化了一个Image类型的变量img。这也是一种图片类型,有好几种形式的(详情见本节TIPS 1)。变量img就是我们打开的图片,这个类后面的第一个参数说明是彩色rgb类型的图片(在这里面跟halcon里面的顺序颠倒了),第二个参数是说明图片是byte格式的。接下来一行是对控件属性的设置,imageBox控件的尺寸模式,设置为StretchImage模式,我仿佛学了一个单词。这个模式是把你打开的图片缩放成控件的尺寸,相当于刚好满控件显示的意思。如果不能理解我的解释,在待会儿运行时候你可以注释掉这一行,或者改成其他的SizeMode看看效果,毕竟还有好几种。最后一步就是显示图片了,没什么好说的好像。
敲完这么多代码,选择"项目"-->"firstEmguCVProgram属性",然后在"生成"里面把平台目标改成x64,如下图(4-2-7,4-2-8)。
![bfe012554bd7eeb5a3336e62a65f40cc.png](https://i-blog.csdnimg.cn/blog_migrate/55fecd49f5625f43b96b938cf6324634.jpeg)
图 4-2-7
![5789d33d1cdc42df4fa73b303f0298d1.png](https://i-blog.csdnimg.cn/blog_migrate/ca4745910b4b0ba045040415f228aac5.jpeg)
图 4-2-8
然后F5运行,然后不出意外会报错!如下图(4-2-9):
![ff0d0f528f92aad557f1534795b513b8.png](https://i-blog.csdnimg.cn/blog_migrate/61c04e8e3f9ec79ce4d5e114b66972d2.jpeg)
图 4-2-9
看到我红框框的地方了吗?必须要这样才能记得深刻我觉得。这个bug怎么处理?在路径(C:Emguemgucv-windows-universal 3.0.0.2157binx64)中找到
![28f9f6411e3104167fd4cf5e2722fc2d.png](https://i-blog.csdnimg.cn/blog_migrate/11733c734803500eba6e42989f18fdca.jpeg)
,然后把它复制到工程文件的Debug目录下,再运行就可以了。单击"打开照片"按钮,在自己电脑里面随便找一张图片打开试试。
![b56f2d758ba7f848ec806b2a13782c88.png](https://i-blog.csdnimg.cn/blog_migrate/d2d5962e71469aaef29887ab908a50d0.jpeg)
图 4-2-10
接下来我们进行第二步,也就是本节的重点了:连接相机并且抓取图片。首先你得有个相机,连接在电脑上。然后双击"采集"button,在里面敲入以下代码:
![7839715db1918cf472de5ef0a3ea5e03.png](https://i-blog.csdnimg.cn/blog_migrate/976b334fe170ca8b6bef9b4e977c447f.jpeg)
图 4-2-11
这样每点一次采集按钮,就会自动抓一帧图片并显示出来了。那么问题来了,相机在什么时候连上的呢?答:实例化的时候。这儿的if里面的判断就是看下相机是否连上。而实例化时候括号里面的序号就是用来分辨不同相机的。如果有两种型号的相机各一个,怎么知道每个序号对应哪个呢?(好问题!答案详见本节TIPS 2)。上图(4-2-11)里面的第一步是把抓到的那一帧图片赋值给mat1,如果你F12进去,或者鼠标悬停在这个单词上也可以看到它的解释:在grab()后恢复一个Gray image。这儿我个人觉得是不能把gray image简单理解成灰度图的,毕竟它返回的是一个彩图,不是黑白图。应该是指灰度图的形式。毕竟bgr其实就是三个通道的灰度图嘛。所以我的注释直接写抓到的图片了。下一步就是显示了。
这样的话,第二个功能算是实现了。有没有迫不及待想拍照试试?其实我也是[捂脸],太贪玩。。。
![4a6bf6e9c9f5eeaf4ea5f81a975cb902.png](https://i-blog.csdnimg.cn/blog_migrate/d0cac9510cc15bfc5f6a17e9a2c94431.jpeg)
图 4-2-12
还记得这个新年快乐嘛?随手一拍,它又入镜了。哈哈~~
最后我们要实现连续拍照了!双击"实时"按钮,进入该按钮编程界面。
![ba1386b81fe65e68543151ca408fd18e.png](https://i-blog.csdnimg.cn/blog_migrate/35d8b494cc52ef830fd91d58ef693242.jpeg)
图 4-2-13
这个里面(4-2-13)有一行灵魂代码,就是我画红框的这一行,这个"+="其实是C#范畴了,属于一种委托,整行意思就是每次图片被抓取后,就会触发一次后面那个方法,那个方法的定义就在下面,也就两步,第一步把抓取到的图片赋值给mat2,第二步显示出来。其实这个事件的命名都是系统自动给的,我输入到+=的时候自动弹出让我按Tab键,我就按Tab键了,不用自己编名字的感觉挺好哈。红框框下面一句cap2.Start()是用来开关这个触发事件的。话不多说,跑一下看看!F5,然后单击"实时":
![d4e86dc29fd21f9a0fea4c69a872561e.png](https://i-blog.csdnimg.cn/blog_migrate/082ffff340bac6ddf63a40e5b0dd741b.jpeg)
图4-2-14
靠!又报错!??(4-2-14)额,别问我,我也不晓得为什么,于是我屏蔽了这一行(这行代码是系统赠送的,不是我写的),然后就可以了。。。。
好啦!屏蔽掉之后果然没问题了,可是大功告成了吗?并木有~~。我们复杂一点点这个"实时"按钮:
![0c1cb93147ff2b9b0fb3ad30cdb307e6.png](https://i-blog.csdnimg.cn/blog_migrate/5527e55b3b06d82f51bb91820c4476a1.jpeg)
图 4-2-15
你也这样敲一下,然后运行程序试试。没什么特别的彩蛋,就是让你的实时显示多了一个暂停功能,但是总觉得那步灵魂算子(cap2.ImageGrabbed += Cap2_ImageGrabbed;)放这里面,每点击两次就要执行一次有点别扭,私以为你也可以把它放到form_load()里面。稍微变动下代码就可以了,算是留给你们的家庭作业吧!
经过本节,相信大家会对emguCV有了初步了解了。有的人会觉得麻烦,有的人会觉得不适应,这都不是问题。问题是愿不愿意去克服这些。肯不肯花时间去努力,去超越。
本节TIPS:
1) Image类型主要有以下(4-2-16)几种形式:
![1e7c7c820f9ca6bdaa9d960c8446beb5.png](https://i-blog.csdnimg.cn/blog_migrate/b76f09e890a23cc68baf713488b0fc20.jpeg)
图 4-2-16
你以为这些都是我总结的吗?哈!我可没这个本事,好啦,告诉你们真是情况吧,知识点啊!你把鼠标移到Image上,然后F1,额呸!F12。。就可以进入这个库的从元数据,里面有这个数据类型的所有形式,每种形式对应的注释也是有的。我自学也是这样学习的。同理,最开始声明的全局变量Mat类型的变量肯定也不止一种类型吧?你去F12一下看看(4-2-17):
![163c866ee9aee264c8efbbf44babbe7e.png](https://i-blog.csdnimg.cn/blog_migrate/a480b4a67801506458fff38bd31c397b.jpeg)
图 4-2-17
简单对比了一下,两种类型的声明还是有一些差异的,比如Mat可以声明一个没有参数的变量,但是Image的就不行了。这也就是为什么我一开始全局变量声明Mat类型的原因。
2) 不得不承认要说的知识点真的好多,不过不用灰心,这些知识我也都是自学的。我行你就也行,毕竟你还比我多了这本书呢!踩在我的肩膀上你还敢说不行?回到主题吧!不同型号相机怎么给编号呢?打渔方法:F12 Capture,你会发现其实Capture也有描述相机接口类型的参数的,下图中(4-2-18)红框框内。
![d597be56c0c0c6e00c4736b0a9271e29.png](https://i-blog.csdnimg.cn/blog_migrate/a11e141b65a7410992bf7ad0e7696371.jpeg)
图 4-2-18
可是你应该是不知道怎么写这个相机接口类型(CaptureType)的。对了,继续F12这个CaptureType!你会发现各种接口类型都有了编号,如下图(4-2-19)。到这儿,后面的就不用我教了,目送你们远去的背影……
![73057b8b8fa649b592e47232851c490b.png](https://i-blog.csdnimg.cn/blog_migrate/43c6e4c970384a5ca30d8f2c91d84c8c.jpeg)
图 4-2-19