由于学校要求暑期实习,于是找了一位学长开的公司,接了一个项目,是对海康威视工业相机(MV_CE200_10GM)进行二次开发,读取其图像并做分析处理。 于是花了一点时间查找的相关资料并记录一些入门要点。
想先说说一些 “尝试授人与渔” 的话,也是自己的一点经验和体会。
对于硬件开发,个人觉得最为重要的两点就是手册+例程(而不是疯狂地去找各种视频教程)。比如基于某一款单片机开发,肯定是需要有手册的,了解它的引脚特性和硬件资源,这种资料一般来自官网。而编程的时候,如果能有一个例程引导,会比完全按照手册去摸索强得多。
同样,这个工业相机的二次开发也是如此:手册+例程,值得称赞的是,海康威视官网完全提供这两者,省去了开发者找资料的大量时间。因此下面简单演示一下官网资料的查找。
这里需要区分两个官网:海康威视【https://www.hikvision.com/cn/】 和 海康机器人【https://www.hikrobotics.com/cn】,如下图所示。
本项目中使用到的是CE系列工业相机,具体型号为MV_CE200_10GM,故要选择海康机器人官网。
打开官网后,不要直接搜索所使用相机的型号(因为搜不到。。。),按照下图可以找到相机的型号:
然后向下滚动,找到产品列表,选择对应的相机型号即可。
可见其实官网给的资料还是非常丰富的,个人建议结合技术规格书(产品数据手册)将用户手册通读一遍,基本能够建立一个大致的印象,之后有什么具体的问题再来查找。
说完了数据手册,再来说说例程。值得一提的是,海康机器人还提供了一个能够显示相机画面的软件——MVS。
同样,这个软件也可以在海康机器人官网下载得到:
个人觉得这个软件主要有两个作用:①配置相机,即实现相机与电脑的通信,简而言之,如果没办法在MVS中看到相机画面,那开发也就无从谈起了;②查看相机画面,方便对焦,同时设置参数,帮助开发者了解各个参数的作用(用户手册中有详细描述)。
此外,就是MVS安装目录下的各种官方例程了,这个非常有用!首先找到MVS的快捷方式图标,然后右键,找到文件所在位置,之后回退到上一级目录,就能看到MVS安装目录的结构了,如下图所示。
找完了资料,下面就开始开发了,这里需要根据自己的需要选择使用哪种语言开发,因为官方支持的语言还是非常多的。这里选择的是VS2015 + MFC + OpenCV,选用VS还有一个好处就是,它配置OpenCV确实非常方便。
由于开发内容过于琐碎,这里只记录一些关键点。
官方提供的demo支持的VS版本有2008,2010,2012,2015,需要根据自己的需要选择,因为VS的低版本是不兼容高版本的(网上有一些教程似乎可以破解,但比较麻烦),而且VS版本越高,安装包体积越大,可能两个版本后就double了,所以得根据自己电脑的情况和需求程度理智选择。此外就是需要考虑一下OpenCV的版本和VS版本匹配的问题,如下图所示 【附:数据来源链接】
因为我电脑上之前已经安装了OpenCV4.5.1,所以这里选择了VS2015,经过试验发现是能够匹配OpenCV 4.5.1的。
这里有一点需要注意的是:建议直接去官网下载vs最新的2015 community版,不要去一些软件站下载!!!安装包大小为3G左右的最好不要用!!!因为如果安装不成功,想要完全卸载非常麻烦,不信可以去网上搜搜“VS完全卸载”,那都是泪呀。。。。
相比于VS Code,VS配置OpenCV是相当地简单,关于这个也有非常多的教程,这里推荐一个比较简洁的,亲测有效。
下面记录一些关键点:
为了能够使用相机,还需要在工程属性中包含相机相关的头文件和链接库,方法基本和上面配置VS差不多。
官方提供的demo中有6个MFC程序,其他的都是控制台程序,对于需要有界面显示的开发者来说还是非常方便的,即单个功能实现可以参考控制台程序,界面制作可以参考MFC程序,关于MFC的开发,可以看看我之前写的一篇博客,虽然版本不同,但基本是相通的。
由于本人不是计算机专业,所以在开发的过程中也遇到了很多代码上的问题,确实涨了不少知识,在这里简单记录一下。
通过溯源头文件发现,最后相机读取到的数据类型为unsigned char *
类型,因为这个相机为黑白相机,数据格式设置的是8位单通道(Moon8),即每个像素点占用一个字节,其颜色的深度从白到全黑分256个等级。因此就面临一个问题是如何将这种格式的数据转换为OpenCV中使用的类型,方便后续的处理。
参考这个链接,发现这个问题似乎很简单,因为OpenCV的Mat类提供了一个构造函数,能够将unsigned char *
类型转换为Mat类型。
在编程时,我定义了一个指针类型的全局变量,用来表示设备句柄,但是发现定义的一个用来修改设备句柄的函数竟然不起作用,那个设备句柄变量始终为空指针。后来查找相关教程后发现这就是经典的无法修改实参的问题,对于这种问题,一般来说有三种解决办法 【参考链接】
这个问题其实非常低级,但是由于这个变量类型的特殊性(指针类型),导致我找了很久的bug,所以关键还是得要理解这种问题的本质。
首先先说结论,这种问题九成是内存泄漏或内存溢出,存在很多种情况,我当时应该是在一个循环里面调用imshow函数的时候,导致读取相机数据出现内存上的问题,这种情况可以说非常特殊了,其实我也还没真正整明白它内部的原理。这里找到一个链接,比较详细地描述了出现这种报错可能的几种情况,还是非常不错的。
一开始发现imshow函数显示的图片有点问题:只能显示图片的一部分,一开始以为是读取数据不全面的原因,后来发现问题出在imshow函数本身上。即如果直接调用imshow函数,其默认的窗口分辨率是固定的,如果图片超出这个分辨率范围,就只能显示一部分图像了。
解决办法是先使用nameWindow
函数建立一个可变大小的窗口,然后再调用imshow
函数显示图片。
namedWindow("Display", WINDOW_NORMAL); //建立可变大小的窗口
imshow("Display", image);
waitKey(0);
后来在循环中调用imshow函数又发现一个问题,那就是只能显示窗口内容,而不显示图像,经过排查发现是imshow
函数后面没有加waitkey(0)
函数。
相当于是按键检测,但不堵塞循环,非常好用
#include //记得包含这个头文件
if (_kbhit()) // 有按键按下:停止图像采集进程,并做好收尾工作(关闭设备并销毁句柄)
{
int key = _getch(); // 接收按下的按键
if (key == ' ') // 如果按下空格
{
process();
}
}
此外,还可以参考这个链接,提供了两种方案实现按键检测,值得学习。
这个问题网上的资料非常多,不过我这个有一个特殊的需求,那就是能精确到毫秒。这里采用的是SYSTEMTIME
类型的变量,需要包含头文件windows.h
。具体使用方法可以参考这篇博客。