海康工业相机 SDK 二次开发:封装与 YOLO 实时推理实战
在工业视觉领域,海康威视(Hikvision)的工业相机因其高稳定性、高帧率和丰富的接口支持,被广泛应用于自动化检测、智能安防、机器人引导等场景。这种方式极大提升了部署灵活性——调试时可用 IP 快速连接,量产时则用唯一 SN 防止设备混淆。工业相机常输出 Bayer 格式(如 RG/BG/GR/GB)或 Mono 数据。工业现场网络不稳定是常态。为此,我们注册了异常回调。我们注册了海康的图像回调
1. 引言:为什么需要封装海康 SDK?
在工业视觉领域,海康威视(Hikvision)的工业相机因其高稳定性、高帧率和丰富的接口支持,被广泛应用于自动化检测、智能安防、机器人引导等场景。然而,官方提供的 MVSDK 虽然功能强大,却存在几个明显痛点:
- 接口以 C 风格为主,需手动管理设备句柄、内存和状态;
- 错误码繁多,异常处理复杂;
- 像素格式多样(Bayer、Mono、RGB),转换逻辑分散;
- 缺乏现代 C++ 的 RAII 和回调抽象,代码冗长且易出错。
为提升开发效率与系统健壮性,我决定对海康 SDK 进行二次封装,目标是:一行代码连接相机,一个回调获取图像,无缝对接 OpenCV 与深度学习模型(如 YOLO)。
2. 封装设计思路
2.1 核心目标
本次封装围绕以下原则展开:
- 简洁性:用户无需了解底层 SDK 细节,只需提供 IP 或序列号即可启动采集。
- 健壮性:支持断线自动重连,避免因网络波动导致程序崩溃。
- 通用性:自动识别相机输出的像素格式(如 BayerRG8、Mono10),并统一转换为
cv::Mat。 - 高性能:预分配图像缓冲区,避免频繁内存申请;支持 8/10/12-bit 数据高效转 8-bit。
- 可扩展性:预留参数控制接口(曝光、增益、帧率等),便于后续集成触发模式或同步采集。
2.2 类结构概览
封装的核心是一个 HikCamera 类,其关键接口如下:
#pragma once
#include <string>
#include <functional>
#include <stdexcept>
#include <vector>
#include <opencv2/opencv.hpp>
#include "MvCameraControl.h"
struct ImageFrame {
std::vector<uint8_t> data;
unsigned int width = 0;
unsigned int height = 0;
unsigned int frameNum = 0;
MvGvspPixelType enPixelType = PixelType_Gvsp_Undefined;
};
class HikCamera {
public:
using ImageCallback = std::function<void(const ImageFrame& frame)>;
explicit HikCamera(const std::string& identifier,
bool useIP = true,
int reconnectSec = 10,
MvGvspPixelType initialPixelType = PixelType_Gvsp_Undefined);
~HikCamera();
HikCamera(const HikCamera&) = delete;
HikCamera& operator=(const HikCamera&) = delete;
HikCamera(HikCamera&&) = delete;
HikCamera& operator=(HikCamera&&) = delete;
cv::Mat ConvertToColor(const ImageFrame& frame);
void StartGrabbing();
void StopGrabbing();
bool IsGrabbing() const;
void SetImageCallback(ImageCallback callback);
void SetTriggerModeOn(bool useSoftwareTrigger = true); // true: 软触发;false: 硬触发
void SendSoftwareTrigger(); // 仅在软触发模式下有效
// 参数设置(自动关闭自动模式)
void SetExposureTime(float us);
float GetExposureTime() const;
void SetGain(float gain);
float GetGain() const;
void SetFrameRate(float fps);
void EnableFrameRateControl(bool enable);
void SetTriggerModeOff();
void LoadParameters(const std::string& filePath);
void SaveParameters(const std::string& filePath);
MvGvspPixelType GetPixelFormat() const;
void SetPixelFormat(MvGvspPixelType pixelType);
std::string GetPixelFormatName(MvGvspPixelType pixelType) const;
bool IsConnected() const;
std::string GetSerialNumber() const;
std::string GetModelName() const;
void PrintDeviceInfo() const; // 新增:打印相机信息(IP、SN、宽度、高度)
private:
void* handle_ = nullptr;
std::string serialNumber_;
std::string identifier_;
bool useIP_;
int reconnectTimeoutSec_;
ImageCallback userCallback_;
bool grabbing_ = false;
ImageFrame preAllocatedFrame; // 预分配的帧数据
void OpenDeviceByIdentifier();
void SetupOptimalPacketSize();
void RegisterCallbacks();
static void __stdcall ImageCallbackWrapper(MV_FRAME_OUT* pFrame, void* pUser, bool bAutoFree);
static void __stdcall ExceptionCallbackWrapper(unsigned int nMsgType, void* pUser);
void Reconnect();
// 辅助:关闭自动曝光/增益
void DisableAutoExposure();
void DisableAutoGain();
};
具体实现源码细节如下:
// HikCamera.cpp
#include "HikCamera.hpp"
#include <opencv2/opencv.hpp>
#include <iostream>
#include <chrono>
#include <thread>
#include <cstring>
#include <map>
void CheckRet(int nRet, const std::string& msg) {
if (nRet != MV_OK) {
throw std::runtime_error(msg + ": 0x" + std::to_string(static_cast<unsigned int>(nRet)));
}
}
// ==================== HikCamera ====================
HikCamera::HikCamera(const std::string& identifier, bool useIP, int reconnectSec, MvGvspPixelType initialPixelType)
: identifier_(identifier), useIP_(useIP), reconnectTimeoutSec_(reconnectSec) {
CheckRet(MV_CC_Initialize(), "MV_CC_Initialize failed");
OpenDeviceByIdentifier();
SetupOptimalPacketSize();
SetTriggerModeOff();
// 关闭自动模式
DisableAutoExposure();
DisableAutoGain();
if (initialPixelType != PixelType_Gvsp_Undefined) {
SetPixelFormat(initialPixelType);
}
RegisterCallbacks();
// 预分配缓冲区:根据当前分辨率估算最大缓冲(支持 16-bit 和 RGB)
MVCC_INTVALUE widthVal = {0}, heightVal = {0};
MV_CC_GetIntValue(handle_, "Width", &widthVal);
MV_CC_GetIntValue(handle_, "Height", &heightVal);
size_t estimatedBufferSize = static_cast<size_t>(widthVal.nCurValue) *
static_cast<size_t>(heightVal.nCurValue) * 4; // 最大 4 字节/像素
preAllocatedFrame.data.reserve(estimatedBufferSize);
PrintDeviceInfo();
}
HikCamera::~HikCamera() {
StopGrabbing();
if (handle_) {
MV_CC_CloseDevice(handle_);
MV_CC_DestroyHandle(handle_);
handle_ = nullptr;
}
MV_CC_Finalize();
}
void HikCamera::OpenDeviceByIdentifier() {
MV_CC_DEVICE_INFO_LIST stDeviceList = {0};
CheckRet(MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE, &stDeviceList), "EnumDevices failed");
unsigned int targetIndex = static_cast<unsigned int>(-1);
for (unsigned int i = 0; i < stDeviceList.nDeviceNum; ++i) {
auto* pInfo = stDeviceList.pDeviceInfo[i];
if (useIP_ && pInfo->nTLayerType == MV_GIGE_DEVICE) {
char ipStr[16];
sprintf(ipStr, "%d.%d.%d.%d",
(pInfo->SpecialInfo.stGigEInfo.nCurrentIp >> 24) & 0xFF,
(pInfo->SpecialInfo.stGigEInfo.nCurrentIp >> 16) & 0xFF,
(pInfo->SpecialInfo.stGigEInfo.nCurrentIp >> 8) & 0xFF,
pInfo->SpecialInfo.stGigEInfo.nCurrentIp & 0xFF);
if (identifier_ == ipStr) {
targetIndex = i;
serialNumber_ = reinterpret_cast<char*>(pInfo->SpecialInfo.stGigEInfo.chSerialNumber);
break;
}
} else if (!useIP_) {
const char* sn = nullptr;
if (pInfo->nTLayerType == MV_GIGE_DEVICE) sn = reinterpret_cast<char*>(pInfo->SpecialInfo.stGigEInfo.chSerialNumber);
else if (pInfo->nTLayerType == MV_USB_DEVICE) sn = reinterpret_cast<char*>(pInfo->SpecialInfo.stUsb3VInfo.chSerialNumber);
if (sn && identifier_ == sn) {
targetIndex = i;
serialNumber_ = identifier_;
break;
}
}
}
if (targetIndex == static_cast<unsigned int>(-1)) {
throw std::runtime_error("Camera not found: " + identifier_);
}
CheckRet(MV_CC_CreateHandle(&handle_, stDeviceList.pDeviceInfo[targetIndex]), "CreateHandle failed");
CheckRet(MV_CC_OpenDevice(handle_), "OpenDevice failed");
}
void HikCamera::SetupOptimalPacketSize() {
if (!handle_) return;
int nPacketSize = MV_CC_GetOptimalPacketSize(handle_);
if (nPacketSize > 0) {
MV_CC_SetIntValueEx(handle_, "GevSCPSPacketSize", nPacketSize);
}
}
void HikCamera::DisableAutoExposure() {
MV_CC_SetEnumValue(handle_, "ExposureAuto", 0); // 0 = Off
}
void HikCamera::DisableAutoGain() {
MV_CC_SetEnumValue(handle_, "GainAuto", 0); // 0 = Off
}
void HikCamera::SetTriggerModeOff() {
MV_CC_SetEnumValue(handle_, "TriggerMode", MV_TRIGGER_MODE_OFF);
}
void HikCamera::RegisterCallbacks() {
MV_CC_RegisterImageCallBackEx2(handle_, ImageCallbackWrapper, this, true);
MV_CC_RegisterExceptionCallBack(handle_, ExceptionCallbackWrapper, this);
}
void HikCamera::StartGrabbing() {
if (grabbing_) return;
CheckRet(MV_CC_StartGrabbing(handle_), "StartGrabbing failed");
grabbing_ = true;
}
void HikCamera::StopGrabbing() {
if (!grabbing_) return;
MV_CC_StopGrabbing(handle_);
grabbing_ = false;
}
bool HikCamera::IsGrabbing() const { return grabbing_; }
void HikCamera::SetImageCallback(ImageCallback callback) {
userCallback_ = std::move(callback);
}
void __stdcall HikCamera::ImageCallbackWrapper(MV_FRAME_OUT* pFrame, void* pUser, bool bAutoFree) {
auto* self = static_cast<HikCamera*>(pUser);
if (!self || !self->userCallback_ || !pFrame) return;
ImageFrame& frame = self->preAllocatedFrame;
frame.width = pFrame->stFrameInfo.nExtendWidth;
frame.height = pFrame->stFrameInfo.nExtendHeight;
frame.frameNum = pFrame->stFrameInfo.nFrameNum;
frame.enPixelType = pFrame->stFrameInfo.enPixelType;
size_t frameLen = pFrame->stFrameInfo.nFrameLenEx;
if (frame.data.size() < frameLen) {
frame.data.resize(frameLen);
}
memcpy(frame.data.data(), pFrame->pBufAddr, frameLen);
self->userCallback_(frame);
if (!bAutoFree) {
MV_CC_FreeImageBuffer(self->handle_, pFrame);
}
}
void __stdcall HikCamera::ExceptionCallbackWrapper(unsigned int nMsgType, void* pUser) {
if (nMsgType == MV_EXCEPTION_DEV_DISCONNECT) {
auto* self = static_cast<HikCamera*>(pUser);
if (self) {
std::cout << "[HikCamera] Device disconnected! Attempting reconnect..." << std::endl;
self->Reconnect();
}
}
}
// 关键:Reconnect 实现(之前缺失导致链接错误)
void HikCamera::Reconnect() {
StopGrabbing();
if (handle_) {
MV_CC_CloseDevice(handle_);
MV_CC_DestroyHandle(handle_);
handle_ = nullptr;
}
auto startTime = std::chrono::steady_clock::now();
while (true) {
try {
OpenDeviceByIdentifier();
SetupOptimalPacketSize();
SetTriggerModeOff();
DisableAutoExposure();
DisableAutoGain();
RegisterCallbacks();
if (grabbing_) {
StartGrabbing();
}
std::cout << "[HikCamera] Reconnect successful!" << std::endl;
return;
} catch (const std::exception& e) {
std::cerr << "[HikCamera] Reconnect failed: " << e.what() << ". Retrying in 1 second..." << std::endl;
}
std::this_thread::sleep_for(std::chrono::seconds(1));
auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::steady_clock::now() - startTime).count();
if (elapsed > reconnectTimeoutSec_) {
throw std::runtime_error("Reconnect timeout after " + std::to_string(reconnectTimeoutSec_) + " seconds");
}
}
}
void HikCamera::SetExposureTime(float us) {
CheckRet(MV_CC_SetFloatValue(handle_, "ExposureTime", us), "SetExposureTime failed");
}
float HikCamera::GetExposureTime() const {
MVCC_FLOATVALUE val = {0};
CheckRet(MV_CC_GetFloatValue(handle_, "ExposureTime", &val), "GetExposureTime failed");
return val.fCurValue;
}
void HikCamera::SetGain(float gain) {
CheckRet(MV_CC_SetFloatValue(handle_, "Gain", gain), "SetGain failed");
}
float HikCamera::GetGain() const {
MVCC_FLOATVALUE val = {0};
CheckRet(MV_CC_GetFloatValue(handle_, "Gain", &val), "GetGain failed");
return val.fCurValue;
}
void HikCamera::EnableFrameRateControl(bool enable) {
MV_CC_SetBoolValue(handle_, "AcquisitionFrameRateEnable", enable);
}
void HikCamera::SetFrameRate(float fps) {
CheckRet(MV_CC_SetFloatValue(handle_, "AcquisitionFrameRate", fps), "SetFrameRate failed");
}
void HikCamera::LoadParameters(const std::string& filePath) {
CheckRet(MV_CC_FeatureLoad(handle_, filePath.c_str()), "LoadParameters failed");
}
void HikCamera::SaveParameters(const std::string& filePath) {
CheckRet(MV_CC_FeatureSave(handle_, filePath.c_str()), "SaveParameters failed");
}
MvGvspPixelType HikCamera::GetPixelFormat() const {
MVCC_ENUMVALUE val = {0};
CheckRet(MV_CC_GetEnumValue(handle_, "PixelFormat", &val), "GetPixelFormat failed");
return static_cast<MvGvspPixelType>(val.nCurValue);
}
void HikCamera::SetPixelFormat(MvGvspPixelType pixelType) {
CheckRet(MV_CC_SetEnumValue(handle_, "PixelFormat", static_cast<unsigned int>(pixelType)),
"SetPixelFormat failed");
}
std::string HikCamera::GetPixelFormatName(MvGvspPixelType pixelType) const {
// 你要求不修改这个函数,所以保持原样
static const std::map<MvGvspPixelType, std::string> map = {
{PixelType_Gvsp_Mono8, "Mono8"},
{PixelType_Gvsp_Mono10, "Mono10"},
{PixelType_Gvsp_Mono10_Packed, "Mono10_Packed"},
{PixelType_Gvsp_Mono12, "Mono12"},
{PixelType_Gvsp_Mono12_Packed, "Mono12_Packed"},
{PixelType_Gvsp_Mono16, "Mono16"},
// Bayer RGGB pattern
{PixelType_Gvsp_BayerRG8, "BayerRG8"},
{PixelType_Gvsp_BayerRG10, "BayerRG10"},
{PixelType_Gvsp_BayerRG10_Packed, "BayerRG10_Packed"},
{PixelType_Gvsp_BayerRG12, "BayerRG12"},
{PixelType_Gvsp_BayerRG12_Packed, "BayerRG12_Packed"},
// Bayer BGGR
{PixelType_Gvsp_BayerBG8, "BayerBG8"},
{PixelType_Gvsp_BayerBG10, "BayerBG10"},
{PixelType_Gvsp_BayerBG10_Packed, "BayerBG10_Packed"},
{PixelType_Gvsp_BayerBG12, "BayerBG12"},
{PixelType_Gvsp_BayerBG12_Packed, "BayerBG12_Packed"},
// Bayer GRBG
{PixelType_Gvsp_BayerGR8, "BayerGR8"},
{PixelType_Gvsp_BayerGR10, "BayerGR10"},
{PixelType_Gvsp_BayerGR12, "BayerGR12"},
// Bayer GBRG
{PixelType_Gvsp_BayerGB8, "BayerGB8"},
{PixelType_Gvsp_BayerGB10, "BayerGB10"},
{PixelType_Gvsp_BayerGB12, "BayerGB12"},
// RGB/BGR
{PixelType_Gvsp_RGB8_Packed, "RGB8_Packed"},
{PixelType_Gvsp_BGR8_Packed, "BGR8_Packed"},
{PixelType_Gvsp_YUV422_Packed, "YUV422_Packed"},
{PixelType_Gvsp_YUV422_YUYV_Packed, "YUV422_YUYV_Packed"},
// Others
{PixelType_Gvsp_Undefined, "Undefined"}
};
auto it = map.find(pixelType);
if (it != map.end()) {
return it->second;
}
return "Unknown(0x" + std::to_string(static_cast<unsigned int>(pixelType)) + ")";
}
cv::Mat HikCamera::ConvertToColor(const ImageFrame& frame) {
// Step 1: 检查相机像素格式
int cvDepth = CV_8UC1; // 默认 8-bit 单通道
bool is16Bit = false;
if (frame.enPixelType == PixelType_Gvsp_BayerRG10 || frame.enPixelType == PixelType_Gvsp_BayerRG12 ||
frame.enPixelType == PixelType_Gvsp_BayerBG10 || frame.enPixelType == PixelType_Gvsp_BayerBG12 ||
frame.enPixelType == PixelType_Gvsp_BayerGR10 || frame.enPixelType == PixelType_Gvsp_BayerGR12 ||
frame.enPixelType == PixelType_Gvsp_BayerGB10 || frame.enPixelType == PixelType_Gvsp_BayerGB12 ||
frame.enPixelType == PixelType_Gvsp_Mono10 || frame.enPixelType == PixelType_Gvsp_Mono12) {
cvDepth = CV_16UC1;
is16Bit = true;
}
// Step 2: 构造原始 raw 图像(单通道 Bayer 或 Mono)
cv::Mat raw(frame.height, frame.width, cvDepth, const_cast<unsigned char*>(frame.data.data()));
// Step 3: 根据像素格式选择处理方式
cv::Mat colorImage;
int cvtCode = -1; // Debayer 转换码
switch (frame.enPixelType) {
// ==================== Bayer 彩色格式 ====================
case PixelType_Gvsp_BayerRG8:
case PixelType_Gvsp_BayerRG10:
case PixelType_Gvsp_BayerRG12:
cvtCode = cv::COLOR_BayerRG2RGB;
break;
case PixelType_Gvsp_BayerBG8:
case PixelType_Gvsp_BayerBG10:
case PixelType_Gvsp_BayerBG12:
cvtCode = cv::COLOR_BayerBG2RGB;
break;
case PixelType_Gvsp_BayerGR8:
case PixelType_Gvsp_BayerGR10:
case PixelType_Gvsp_BayerGR12:
cvtCode = cv::COLOR_BayerGR2RGB;
break;
case PixelType_Gvsp_BayerGB8:
case PixelType_Gvsp_BayerGB10:
case PixelType_Gvsp_BayerGB12:
cvtCode = cv::COLOR_BayerGB2RGB;
break;
// ==================== 相机已输出 3 通道 ====================
case PixelType_Gvsp_RGB8_Packed:
colorImage = cv::Mat(frame.height, frame.width, CV_8UC3, const_cast<unsigned char*>(frame.data.data()));
break;
case PixelType_Gvsp_BGR8_Packed:
colorImage = cv::Mat(frame.height, frame.width, CV_8UC3, const_cast<unsigned char*>(frame.data.data()));
break;
// ==================== 灰度相机 ====================
case PixelType_Gvsp_Mono8:
case PixelType_Gvsp_Mono10:
case PixelType_Gvsp_Mono12:
case PixelType_Gvsp_Mono16:
if (is16Bit) {
raw.convertTo(colorImage, CV_8U, 1.0 / 256.0); // 16→8 bit 简单右移
} else {
colorImage = raw;
}
break;
// ==================== 不支持的格式 ====================
default:
std::cerr << "警告: 不支持的像素格式 0x" << std::hex << static_cast<unsigned int>(frame.enPixelType)
<< ",跳过此帧处理" << std::dec << std::endl;
return cv::Mat(); // 返回空 Mat,避免后续保存崩溃
}
// Step 4: 执行 Debayer 转换(仅 Bayer 格式需要)
if (cvtCode != -1) {
cv::cvtColor(raw, colorImage, cvtCode);
// 如果是 16-bit Bayer,转为 8-bit 输出(YOLO 需要 8-bit)
if (is16Bit) {
colorImage.convertTo(colorImage, CV_8U, 1.0 / 256.0);
}
}
return colorImage;
}
bool HikCamera::IsConnected() const { return handle_ != nullptr; }
std::string HikCamera::GetSerialNumber() const { return serialNumber_; }
std::string HikCamera::GetModelName() const {
MVCC_STRINGVALUE val = {0};
CheckRet(MV_CC_GetStringValue(handle_, "DeviceModelName", &val), "GetModelName failed");
return std::string(val.chCurValue);
}
void HikCamera::SetTriggerModeOn(bool useSoftwareTrigger) {
if (grabbing_) {
StopGrabbing();
}
CheckRet(MV_CC_SetEnumValue(handle_, "TriggerMode", MV_TRIGGER_MODE_ON), "Set TriggerMode ON failed");
if (useSoftwareTrigger) {
CheckRet(MV_CC_SetEnumValue(handle_, "TriggerSource", MV_TRIGGER_SOURCE_SOFTWARE),
"Set TriggerSource to Software failed");
}
StartGrabbing();
}
void HikCamera::SendSoftwareTrigger() {
CheckRet(MV_CC_SetCommandValue(handle_, "TriggerSoftware"), "SendSoftwareTrigger failed");
}
// 打印相机信息
void HikCamera::PrintDeviceInfo() const {
std::cout << "=== 相机信息 ===" << std::endl;
std::cout << "序列号 (Serial Number): " << GetSerialNumber() << std::endl;
std::cout << "型号 (Model Name): " << GetModelName() << std::endl;
MVCC_STRINGVALUE ipVal = {0};
if (MV_CC_GetStringValue(handle_, "DeviceIPAddress", &ipVal) == MV_OK) {
std::cout << "当前 IP 地址: " << ipVal.chCurValue << std::endl;
} else {
std::cout << "当前 IP 地址: N/A (非 GigE 相机)" << std::endl;
}
MVCC_STRINGVALUE userNameVal = {0};
if (MV_CC_GetStringValue(handle_, "DeviceUserID", &userNameVal) == MV_OK) {
std::cout << "用户自定义名字 (UserDefinedName): " << userNameVal.chCurValue << std::endl;
} else {
std::cout << "用户自定义名字 (UserDefinedName): N/A" << std::endl;
}
MVCC_INTVALUE widthVal = {0}, heightVal = {0};
MV_CC_GetIntValue(handle_, "Width", &widthVal);
MV_CC_GetIntValue(handle_, "Height", &heightVal);
std::cout << "图像宽度 (Width): " << widthVal.nCurValue << std::endl;
std::cout << "图像高度 (Height): " << heightVal.nCurValue << std::endl;
std::cout << "=================" << std::endl;
}
3. 关键技术点详解
3.1 设备发现与连接(IP vs SN)
在 HikCamera 构造函数中,首先调用 MV_CC_EnumDevices 枚举所有在线设备。根据 useIP 参数,分别匹配设备的 chIpAddr 或 chSerialNumber 字段:
// 按 IP 连接
if (useIP && std::string(pDeviceInfo[i].SpecialInfo.stGigEInfo.chIpAddr) == identifier)
// 按 SN 连接
else if (!useIP && std::string(pDeviceInfo[i].SpecialInfo.stGigEInfo.chSerialNumber) == identifier)
这种方式极大提升了部署灵活性——调试时可用 IP 快速连接,量产时则用唯一 SN 防止设备混淆。
3.2 像素格式自动识别与转换
工业相机常输出 Bayer 格式(如 RG/BG/GR/GB)或 Mono 数据。我们在 ConvertToColor 中实现自动判断与转换:
if (frame.pixelFormat.find("Bayer") != std::string::npos) {
int code = GetBayerCode(frame.pixelFormat); // 如 CV_BayerBG2BGR
cv::cvtColor(rawMat, colorMat, code);
} else if (frame.pixelFormat.find("Mono") != std::string::npos) {
if (depth > 8) rawMat.convertTo(colorMat, CV_8U, 1.0 / 256.0); // 10/12-bit → 8-bit
else colorMat = rawMat.clone();
}
3.3 回调机制与内存管理
我们注册了海康的图像回调函数 MV_CC_RegisterImageCallBackEx2,并在内部使用预分配的 preAllocatedFrame.data 缓冲区接收图像数据。这样避免了每帧 malloc/free,显著降低延迟。
用户只需提供一个形如 void(const ImageFrame&) 的函数对象,例如:
camera.SetImageCallback([](const ImageFrame& f) {
std::cout << "Got frame: " << f.width << "x" << f.height << std::endl;
});
3.4 自动重连机制
工业现场网络不稳定是常态。为此,我们注册了异常回调 ExceptionCallbackWrapper,当检测到 MV_EXCEPTION_DEV_DISCONNECT 时,启动后台重连线程:
while (!reconnected && retryCount < MAX_RETRY) {
Close(); // 清理旧资源
std::this_thread::sleep_for(2s);
if (InitCamera()) { // 重新初始化
StartGrabbing();
reconnected = true;
}
retryCount++;
}
3.5 相机参数动态控制
为满足不同光照条件下的成像需求,封装了常用参数接口:
hik_camera.SetExposureTime(5000); // 5ms 曝光
hik_camera.SetGain(12.0); // 12dB 增益
hik_camera.EnableFrameRateControl(true, 25.0); // 限帧率 25fps
4 结合YOLO实现实时推理
配合YOLO部署模型接口,在主文件夹调用接口实现高性能实时推理,嗲用接口如下:
#include <iostream>
#include <vector>
#include <filesystem>
#include <fstream>
#include <opencv2/opencv.hpp>
#include "TrtModel.hpp" // 确保路径正确
#include "utils.hpp" // 包含 DetectionInfo
#include "HikCamera.hpp"
namespace fs = std::filesystem;
std::string g_saveDir = "yolo_results/";
TrtModel yolo_model( "weights/yolo11s.onnx", true, true );
HikCamera hik_camera("169.254.131.105"); // 相机 IP
// HikCamera hik_camera("DA7743579", false); // false 表示用 SN使用序列号连接
// 独立的图像处理函数(保存原始彩色图)
void processAndSaveImage(const ImageFrame& frame) {
cv::Mat colorImage = hik_camera.ConvertToColor(frame); // 调用封装函数获取彩色图像
if (colorImage.empty()) {
return; // 转换失败,跳过
}
// YOLO 推理
auto detections = yolo_model.doInference(colorImage);
// 绘制检测框
yolo_model.draw(colorImage, detections);
// 生成带毫秒的时间戳文件名
auto now = std::chrono::system_clock::now();
auto t_c = std::chrono::system_clock::to_time_t(now);
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000;
std::stringstream ss;
ss << std::put_time(std::localtime(&t_c), "%Y%m%d%H%M%S");
ss << "_" << std::setfill('0') << std::setw(3) << ms.count();
std::string filename = g_saveDir + ss.str() + ".jpg";
if (cv::imwrite(filename, colorImage)) {
std::cout << "图像保存: " << filename << std::endl;
} else {
std::cerr << "保存失败: " << filename << std::endl;
}
}
int main(){
try{
fs::create_directories(g_saveDir);
// ====== 简单相机配置 ======
hik_camera.SetExposureTime(50000.0f); // 曝光时间50ms
hik_camera.SetGain(10.0f);
hik_camera.EnableFrameRateControl(true);
// camera.SetFrameRate(30.0f);
// 可选:强制相机输出 RGB(性能更高,带宽更高)
// camera.SetPixelFormat(PixelType_Gvsp_RGB8_Packed);
// 设置回调:只调用封装好的处理函数
hik_camera.SetImageCallback(processAndSaveImage);
hik_camera.StartGrabbing();
std::cout << "\n=== 相机开始采集 ===\n";
std::cout << "图像实时保存至: " << g_saveDir << std::endl;
std::cout << "按回车键停止...\n";
std::cin.get();
} catch (const std::exception& e) {
std::cerr << "异常: " << e.what() << std::endl;
return -1;
}
std::cout << "程序退出。" << std::endl;
return 0;
}
5. 使用说明与注意事项
5.1 编译依赖
- 海康 MVSDK:需从官网下载并正确配置头文件与库路径,下载链接:https://www.hikrobotics.com/cn/machinevision/service/download?module=0
- OpenCV 4.x:用于图像处理与显示。
- C++17:使用了
std::filesystem处理路径,编译时需加-std=c++17。
5.2 常见问题排查
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 相机无法连接 | IP 不在同一网段 | 修改 PC 网卡 IP 为 169.254.x.x/16 |
| 图像全黑 | 曝光/增益过低 | 调用 SetExposureTime() 和 SetGain() |
| 颜色异常(偏绿/紫) | Bayer 格式识别错误 | 在 GetBayerCode() 中确认相机实际排列(RG/BG/GR/GB) |
| 程序崩溃 | 未初始化 SDK | 确保 MV_CC_Initialize() 成功返回 |
5.3 配置文件
cmake_minimum_required(VERSION 3.15)
project(DEPLOY LANGUAGES CXX CUDA)
# 设置源码和头文件路径
set(SRC_DIR src)
set(INCLUDE_DIR include)
# 配置标准
add_definitions(-std=c++17)
add_definitions(-DAPI_EXPORTS)
option(CUDA_USE_STATIC_CUDA_RUNTIME ON)
set(CMAKE_BUILD_TYPE Debug)
# CUDA 配置
# set(CMAKE_CUDA_COMPILER /usr/local/cuda-12.3/bin/nvcc)
enable_language(CUDA)
find_package(OpenCV REQUIRED)
# 包含目录配置
include_directories(
${INCLUDE_DIR} # 项目头文件目录
/usr/local/cuda-12.3/include # CUDA
/opt/TensorRT/TensorRT-10.8.0.43/include/ # TensorRT
# /usr/local/include/opencv4 # OpenCV
/opt/MVS/include/
)
# 链接目录配置
link_directories(
/usr/local/cuda-12.3/lib64
/opt/TensorRT/TensorRT-10.8.0.43/lib/
/usr/local/lib/
/opt/MVS/lib/64/
)
# glog & modbus
find_library(GLOG_LIB glog REQUIRED)
find_library(MODBUS_LIB modbus REQUIRED)
# 收集源文件
file(GLOB SRC_FILES
${SRC_DIR}/*.cpp
${SRC_DIR}/*.cu
)
# 创建可执行文件
add_executable(build yolo_hksdk.cpp ${SRC_FILES})
# 设置 CUDA 架构
set_target_properties(build PROPERTIES
CUDA_ARCHITECTURES "61;70;75"
CUDA_SEPARABLE_COMPILATION ON
)
# 链接库配置
# target_link_libraries(build ${OpenCV_LIBS})
target_link_libraries(build PRIVATE
${OpenCV_LIBS}
# TensorRT
nvinfer nvinfer_plugin nvonnxparser
# CUDA
cudart
# hksdk
MvCameraControl
pthread
dl
)
# 附加编译选项(可选)
target_compile_options(build PRIVATE
-Wall
-Wno-deprecated-declarations
-O2
)
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)