海康工業相機的應用部署不是簡簡單單!?

SkyXZ發表於2025-01-18

作者:SkyXZ

CSDN:SkyXZ~-CSDN部落格

部落格園:SkyXZ - 部落格園

筆者使用的裝置及環境:WSL2-Ubuntu22.04+MV-CS016-10UC

不會吧?不會吧?不會還有人拿到海康工業相機還是一臉懵叭?不會還有人覺得海康相機的API使用很難叭?不用慌!這篇文章從官方文件涵蓋了海康相機官方MVS軟體的使用以及Linux海康SDK-API包的理解上手,一篇文章帶你走出海康相機使用新手村!!!讓你一文讀完之後便可自信的對朋友說:“海康相機?簡簡單單!!!”

參考資料:

  • 海康工業相機客戶端MVS下載地址:海康機器人-機器視覺-下載中心
  • 海康工業相機鏡頭選型平臺地址:海康機器人-機器視覺-工具
  • 海康機器人USB3.0工業面陣相機使用者手冊:海康機器人USB3.0工業面陣相機使用者手冊V3.0.1_9724.pdf
  • 海康機器人千兆網口工業面陣相機使用者手冊:海康機器人千兆網口工業面陣相機使用者手冊V4.1.2_7307.pdf

一、海康官方MVS客戶端使用教程

我們首先根據自己的系統在官網下載我們的MVS客戶端,我的系統為Linux,因此我選擇下載第二個Linux版本的客戶端

image-20250118041419982

下載完成後我們會得到一個ZIP壓縮包,我們解壓後可以看到有很多不同CPU架構的版本,我們選擇適合自己系統的版本使用如下命令即可完成安裝,安裝完成後的客戶端將在/opt/MVS目錄下

微信截圖_20250117134813

sudo dpkg -i MVS-***********.deb #自行替換適合自己的系統版本安裝包

我們使用如下命令即可進入MVS安裝環境並且啟動我們的客戶端

cd /opt/MVS/bin #進入軟體目錄
./MVS.sh #執行客戶端

執行客戶端後我們可以看到我們的客戶端主介面如下,左邊主要為我們的裝置區在這個區域我們可以看到當前連線到我們電腦的所有海康相機裝置,最上方為選單欄提供檔案、檢視、設定、工具和幫助的功能,下方為控制工具條可以為相機引數儲存等操作提供快捷入口

image-20250118042210593

接著我們選中我們的相機可以看到我們進入瞭如下介面,在這個介面中左下方為介面和裝置資訊獲取視窗可以顯示裝置詳細資訊,中間為影像預覽視窗上方左一按鈕點選後即可開啟相機實時取流檢視影像,右邊為屬性設定視窗,可以對相機的基本引數進行設定,如:觸發模式、增益、曝光時間、幀率、畫素格式等等

image-20250118042654848

由於屬性設定客戶端中有中文介紹,那麼我們接著主要開始講解我們的SDK-API的使用方法叭

二、海康相機開發實戰上手

在我們上一節安裝的MVS目錄下存在著海康相機驅動所需要的全部標頭檔案以及連結庫,我們首先在我們的工作空間下面新建一個資料夾,接著將MVS安裝目錄下的includelib檔案全部複製進來

mkdir -P HK_Camera/src #建立工作目錄及原始碼資料夾
cd HK_Camera/ #進入工作目錄
cp -r /opt/MVS/include/ HK_Camera/  #複製標頭檔案
cp -r /opt/MVS/lib/ HK_Camera/ #複製連結庫
touch CmakeLists.txt #建立Cmake檔案

我們首先來看一下海康相機元件包的專案結構叭,要驅動海康工業相機需要有兩個主要的驅動包includelib,其具體的結構以及對應的作用請看下圖:

HK_Camera/
├── CmakeLists.txt
├── include/
│ ├── CameraParams.h
│ ├── MvCameraControl.h
│ ├── MvErrorDefine.h
│ ├── MvISPErrorDefine.h
│ ├── MvObsoleteInterfaces.h
│ ├── ObsoleteCamParams.h
│ └── PixelType.h
├── lib/
│ ├── 32/
│ ├── 64/
│ └── CLProtocol/
└── src/

  • CameraParams.h :定義了相機相關的引數結構體和列舉型別,包含了相機的基本資訊結構(如GigE相機資訊、USB相機資訊等),定義了影像採集相關的引數(如影像大小、畫素格式等),定義了相機工作模式(如單幀模式、連續採集模式等),定義了網路傳輸相關的引數

  • MvCameraControl.h :定義了相機控制的主要API介面,包含相機的初始化、連線、斷開等基本操作函式,包含影像採集相關的函式(開始採集、停止採集、獲取影像等),包含引數設定和獲取的函式,包含檔案操作相關的函式,是SDK的主要介面檔案

image-20250117141841904

  • MvErrorDefine.h :定義了SDK所有的錯誤碼,包含通用錯誤碼(0x80000000-0x800000FF)、GenICam錯誤碼(0x80000100-0x800001FF)、GigE相關錯誤碼(0x80000200-0x800002FF)、USB相關錯誤碼(0x80000300-0x800003FF)、韌體升級相關錯誤碼(0x80000400-0x800004FF)
  • MvISPErrorDefine.h :定義了影像處理(ISP)相關的錯誤碼,包含通用錯誤碼、記憶體相關錯誤碼、影像格式相關錯誤碼、降噪處理相關錯誤碼、去汙點相關錯誤碼
  • PixelType.h :定義了相機支援的所有畫素格式型別(enum MvGvspPixelType),主要包含以下幾類畫素格式:Mono(單色): Mono8/10/12/16等、Bayer: BayerGR8/10/12, BayerRG8/10/12等、RGB: RGB8_Packed, BGR8_Packed等、YUV: YUV411/422/444等、3D點雲相關格式
  • ObsoleteCamParams.h :定義了一些已過時但仍支援的相機引數結構體
  • MvObsoleteInterfaces.h :定義了一些已過時但仍支援的介面函式

由於海康的這些標頭檔案裡的API有著非常非常詳細的雙語介紹(如下圖),因此我們將以海康工業相機MV-CS016-10UC來主要介紹使用海康相機的API使用及相機的使用流程,類似於OpenCV呼叫免驅攝像頭一般,海康攝像頭的使用也主要分為四步,分別是初始化相機——>設定相機引數——>開始取圖——>停止採集和清理資源,接下來我們將一邊介紹主要使用的API一邊手把手帶著大家使能我們的海康相機,首先我們建立我們的main.cc

touch src/main.cc #建立檔案

接著我們開始編寫我們的Cmake檔案,由於Cmake比較簡單,我們便不詳細的展開說明了:

#step 1 設定我們的專案以及版本最小需求
cmake_minimum_required(VERSION 3.10)
project(HK_Camera)
#step 2 設定C++標準
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
#step 3 設定編譯型別
if(NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE Release)
endif()
#step 4 新增海康相機SDK路徑
set(MVCAM_SDK_PATH "${PROJECT_SOURCE_DIR}/lib/64")  # SDK庫檔案路徑
set(MVCAM_INCLUDE_PATH "${PROJECT_SOURCE_DIR}/include")  # SDK標頭檔案路徑
#step 5 新增標頭檔案路徑
include_directories(
    ${PROJECT_SOURCE_DIR}/include
    ${MVCAM_INCLUDE_PATH}
)
#step 6 新增庫檔案路徑
link_directories(
    ${MVCAM_SDK_PATH}
)
#step 7 新增原始檔
add_executable(${PROJECT_NAME} 
    src/main.cc
) 
#step 8 連結海康相機庫
target_link_libraries(${PROJECT_NAME}
    MvCameraControl  # 海康相機主庫
    pthread         # 執行緒庫
)
#step 9 設定編譯選項
target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -O2)
#step 10 安裝目標
install(TARGETS ${PROJECT_NAME}
    RUNTIME DESTINATION bin
) 

(1)海康相機使用基本部署流程

Cmake編寫完成後我們開始編寫我們的程式碼,首先匯入一些我們需要的標頭檔案

#include <iostream>
#include <string> 
#include <cstring>  // for memset
#include "MvCameraControl.h"
#include "CameraParams.h"  // for MVCC_INTVALUE
#include "PixelType.h"     // for PixelType_Gvsp_BGR8_Packed

接著為了便於我們的使用,我們首先建立一個HKCamera類,其中包含有五個主要的函式分別是InitCamera()SetParameters()StartGrabbing()GetOneFrame()StopGrabbing()分別用來初始化我們的相機、設定相機引數、開始取流、獲取一幀影像以及停止採集釋放資源

class HKCamera{
    public:
    	bool InitCamera(); // 初始化相機
    	bool SetParameters(); // 設定相機引數
    	bool StartGrabbing(); // 開始取圖
    	bool GetOneFrame(); // 獲取一幀影像
    	void StopGrabbing() // 停止採集
}

我們開始首先完成初始化相機的函式,要使用我們的海康相機那麼首先肯定就需要先知道當前裝置連結了哪些相機,因此我們便需要先使用MV_CC_EnumDevices函式來列舉當前連結上主機的網口orUSB海康相機,海康官方的API以及MV_CC_DEVICE_INFO_LIST結構體解析如下:

typedef struct _MV_CC_DEVICE_INFO_LIST_
{
    unsigned int        nDeviceNum;                    ///< [OUT] \~chinese 線上裝置數量           
    MV_CC_DEVICE_INFO*  pDeviceInfo[MV_MAX_DEVICE_NUM];///< [OUT] \~chinese 支援最多256個裝置      
}MV_CC_DEVICE_INFO_LIST;
/********************************************************************//**
 *  @~chinese
 *  @brief  列舉裝置
 *  @param  nTLayerType      [IN]            列舉傳輸層, 引數定義參見CameraParams.h定義, 如: #define MV_GIGE_DEVICE 0x00000001 GigE裝置
 *  @param  pstDevList       [IN][OUT]       裝置列表
 *  @return 成功,返回MV_OK;錯誤,返回錯誤碼 
 *  @remarks 裝置列表的記憶體是在SDK內部分配的,多執行緒呼叫該介面時會進行裝置列表記憶體的釋放和申請,建議儘量避免多執行緒列舉操作。
 *  @remarks 引數列舉傳輸層,適配傳入MV_GIGE_DEVICE、MV_1394_DEVICE、MV_USB_DEVICE、MV_CAMERALINK_DEVICE;MV_GIGE_DEVICE該引數
             傳出所有GiGE相關的裝置資訊(包含虛擬GiGE和GenTL下的GiGE裝置),MV_USB_DEVICE該引數傳出所有USB裝置,包含虛擬USB裝置。
 ************************************************************************/
