海康相机SDK开发in VS2015+Qt5
1 开发环境
本章节介绍工业相机二次开发环境的安装,安装后各目录所包含的文件,以及客户端的展示效果。
1.1 安装包获取
从官网下载最新版本的MVS安装包,支持Windows xp、Windows 7、Windows 8、Windows 10的32和64位系统。安装过程默认即可。
官网下载链接:http://www.hikvision.com/cn/download_more_960.html
1.2 安装目录介绍
MVS安装包由四个组件构成,分别是MVS客户端、SDK开发包、驱动、GenICam。安装过程大概1-3分钟。我安装在“D:\Program\MVS”路径,目录结构如下:
1.3 效果展示
建议安装成功后,连接相机,打开MVS客户端,查看相机连接和图像预览的效果,确认环境正常后,再开始基于SDK的二次开发。如下:
观察三个指标:
1) 带宽。正常值在100Mbps以上即认为正常;
2) 错误数。非0即表示有丢帧,不正常;
3) 丢包数。非0,不正常。参考第四章常见问题的解决方法。
2 产品概述
本章介绍SDK在整个机器视觉系统中的层次定位,可实现的功能,基本的开发调用流程,以及常用的接口。
2.1 SDK定位
2.2 基本接口调用流程
2.3 参数配置
1) 相机所有开放的参数可参考MVS的属性树,只要在属性树中看得到的节点,都可以通过SDK来获取和设置相应的值。
2) 每个节点分别属于哪种数据类型,可参考如下控件形式:
3) 节点的关键字,可参考以下表格:
e.g:
设置宽度为2592 ->MV_CC_SetIntValue(handle, “Width”, 2592);
4) 详细的函数说明,可以参考SDK 使用手册。
3 具体海康相机SDK开发
这里我采用的是型号为MV-CA050-10GC的海康工业相机,开发平台是VS2015,界面是在Qt5框架上开发的。
3.1 配置Qt5开发环境和项目创建
3.1.1 配置Qt5开发环境
在VS2015中配置Qt5开发环境,具体可以参考我发布的文章《VS2015_Qt5_Halcon混合编程》第一章。
3.1.2 创建Qt Application项目
在VS2015中创建一个Qt Application项目
双击“hkcamera.ui”,如下图所示添加一个Lable用于显示,四个Push Button用于按钮控制
3.2 配置海康相机SDK开发环境
3.2.1 添加附加包含目录
项目 --- 属性 --- 属性页 --- C/C++ --- 常规--- 附加包含目录,添加如下路径:
E:\code\Libraries\HKSDK\Includes
3.2.2 添加附加库目录
项目 --- 属性 --- 属性页 --- 链接器 --- 常规 --- 附加库目录,添加如下路径
E:\code\Libraries\HKSDK\Libraries\win64
3.2.3 添加附加依赖项
项目 --- 属性 --- 属性页 --- 链接器 --- 输入 --- 附加依赖项,添加
MvCameraControl.lib
3.3 SDK开发
3.3.1 枚举设备
可通过函数MV_CC_EnumDevices(IN unsigned int nTLayerType, IN&OUT
MV_CC_DEVICE_INFO_LIST* pstDevList)来枚举相机。
nTLayerType:用户输入的传输层类型(也就是相机类型),一般有MV_GIGE_DEVICE,MV_USB_DEVICE分别对应GigE和U3V相机。 pstDevList:枚举到的设备都存储到这个结构体中了,供之后使用。
也可以通过函数MV_CC_EnumerateTls来枚举支持的设备类型(传输层类型)和函数MV_CC_EnumDevicesEx来枚举子网内指定的传输协议和指定厂商的所有设备。
/************************************************************************/ /* 1.枚举设备 MV_CC_EnumDevices/MV_CC_EnumerateTls/MV_CC_EnumDevicesEx */ /************************************************************************/ //枚举子网内指定的传输协议对应的所有设备 unsigned int nTLayerType = MV_GIGE_DEVICE | MV_USB_DEVICE; MV_CC_DEVICE_INFO_LIST m_stDevList = { 0 }; int nRet = MV_CC_EnumDevices(nTLayerType, &m_stDevList);
3.3.2 创建句柄
可通过函数MV_CC_CreateHandle(OUT void ** handle, IN const MV_CC_DEVICE_INFO* pstDevInfo)创建句柄。
handle:创建句柄成功后,该句柄返回到handle。pstDevInfo:用户输入的设备信息,枚举设备时所获取,这样的话该设备就和该句柄绑定在一起了,以后只用句柄就完成所有任务。
也可以通过函数MV_CC_CreateHandleWithoutLog创建无日志的句柄。
/************************************************************************/ /* 2.创建句柄 MV_CC_CreateHandle/MV_CC_CreateHandleWithoutLog */ /************************************************************************/ //选择查找到的第一台在线设备,创建设备句柄 int nDeviceIndex = 0; MV_CC_DEVICE_INFO m_stDevInfo = { 0 }; memcpy(&m_stDevInfo, m_stDevList.pDeviceInfo[nDeviceIndex],sizeof(MV_CC_DEVICE_INFO)); nRet = MV_CC_CreateHandle(&m_handle, &m_stDevInfo);
3.3.3 打开设备
可通过函数MV_CC_OpenDevice(IN void* handle, IN unsigned int nAccessMode = MV_ACCESS_Exclusive, IN unsigned short nSwitchoverKey = 0)打开设备。
这个函数只需要输入一个参数即可,就是上面创建成功的句柄handle,后两个参数一般使用默认参数,返回成功后表示打开了对应相机。
/************************************************************************/ /* 3.打开设备 MV_CC_OpenDevice */ /************************************************************************/ //连接设备 //nRet = MV_CC_OpenDevice(m_handle, nAccessMode, nSwitchoverKey); nRet = MV_CC_OpenDevice(m_handle);
3.3.4 开启抓图
可通过函数MV_CC_StartGrabbing(IN void* handle)开始抓图。
此操作依然只输入一个handle即可,但开启抓图并没有图像,只是有流数据传输而已。若需要取图有两种方式,一种注册回调,另一种主动调用MV_CC_GetOneFrameTimeout来取图。
/************************************************************************/ /* 4.开启抓图 MV_CC_StartGrabbing */ /************************************************************************/ //开始采集图像 nRet = MV_CC_StartGrabbing(m_handle);
3.3.5 获取一帧图像
图像数据采集有两种方式,两种方式不能复用:
1) 调用MV_CC_StartGrabbing开始采集,然后在应用层循环调用MV_CC_GetOneFrame获取指定像素格式的帧数据,获取帧数据时上层应用程序需要根据帧率控制好调用该接口的频率。
通过函数MV_CC_GetOneFrameTimeout (IN void* handle, IN&OUT unsigned char * pData , IN unsigned int nDataSize, IN&OUT MV_FRAME_OUT_INFO_EX* pFrameInfo,int nMsec)获取一帧。所获取的帧属于裸数据,数据保存在pData,并无图像格式(具体数据格式可以提前设定)。pFrameInfo表示输出帧的信息。
也可以通过函数MV_CC_GetOneFrame获取一帧图像数据;函数MV_CC_GetOneFrameEx获取一帧图像数据,支持获取chunk信息;函数MV_CC_GetImageForRGB获取一帧RGB24数据,查询内存里面帧数据并且转换成RGB24格式返回,支持设置超时时间;函数MV_CC_GetImageForBGR获取一帧BGR24数据,查询内存里面帧数据并且转换成BGR24格式返回,支持设置超时时间。
/************************************************************************/ /* 5.获取一帧图像 MV_CC_GetOneFrameTimeout */ /************************************************************************/ //获取一帧数据的大小 MVCC_INTVALUE stIntvalue = { 0 }; nRet = MV_CC_GetIntValue(m_handle, "PayloadSize", &stIntvalue); //一帧数据大小+预留字节(用于SDK内部处理) int nBufSize = stIntvalue.nCurValue + 2048; unsigned int nTestFrameSize = 0; unsigned char* pFrameBuf = NULL; pFrameBuf = (unsigned char*)malloc(nBufSize); MV_FRAME_OUT_INFO stInfo; memset(&stInfo, 0, sizeof(MV_FRAME_OUT_INFO)); //上层应用程序需要根据帧率,控制好调用该接口的频率 //此次代码仅供参考,实际应用建议另建线程进行图像帧采集和处理 //pFrameBuf是相机采集到的一帧原始图像数据 nRet = MV_CC_GetOneFrame(m_handle, pFrameBuf, nBufSize, &stInfo);
2) 调用MV_CC_RegisterImageCallBack设置图像数据回调函数,然后调用MV_CC_StartGrabbing开始采集,采集的图像数据在设置的回调函数中返回。
在创建句柄后,通过注册回调函数MV_CC_RegisterImageCallBack,然后通过回调函数ImageCallBack获得一帧图像数据。
先在源文件中注册数据回调函数,
//注册数据回调函数 nRet = MV_CC_RegisterImageCallBack(m_handle, ImageCallBack, NULL); if (MV_OK != nRet) { printf("error: RegisterImageCallBack fail [%x]\n", nRet); return; }
再在头文件中声明回调函数,注意声明回调函数为静态函数,
static void __stdcall ImageCallBack(unsigned char * pData, MV_FRAME_OUT_INFO * pFrameInfo, void * pUser);
最后在源文件中定义回调函数。
void __stdcall hkCamera::ImageCallBack(unsigned char * pData, MV_FRAME_OUT_INFO* pFrameInfo, void* pUser) { if (pFrameInfo) { // 输出时加上当前系统时间 char szInfo[128] = { 0 }; SYSTEMTIME sys; GetLocalTime(&sys); sprintf_s(szInfo, 128, "[%d-%02d-%02d %02d:%02d:%02d:%04d] : GetOneFrame succeed, width[%d], height[%d]", sys.wYear, sys.wMonth, sys.wDay, sys.wHour, sys.wMinute, sys.wSecond, sys.wMilliseconds, pFrameInfo->nWidth, pFrameInfo->nHeight); printf("%s\n", szInfo); } }
3.3.6 显示图像
可以通过函数MV_CC_Display(IN void* handle, IN void* hWnd)来实时显示采集到的图像。该函数需要在MV_CC_StartGrabbing之后调用,显示采集到的图像。如果相机当前采集图像是JPEG压缩的格式,则不支持调用该函数接口进行显示。
/************************************************************************/ /* 6.显示图像 MV_CC_Display */ /************************************************************************/ //获取窗口句柄 HWND MainWndID = (HWND)this->ui.label->winId(); //显示图像 nRet = MV_CC_Display(m_handle, MainWndID);
3.3.7 保存图像
可以通过函数MV_CC_SaveImage(IN&OUT MV_SAVE_IMAGE_PARAM* pSaveParam)将原始图像数据转换成图片格式并保存在指定内存里,再通过函数fwrite写入文件中。
也可以通过函数MV_CC_SaveImageEx将原始图像数据转换成图片格式并保存在指定内存中,可支持设置JPEG编码质量。
/************************************************************************/ /* 7.保存图像 MV_CC_SaveImage/MV_CC_SaveImageEx */ /************************************************************************/ //图片数据输入输出参数 MV_SAVE_IMAGE_PARAM stParam = { 0 }; //源数据 stParam.pData = pFrameBuf; //原始图像数据 stParam.nDataLen = stInfo.nFrameLen; //原始图像数据长度 stParam.enPixelType = stInfo.enPixelType; //原始图像数据的像素格式YUYV stParam.nWidth = stInfo.nWidth; //图像宽 stParam.nHeight = stInfo.nHeight; //图像高 //目标数据 /** enum _MV_SAVE_IAMGE_TYPE_{ MV_Image_Undefined = 0, //未定义 MV_Image_Bmp = 1, //BMP图片 MV_Image_Jpeg = 2, //JPEG图片 MV_Image_Png = 3, //PNG图片,暂不支持 MV_Image_Tif = 4, //TIF图片,暂不支持 }MV_SAVE_IAMGE_TYPE **/ stParam.enImageType = MV_Image_Bmp; stParam.nBufferSize = nBufSize; //存储节点的大小 unsigned char* pImage = (unsigned char*)malloc(nBufSize); stParam.pImageBuffer = pImage; //输出数据缓冲区,存放转换之后的图片数据 nRet = MV_CC_SaveImage(&stParam); //将转换之后图片数据保存成文件 FILE* fp = fopen("image", "wb"); fwrite(pImage, 1, stParam.nImageLen, fp); fclose(fp); free(pImage);
3.3.8 停止抓图
可通过函数 MV_CC_StopGrabbing(IN void* handle)来停止抓图。
只输入一个handle即可成功停止抓图,便没有数据流动了。
/************************************************************************/ /* 6. 停止抓图 MV_CC_StopGrabbing */ /************************************************************************/ //停止采集图像 nRet = MV_CC_StopGrabbing(m_handle);
3.3.9 关闭设备
可通过函数MV_CC_CloseDevice(IN void* handle)来关闭设备。
只输入一个handle即可成功关闭设备。
/************************************************************************/ /* 7. 关闭设备 MV_CC_CloseDevice */ /************************************************************************/ //关闭设备,释放资源 nRet = MV_CC_CloseDevice(m_handle);
3.3.10 销毁句柄
可通过函数MV_CC_DestroyHandle(IN void * handle)来销毁句柄。
只输入一个handle即可销毁句柄。
/************************************************************************/ /* 8. 销毁句柄 MV_CC_DestroyHandle */ /************************************************************************/ //销毁句柄,释放资源 nRet = MV_CC_DestroyHandle(m_handle);
4 混合编程
4.1 与Qt混合编程
可通过函数memcpy(OUT void* dst, IN void const* src, IN size_t size) 把资源内存(src所指向的内存区域)拷贝到目标内存(dest所指向的内存区域),从而将unsigned char*格式的图像数据转换为QImage格式的图像数据,这里要注意输入数据的格式,代码中输入的unsigned char* pFrameBuf数据格式分别为Mono8的灰度图像和RGB8_Packed的彩色图像。
/**************************unsigned char* 图像转换为QImage************************/ //新建一个灰度图像image,对应灰度相机Mono8的灰度图像 QImage * image = new QImage(stInfo.nWidth, stInfo.nHeight,QImage::Format_Indexed8); //memcpy 函数用于 把资源内存(src所指向的内存区域)拷贝到目标内存(dest所指向的内存区域) //bits()方法获取图像像素字节数据的首地址 memcpy((*image).bits(), pFrameBuf, stInfo.nWidth*stInfo.nHeight); /************************************************/ //新建一个彩色图像image,对应彩色相机RGB8_Packed的彩色图像 // QImage::Format_RGB888,存入格式为R, G, B 对应 0,1,2 QImage *image = new QImage(stInfo.nWidth, stInfo.nHeight,QImage::Format_RGB888); memcpy((*image).bits(), pFrameBuf, stInfo.nWidth * stInfo.nHeight * 3);
4.2 与Halcon混合编程
4.2.1 配置Halcon开发环境
在VS2015中配置Halcon开发环境,具体可以参考我发布的文章《VS2015_Qt5_Halcon混合编程》第二章。
4.2.2 数据格式转换
通过Halcon中的函数GenImage3 (OUT HObject* ImageRGB, IN const HTuple& Type, IN const HTuple& Width, IN const HTuple& Height, IN const HTuple& PixelPointerRed, IN const HTuple& PixelPointerGreen, IN const HTuple& PixelPointerBlue),将SDK中获得的unsigned char*格式的原始图像数据转换为HAlcon中使用的HObject格式的图像数据,如果输入图像是灰度图像则通过函数GenImage1转换。
这里要注意获取的那一帧原始图像数据的格式,原始图像数据格式不一样,甚至是RGB三个分量的顺序不同,转换算法就得进行调整,下面代码中原始图像数据unsigned char* pFrameBuf的格式为RGB8_Packed。
转换算法为:由于原始图像数据pFrameBuf的格式是RGB8_Packed,存储格式为RGB RGB RGB…,所以将pFrameBuf通过for循环进行拆分,分别存放入新建的三个颜色分量中,再通过函数GenImage3转换为HObject格式的ho_Image。
/**************************unsigned char* 图像转换为HObject************************/ int hgt = stInfo.nHeight; int wid = stInfo.nWidth; unsigned char * dataRed = new unsigned char[wid * hgt]; unsigned char * dataGreen = new unsigned char[wid * hgt]; unsigned char * dataBlue = new unsigned char[wid * hgt]; unsigned char * data = new unsigned char[wid * hgt * 3]; memcpy(data, pFrameBuf, wid * hgt * 3); for (int i = 0; i <wid * hgt; i++) { dataRed[i] = (data[3 * i ]); dataGreen[i] = data[3 * i + 1]; dataBlue[i] = data[3 * i +2]; } GenImage3(&ho_Image, "byte", wid, hgt, (Hlong)(dataRed), (Hlong)(dataGreen), (Hlong)(dataBlue)); WriteImage(ho_Image, "bmp", 0, HTuple("E:/code/Photo/") + 1); Sleep(500); delete[] dataRed; delete[] dataGreen; delete[] dataBlue; delete[] data;
5 遇到的问题
1) 没注意从相机中获取的一帧原始图像的格式,以为默认就是RGB24格式的,导致后面的转换出了bug,找了好久才发现默认的格式是YUYV的。解决办法是:a.通过海康相机自带的客户端设置像素格式为RGB8_Packed,b.通过函数MV_CC_SetPixelFormat设置相机图像的像素格式
//设置相机图像的像素格式 unsigned int enValue = PixelType_Gvsp_RGB8_Packed; nRet = MV_CC_SetPixelFormat(m_handle, enValue); if (MV_OK != nRet) { printf("error: SetPixelFormat fail [%x]\n", nRet); return; }
2) 没有注意例程中说明的“上层应用程序需要根据帧率,控制好调用该接口的频率”,我的相机的采集速度是一秒20帧,结果我程序获取的帧速超过了这个采集速度,所以程序在while循环获取一帧图像时,程序报错。解决办法有:a.采用回调函数采图,b.不用循环采图没有问题,c.加一个Sleep函数也可以解决。
6 本文程序代码
本文程序代码和操作手册已经被上传到CSDN中,其中代码有两份:
7 参考文献
代码资料:Samuel_yin / IplImageToHImage.cpp
版权声明:
本文首发于onefish51的博客(http://www.cnblogs.com/onefish51和https://blog.csdn.net/weixin_31075593),未经允许不得转载,版权所有,侵权必究。