海康威视相机开发(一)

海康威视相机 + opencv初体验
简单实现打开相机的连接,打开,控制相机的帧率,曝光时间等,使用opencv实时处理图片并输出


文章目录

  • 平台
  • 环境配置
    • 下载
    • 配置
  • 开发流程
    • 设备连接
    • 主动取流流程
    • 代码
    • 代码效果

平台

windows10 + vs2019 + MV-CA050-10GM + HIKCAMERA SDK版本v3.3.0 + OpenCV3.3.1

环境配置

下载

HIKCAMERA SDK下载:https://www.hikrobotics.com/service/download/0/0
海康威视相机开发(一)_第1张图片
下载安装后,会有一个客户端和SDK
海康威视相机开发(一)_第2张图片
我们开发所需要的SDK就在Development文件夹里边,里边有我们所需的头文件、库文件、文档、教程demo等等,是非常好学习资料。
OpenCV3.3.1下载:

配置

配置比较简单,将我们所需的可执行文件、库文件、头文件、附加依赖项包含进来就可以了
可执行文件
海康威视相机开发(一)_第3张图片
这一步也可以在环境变量里添加到PATH里面,主要是为了让OpenCV正常运行。出现错误,重启一次就OK。
头文件
海康威视相机开发(一)_第4张图片
库文件
在这里插入图片描述
附加依赖项
在这里插入图片描述

开发流程

Development文件夹里边里边的开发指南写的相当完善:

设备连接

对设备进行操作,实现图像采集、参数配置等功能,需要先连接设备(打开设备),具体流程如下图所示。
海康威视相机开发(一)_第5张图片
详细步骤

  1. (可选)调用 MV_CC_EnumDevices() 枚举子网内指定传输协议对应的所有设备。 可通过nTLayerType在结构 MV_CC_DEVICE_INFO() 中获取设备信息。
  2. (可选)在打开指定设备前,调用 MV_CC_IsDeviceAccessible() 检查指定设备是否可访问。
  3. 调用 MV_CC_CreateHandle() 创建设备句柄。
  4. 调用 MV_CC_OpenDevice() 打开设备。
  5. (可选)调用 MV_CC_GetAllMatchInfo()以获取设备信息。
  6. 调用 MV_CC_CloseDevice() 关闭设备。
  7. 调用 MV_CC_DestroyHandle() 销毁句柄并释放资源。

主动取流流程

SDK提供主动获取图像的接口,用户可以在开启取流后直接调用此接口获取图像,也可以使用异步方式(线程、定时器等)获取图像。
主动获取图像有两种方式(两种方式不能同时使用):

  • 方式一:调用 MV_CC_StartGrabbing() 开始采集,需要自己开启一个buffer,然后在应用层循环调用 MV_CC_GetOneFrameTimeout() 获取指定像素格式的帧数据,获取帧数据时上层应用程序需要根据帧率控制好调用该接口的频率。
  • 方式二:调用 MV_CC_StartGrabbing() 开始采集,然后在应用层调用 MV_CC_GetImageBuffer() 获取指定像素格式的帧数据,然后调用 MV_CC_FreeImageBuffer() 释放buffer,获取帧数据时上层应用程序需要根据帧率控制好调用该接口的频率。
    海康威视相机开发(一)_第6张图片
    详细步骤
  1. (可选)调用 MV_CC_EnumDevices() 枚举子网内指定传输协议对应的所有设备。 可通过nTLayerType在结构 MV_CC_DEVICE_INFO() 中获取设备信息。
  2. (可选)打开指定设备前,调用 MV_CC_IsDeviceAccessible() 检查指定设备是否可访问。
  3. 调用 MV_CC_CreateHandle() 创建设备句柄。
  4. 调用 MV_CC_OpenDevice() 打开设备。
  5. (可选)执行以下一个或多个操作以获取/设置相机不同类型的参数。
    • 获取/设置Int类型节点值 调用 MV_CC_GetIntValue() / MV_CC_SetIntValue()
    • 获取/设置Float类型节点值 调用 MV_CC_GetFloatValue() / MV_CC_SetFloatValue()
    • 获取/设置Enum类型节点值 调用 MV_CC_GetEnumValue() / MV_CC_SetEnumValue()
    • 获取/设置Bool类型节点值 调用 MV_CC_GetBoolValue() / MV_CC_SetBoolValue()
    • 获取/设置String类型节点值 调用 MV_CC_GetStringValue() / MV_CC_SetStringValue()
    • 设置Command类型节点值 调用 MV_CC_SetCommandValue()
  6. 图像采集:
    • (可选)调用 MV_CC_SetImageNodeNum() 设置图像缓存节点个数。当获取的图像数超过这个设定值,最早的图像数据会被自动丢弃。
    • 调用 MV_CC_StartGrabbing() 开始取流。
    • 对于原始图像数据,可调用 MV_CC_ConvertPixelType() 转换图像的像素格式,也可调用 MV_CC_SaveImage() 转换成JPEG或BMP格式的图片,并保存成图片文件。
    • 在应用程序层中重复调用 MV_CC_GetOneFrameTimeout() 来获取图片数据。
  7. 调用 MV_CC_StopGrabbing() 停止采集。
  8. 调用 MV_CC_CloseDevice() 关闭设备。
  9. 调用 MV_CC_DestroyHandle() 销毁句柄并释放资源。