MV_CAMCTRL_API int __stdcall MV_CC_EnumDevices(IN unsigned int nTLayerType, IN OUT MV_CC_DEVICE_INFO_LIST* pstDevList);

因此根據API的描述,我們首先需要建立一個MV_CC_DEVICE_INFO_LIST型別的結構體,同時我們還需要定義一個nRet變數來獲取呼叫API後返回的值來判斷我們是否成功使用了API,因此我們的程式碼應該為下述形式:

MV_CC_DEVICE_INFO_LIST stDeviceList;//定義MV_CC_DEVICE_INFO_LIST型別的結構體stDeviceList
memset(&stDeviceList, 0, sizeof(MV_CC_DEVICE_INFO_LIST));//將結構體stDeviceList記憶體清零,防止垃圾值影響
int nRet = MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE, &stDeviceList);// 列舉裝置
if (MV_OK != nRet) { //判斷返回值為MV_OK即正常執行
    std::cout << "列舉裝置失敗! nRet [" << nRet << "]" << std::endl;
    return false;
}

在列舉了相機之後如果相機數量不為零我們便可以選擇開啟我們的海康相機啦,我們首先檢視一下開啟相機的API介紹

/********************************************************************//**
 *  @~chinese
 *  @brief  開啟裝置
 *  @param  handle        [IN] 裝置控制代碼
 *  @param  nAccessMode   [IN] 訪問許可權, 引數定義參見CameraParams.h定義, 如:#define MV_ACCESS_Exclusive 1  (僅對 MV_GIGE_DEVICE/MV_GENTL_GIGE_DEVICE 型別的裝置有效)
 *  @param  nSwitchoverKey[IN] 切換訪問許可權時的金鑰(僅對 MV_GIGE_DEVICE 型別的裝置有效)
 *  @return 成功,返回MV_OK;錯誤,返回錯誤碼
 *  @remarks 根據設定的裝置引數,找到對應的裝置,連線裝置, 呼叫介面時可不傳入nAccessMode和nSwitchoverKey,此時預設裝置訪問模式為獨佔許可權。
            MV_GIGE_DEVICE 型別裝置,目前相機韌體暫不支援MV_ACCESS_ExclusiveWithSwitch、MV_ACCESS_ControlWithSwitch、MV_ACCESS_ControlSwitchEnable、MV_ACCESS_ControlSwitchEnableWithKey這四種搶佔模式, SDK介面支援設定
            MV_GENTL_GIGE_DEVICE 裝置只支援 nAccessMode 是 MV_ACCESS_Exclusive 、MV_ACCESS_Control 、MV_ACCESS_Monitor許可權
            對於U3V裝置,CXP,Cameralink(MV_CAMERALINK_DEVICE、MV_GENTL_CAMERALINK_DEVICE), Xof裝置, 虛擬GEV, 虛擬U3V裝置:nAccessMode、nSwitchoverKey這兩個引數無效; 預設以控制許可權開啟裝置;
            該介面支援網口裝置不列舉直接開啟,不支援U口和GenTL裝置不列舉開啟裝置
 ************************************************************************/
#ifndef __cplusplus //用於區分C和C++編譯環境
// C語言版本的函式宣告
MV_CAMCTRL_API int __stdcall MV_CC_OpenDevice(IN void* handle, IN unsigned int nAccessMode, IN unsigned short nSwitchoverKey);
#else
// C++語言版本的函式宣告(帶預設引數)
MV_CAMCTRL_API int __stdcall MV_CC_OpenDevice(IN void* handle, IN unsigned int nAccessMode = MV_ACCESS_Exclusive, IN unsigned short nSwitchoverKey = 0);
#endif

根據這個API的說明我們可以知道我們在使用這個函式前還需要利用MV_CC_CreateHandle這個API建立一個控制代碼同時一個裝置同時只能被一個程序以獨佔方式開啟,並且關閉裝置時需要呼叫MV_CC_CloseDevice函式,同時我們瞭解到這個API還有一個可選引數nAccessMode,可以用來設定訪問許可權,分別有MV_ACCESS_Exclusive獨佔許可權、MV_ACCESS_Control控制許可權、MV_ACCESS_Monitor監控許可權、為了開啟我們的攝像頭我們還需再次檢視MV_CC_CreateHandleAPI介紹

/********************************************************************//**
 *  @~chinese
 *  @brief  建立裝置控制代碼
 *  @param  handle                      [IN][OUT]       裝置控制代碼
 *  @param  pstDevInfo                  [IN]            裝置資訊結構體
 *  @return 成功,返回MV_OK;錯誤,返回錯誤碼 
 *  @remarks 根據輸入的裝置資訊,建立庫內部必須的資源和初始化內部模組
             透過該介面建立控制代碼,呼叫SDK介面,會預設生成SDK日誌檔案,如果不需要生成日誌檔案,可以將日誌配置檔案中的日誌等級改成off
 ************************************************************************/
MV_CAMCTRL_API int __stdcall MV_CC_CreateHandle(IN OUT void ** handle, IN const MV_CC_DEVICE_INFO* pstDevInfo);

根據這個API的說明,我們需要使用之前建立的MV_CC_DEVICE_INFO_LIST型別結構體stDeviceList中的成員變數pDeviceInfo,於是我們的程式碼便應該如下:

if (stDeviceList.nDeviceNum > 0) {
    nRet = MV_CC_CreateHandle(&handle, stDeviceList.pDeviceInfo[0]);    // 建立控制代碼
    if (MV_OK != nRet) {
        std::cout << "建立控制代碼失敗! nRet [" << nRet << "]" << std::endl;
        return false;
    }
    nRet = MV_CC_OpenDevice(handle);    // 開啟裝置
    if (MV_OK != nRet) {
        std::cout << "開啟裝置失敗! nRet [" << nRet << "]" << std::endl;
        return false;
    }
} else {
    std::cout << "未找到裝置!" << std::endl;
    return false;
}

至此我們的InitCamera()函式便搭建完成啦,完整程式碼如下:

bool InitCamera() {
    MV_CC_DEVICE_INFO_LIST stDeviceList;
    memset(&stDeviceList, 0, sizeof(MV_CC_DEVICE_INFO_LIST));

    // 列舉裝置
    int nRet = MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE, &stDeviceList);
    if (MV_OK != nRet) {
        std::cout << "列舉裝置失敗! nRet [" << nRet << "]" << std::endl;
        return false;
    }

    if (stDeviceList.nDeviceNum > 0) {
        // 建立控制代碼
        nRet = MV_CC_CreateHandle(&handle, stDeviceList.pDeviceInfo[0]);
        if (MV_OK != nRet) {
            std::cout << "建立控制代碼失敗! nRet [" << nRet << "]" << std::endl;
            return false;
        }

        // 開啟裝置
        nRet = MV_CC_OpenDevice(handle);
        if (MV_OK != nRet) {
            std::cout << "開啟裝置失敗! nRet [" << nRet << "]" << std::endl;
            return false;
        }
    } else {
        std::cout << "未找到裝置!" << std::endl;
        return false;
    }
    return true;
}

完成了初始化相機的函式接下來我們便來完成我們SetParameters()設定相機基本引數的函式,首先我們根據海康的使用者手冊可以知道我們有幾個基本的引數可以進行設定,分別是相機的觸發模式畫素格式曝光時間增益Buff相機幀率等,我們在這裡只對這四個常見引數進行設定,透過查閱API手冊我們可以知道這四個引數設定分別對應以下兩個APIMV_CC_SetEnumValueMV_CC_SetFloatValue同時我們可以透過MV_CC_GetFloatValueAPI來獲取每一個屬性鍵值的可調節引數範圍,這些函式的API以及引數列表中涉及到的結構體介紹如下:

/********************************************************************//**
 *  @~chinese
 *  @brief  設定Enum型屬性值
 *  @param  handle                      [IN]            裝置控制代碼/採集卡控制代碼
 *  @param  strKey                      [IN]            屬性鍵值,如獲取畫素格式資訊則為"PixelFormat"
 *  @param  nValue                      [IN]            想要設定的裝置的屬性值
 *  @return 成功,返回MV_OK,失敗,返回錯誤碼
 *  @remarks 連線裝置之後呼叫該介面可以設定Enum型別的指定節點的值。
 ************************************************************************/
MV_CAMCTRL_API int __stdcall MV_CC_SetEnumValue(IN void* handle,IN const char* strKey,IN unsigned int nValue);

/********************************************************************//**
 *  @~chinese
 *  @brief  設定float型屬性值
 *  @param  handle                      [IN]            裝置控制代碼/採集卡控制代碼
 *  @param  strKey                      [IN]            屬性鍵值
 *  @param  fValue                      [IN]            想要設定的裝置的屬性值
 *  @return 成功,返回MV_OK,失敗,返回錯誤碼
 *  @remarks 連線裝置之後呼叫該介面可以設定float型別的指定節點的值。
 ************************************************************************/
MV_CAMCTRL_API int __stdcall MV_CC_SetFloatValue(IN void* handle,IN const char* strKey,IN float fValue);

/********************************************************************//**
 *  @~chinese
 *  @brief  獲取Float屬性值
 *  @param  handle                      [IN]            裝置控制代碼/採集卡控制代碼
 *  @param  strKey                      [IN]            屬性鍵值
 *  @param  pstFloatValue               [IN][OUT]       返回給呼叫者有關裝置屬性結構體指標
 *  @return 成功,返回MV_OK,失敗,返回錯誤碼
 *  @remarks 連線裝置之後呼叫該介面可以獲取float型別的指定節點的值。
 ************************************************************************/
