对海康威视工业相机进行取图

  之前通过海康SDK开发时,取相机当前帧图像一直用的是MV_CC_GetOneFrameTimeout函数,因为每次都是需要的时候去主动取一帧图像,也没关注CPU的占用率。
  最近在海康相机SDK的基础之上封装了一层ROS2节点,用来对外发布相机的图像,需要一直采集相机的图像,重复调用MV_CC_GetOneFrameTimeout函数函数发现CPU占用率挺高的(本质还是MV_CC_StartGrabbing开始采集比较吃CPU资源),一开始以为是代码有问题,排查一圈再次阅读SDK源码时发现提供了另一种取图的方法MV_CC_GetImageBuffer,看到了更高的效率,换成这样之后果然CPU占用降了一半。
在这里插入图片描述

API介绍:MV_CC_GetOneFrameTimeout、MV_CC_GetImageBuffer

 /********************************************************************//**
 *  @~chinese
 *  @brief  采用超时机制获取一帧图片,SDK内部等待直到有数据时返回 
 *  @param  handle                      [IN]            设备句柄
 *  @param  pData                       [IN][OUT]       图像数据接收指针 
 *  @param  nDataSize                   [IN]            接收缓存大小 
 *  @param  pstFrameInfo                [IN][OUT]       图像信息结构体 
 *  @param  nMsec                       [IN]            等待超时时间 
 *  @return 成功,返回MV_OK;失败,返回错误码
 *  @remarks  • 调用该接口获取图像数据帧之前需要先调用 MV_CC_StartGrabbing() 启动图像采集。该接口为主动式获取帧数据,上层应用程序需要根据帧率,控制好调用该接口的频率。该接口支持设置超时时间,SDK内部等待直到有数据时返回,可以增加取流平稳性,适合用于对平稳性要求较高的场合。
              • 该接口对于U3V、GIGE设备均可支持。
              • 该接口不支持CameraLink设备。 
 
 *  @~english
 *  @brief  Timeout mechanism is used to get image, and the SDK waits inside until the data is returned
 *  @param  handle                      [IN]            Device handle
 *  @param  pData                       [IN][OUT]       Image data receiving buffer
 *  @param  nDataSize                   [IN]            Buffer size
 *  @param  pstFrameInfo                [IN][OUT]       Image information structure
 *  @param  nMsec                       [IN]            Waiting timeout
 *  @return Success, return MV_OK. Failure, return error code
 *  @remarks Before calling this API to get image data frame, call MV_CC_StartGrabbing to start image acquisition.
             This API can get frame data actively, the upper layer program should control the frequency of calling this API according to the frame rate.
             This API supports setting timeout, SDK will wait to return until data appears. This function will increase the streaming stability, which can be used in the situation with high stability requirement.
             Both the USB3Vision and GIGE camera can support this API.
             This API is not supported by CameraLink device.
 ***********************************************************************/
MV_CAMCTRL_API int __stdcall MV_CC_GetOneFrameTimeout(IN void* handle, IN OUT unsigned char* pData , IN unsigned int nDataSize, IN OUT MV_FRAME_OUT_INFO_EX* pstFrameInfo, IN unsigned int nMsec);