下面尝试使用第一种方式进行主动获取图片

代码

#include 
#include 
#include 
#include "windows.h"
#include "MvCameraControl.h"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "string.h"
#include "time.h"


HWND g_hwnd = NULL;
bool g_bExit = false;
unsigned int g_nPayloadSize = 0;

// 以时间作为图片名 精确到毫秒
std::string GetTimeAsFileName()
{
    SYSTEMTIME st = { 0 };
    GetLocalTime(&st);  //获取当前时间 可精确到ms
    char filename[100] = { 0 };

    sprintf(filename, "pictures_src/%d%02d%02d_%02d%02d%02d%03d.bmp", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
    return filename;
}



// 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);
        printf("DeviceVersion: %s\n\n", pstMVDevInfo->SpecialInfo.stGigEInfo.chDeviceVersion);
        printf("ManufacturerName: %s\n\n", pstMVDevInfo->SpecialInfo.stGigEInfo.chManufacturerName);


    }
    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
    {
        printf("Not support.\n");
    }

    return true;
}

int RGB2BGR(unsigned char* pRgbData, unsigned int nWidth, unsigned int nHeight)
{
    if (NULL == pRgbData)
    {
        return MV_E_PARAMETER;
    }

    for (unsigned int j = 0; j < nHeight; j++)
    {
        for (unsigned int i = 0; i < nWidth; i++)
        {
            unsigned char red = pRgbData[j * (nWidth * 3) + i * 3];
            pRgbData[j * (nWidth * 3) + i * 3] = pRgbData[j * (nWidth * 3) + i * 3 + 2];
            pRgbData[j * (nWidth * 3) + i * 3 + 2] = red;
        }
    }

    return MV_OK;
}


// convert data stream in Mat format
bool Convert2Mat(MV_FRAME_OUT_INFO_EX* pstImageInfo, unsigned char* pData, cv::Mat& mat_img)
{
    cv::Mat srcImage;
    if (pstImageInfo->enPixelType == PixelType_Gvsp_Mono8)
    {
        srcImage = cv::Mat(pstImageInfo->nHeight, pstImageInfo->nWidth, CV_8UC1, pData);
    }
    else if (pstImageInfo->enPixelType == PixelType_Gvsp_RGB8_Packed)
    {
        RGB2BGR(pData, pstImageInfo->nWidth, pstImageInfo->nHeight);
        srcImage = cv::Mat(pstImageInfo->nHeight, pstImageInfo->nWidth, CV_8UC3, pData);
    }
    else
    {
        printf("unsupported pixel format\n");
        return false;
    }

    if (NULL == srcImage.data)
    {
        return false;
    }

    //save converted image in a local file
    std::string filename = GetTimeAsFileName();
    try {
#if defined (VC9_COMPILE)
        cvSaveImage(filename, &(IplImage(srcImage)));
#else
        //cv::imwrite(filename, srcImage);
#endif
    }
    catch (cv::Exception& ex) {
        fprintf(stderr, "Exception saving image to bmp format: %s\n", ex.what());
    }
    mat_img = srcImage.clone();
    srcImage.release();

    return true;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
    case WM_DESTROY:
        PostQuitMessage(0);
        g_hwnd = NULL;
        break;
    }

    return DefWindowProc(hWnd, msg, wParam, lParam);
}