MV_CAMCTRL_API int __stdcall MV_CC_GetFloatValue(IN void* handle,IN const char* strKey,IN OUT MVCC_FLOATVALUE *pstFloatValue);

/// \~chinese Float型別值               \~english Float Value
typedef struct _MVCC_FLOATVALUE_T
{
    float               fCurValue;     ///< [OUT] \~chinese 當前值                 
    float               fMax;          ///< [OUT] \~chinese 最大值               
    float               fMin;          ///< [OUT] \~chinese 最小值            
    unsigned int        nReserved[4];  ///<       \~chinese 預留      
}MVCC_FLOATVALUE;

我們透過查閱使用者手冊可以知道海康工業相機的屬性鍵值遵循GenICam標準,因此我們能調整和獲取的常用屬性鍵值有如下幾個:

"AcquisitionFrameRate"      // 採集幀率
"AcquisitionFrameRateEnable" // 幀率控制使能
"ExposureTime"              // 曝光時間
"ExposureAuto"              // 自動曝光
"Gain"                      // 增益
"GainAuto"                  // 自動增益
"TriggerMode"               // 觸發模式
"TriggerSource"             // 觸發源
"Width"                     // 影像寬度
"Height"                    // 影像高度

因此,根據上述的分析,我們首先設定我們相機的觸發模式圍為OFF內觸發模式,同時設定我們的畫素格式為BayerRG8

int nRet;
nRet = MV_CC_SetEnumValue(handle, "TriggerMode", 0);// 設定觸發模式為off
if (MV_OK != nRet) {
    std::cout << "設定觸發模式失敗! nRet [" << nRet << "]" << std::endl;
    return false;
}
// 設定畫素格式為BGR8
nRet = MV_CC_SetEnumValue(handle, "PixelFormat", PixelType_Gvsp_BGR8_Packed);
if (MV_OK != nRet) {
    std::cout << "設定畫素格式失敗! nRet [" << nRet << "]" << std::endl;
    return false;
}
std::cout << "設定畫素格式為BGR8_Packed" << std::endl;

接著為了避免我們的引數設定有問題以及為了便於自檢,我們首先利用MV_CC_GetFloatValue來獲取當前我們使用的相機的每一個屬性引數的可調節範圍,再根據獲取到的可調節範圍來檢查我們設定引數的可用性並進一步修改我們的引數,因此我們以曝光時間的設定為例子程式碼應該為如下形式:我們首先建立一個MVCC_FLOATVALUE形式的結構體stExposureTime用來獲取曝光時間的可調節資訊,接著判定我們設定的exposureTime是否超過了stExposureTime.fMinstExposureTime.fMax的範圍,如果沒有那麼我們便將exposureTime使用MV_CC_SetFloatValueAPI進行設定

// 獲取和設定曝光時間
MVCC_FLOATVALUE stExposureTime = {0};
nRet = MV_CC_GetFloatValue(handle, "ExposureTime", &stExposureTime);
if (MV_OK == nRet) {
    float exposureTime = 10000.0f;  // 預設10ms
    if (exposureTime < stExposureTime.fMin) exposureTime = stExposureTime.fMin;
    if (exposureTime > stExposureTime.fMax) exposureTime = stExposureTime.fMax;
    std::cout << "曝光時間範圍: [" << stExposureTime.fMin << ", " << stExposureTime.fMax 
              << "], 當前設定: " << exposureTime << std::endl;
    nRet = MV_CC_SetFloatValue(handle, "ExposureTime", exposureTime);
    if (MV_OK != nRet) {
        std::cout << "設定曝光時間失敗! nRet [" << nRet << "]" << std::endl;
        return false;
    }
}

至此我們的InitCamera()函式便搭建完成啦,完整程式碼如下:

bool SetParameters() {// 設定相機引數
    int nRet;
    nRet = MV_CC_SetEnumValue(handle, "TriggerMode", 0);// 設定觸發模式為off
    if (MV_OK != nRet) {
        std::cout << "設定觸發模式失敗! nRet [" << nRet << "]" << std::endl;
        return false;
    }
    // 設定畫素格式為BGR8
    nRet = MV_CC_SetEnumValue(handle, "PixelFormat", PixelType_Gvsp_BGR8_Packed);
    if (MV_OK != nRet) {
        std::cout << "設定畫素格式失敗! nRet [" << nRet << "]" << std::endl;
        return false;
    }
    std::cout << "設定畫素格式為BGR8_Packed" << std::endl;
    // 獲取和設定曝光時間
    MVCC_FLOATVALUE stExposureTime = {0};
    nRet = MV_CC_GetFloatValue(handle, "ExposureTime", &stExposureTime);
    if (MV_OK == nRet) {
        float exposureTime = 10000.0f;  // 預設10ms
        if (exposureTime < stExposureTime.fMin) exposureTime = stExposureTime.fMin;
        if (exposureTime > stExposureTime.fMax) exposureTime = stExposureTime.fMax;
        std::cout << "曝光時間範圍: [" << stExposureTime.fMin << ", " << stExposureTime.fMax 
                  << "], 當前設定: " << exposureTime << std::endl;
        nRet = MV_CC_SetFloatValue(handle, "ExposureTime", exposureTime);
        if (MV_OK != nRet) {
            std::cout << "設定曝光時間失敗! nRet [" << nRet << "]" << std::endl;
            return false;
        }
    }
    // 獲取和設定增益
    MVCC_FLOATVALUE stGain = {0};
    nRet = MV_CC_GetFloatValue(handle, "Gain", &stGain);
    if (MV_OK == nRet) {
        std::cout << "增益範圍: [" << stGain.fMin << ", " << stGain.fMax 
                  << "], 當前值: " << stGain.fCurValue << std::endl;
        if (stGain.fCurValue < stGain.fMin || stGain.fCurValue > stGain.fMax) {
            float gain = stGain.fMin;  // 使用最小值
            nRet = MV_CC_SetFloatValue(handle, "Gain", gain);
            if (MV_OK != nRet) {
                std::cout << "設定增益失敗! nRet [" << nRet << "]" << std::endl;
                return false;
            }
        }
    }
    // 獲取和設定幀率
    MVCC_FLOATVALUE stFrameRate = {0};
    nRet = MV_CC_GetFloatValue(handle, "AcquisitionFrameRate", &stFrameRate);
    if (MV_OK == nRet) {
        float frameRate = 30.0f;  // 預設30fps
        if (frameRate < stFrameRate.fMin) frameRate = stFrameRate.fMin;
        if (frameRate > stFrameRate.fMax) frameRate = stFrameRate.fMax;
        std::cout << "幀率範圍: [" << stFrameRate.fMin << ", " << stFrameRate.fMax 
                  << "], 當前設定: " << frameRate << std::endl;
        nRet = MV_CC_SetFloatValue(handle, "AcquisitionFrameRate", frameRate);
        if (MV_OK != nRet) {
            std::cout << "設定幀率失敗! nRet [" << nRet << "]" << std::endl;
            // 這個錯誤不影響主要功能,可以繼續
        }
    }
    return true;
}

完成了相機基本引數的設定我們便可以開始取流啦!!!這部分有同學就會問了,欸?我們前面就已經開啟攝像頭了,為什麼這裡還會有取流和取幀兩個函式呢?這就是海康攝像頭和OpenCV使用免驅攝像頭邏輯上的區別了,我們可以把這個過程類比於我們電動抽水機,前面開啟攝像頭的函式可以理解為給抽水機上電,而我們這裡的開始取流StartGrabbing()便是讓影像暫存在相機內部的快取中,可以理解為讓抽水機開始工作一直出水,而我們的取幀函式GetOneFrame()便是從相機的快取中讀取一幀影像並將影像資料複製到我們準備好的記憶體中,可以理解為拿一個水桶來接水,這樣我們便可以隨接隨用

理解了為什麼我們便開始講解取流函式StartGrabbing(),在海康的API裡面有一個專門的函式便是用來取流的,他的API介紹如下:

/********************************************************************//**
 *  @~chinese
 *  @brief  開始取流
 *  @param  handle                      [IN]            裝置控制代碼
 *  @return 成功,返回MV_OK;錯誤,返回錯誤碼
 *  @remarks 該介面不支援MV_CAMERALINK_DEVICE 型別的裝置。
 ***********************************************************************/
MV_CAMCTRL_API int __stdcall MV_CC_StartGrabbing(IN void* handle);

可以看到使用這個函式非常的簡單,和我們上面的操作非常類似,只要將我們建立的控制代碼傳入其中即可,按照如下程式碼呼叫了之後我們的相機便會開始取流並將影像儲存在相機內部的快取裡面臨時儲存

int nRet = MV_CC_StartGrabbing(handle);// 開始取流
if (MV_OK != nRet) {
    std::cout << "開始取流失敗! nRet [" << nRet << "]" << std::endl;
    return false;
}

接著我們便可以獲取當前屬性引數下的影像資料包的大小,由於這個屬性引數不變那麼每幀的影像資料大小的範圍便也不會變,因此我們只需要獲取一次資料包的大小便可以,所以我們將資料包大小獲取放在開始取流的函式里面,我們看到他的API介紹如下:

/************************************************************************
 *  @fn     MV_CAMCTRL_API int __stdcall MV_CC_GetIntValue(IN void* handle,
                                                           IN const char* strKey,
                                                           OUT MVCC_INTVALUE *pIntValue);
 *  @brief  獲取Integer屬性值(建議改用MV_CC_GetIntValueEx介面)
 *  @param  void* handle                [IN]        相機控制代碼
 *  @param  char* strKey                [IN]        屬性鍵值,如獲取寬度資訊則為"Width"
 *  @param  MVCC_INTVALUE* pstValue     [IN][OUT]   返回給呼叫者有關相機屬性結構體指標
 *  @return 成功,返回MV_OK,失敗,返回錯誤碼
 ************************************************************************/