/********************************************************************//**
 *  @~chinese
 *  @brief  使用内部缓存获取一帧图片(与 MV_CC_Display() 不能同时使用) 
 *  @param  handle                      [IN]            设备句柄 
 *  @param  pstFrame                    [IN][OUT]       图像数据和图像信息 
 *  @param  nMsec                       [IN]            等待超时时间,输入INFINITE时表示无限等待,直到收到一帧数据或者停止取流
 *  @return 成功,返回MV_OK;失败,返回错误码
 *  @remarks • 调用该接口获取图像数据帧之前需要先调用 MV_CC_StartGrabbing() 启动图像采集。该接口为主动式获取帧数据,上层应用程序需要根据帧率,控制好调用该接口的频率。该接口支持设置超时时间,SDK内部等待直到有数据时返回,可以增加取流平稳性,适合用于对平稳性要求较高的场合。 
             • 该接口与 MV_CC_FreeImageBuffer() 配套使用,当处理完取到的数据后,需要用 MV_CC_FreeImageBuffer() 接口将pstFrame内的数据指针权限进行释放。 
             • 该接口与 MV_CC_GetOneFrameTimeout() 相比,有着更高的效率。且其取流缓存的分配是由sdk内部自动分配的,而 MV_CC_GetOneFrameTimeout() 接口是需要客户自行分配。
             • 该接口在调用 MV_CC_Display() 后无法取流。 
             • 该接口对于U3V、GIGE设备均可支持。 
             • 该接口不支持CameraLink设备。
 
 *  @~english
 *  @brief  Get a frame of an image using an internal cache
 *  @param  handle                      [IN]            Device handle
 *  @param  pstFrame                    [IN][OUT]       Image data and image information
 *  @param  nMsec                       [IN]            Waiting timeout
 *  @return Success, return MV_OK. Failure, return error code
 *  @remarks Before calling this API to get image data frame, you should call MV_CC_StartGrabbing to start image acquisition.
             This API can get frame data actively, the upper layer program should control the frequency of calling this API according to the frame rate. This API support setting timeout, and SDK will wait to return until data appears. This function will increase the streaming stability, which can be used in the situation with high stability requirement. 
             This API and MV_CC_FreeImageBuffer should be called in pairs, after processing the acquired data, you should call MV_CC_FreeImageBuffer to release the data pointer permission of pFrame. 
             This interface is more efficient than MV_CC_GetOneFrameTimeout. The allocation of the stream cache is automatically allocated within the SDK.The MV_CC_GetOneFrameTimeout interface needs to be allocated by customers themselves. 
             This API cannot be called to stream after calling MV_CC_Display.
             This API is not supported by CameraLink device. 
             This API is supported by both USB3 vision camera and GigE camera. 
 *****************************************************************************/
MV_CAMCTRL_API int __stdcall MV_CC_GetImageBuffer(IN void* handle, IN OUT MV_FRAME_OUT* pstFrame, IN unsigned int nMsec);

MV_CC_GetOneFrameTimeout 使用示例

Camera::ImageInfo HikCamera::getOneFrameImageInfo()
// std::shared_ptr<Camera::ImageInfo> HikCamera::getOneFrameImageInfo()
{
    
    
    if (!connected_) {
    
    
        return {
    
    };
    }

    // std::shared_ptr<Camera::ImageInfo> image_info =
    // std::make_shared<Camera::ImageInfo>();
    Camera::ImageInfo image_info;
    unsigned int m_buf_size_for_save_image = 0;
    unsigned int m_rec_buf_size = 0;

    MVCC_INTVALUE int_val;
    memset(&int_val, 0, sizeof(MVCC_INTVALUE));

    int result = MV_CC_GetIntValue(handle_, "PayloadSize", &int_val);
    if (result != 0) {
    
    
        std::cout << "Get integer wrong: " << result << std::endl;
    }

    m_rec_buf_size = int_val.nCurValue;
    unsigned char *m_image_data;
    m_image_data = (unsigned char *)malloc(m_rec_buf_size);

    MV_FRAME_OUT_INFO_EX frame_image_info = {
    
     0 };
    result = MV_CC_GetOneFrameTimeout(handle_, m_image_data, m_rec_buf_size,
                                      &frame_image_info, 500);
    if (result != 0) {
    
    
        std::cout << "Get one frame image wrong: " << result << std::endl;
    }
    m_buf_size_for_save_image =
        frame_image_info.nWidth * frame_image_info.nHeight * 3 + 2048;
    unsigned char *m_buf_for_save_image;
    m_buf_for_save_image = (unsigned char *)malloc(m_buf_size_for_save_image);

    bool is_mono; //判断是否为黑白图像
    switch (frame_image_info.enPixelType) {
    
    
    case PixelType_Gvsp_Mono8:
    case PixelType_Gvsp_Mono10:
    case PixelType_Gvsp_Mono10_Packed:
    case PixelType_Gvsp_Mono12:
    case PixelType_Gvsp_Mono12_Packed:
        is_mono = true;
        break;
    default:
        is_mono = false;
        break;
    }
    if (is_mono) {
    
    
        image_info.height = frame_image_info.nHeight;
        image_info.width = frame_image_info.nWidth;
        image_info.data =
            std::vector<uint8_t>(m_image_data, m_image_data + m_rec_buf_size);
        image_info.channels = 1;
    } else {
    
    
        // 转换图像格式为BGR8
        MV_CC_PIXEL_CONVERT_PARAM pixel_convert_param = {
    
     0 };
        memset(&pixel_convert_param, 0, sizeof(MV_CC_PIXEL_CONVERT_PARAM));
        pixel_convert_param.nWidth = frame_image_info.nWidth;
        pixel_convert_param.nHeight = frame_image_info.nHeight;
        pixel_convert_param.pSrcData = m_image_data;
        pixel_convert_param.nSrcDataLen =
            frame_image_info.nFrameLen; // 输入数据大小
        pixel_convert_param.enSrcPixelType =
            frame_image_info.enPixelType; // 输入像素格式
        pixel_convert_param.enDstPixelType =
            PixelType_Gvsp_BGR8_Packed; // 输出像素格式

        pixel_convert_param.pDstBuffer = m_buf_for_save_image; // 输出数据缓存
        pixel_convert_param.nDstBufferSize =
            m_buf_size_for_save_image; // 输出缓存大小
        MV_CC_ConvertPixelType(handle_, &pixel_convert_param);

        image_info.height = frame_image_info.nHeight;
        image_info.width = frame_image_info.nWidth;
        image_info.data = std::vector<uint8_t>(
            m_buf_for_save_image,
            m_buf_for_save_image + m_buf_size_for_save_image);
        image_info.channels = 3;
    }

    free(m_image_data);
    free(m_buf_for_save_image);

    return image_info;
}