static  unsigned int __stdcall CreateRenderWindow(void* pUser)
{
    HINSTANCE hInstance = ::GetModuleHandle(NULL);              //获取应用程序的模块句柄
    WNDCLASSEX wc;
    wc.cbSize = sizeof(wc);
    wc.style = CS_HREDRAW | CS_VREDRAW;              //窗口的风格
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = ::LoadIcon(NULL, IDI_APPLICATION);    //图标风格
    wc.hIconSm = ::LoadIcon(NULL, IDI_APPLICATION);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);          //背景色
    wc.hCursor = ::LoadCursor(NULL, IDC_ARROW);        //鼠标风格
    wc.lpfnWndProc = WndProc;                              //自定义消息处理函数
    wc.lpszMenuName = NULL;
    wc.lpszClassName = "RenderWindow";                       //该窗口类的名称

    if (!RegisterClassEx(&wc))
    {
        return 0;
    }

    DWORD style = WS_OVERLAPPEDWINDOW;
    DWORD styleEx = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
    RECT rect = { 0, 0, 640, 480 };

    AdjustWindowRectEx(&rect, style, false, styleEx);

    HWND hWnd = CreateWindowEx(styleEx, "RenderWindow", "Display", style, 0, 0,
        rect.right - rect.left, rect.bottom - rect.top, NULL, NULL, hInstance, NULL);
    if (hWnd == NULL)
    {
        return 0;
    }

    ::UpdateWindow(hWnd);
    ::ShowWindow(hWnd, SW_SHOW);

    g_hwnd = hWnd;

    MSG msg = { 0 };
    while (msg.message != WM_QUIT)
    {
        if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    return 0;
}