MV_CAMCTRL_API int __stdcall MV_CC_GetIntValue(IN void* handle,IN const char* strKey,OUT MVCC_INTVALUE *pIntValue);

其中還涉及到了一個資料包結構體MVCC_INTVALUE,求定義如下:

typedef struct _MVCC_INTVALUE_T
{
    unsigned int        nCurValue; ///< [OUT] \~chinese 當前值                 
    unsigned int        nMax;      ///< [OUT] \~chinese 最大值              
    unsigned int        nMin;      ///< [OUT] \~chinese 最小值          
    unsigned int        nInc;      ///< [OUT] \~chinese             
    unsigned int        nReserved[4];///<       \~chinese 預留             
}MVCC_INTVALUE;

瞭解了其定義後我們便可以使用這個API來獲取資料包的大小並用malloc函式來分配我們主機的記憶體

// 獲取資料包大小
MVCC_INTVALUE stParam;
nRet = MV_CC_GetIntValue(handle, "PayloadSize", &stParam);
if (MV_OK != nRet) {
    std::cout << "獲取資料包大小失敗! nRet [" << nRet << "]" << std::endl;
    return false;
}
// 分配資源
nDataSize = stParam.nCurValue;
pData = (unsigned char*)malloc(nDataSize);
if (pData == nullptr) {
    std::cout << "記憶體分配失敗!" << std::endl;
    return false;
}

至此我們的StartGrabbing()函式便搭建完成啦,完整程式碼如下:

bool StartGrabbing() {
    // 開始取流
    int nRet = MV_CC_StartGrabbing(handle);
    if (MV_OK != nRet) {
        std::cout << "開始取流失敗! nRet [" << nRet << "]" << std::endl;
        return false;
    }
    // 獲取資料包大小
    MVCC_INTVALUE stParam;
    nRet = MV_CC_GetIntValue(handle, "PayloadSize", &stParam);
    if (MV_OK != nRet) {
        std::cout << "獲取資料包大小失敗! nRet [" << nRet << "]" << std::endl;
        return false;
    }
    // 分配資源
    nDataSize = stParam.nCurValue;
    pData = (unsigned char*)malloc(nDataSize);
    if (pData == nullptr) {
        std::cout << "記憶體分配失敗!" << std::endl;
        return false;
    }
    return true;
}

接著我們再來編寫我們的取幀函式GetOneFrame(),海康給了我們一個專門的獲取一幀影像的API,具體其介紹如下,我們可以看到這個API採用的是超時機制,因此SDK內部會一直等待直到有資料時才會返回一幀影像

/********************************************************************//**
 *  @~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裝置。
 ***********************************************************************/
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);

因此我們便可以用如下的方式來使用這個API,同時我們在這裡再加上一次錯誤判定,以防止控制代碼或者分配的記憶體地址指標為空:

if (handle == nullptr || pData == nullptr) {
    return false;
}
int nRet = MV_CC_GetOneFrameTimeout(handle, pData, nDataSize, &stImageInfo, 1000);
if (MV_OK != nRet) {
    std::cout << "獲取一幀影像失敗! nRet [" << nRet << "]" << std::endl;
    return false;
}
std::cout << "獲取一幀影像成功: Width[" << stImageInfo.nWidth 
      << "] Height[" << stImageInfo.nHeight 
          << "] FrameNum[" << stImageInfo.nFrameNum << "]" << std::endl;
return true;

至此我們的StartGrabbing()函式便搭建完成啦,完整程式碼如下:

// 獲取一幀影像
bool GetOneFrame() {
    if (handle == nullptr || pData == nullptr) {
        return false;
    }
    int nRet = MV_CC_GetOneFrameTimeout(handle, pData, nDataSize, &stImageInfo, 1000);
    if (MV_OK != nRet) {
        std::cout << "獲取一幀影像失敗! nRet [" << nRet << "]" << std::endl;
        return false;
    }
    std::cout << "獲取一幀影像成功: Width[" << stImageInfo.nWidth 
              << "] Height[" << stImageInfo.nHeight 
              << "] FrameNum[" << stImageInfo.nFrameNum << "]" << std::endl;
    return true;
}

最後我們只需要編寫釋放資源的函式StopGrabbing()便大功告成啦!釋放資源比較簡單,我們只需要按照我們開始的順序倒敘關閉即可,即:停止取流、關閉相機、摧毀控制代碼,最後釋放我們主機的記憶體即可,他們對應的API介紹如下:

/********************************************************************//**
 *  @~chinese
 *  @brief  停止取流
 *  @param  handle                      [IN]            裝置控制代碼
 *  @return 成功,返回MV_OK;錯誤,返回錯誤碼
 *  @remarks 該介面不支援MV_CAMERALINK_DEVICE 型別的裝置。
 ***********************************************************************/
MV_CAMCTRL_API int __stdcall MV_CC_StopGrabbing(IN void* handle);
/********************************************************************//**
 *  @~chinese
 *  @brief  關閉裝置
 *  @param  handle                      [IN]            裝置控制代碼
 *  @return 成功,返回MV_OK;錯誤,返回錯誤碼
 *  @remarks 透過MV_CC_OpenDevice連線裝置後,可以透過該介面斷開裝置連線,釋放資源
 ***********************************************************************/
MV_CAMCTRL_API int __stdcall MV_CC_CloseDevice(IN void* handle);
/********************************************************************//**
 *  @~chinese
 *  @brief  銷燬裝置控制代碼
 *  @param  handle                      [IN]            裝置控制代碼
 *  @return 成功,返回MV_OK;錯誤,返回錯誤碼 
 *  @remarks MV_CC_DestroyHandle 如果傳入採集卡控制代碼,其效果和 MV_CC_DestroyInterface 相同;
 ************************************************************************/
MV_CAMCTRL_API int __stdcall MV_CC_DestroyHandle(IN void * handle);

因此我們最後的函式的程式碼長這樣:

// 停止採集
void StopGrabbing() {
    if (handle != nullptr) {
        MV_CC_StopGrabbing(handle);
        MV_CC_CloseDevice(handle);
        MV_CC_DestroyHandle(handle);
        handle = nullptr;
    }
    if (pData != nullptr) {
        free(pData);
        pData = nullptr;
    }
}

最後我們只需要在主函式依次進行呼叫即可,這部分程式碼比較簡單,我相信來了解海康工業相機的同學這部分不需要再展開細講啦:

int main() {
    HKCamera camera;
    // 初始化相機
    if (!camera.InitCamera()) {
        std::cout << "相機初始化失敗!" << std::endl;
        return -1;
    }
    std::cout << "相機初始化成功!" << std::endl;
    // 設定引數
    if (!camera.SetParameters()) {
        std::cout << "設定相機引數失敗!" << std::endl;
        return -1;
    }
    std::cout << "設定相機引數成功!" << std::endl;
    // 開始取圖
    if (!camera.StartGrabbing()) {
        std::cout << "開始取圖失敗!" << std::endl;
        return -1;
    }
    std::cout << "開始取圖成功!" << std::endl;
    // 獲取10幀影像
    for (int i = 0; i < 10; i++) {
        if (!camera.GetOneFrame()) {
            break;
        }
    }
    // 停止採集
    camera.StopGrabbing();
    std::cout << "停止採集完成!" << std::endl;
    return 0;
}

最後我們完整的程式碼及執行效果如下:(但是又有同學要問了,我們該如何顯示影像呢?請繼續翻到下面,我將在下面進行介紹)

image-20250117235331337

