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

    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:codeLibrariesHKSDKIncludes

    clip_image017[4]

    3.2.2 添加附加库目录

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

    E:codeLibrariesHKSDKLibrarieswin64

    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]
    ", 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
    ", 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]
    ", 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),未经允许不得转载,版权所有,侵权必究。

  • 相关阅读:
    MySQL 基础笔记
    form表单如何提交list集合到服务器实现数据交互
    FileInputStream&FileOutputStream文件复制后文件变大
    Exception:NoSuchMethodException
    Exception :java.lang.NoClassDefFoundError: org/jaxen/NamespaceContext
    C:Program FilesJavajdk1.8.0_11injava.exe'' finished with non-zero exit value
    Android导入Unity 3D运行后提示:your hardware does not support this application
    关于android support 升级为androidx
    错误整理 (关于unity打包)
    unity 打包报错Could not find com.tencent.mm.opensdk:wechat-sdk-android-without-mta:5.1.4
  • 原文地址:https://www.cnblogs.com/onefish51/p/9378085.html
Copyright © 2020-2023  润新知