MV_CC_GetImageBuffer 使用示例

GrabImage.cpp
获取相机图像
该示例程序说明如何通过主动取图的方式获取相机图像。创建句柄,打开相机,开始取流,创建取流线程(用户分配采集buffer, MV_CC_GetImageBuffer() 取图(拷贝)),停止取流,关闭相机,销毁句柄。

#include <stdio.h>
#include <Windows.h>
#include <process.h>
#include <conio.h>
#include "MvCameraControl.h"

bool g_bExit = false;

// ch:等待按键输入 | en:Wait for key press
void WaitForKeyPress(void)
{
    
    
    while(!_kbhit())
    {
    
    
        Sleep(10);
    }
    _getch();
}

bool PrintDeviceInfo(MV_CC_DEVICE_INFO* pstMVDevInfo)
{
    
    
    if (NULL == pstMVDevInfo)
    {
    
    
        printf("The Pointer of pstMVDevInfo is NULL!\n");
        return false;
    }
    if (pstMVDevInfo->nTLayerType == MV_GIGE_DEVICE)
    {
    
    
        int nIp1 = ((pstMVDevInfo->SpecialInfo.stGigEInfo.nCurrentIp & 0xff000000) >> 24);
        int nIp2 = ((pstMVDevInfo->SpecialInfo.stGigEInfo.nCurrentIp & 0x00ff0000) >> 16);
        int nIp3 = ((pstMVDevInfo->SpecialInfo.stGigEInfo.nCurrentIp & 0x0000ff00) >> 8);
        int nIp4 = (pstMVDevInfo->SpecialInfo.stGigEInfo.nCurrentIp & 0x000000ff);

        // ch:打印当前相机ip和用户自定义名字 | en:print current ip and user defined name
        printf("CurrentIp: %d.%d.%d.%d\n" , nIp1, nIp2, nIp3, nIp4);
        printf("UserDefinedName: %s\n\n" , pstMVDevInfo->SpecialInfo.stGigEInfo.chUserDefinedName);
    }
    else if (pstMVDevInfo->nTLayerType == MV_USB_DEVICE)
    {
    
    
        printf("UserDefinedName: %s\n", pstMVDevInfo->SpecialInfo.stUsb3VInfo.chUserDefinedName);
        printf("Serial Number: %s\n", pstMVDevInfo->SpecialInfo.stUsb3VInfo.chSerialNumber);
        printf("Device Number: %d\n\n", pstMVDevInfo->SpecialInfo.stUsb3VInfo.nDeviceNumber);
    }
    else if (pstMVDevInfo->nTLayerType == MV_GENTL_GIGE_DEVICE)
    {
    
    
        printf("UserDefinedName: %s\n", pstMVDevInfo->SpecialInfo.stGigEInfo.chUserDefinedName);
        printf("Serial Number: %s\n", pstMVDevInfo->SpecialInfo.stGigEInfo.chSerialNumber);
        printf("Model Name: %s\n\n", pstMVDevInfo->SpecialInfo.stGigEInfo.chModelName);
    }
    else if (pstMVDevInfo->nTLayerType == MV_GENTL_CAMERALINK_DEVICE)
    {
    
    
        printf("UserDefinedName: %s\n", pstMVDevInfo->SpecialInfo.stCMLInfo.chUserDefinedName);
        printf("Serial Number: %s\n", pstMVDevInfo->SpecialInfo.stCMLInfo.chSerialNumber);
        printf("Model Name: %s\n\n", pstMVDevInfo->SpecialInfo.stCMLInfo.chModelName);
    }
    else if (pstMVDevInfo->nTLayerType == MV_GENTL_CXP_DEVICE)
    {
    
    
        printf("UserDefinedName: %s\n", pstMVDevInfo->SpecialInfo.stCXPInfo.chUserDefinedName);
        printf("Serial Number: %s\n", pstMVDevInfo->SpecialInfo.stCXPInfo.chSerialNumber);
        printf("Model Name: %s\n\n", pstMVDevInfo->SpecialInfo.stCXPInfo.chModelName);
    }
    else if (pstMVDevInfo->nTLayerType == MV_GENTL_XOF_DEVICE)
    {
    
    
        printf("UserDefinedName: %s\n", pstMVDevInfo->SpecialInfo.stXoFInfo.chUserDefinedName);
        printf("Serial Number: %s\n", pstMVDevInfo->SpecialInfo.stXoFInfo.chSerialNumber);
        printf("Model Name: %s\n\n", pstMVDevInfo->SpecialInfo.stXoFInfo.chModelName);
    }
    else
    {
    
    
        printf("Not support.\n");
    }

    return true;
}