#include <iostream>
#include <string>
#include <cstring>  // for memset
#include "MvCameraControl.h"
#include "CameraParams.h"  // for MVCC_INTVALUE
#include "PixelType.h"     // for PixelType_Gvsp_BGR8_Packed
class HKCamera {
private:
    void* handle = nullptr;
    MVCC_INTVALUE stParam;
    MV_FRAME_OUT_INFO_EX stImageInfo = {0};
    unsigned char* pData = nullptr;
    unsigned int nDataSize = 0;
public:
    bool InitCamera() {
        MV_CC_DEVICE_INFO_LIST stDeviceList;
        memset(&stDeviceList, 0, sizeof(MV_CC_DEVICE_INFO_LIST));
        // 列舉裝置
        int nRet = MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE, &stDeviceList);
        if (MV_OK != nRet) {
            std::cout << "列舉裝置失敗! nRet [" << nRet << "]" << std::endl;
            return false;
        }
        if (stDeviceList.nDeviceNum > 0) {
            // 建立控制代碼
            nRet = MV_CC_CreateHandle(&handle, stDeviceList.pDeviceInfo[0]);
            if (MV_OK != nRet) {
                std::cout << "建立控制代碼失敗! nRet [" << nRet << "]" << std::endl;
                return false;
            }
            // 開啟裝置
            nRet = MV_CC_OpenDevice(handle);
            if (MV_OK != nRet) {
                std::cout << "開啟裝置失敗! nRet [" << nRet << "]" << std::endl;
                return false;
            }
        } else {
            std::cout << "未找到裝置!" << std::endl;
            return false;
        }
        return true;
    }
    // 設定相機引數
    bool SetParameters() {
        int nRet;
        // 設定觸發模式為off
        nRet = MV_CC_SetEnumValue(handle, "TriggerMode", 0);
        if (MV_OK != nRet) {
            std::cout << "設定觸發模式失敗! nRet [" << nRet << "]" << std::endl;
            return false;
        }
        // 設定畫素格式為BGR8
        nRet = MV_CC_SetEnumValue(handle, "PixelFormat", PixelType_Gvsp_BGR8_Packed);
        if (MV_OK != nRet) {
            std::cout << "設定畫素格式失敗! nRet [" << nRet << "]" << std::endl;
            return false;
        }
        std::cout << "設定畫素格式為BGR8_Packed" << std::endl;
        // 獲取和設定曝光時間
        MVCC_FLOATVALUE stExposureTime = {0};
        nRet = MV_CC_GetFloatValue(handle, "ExposureTime", &stExposureTime);
        if (MV_OK == nRet) {
            float exposureTime = 10000.0f;  // 預設10ms
            if (exposureTime < stExposureTime.fMin) exposureTime = stExposureTime.fMin;
            if (exposureTime > stExposureTime.fMax) exposureTime = stExposureTime.fMax;
            
            std::cout << "曝光時間範圍: [" << stExposureTime.fMin << ", " << stExposureTime.fMax 
                      << "], 當前設定: " << exposureTime << std::endl;
            
            nRet = MV_CC_SetFloatValue(handle, "ExposureTime", exposureTime);
            if (MV_OK != nRet) {
                std::cout << "設定曝光時間失敗! nRet [" << nRet << "]" << std::endl;
                return false;
            }
        }
        // 獲取和設定增益
        MVCC_FLOATVALUE stGain = {0};
        nRet = MV_CC_GetFloatValue(handle, "Gain", &stGain);
        if (MV_OK == nRet) {
            std::cout << "增益範圍: [" << stGain.fMin << ", " << stGain.fMax 
                      << "], 當前值: " << stGain.fCurValue << std::endl;
            
            if (stGain.fCurValue < stGain.fMin || stGain.fCurValue > stGain.fMax) {
                float gain = stGain.fMin;  // 使用最小值
                nRet = MV_CC_SetFloatValue(handle, "Gain", gain);
                if (MV_OK != nRet) {
                    std::cout << "設定增益失敗! nRet [" << nRet << "]" << std::endl;
                    return false;
                }
            }
        }
        // 獲取和設定幀率
        MVCC_FLOATVALUE stFrameRate = {0};
        nRet = MV_CC_GetFloatValue(handle, "AcquisitionFrameRate", &stFrameRate);
        if (MV_OK == nRet) {
            float frameRate = 30.0f;  // 預設30fps
            if (frameRate < stFrameRate.fMin) frameRate = stFrameRate.fMin;
            if (frameRate > stFrameRate.fMax) frameRate = stFrameRate.fMax;
            
            std::cout << "幀率範圍: [" << stFrameRate.fMin << ", " << stFrameRate.fMax 
                      << "], 當前設定: " << frameRate << std::endl;
            
            nRet = MV_CC_SetFloatValue(handle, "AcquisitionFrameRate", frameRate);
            if (MV_OK != nRet) {
                std::cout << "設定幀率失敗! nRet [" << nRet << "]" << std::endl;
                // 這個錯誤不影響主要功能,可以繼續
            }
        }
        return true;
    }
    // 開始取圖
    bool StartGrabbing() {
        // 開始取流
        int nRet = MV_CC_StartGrabbing(handle);
        if (MV_OK != nRet) {
            std::cout << "開始取流失敗! nRet [" << nRet << "]" << std::endl;
            return false;
        }
        // 獲取資料包大小
        MVCC_INTVALUE stParam;
        nRet = MV_CC_GetIntValue(handle, "PayloadSize", &stParam);
        if (MV_OK != nRet) {
            std::cout << "獲取資料包大小失敗! nRet [" << nRet << "]" << std::endl;
            return false;
        }
        // 分配資源
        nDataSize = stParam.nCurValue;
        pData = (unsigned char*)malloc(nDataSize);
        if (pData == nullptr) {
            std::cout << "記憶體分配失敗!" << std::endl;
            return false;
        }
        return true;
    }
    // 獲取一幀影像
    bool GetOneFrame() {
        if (handle == nullptr || pData == nullptr) {
            return false;
        }
        int nRet = MV_CC_GetOneFrameTimeout(handle, pData, nDataSize, &stImageInfo, 1000);
        if (MV_OK != nRet) {
            std::cout << "獲取一幀影像失敗! nRet [" << nRet << "]" << std::endl;
            return false;
        }
        std::cout << "獲取一幀影像成功: Width[" << stImageInfo.nWidth 
                  << "] Height[" << stImageInfo.nHeight 
                  << "] FrameNum[" << stImageInfo.nFrameNum << "]" << std::endl;
        return true;
    }
    // 停止採集
    void StopGrabbing() {
        if (handle != nullptr) {
            MV_CC_StopGrabbing(handle);
            MV_CC_CloseDevice(handle);
            MV_CC_DestroyHandle(handle);
            handle = nullptr;
        }
        if (pData != nullptr) {
            free(pData);
            pData = nullptr;
        }
    }
    ~HKCamera() {
        StopGrabbing();
    }
};
int main() {
    HKCamera camera;
    // 初始化相機
    if (!camera.InitCamera()) {
        std::cout << "相機初始化失敗!" << std::endl;
        return -1;
    }
    std::cout << "相機初始化成功!" << std::endl;
    // 設定引數
    if (!camera.SetParameters()) {
        std::cout << "設定相機引數失敗!" << std::endl;
        return -1;
    }
    std::cout << "設定相機引數成功!" << std::endl;
    // 開始取圖
    if (!camera.StartGrabbing()) {
        std::cout << "開始取圖失敗!" << std::endl;
        return -1;
    }
    std::cout << "開始取圖成功!" << std::endl;
    // 獲取10幀影像
    for (int i = 0; i < 10; i++) {
        if (!camera.GetOneFrame()) {
            break;
        }
    }
    // 停止採集
    camera.StopGrabbing();
    std::cout << "停止採集完成!" << std::endl;
    return 0;
}

(2)海康相機+OpenCV實時取流部署教程

如果我們要顯示採集的影像的話我們有兩個方法,首先便是我們的OpenCV,再然後便是海康提供給我們的影像顯示API,我們首先介紹大家都會的OpenCV的方案,我們先新引入OpenCV的標頭檔案:

#include <opencv2/opencv.hpp>

接著我們在建構函式部分新增CV影像顯示的Frame以及我們在建構函式中初始化我們的輸出幀和資料包大小結構體:

