海康相机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”路径,目录结构如下:

clip_image001[4]

1.3 效果展示

建议安装成功后,连接相机,打开MVS客户端,查看相机连接和图像预览的效果,确认环境正常后,再开始基于SDK的二次开发。如下:

clip_image003[4]

观察三个指标:

1) 带宽。正常值在100Mbps以上即认为正常;

2) 错误数。非0即表示有丢帧,不正常;

3) 丢包数。非0,不正常。参考第四章常见问题的解决方法。

clip_image005[4]

2 产品概述

本章介绍SDK在整个机器视觉系统中的层次定位,可实现的功能,基本的开发调用流程,以及常用的接口。

2.1 SDK定位

clip_image007[4]

2.2 基本接口调用流程

clip_image009[4]

2.3 参数配置

1) 相机所有开放的参数可参考MVS的属性树,只要在属性树中看得到的节点,都可以通过SDK来获取和设置相应的值。

2) 每个节点分别属于哪种数据类型,可参考如下控件形式:

3) 节点的关键字,可参考以下表格:

HikCameraNode面阵相机.xlsx

HikCameraNode线阵相机.xlsx

e.g:

设置宽度为2592 ->MV_CC_SetIntValue(handle, “Width”, 2592);

4) 详细的函数说明,可以参考SDK 使用手册。

工业相机SDK 使用手册.chm

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项目

clip_image011[4]

clip_image013[4]

双击“hkcamera.ui”,如下图所示添加一个Lable用于显示,四个Push Button用于按钮控制

clip_image015[4]

3.2 配置海康相机SDK开发环境

3.2.1 添加附加包含目录

项目 --- 属性 --- 属性页 --- C/C++ --- 常规--- 附加包含目录添加如下路径:

E:\code\Libraries\HKSDK\Includes

clip_image017[4]

3.2.2 添加附加库目录

项目 --- 属性 --- 属性页 --- 链接器 --- 常规 --- 附加库目录,添加如下路径

E:\code\Libraries\HKSDK\Libraries\win64

clip_image019[4]

3.2.3 添加附加依赖项

项目 --- 属性 --- 属性页 --- 链接器 --- 输入 --- 附加依赖项,添加

MvCameraControl.lib

clip_image020[4]

3.3 SDK开发

clip_image022[4]

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中,其中代码有两份:

海康相机SDK二次开发与Qt混合编程

海康相机SDK二次开发与Halcon混合编程

7 参考文献

VS2015_Qt5_Halcon混合编程

BYTE类型转换Hobject类型

代码资料:Samuel_yin / IplImageToHImage.cpp

版权声明:

本文首发于onefish51的博客(http://www.cnblogs.com/onefish51https://blog.csdn.net/weixin_31075593),未经允许不得转载,版权所有,侵权必究。

posted @ 2018-07-27 15:58  onefish51  阅读(17149)  评论(2编辑  收藏  举报