static  unsigned int __stdcall WorkThread(void* pUser)
{
    
    
    int nRet = MV_OK;
    MV_FRAME_OUT stOutFrame = {
    
    0};

    while(true)
    {
    
    
        nRet = MV_CC_GetImageBuffer(pUser, &stOutFrame, 1000);
        if (nRet == MV_OK)
        {
    
    
            printf("Get Image Buffer: Width[%d], Height[%d], FrameNum[%d]\n",
                stOutFrame.stFrameInfo.nWidth, stOutFrame.stFrameInfo.nHeight, stOutFrame.stFrameInfo.nFrameNum);

            nRet = MV_CC_FreeImageBuffer(pUser, &stOutFrame);
            if(nRet != MV_OK)
            {
    
    
                printf("Free Image Buffer fail! nRet [0x%x]\n", nRet);
            }
        }
        else
        {
    
    
            printf("Get Image fail! nRet [0x%x]\n", nRet);
        }
        if(g_bExit)
        {
    
    
            break;
        }
    }

    return 0;
}

int main()
{
    
    
    int nRet = MV_OK;
    void* handle = NULL;

    do 
    {
    
    
        // ch:初始化SDK | en:Initialize SDK
        nRet = MV_CC_Initialize();
        if (MV_OK != nRet)
        {
    
    
            printf("Initialize SDK fail! nRet [0x%x]\n", nRet);
            break;
        }

        // ch:枚举设备 | en:Enum device
        MV_CC_DEVICE_INFO_LIST stDeviceList;
        memset(&stDeviceList, 0, sizeof(MV_CC_DEVICE_INFO_LIST));
        nRet = MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE | MV_GENTL_CAMERALINK_DEVICE | MV_GENTL_CXP_DEVICE | MV_GENTL_XOF_DEVICE, &stDeviceList);
        if (MV_OK != nRet)
        {
    
    
            printf("Enum Devices fail! nRet [0x%x]\n", nRet);
            break;
        }

        if (stDeviceList.nDeviceNum > 0)
        {
    
    
            for (unsigned int i = 0; i < stDeviceList.nDeviceNum; i++)
            {
    
    
                printf("[device %d]:\n", i);
                MV_CC_DEVICE_INFO* pDeviceInfo = stDeviceList.pDeviceInfo[i];
                if (NULL == pDeviceInfo)
                {
    
    
                    break;
                } 
                PrintDeviceInfo(pDeviceInfo);            
            }  
        } 
        else
        {
    
    
            printf("Find No Devices!\n");
            break;
        }

        printf("Please Input camera index(0-%d):", stDeviceList.nDeviceNum-1);
        unsigned int nIndex = 0;
        scanf_s("%d", &nIndex);

        if (nIndex >= stDeviceList.nDeviceNum)
        {
    
    
            printf("Input error!\n");
            break;
        }

        // ch:选择设备并创建句柄 | en:Select device and create handle
        nRet = MV_CC_CreateHandle(&handle, stDeviceList.pDeviceInfo[nIndex]);
        if (MV_OK != nRet)
        {
    
    
            printf("Create Handle fail! nRet [0x%x]\n", nRet);
            break;
        }

        // ch:打开设备 | en:Open device
        nRet = MV_CC_OpenDevice(handle);
        if (MV_OK != nRet)
        {
    
    
            printf("Open Device fail! nRet [0x%x]\n", nRet);
            break;
        }

        // ch:探测网络最佳包大小(只对GigE相机有效) | en:Detection network optimal package size(It only works for the GigE camera)
        if (stDeviceList.pDeviceInfo[nIndex]->nTLayerType == MV_GIGE_DEVICE)
        {
    
    
            int nPacketSize = MV_CC_GetOptimalPacketSize(handle);
            if (nPacketSize > 0)
            {
    
    
                nRet = MV_CC_SetIntValueEx(handle,"GevSCPSPacketSize",nPacketSize);
                if(nRet != MV_OK)
                {
    
    
                    printf("Warning: Set Packet Size fail nRet [0x%x]!", nRet);
                }
            }
            else
            {
    
    
                printf("Warning: Get Packet Size fail nRet [0x%x]!", nPacketSize);
            }
        }

        // ch:设置触发模式为off | en:Set trigger mode as off
        nRet = MV_CC_SetEnumValue(handle, "TriggerMode", 0);
        if (MV_OK != nRet)
        {
    
    
            printf("Set Trigger Mode fail! nRet [0x%x]\n", nRet);
            break;
        }

        // ch:开始取流 | en:Start grab image
        nRet = MV_CC_StartGrabbing(handle);
        if (MV_OK != nRet)
        {
    
    
            printf("Start Grabbing fail! nRet [0x%x]\n", nRet);
            break;
        }

        unsigned int nThreadID = 0;
        void* hThreadHandle = (void*) _beginthreadex( NULL , 0 , WorkThread , handle, 0 , &nThreadID );
        if (NULL == hThreadHandle)
        {
    
    
            break;
        }

        printf("Press a key to stop grabbing.\n");
        WaitForKeyPress();

        g_bExit = true;
        Sleep(1000);

        // ch:停止取流 | en:Stop grab image
        nRet = MV_CC_StopGrabbing(handle);
        if (MV_OK != nRet)
        {
    
    
            printf("Stop Grabbing fail! nRet [0x%x]\n", nRet);
            break;
        }

        // ch:关闭设备 | Close device
        nRet = MV_CC_CloseDevice(handle);
        if (MV_OK != nRet)
        {
    
    
            printf("ClosDevice fail! nRet [0x%x]\n", nRet);
            break;
        }

        // ch:销毁句柄 | Destroy handle
        nRet = MV_CC_DestroyHandle(handle);
        if (MV_OK != nRet)
        {
    
    
            printf("Destroy Handle fail! nRet [0x%x]\n", nRet);
            break;
        }
        handle = NULL;
    } while (0);
 
    if (handle != NULL)
    {
    
    
        MV_CC_DestroyHandle(handle);
        handle = NULL;
    }
    

    // ch:反初始化SDK | en:Finalize SDK
    MV_CC_Finalize();

    printf("Press a key to exit.\n");
    WaitForKeyPress();

    return 0;
}