class HKCamera {
    private:
    	cv::Mat frame;  // OpenCV影像
    public:
        HKCamera() {
            // 在建構函式中初始化結構體
            memset(&stParam, 0, sizeof(MVCC_INTVALUE));
            memset(&stImageInfo, 0, sizeof(MV_FRAME_OUT_INFO_EX));
        }

然後我們把GetOneFrame()函式變為GetOneFrameAndShow()然後在函式里面新增OpenCV獲取幀的程式碼,將海康與OpenCV進行橋接,由於我們前面設定的採集格式為BGR因此我們可以直接使用BGR資料建立Mat:

bool GetOneFrameAndShow() {
    if (handle == nullptr || pData == nullptr) {
        return false;
    }

    int nRet = MV_CC_GetOneFrameTimeout(handle, pData, nDataSize, &stImageInfo, 1000);
    if (MV_OK != nRet) {
        std::cout << "獲取一幀影像失敗! nRet [" << nRet << "]" << std::endl;
        return false;
    }

    std::cout << "獲取一幀影像成功: Width[" << stImageInfo.nWidth 
              << "] Height[" << stImageInfo.nHeight 
              << "] FrameNum[" << stImageInfo.nFrameNum << "]" << std::endl;
/*---------------------新增以下影像轉換及顯示程式碼-----------------------*/
    // 轉換為OpenCV格式並顯示
    if (stImageInfo.enPixelType == PixelType_Gvsp_BGR8_Packed) {
        // 直接使用BGR資料建立Mat
        frame = cv::Mat(stImageInfo.nHeight, stImageInfo.nWidth, CV_8UC3, pData);
        // 簡單的影像增強
        cv::Mat temp;
        // 輕微提升對比度
        frame.convertTo(temp, -1, 1.1, 0);
        frame = temp;
    } else {
        std::cout << "不支援的畫素格式: 0x" << std::hex << stImageInfo.enPixelType << std::dec << std::endl;
        return false;
    }
    if (!frame.empty()) {
        cv::imshow("Camera", frame);
        cv::waitKey(1);
    }
/*---------------------新增以上影像轉換及顯示程式碼-----------------------*/
    return true;
}

然後我們在主函式里把原先的獲取10幀影像修改為while迴圈持續獲得影像,這樣便可以實時顯示我們攝像頭獲取的影像:

// 持續獲取並顯示影像,直到按下ESC鍵
while (true) {
    if (!camera.GetOneFrameAndShow()) {
        break;
    }
    // 檢查ESC鍵是否按下
    char key = cv::waitKey(1);
    if (key == 27) {  // ESC鍵的ASCII碼
        break;
    }

最後我們在CmakeLists.txt中新增OpenCV的依賴即可:

# 查詢OpenCV包
find_package(OpenCV REQUIRED)
include_directories(
    ${PROJECT_SOURCE_DIR}/include
    ${MVCAM_INCLUDE_PATH}
    ${OpenCV_INCLUDE_DIRS}
)
# 連結海康相機庫和OpenCV庫
target_link_libraries(${PROJECT_NAME}
    MvCameraControl  # 海康相機主庫
    pthread         # 執行緒庫
    ${OpenCV_LIBS}  # OpenCV庫
)

最後我們OpenCV顯示影像的完整的程式碼及顯示效果如下:

image-20250118034644685

image-20250118034858385

#include <iostream>
#include <string>
#include <cstring>  // for memset
#include <opencv2/opencv.hpp>
#include "MvCameraControl.h"
#include "CameraParams.h"  // for MVCC_INTVALUE
#include "PixelType.h"     // for PixelType_Gvsp_BGR8_Packed

class HKCamera {
private:
    void* handle = nullptr;
    MVCC_INTVALUE stParam;
    MV_FRAME_OUT_INFO_EX stImageInfo = {0};
    unsigned char* pData = nullptr;
    unsigned int nDataSize = 0;
    cv::Mat frame;  // OpenCV影像
public:
    HKCamera() {
        // 在建構函式中初始化結構體
        memset(&stParam, 0, sizeof(MVCC_INTVALUE));
        memset(&stImageInfo, 0, sizeof(MV_FRAME_OUT_INFO_EX));
    }
    bool InitCamera() {
        MV_CC_DEVICE_INFO_LIST stDeviceList;
        memset(&stDeviceList, 0, sizeof(MV_CC_DEVICE_INFO_LIST));
        // 列舉裝置
        int nRet = MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE, &stDeviceList);
        if (MV_OK != nRet) {
            std::cout << "列舉裝置失敗! nRet [" << nRet << "]" << std::endl;
            return false;
        }
        if (stDeviceList.nDeviceNum > 0) {
            // 建立控制代碼
            nRet = MV_CC_CreateHandle(&handle, stDeviceList.pDeviceInfo[0]);
            if (MV_OK != nRet) {
                std::cout << "建立控制代碼失敗! nRet [" << nRet << "]" << std::endl;
                return false;
            }
            // 開啟裝置
            nRet = MV_CC_OpenDevice(handle);
            if (MV_OK != nRet) {
                std::cout << "開啟裝置失敗! nRet [" << nRet << "]" << std::endl;
                return false;
            }
        } else {
            std::cout << "未找到裝置!" << std::endl;
            return false;
        }
        return true;
    }
    // 設定相機引數
    bool SetParameters() {
        int nRet;
        // 設定觸發模式為off
        nRet = MV_CC_SetEnumValue(handle, "TriggerMode", 0);
        if (MV_OK != nRet) {
            std::cout << "設定觸發模式失敗! nRet [" << nRet << "]" << std::endl;
            return false;
        }
        // 設定畫素格式為BGR8
        nRet = MV_CC_SetEnumValue(handle, "PixelFormat", PixelType_Gvsp_BGR8_Packed);
        if (MV_OK != nRet) {
            std::cout << "設定畫素格式失敗! nRet [" << nRet << "]" << std::endl;
            return false;
        }
        std::cout << "設定畫素格式為BGR8_Packed" << std::endl;
        // 獲取和設定曝光時間
        MVCC_FLOATVALUE stExposureTime = {0};
        nRet = MV_CC_GetFloatValue(handle, "ExposureTime", &stExposureTime);
        if (MV_OK == nRet) {
            float exposureTime = 10000.0f;  // 預設10ms
            if (exposureTime < stExposureTime.fMin) exposureTime = stExposureTime.fMin;
            if (exposureTime > stExposureTime.fMax) exposureTime = stExposureTime.fMax;
            
            std::cout << "曝光時間範圍: [" << stExposureTime.fMin << ", " << stExposureTime.fMax 
                      << "], 當前設定: " << exposureTime << std::endl;
            
            nRet = MV_CC_SetFloatValue(handle, "ExposureTime", exposureTime);
            if (MV_OK != nRet) {
                std::cout << "設定曝光時間失敗! nRet [" << nRet << "]" << std::endl;
                return false;
            }
        }
        // 獲取和設定增益
        MVCC_FLOATVALUE stGain = {0};
        nRet = MV_CC_GetFloatValue(handle, "Gain", &stGain);
        if (MV_OK == nRet) {
            std::cout << "增益範圍: [" << stGain.fMin << ", " << stGain.fMax 
                      << "], 當前值: " << stGain.fCurValue << std::endl;
            if (stGain.fCurValue < stGain.fMin || stGain.fCurValue > stGain.fMax) {
                float gain = stGain.fMin;  // 使用最小值
                nRet = MV_CC_SetFloatValue(handle, "Gain", gain);
                if (MV_OK != nRet) {
                    std::cout << "設定增益失敗! nRet [" << nRet << "]" << std::endl;
                    return false;
                }
            }
        }
        // 獲取和設定幀率
        MVCC_FLOATVALUE stFrameRate = {0};
        nRet = MV_CC_GetFloatValue(handle, "AcquisitionFrameRate", &stFrameRate);
        if (MV_OK == nRet) {
            float frameRate = 30.0f;  // 預設30fps
            if (frameRate < stFrameRate.fMin) frameRate = stFrameRate.fMin;
            if (frameRate > stFrameRate.fMax) frameRate = stFrameRate.fMax;
            
            std::cout << "幀率範圍: [" << stFrameRate.fMin << ", " << stFrameRate.fMax 
                      << "], 當前設定: " << frameRate << std::endl;
            nRet = MV_CC_SetFloatValue(handle, "AcquisitionFrameRate", frameRate);
            if (MV_OK != nRet) {
                std::cout << "設定幀率失敗! nRet [" << nRet << "]" << std::endl;
                // 這個錯誤不影響主要功能,可以繼續
            }
        }
        return true;
    }
    // 開始取圖
    bool StartGrabbing() {
        // 開始取流
        int nRet = MV_CC_StartGrabbing(handle);
        if (MV_OK != nRet) {
            std::cout << "開始取流失敗! nRet [" << nRet << "]" << std::endl;
            return false;
        }
        // 獲取資料包大小
        MVCC_INTVALUE stParam;
        nRet = MV_CC_GetIntValue(handle, "PayloadSize", &stParam);
        if (MV_OK != nRet) {
            std::cout << "獲取資料包大小失敗! nRet [" << nRet << "]" << std::endl;
            return false;
        }
        // 分配資源
        nDataSize = stParam.nCurValue;
        pData = (unsigned char*)malloc(nDataSize);
        if (pData == nullptr) {
            std::cout << "記憶體分配失敗!" << std::endl;
            return false;
        }
        return true;
    }
    // 獲取一幀影像並顯示
    bool GetOneFrameAndShow() {
        if (handle == nullptr || pData == nullptr) {
            return false;
        }
        int nRet = MV_CC_GetOneFrameTimeout(handle, pData, nDataSize, &stImageInfo, 1000);
        if (MV_OK != nRet) {
            std::cout << "獲取一幀影像失敗! nRet [" << nRet << "]" << std::endl;
            return false;
        }
        std::cout << "獲取一幀影像成功: Width[" << stImageInfo.nWidth 
                  << "] Height[" << stImageInfo.nHeight 
                  << "] PixelType[0x" << std::hex << stImageInfo.enPixelType << std::dec
                  << "] FrameNum[" << stImageInfo.nFrameNum << "]" << std::endl;
        // 轉換為OpenCV格式並顯示
        if (stImageInfo.enPixelType == PixelType_Gvsp_BGR8_Packed) {
            // 直接使用BGR資料建立Mat
            frame = cv::Mat(stImageInfo.nHeight, stImageInfo.nWidth, CV_8UC3, pData);
            // 簡單的影像增強
            cv::Mat temp;
            // 輕微提升對比度
            frame.convertTo(temp, -1, 1.1, 0);
            frame = temp;
        } else {
            std::cout << "不支援的畫素格式: 0x" << std::hex << stImageInfo.enPixelType << std::dec << std::endl;
            return false;
        }
        if (!frame.empty()) {
            cv::imshow("Camera", frame);
            cv::waitKey(1);
        }
        return true;
    }
    // 停止採集
    void StopGrabbing() {
        if (handle != nullptr) {
            MV_CC_StopGrabbing(handle);
            MV_CC_CloseDevice(handle);
            MV_CC_DestroyHandle(handle);
            handle = nullptr;
        }
        if (pData != nullptr) {
            free(pData);
            pData = nullptr;
        }
    }
    ~HKCamera() {
        StopGrabbing();
    }
};
int main() {
    HKCamera camera;
    // 初始化相機
    if (!camera.InitCamera()) {
        std::cout << "相機初始化失敗!" << std::endl;
        return -1;
    }
    std::cout << "相機初始化成功!" << std::endl;
    // 設定引數
    if (!camera.SetParameters()) {
        std::cout << "設定相機引數失敗!" << std::endl;
        return -1;
    }
    std::cout << "設定相機引數成功!" << std::endl;
    // 開始取圖
    if (!camera.StartGrabbing()) {
        std::cout << "開始取圖失敗!" << std::endl;
        return -1;
    }
    std::cout << "開始取圖成功!" << std::endl;
    // 持續獲取並顯示影像,直到按下ESC鍵
    while (true) {
        if (!camera.GetOneFrameAndShow()) {
            break;
        }
        // 檢查ESC鍵是否按下
        char key = cv::waitKey(1);
        if (key == 27) {  // ESC鍵的ASCII碼
            break;
        }
    }
    // 停止採集
    camera.StopGrabbing();
    std::cout << "停止採集完成!" << std::endl;
    return 0;
}

(3)海康相機官方簡易API+X11視窗實時取流部署教程

但是我們會發現,如果我們攝像頭採集的是BGR格式那麼用OpenCV會比較方便,但是如果我們採集的不是BGR格式呢?那用OpenCV來顯示影像便還需要進行影像的轉換,非常的複雜!!!但是如果我們用海康官方的API再加Linux的OpenGL來顯示影像那麼我們便無需關心影像格式的轉換問題啦!接下來我們便開始用海康官方顯示API+Linux-OpenGL-X11來實時顯示我們的影像,海康API中有關顯示的函式有三個(其中一個即將被廢除),具體介紹如下:

/********************************************************************//**
 *  @~chinese
 *  @brief  顯示一幀影像
 *  @param  handle                      [IN]            裝置控制代碼
 *  @param  pstDisplayInfo              [IN]            影像資訊
 *  @return 成功,返回MV_OK;錯誤,返回錯誤碼 
 *  @remarks 與裝置型別無關,渲染模式為D3D時,支援的最大解析度為16384 * 163840
 ***********************************************************************/
MV_CAMCTRL_API int __stdcall MV_CC_DisplayOneFrame(IN void* handle, IN MV_DISPLAY_FRAME_INFO* pstDisplayInfo);
/********************************************************************//**
 *  @~chinese
 *  @brief  顯示一幀影像
 *  @param  handle                      [IN]            裝置控制代碼
 *  @param  hWnd                        [IN]            視窗控制代碼
 *  @param  pstDisplayInfo              [IN]            影像資訊
 *  @return 成功,返回MV_OK;錯誤,返回錯誤碼
 *  @remarks 該介面支援渲染寬高大小至int型別
 *           渲染模式為D3D時,支援的最大解析度為16384 * 163840
 ***********************************************************************/
MV_CAMCTRL_API int __stdcall MV_CC_DisplayOneFrameEx(IN void* handle, IN void* hWnd, IN MV_DISPLAY_FRAME_INFO_EX* pstDisplayInfo);

/********************************************************************//**
 *  @~chinese
 *  @brief  顯示一幀影像
 *  @param  handle                      [IN]            裝置控制代碼
 *  @param  hWnd                        [IN]            視窗控制代碼
 *  @param  pstImage                    [IN]            影像資訊
 *  @param  enRenderMode                [IN]            渲染方式,Windows:0-GDI 1-D3D 2-OpenGL Linux:0-OpenGL 
 *  @return 成功,返回MV_OK;錯誤,返回錯誤碼
 *  @remarks 可選擇OpenGL渲染模式,支援PixelType_Gvsp_RGB8_Packed,PixelType_Gvsp_BGR8_Packed,PixelType_Gvsp_Mono8三種畫素格式影像大小超過4GB的渲染,其他渲染模式不支援。
             若影像大小未超過4GB,支援寬高大小至int型別
             呼叫時需要輸入MV_CC_IMAGE結構體中nImageLen的值
			 渲染模式為D3D時,支援的最大解析度為16384 * 163840
 ***********************************************************************/
MV_CAMCTRL_API int __stdcall MV_CC_DisplayOneFrameEx2(IN void* handle, IN void* hWnd, IN MV_CC_IMAGE* pstImage, unsigned int enRenderMode);

MV_CC_DisplayOneFrame()函式為基礎函式他的功能最簡單但解析度限制較大並且即將被廢除、MV_CC_DisplayOneFrameEx()函式為擴充套件函式,他支援更大解析度也支援Windows上的D3D渲染但是不支援超大影像而MV_CC_DisplayOneFrameEx2()函式支援選擇渲染模式支援超大影像也支援更多畫素格式且效能最好

接下來我們將以最簡單的MV_CC_DisplayOneFrame()函式來完成示例,我們使用X11視窗來顯示影像,首先我們匯入X11包,接著我們在類中新增建立視窗控制代碼函式和顯示函式並且修改我們的取幀函式:

#include <X11/Xlib.h>
class HKCamera {
private:
    void* displayHandle = nullptr;  // 顯示視窗控制代碼
public:
    HKCamera() {
        // 在建構函式中正確初始化結構體
        memset(&stParam, 0, sizeof(MVCC_INTVALUE));
        memset(&stImageInfo, 0, sizeof(MV_FRAME_OUT_INFO_EX));
    }
    bool SetDisplayWindow(void* windowHandle) //建立顯示視窗控制代碼
    bool DisplayOneFrame() //新建顯示
    bool GetOneFrameAndShow()//修改GetOneFrame()函式

接著我們需要利用我們的SetDisplayWindow(void* windowHandle)函式來建立一個X11視窗的控制代碼:

// 設定顯示視窗
bool SetDisplayWindow(void* windowHandle) {
    displayHandle = windowHandle;
    return true;
}

然後我們來建立我們的DisplayOneFrame()函式,根據我們上面的函式介紹,我們需要在使用MV_CC_DisplayOneFrame()函式前先定義一個MV_DISPLAY_FRAME_INFO型別的結構體

typedef struct _MV_DISPLAY_FRAME_INFO_
{
	void*                   hWnd;         ///< [IN] \~chinese 視窗控制代碼                \~english HWND
	unsigned char*          pData;        ///< [IN] \~chinese 顯示的資料              \~english Data Buffer
	unsigned int            nDataLen;     ///< [IN] \~chinese 資料長度                \~english Data Size
	unsigned short          nWidth;       ///< [IN] \~chinese 影像寬                  \~english Width
	unsigned short          nHeight;      ///< [IN] \~chinese 影像高                  \~english Height
	enum MvGvspPixelType    enPixelType;  ///< [IN] \~chinese 畫素格式                \~english Pixel format
	unsigned int            enRenderMode; ///  [IN] \~chinese 影像渲染方式Windows:0-GDI(預設), 1-D3D, 2-OPENGL Linux: 0-OPENGL(預設) 
	unsigned int            nRes[3];      ///<      \~chinese 保留                    \~english Reserved
}MV_DISPLAY_FRAME_INFO;

根據上述結構體的定義,我們先行配置我們的stDisplayInfo引數

MV_DISPLAY_FRAME_INFO stDisplayInfo = {0};
stDisplayInfo.hWnd = displayHandle;
stDisplayInfo.pData = pData;
stDisplayInfo.nDataLen = stImageInfo.nFrameLen;
stDisplayInfo.nWidth = stImageInfo.nWidth;
stDisplayInfo.nHeight = stImageInfo.nHeight;
stDisplayInfo.enPixelType = stImageInfo.enPixelType;

在定義完這個結構體之後我們便可以使用顯示函式API啦,具體的使用程式碼如下:

// 顯示一幀影像
bool DisplayOneFrame() {
    if (handle == nullptr || pData == nullptr || displayHandle == nullptr) {
        return false;
    }
    // 準備顯示資訊
    MV_DISPLAY_FRAME_INFO stDisplayInfo = {0};
    stDisplayInfo.hWnd = displayHandle;
    stDisplayInfo.pData = pData;
    stDisplayInfo.nDataLen = stImageInfo.nFrameLen;
    stDisplayInfo.nWidth = stImageInfo.nWidth;
    stDisplayInfo.nHeight = stImageInfo.nHeight;
    stDisplayInfo.enPixelType = stImageInfo.enPixelType;
    // 顯示影像
    int nRet = MV_CC_DisplayOneFrame(handle, &stDisplayInfo);
    if (MV_OK != nRet) {
        std::cout << "顯示影像失敗! nRet [" << nRet << "]" << std::endl;
        return false;
    }
    return true;
}

接著我們將原來的GetOneFrame()函式中間新增影像顯示函式DisplayOneFrame()即可

// 獲取一幀影像並顯示
bool GetOneFrameAndShow() {
    if (handle == nullptr || pData == nullptr) {
        return false;
    }
    int nRet = MV_CC_GetOneFrameTimeout(handle, pData, nDataSize, &stImageInfo, 1000);
    if (MV_OK != nRet) {
        std::cout << "獲取一幀影像失敗! nRet [" << nRet << "]" << std::endl;
        return false;
    }
    std::cout << "獲取一幀影像成功: Width[" << stImageInfo.nWidth 
              << "] Height[" << stImageInfo.nHeight 
              << "] FrameNum[" << stImageInfo.nFrameNum << "]" << std::endl;

    /* --------------顯示影像 -----------------------*/
    if (displayHandle != nullptr) {
        return DisplayOneFrame();
    }
    /* --------------顯示影像 -----------------------*/
    return true;
}

之後便開始修改我們的主函式啦首先在主函式中建立X11視窗並且捕獲螢幕資訊:

// 建立X11視窗
Display* display = XOpenDisplay(NULL);
if (!display) {
    std::cout << "無法連線到X伺服器!" << std::endl;
    return -1;
}
// 獲取螢幕資訊
int screen = DefaultScreen(display);
Window root = DefaultRootWindow(display);

接著我們設定視窗的基本引數,並設定視窗標題和視窗關閉事件

// 建立視窗
Window window = XCreateSimpleWindow(
    display,        // Display
    root,          // 父視窗
    0, 0,          // 位置
    1440, 1080,    // 大小(使用相機解析度)
    1,             // 邊框寬度
    BlackPixel(display, screen),  // 邊框顏色
    WhitePixel(display, screen)   // 背景顏色
);
// 設定視窗標題
XStoreName(display, window, "Camera Display");
// 設定視窗接收關閉事件
Atom wmDeleteMessage = XInternAtom(display, "WM_DELETE_WINDOW", False);
XSetWMProtocols(display, window, &wmDeleteMessage, 1);

最後我們顯示視窗並持續捕獲影像並使用X11事件顯示即可

// 顯示視窗
XMapWindow(display, window);
XFlush(display);
// 設定顯示視窗
camera.SetDisplayWindow((void*)window);
// 開始取圖
if (!camera.StartGrabbing()) {
    std::cout << "開始取圖失敗!" << std::endl;
    XCloseDisplay(display);
    return -1;
}
std::cout << "開始取圖成功!" << std::endl;
// 持續獲取並顯示影像
bool running = true;
while (running) {
    if (!camera.GetOneFrameAndShow()) {
        break;
    }
    // 處理X11事件
    while (XPending(display)) {
        XEvent event;
        XNextEvent(display, &event);
        // 處理視窗關閉事件
        if (event.type == ClientMessage) {
            if (event.xclient.data.l[0] == wmDeleteMessage) {
                running = false;
            }
        }
    }
}

接著我們修改一下我們的Cmake即可:

# 查詢X11包
find_package(X11 REQUIRED)
include_directories(
    ${PROJECT_SOURCE_DIR}/include
    ${MVCAM_INCLUDE_PATH}
    ${OpenCV_INCLUDE_DIRS}
)
# 連結海康相機庫和X11庫
target_link_libraries(${PROJECT_NAME}
    MvCameraControl  # 海康相機主庫
    pthread         # 執行緒庫
    ${X11_LIBRARIES}  # X11庫
)

最後我們X11串列埠+海康的顯示API的完整的程式碼及效果如下:

image-20250118034359365

image-20250118034341634

#include <iostream>
#include <string>
#include <cstring>  // for memset
#include <X11/Xlib.h>
#include "MvCameraControl.h"
#include "CameraParams.h"  // for MVCC_INTVALUE
#include "PixelType.h"     // for PixelType_Gvsp_BGR8_Packed
class HKCamera {
private:
    void* handle = nullptr;
    MVCC_INTVALUE stParam;
    MV_FRAME_OUT_INFO_EX stImageInfo = {0};
    unsigned char* pData = nullptr;
    unsigned int nDataSize = 0;
    void* displayHandle = nullptr;  // 顯示視窗控制代碼
public:
    HKCamera() {
        // 在建構函式中正確初始化結構體
        memset(&stParam, 0, sizeof(MVCC_INTVALUE));
        memset(&stImageInfo, 0, sizeof(MV_FRAME_OUT_INFO_EX));
    }
    bool InitCamera() {
        MV_CC_DEVICE_INFO_LIST stDeviceList;
        memset(&stDeviceList, 0, sizeof(MV_CC_DEVICE_INFO_LIST));
        // 列舉裝置
        int nRet = MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE, &stDeviceList);
        if (MV_OK != nRet) {
            std::cout << "列舉裝置失敗! nRet [" << nRet << "]" << std::endl;
            return false;
        }
        if (stDeviceList.nDeviceNum > 0) {
            // 建立控制代碼
            nRet = MV_CC_CreateHandle(&handle, stDeviceList.pDeviceInfo[0]);
            if (MV_OK != nRet) {
                std::cout << "建立控制代碼失敗! nRet [" << nRet << "]" << std::endl;
                return false;
            }
            // 開啟裝置
            nRet = MV_CC_OpenDevice(handle);
            if (MV_OK != nRet) {
                std::cout << "開啟裝置失敗! nRet [" << nRet << "]" << std::endl;
                return false;
            }
        } else {
            std::cout << "未找到裝置!" << std::endl;
            return false;
        }
        return true;
    }
    // 設定相機引數
    bool SetParameters() {
        int nRet;
        // 設定觸發模式為off
        nRet = MV_CC_SetEnumValue(handle, "TriggerMode", 0);
        if (MV_OK != nRet) {
            std::cout << "設定觸發模式失敗! nRet [" << nRet << "]" << std::endl;
            return false;
        }
        // 設定畫素格式為BGR8
        nRet = MV_CC_SetEnumValue(handle, "PixelFormat", PixelType_Gvsp_BGR8_Packed);
        if (MV_OK != nRet) {
            std::cout << "設定畫素格式失敗! nRet [" << nRet << "]" << std::endl;
            return false;
        }
        std::cout << "設定畫素格式為BGR8_Packed" << std::endl;
        // 獲取和設定曝光時間
        MVCC_FLOATVALUE stExposureTime = {0};
        nRet = MV_CC_GetFloatValue(handle, "ExposureTime", &stExposureTime);
        if (MV_OK == nRet) {
            float exposureTime = 10000.0f;  // 預設10ms
            if (exposureTime < stExposureTime.fMin) exposureTime = stExposureTime.fMin;
            if (exposureTime > stExposureTime.fMax) exposureTime = stExposureTime.fMax;
            
            std::cout << "曝光時間範圍: [" << stExposureTime.fMin << ", " << stExposureTime.fMax 
                      << "], 當前設定: " << exposureTime << std::endl;
            
            nRet = MV_CC_SetFloatValue(handle, "ExposureTime", exposureTime);
            if (MV_OK != nRet) {
                std::cout << "設定曝光時間失敗! nRet [" << nRet << "]" << std::endl;
                return false;
            }
        }
        // 獲取和設定增益
        MVCC_FLOATVALUE stGain = {0};
        nRet = MV_CC_GetFloatValue(handle, "Gain", &stGain);
        if (MV_OK == nRet) {
            std::cout << "增益範圍: [" << stGain.fMin << ", " << stGain.fMax 
                      << "], 當前值: " << stGain.fCurValue << std::endl;
            
            if (stGain.fCurValue < stGain.fMin || stGain.fCurValue > stGain.fMax) {
                float gain = stGain.fMin;  // 使用最小值
                nRet = MV_CC_SetFloatValue(handle, "Gain", gain);
                if (MV_OK != nRet) {
                    std::cout << "設定增益失敗! nRet [" << nRet << "]" << std::endl;
                    return false;
                }
            }
        }
        // 獲取和設定幀率
        MVCC_FLOATVALUE stFrameRate = {0};
        nRet = MV_CC_GetFloatValue(handle, "AcquisitionFrameRate", &stFrameRate);
        if (MV_OK == nRet) {
            float frameRate = 30.0f;  // 預設30fps
            if (frameRate < stFrameRate.fMin) frameRate = stFrameRate.fMin;
            if (frameRate > stFrameRate.fMax) frameRate = stFrameRate.fMax;
            
            std::cout << "幀率範圍: [" << stFrameRate.fMin << ", " << stFrameRate.fMax 
                      << "], 當前設定: " << frameRate << std::endl;
            
            nRet = MV_CC_SetFloatValue(handle, "AcquisitionFrameRate", frameRate);
            if (MV_OK != nRet) {
                std::cout << "設定幀率失敗! nRet [" << nRet << "]" << std::endl;
                // 這個錯誤不影響主要功能,可以繼續
            }
        }
        return true;
    }
    // 開始取圖
    bool StartGrabbing() {
        // 開始取流
        int nRet = MV_CC_StartGrabbing(handle);
        if (MV_OK != nRet) {
            std::cout << "開始取流失敗! nRet [" << nRet << "]" << std::endl;
            return false;
        }
        // 獲取資料包大小
        MVCC_INTVALUE stParam;
        nRet = MV_CC_GetIntValue(handle, "PayloadSize", &stParam);
        if (MV_OK != nRet) {
            std::cout << "獲取資料包大小失敗! nRet [" << nRet << "]" << std::endl;
            return false;
        }
        // 分配資源
        nDataSize = stParam.nCurValue;
        pData = (unsigned char*)malloc(nDataSize);
        if (pData == nullptr) {
            std::cout << "記憶體分配失敗!" << std::endl;
            return false;
        }
        return true;
    }
    // 設定顯示視窗
    bool SetDisplayWindow(void* windowHandle) {
        displayHandle = windowHandle;
        return true;
    }
    // 顯示一幀影像
    bool DisplayOneFrame() {
        if (handle == nullptr || pData == nullptr || displayHandle == nullptr) {
            return false;
        }
        // 準備顯示資訊
        MV_DISPLAY_FRAME_INFO stDisplayInfo = {0};
        stDisplayInfo.hWnd = displayHandle;
        stDisplayInfo.pData = pData;
        stDisplayInfo.nDataLen = stImageInfo.nFrameLen;
        stDisplayInfo.nWidth = stImageInfo.nWidth;
        stDisplayInfo.nHeight = stImageInfo.nHeight;
        stDisplayInfo.enPixelType = stImageInfo.enPixelType;
        // 顯示影像
        int nRet = MV_CC_DisplayOneFrame(handle, &stDisplayInfo);
        if (MV_OK != nRet) {
            std::cout << "顯示影像失敗! nRet [" << nRet << "]" << std::endl;
            return false;
        }
        return true;
    }
    // 獲取一幀影像並顯示
    bool GetOneFrameAndShow() {
        if (handle == nullptr || pData == nullptr) {
            return false;
        }
        int nRet = MV_CC_GetOneFrameTimeout(handle, pData, nDataSize, &stImageInfo, 1000);
        if (MV_OK != nRet) {
            std::cout << "獲取一幀影像失敗! nRet [" << nRet << "]" << std::endl;
            return false;
        }
        std::cout << "獲取一幀影像成功: Width[" << stImageInfo.nWidth 
                  << "] Height[" << stImageInfo.nHeight 
                  << "] FrameNum[" << stImageInfo.nFrameNum << "]" << std::endl;
        // 顯示影像
        if (displayHandle != nullptr) {
            return DisplayOneFrame();
        }

        return true;
    }
    // 停止採集
    void StopGrabbing() {
        if (handle != nullptr) {
            MV_CC_StopGrabbing(handle);
            MV_CC_CloseDevice(handle);
            MV_CC_DestroyHandle(handle);
            handle = nullptr;
        }
        if (pData != nullptr) {
            free(pData);
            pData = nullptr;
        }
    }
    ~HKCamera() {
        StopGrabbing();
    }
};
int main() {
    HKCamera camera;
    // 初始化相機
    if (!camera.InitCamera()) {
        std::cout << "相機初始化失敗!" << std::endl;
        return -1;
    }
    std::cout << "相機初始化成功!" << std::endl;
    // 設定引數
    if (!camera.SetParameters()) {
        std::cout << "設定相機引數失敗!" << std::endl;
        return -1;
    }
    std::cout << "設定相機引數成功!" << std::endl;
    // 建立X11視窗
    Display* display = XOpenDisplay(NULL);
    if (!display) {
        std::cout << "無法連線到X伺服器!" << std::endl;
        return -1;
    }
    // 獲取螢幕資訊
    int screen = DefaultScreen(display);
    Window root = DefaultRootWindow(display);
    // 建立視窗
    Window window = XCreateSimpleWindow(
        display,        // Display
        root,          // 父視窗
        0, 0,          // 位置
        1440, 1080,    // 大小(使用相機解析度)
        1,             // 邊框寬度
        BlackPixel(display, screen),  // 邊框顏色
        WhitePixel(display, screen)   // 背景顏色
    );
    // 設定視窗標題
    XStoreName(display, window, "Camera Display");
    // 設定視窗接收關閉事件
    Atom wmDeleteMessage = XInternAtom(display, "WM_DELETE_WINDOW", False);
    XSetWMProtocols(display, window, &wmDeleteMessage, 1);
    // 顯示視窗
    XMapWindow(display, window);
    XFlush(display);
    // 設定顯示視窗
    camera.SetDisplayWindow((void*)window);
    // 開始取圖
    if (!camera.StartGrabbing()) {
        std::cout << "開始取圖失敗!" << std::endl;
        XCloseDisplay(display);
        return -1;
    }
    std::cout << "開始取圖成功!" << std::endl;
    // 持續獲取並顯示影像
    bool running = true;
    while (running) {
        if (!camera.GetOneFrameAndShow()) {
            break;
        }
        // 處理X11事件
        while (XPending(display)) {
            XEvent event;
            XNextEvent(display, &event);
            
            // 處理視窗關閉事件
            if (event.type == ClientMessage) {
                if (event.xclient.data.l[0] == wmDeleteMessage) {
                    running = false;
                }
            }
        }
    }
    // 停止採集
    camera.StopGrabbing();
    std::cout << "停止採集完成!" << std::endl;
    // 關閉X11連線
    XCloseDisplay(display);
    return 0;
}

相關文章