static  unsigned int __stdcall WorkThread(void* pUser)
{
    int nRet = MV_OK;

    MV_FRAME_OUT_INFO_EX stImageInfo = { 0 };
    MV_DISPLAY_FRAME_INFO stDisplayInfo = { 0 };
    unsigned char* pData = (unsigned char*)malloc(sizeof(unsigned char) * (g_nPayloadSize));
    if (pData == NULL)
    {
        return 0;
    }
    unsigned int nDataSize = g_nPayloadSize;
    cv::namedWindow("test_img", cv::WINDOW_FREERATIO);
    cv::namedWindow("edge_img", cv::WINDOW_FREERATIO);


    for (int i = 0; ; i++)
    {
        cv::waitKey(0);
        nRet = MV_CC_GetOneFrameTimeout(pUser, pData, nDataSize, &stImageInfo, 1000);
        if (nRet == MV_OK)
        {
            printf("Get One Frame: Width[%d], Height[%d], nFrameNum[%d]\n",
                stImageInfo.nWidth, stImageInfo.nHeight, stImageInfo.nFrameNum);

            if (g_hwnd)
            {
                stDisplayInfo.hWnd = g_hwnd;
                stDisplayInfo.pData = pData;
                stDisplayInfo.nDataLen = stImageInfo.nFrameLen;
                stDisplayInfo.nWidth = stImageInfo.nWidth;
                stDisplayInfo.nHeight = stImageInfo.nHeight;
                stDisplayInfo.enPixelType = stImageInfo.enPixelType;

                MV_CC_DisplayOneFrame(pUser, &stDisplayInfo);

                // 数据去转换
                bool bConvertRet = false;
                cv::Mat mat_img, threshold_img, edge, gray;
                bConvertRet = Convert2Mat(&stImageInfo, pData, mat_img);

                // print result
                if (bConvertRet)
                {
                    printf("OpenCV format convert finished.\n");
                    cv::threshold(mat_img, threshold_img, 100, 255, cv::THRESH_BINARY);
                    //cv::cvtColor(mat_img, gray, cv::COLOR_BGR2GRAY);
                    cv::Canny(mat_img, edge, 100, 200, 3);
                    cv::imshow("test_img", threshold_img);
                    cv::imshow("edge_img", edge);

                    /* free(pData);
                        pData = NULL;*/
                }
                else
                {
                    printf("OpenCV format convert failed.\n");
                    free(pData);
                    /* pData = NULL;
                        break;*/
                }
            }
        }
        else
        {
            printf("No data[0x%x]\n", nRet);
        }
        if (g_bExit)
        {
            break;
        }
        
    }
    
    cv::destroyAllWindows();
    free(pData);

    return 0;
}

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

    do
    {
        // 获取SDK版本信息
        unsigned int sdk_V = MV_CC_GetSDKVersion();
        printf("SDK version is [0x%x]\n", sdk_V);
        // 获取支持的传输层
        int tls = MV_CC_EnumerateTls();
        printf("surpport tls is: [%d]\n", tls);
        // 根据厂商名字枚举设备
       /* MV_CC_DEVICE_INFO_LIST stDeviceList_ex = { 0 };
        nRet = MV_CC_EnumDevicesEx(MV_GIGE_DEVICE | MV_USB_DEVICE, &stDeviceList_ex, "hikvision");
        if (MV_OK != nRet)
        {
            printf("Enum Devices with ManufacturerName fail! nRet [0x%x]\n", nRet);
            break;
        }*/

        // ch:枚举设备 | en:Enum device
        MV_CC_DEVICE_INFO_LIST stDeviceList = { 0 };
        nRet = MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_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];
               /* bool access = MV_CC_IsDeviceAccessible(pDeviceInfo, 1);
                if (!access)
                {
                    printf("[device %d]:could not access... nRet:[0x%x]\n", i, nRet);
                }*/
                if (NULL == pDeviceInfo)
                {
                    break;
                }
                PrintDeviceInfo(pDeviceInfo);
            }
        }
        else
        {
            printf("Find No Devices!\n");
            break;
        }


        printf("Please Input camera index:");
        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;
        }

        // 设置相机常见参数
        nRet = MV_CC_SetFrameRate(handle, 10.f);
        if (MV_OK != nRet)
        {
            printf("set FrameRate fail! nRet [0x%x]\n", nRet);
            break;
        }

        nRet = MV_CC_SetExposureTime(handle, 2000.f);
        if (MV_OK != nRet)
        {
            printf("set Exposure Time fail! nRet [0x%x]\n", nRet);
            break;
        }

        // 保存相机参数
        printf("Start export the camera properties to the file\n");
        printf("Wait......\n");

        nRet = MV_CC_FeatureSave(handle, "FeatureFile.ini");
        if (MV_OK != nRet)
        {
            printf("Save Feature fail! nRet [0x%x]\n", nRet);
            break;
        }
        printf("Finish export the camera properties to the file\n\n");

        // 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_SetIntValue(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:Get payload size
        MVCC_INTVALUE stParam = { 0 };
        nRet = MV_CC_GetIntValue(handle, "PayloadSize", &stParam);
        if (MV_OK != nRet)
        {
            printf("Get PayloadSize fail! nRet [0x%x]\n", nRet);
            break;
        }
        g_nPayloadSize = stParam.nCurValue;

        unsigned int nThreadID = 0;
        void* hCreateWindow = (void*)_beginthreadex(NULL, 0, CreateRenderWindow, handle, 0, &nThreadID);
        if (NULL == hCreateWindow)
        {
            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;
        }

        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;
        WaitForSingleObject(hThreadHandle, INFINITE);
        CloseHandle(hThreadHandle);

        // 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;
        }
    } while (0);


    if (nRet != MV_OK)
    {
        if (handle != NULL)
        {
            MV_CC_DestroyHandle(handle);
            handle = NULL;
        }
    }

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

    return 0;
}

代码效果

选择好设备序号,我目前只有一个相机,所以设备号为0,按下回车,把鼠标放在窗口上边,按一下enter或空格取一张图片,display显示原图,剩下两个窗口分别显示二值化图和通过canny提取的边缘轮廓图。
海康威视相机开发(一)_第7张图片

你可能感兴趣的:(相机开发,网络,机器学习,经验分享,程序人生)