Files
eUi/subModule/Hd_CameraModule_HIK_General/Hd_CameraModule_HIK_General.cpp
T

471 lines
16 KiB
C++
Raw Normal View History

2025-11-16 19:29:29 +08:00
#include "Hd_CameraModule_HIK_General.h"
2026-04-26 12:25:13 +08:00
#include <qtimer.h>
2025-11-16 19:29:29 +08:00
using namespace std;
#include <QTextCodec>
#include <thread>
#include <atomic>
#include <chrono>
#include <mutex>
2026-04-26 12:25:13 +08:00
#include <qelapsedtimer.h>
2025-11-16 19:29:29 +08:00
enum statusEnum {
CONNECTED,
RECONNECTING,
DISCONNECTED
};
enum triggerModeEnum {
SOFTWARE,
HARDWARE,
EMPTY
};
2025-11-27 00:36:14 +08:00
std::atomic<statusEnum> cameraStatus(DISCONNECTED);
std::atomic<bool> reconnecting(false);
std::mutex handleMutex;
2025-11-16 19:29:29 +08:00
triggerModeEnum triggerMode = HARDWARE;
int getImageTimeOut = 3000;
bool runFlag = true;
//总取图数
int imgIndex = 0;
MV_CC_DEVICE_INFO_LIST m_stDevList; // ch:设备信息列表结构体变量,用来存储设备列表
string Username = "";
int cameraIndex = -1;
QString lastError;
QQueue<cv::Mat> queuepic;
QMutex* n_mutex = new QMutex();
2026-04-26 12:25:13 +08:00
class callbackCollectTimeoutTimerClass : public QThread {
public:
void run() {
runFlag.store(true);
counter = 0;
while (runFlag.load() && counter < interval.load()) {
QThread::msleep(ontTimeSleepInterval);
counter += ontTimeSleepInterval;
}
if (runFlag.load() && counter >= interval.load())
timeoutAction();
}
void timeoutAction() {
QMutexLocker locker(n_mutex);
if (queuepic.size() > 0) {
queuepic.clear();
qDebug() << QString::fromStdString(Username) << "上次回调后未取图,已因超时清除缓存,interval" << interval;
std::cout << Username << "上次回调后未取图,已因超时清除缓存,interval" << interval << std::endl;
}
}
void setInterval(int msec) {
interval.store(msec);
}
void stop() {
runFlag.store(false);
}
private:
int counter = 0;
std::atomic<bool> runFlag = false;
std::atomic<int> interval = 1500;
int ontTimeSleepInterval = 10;
};
callbackCollectTimeoutTimerClass callbackCollectTimeoutTimer;
2025-11-16 19:29:29 +08:00
void* handle = NULL;
void CloseDevice();
//0106 断线重连
void __stdcall ReconnectDevice(unsigned int nMsgType, void* pUser);
void __stdcall ImageCallBackEx(unsigned char* pData0, MV_FRAME_OUT_INFO_EX* pFrameInfo0, void* pUser0);
//搜索所有设备
int SearchDevice(){
if (m_stDevList.nDeviceNum == 0) {
memset(&m_stDevList, 0, sizeof(MV_CC_DEVICE_INFO_LIST));
// ch:枚举子网内所有设备 | en:Enumerate all devices within subnet
int nRet = MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE, &m_stDevList);
if (MV_OK != nRet)
{
return -1;
}
}
for (int i = 0; i < m_stDevList.nDeviceNum; i++)
{
//SN
unsigned char* name = m_stDevList.pDeviceInfo[i]->SpecialInfo.stGigEInfo.chSerialNumber;
string userName = static_cast<string>((LPCSTR)name);
if (Username == userName)
{
return i;
break;
}
name = m_stDevList.pDeviceInfo[i]->SpecialInfo.stUsb3VInfo.chSerialNumber;
userName = static_cast<string>((LPCSTR)name);
if (Username == userName)
{
return i;
break;
}
}
return -1;
}
bool connectDevice()
{
int cameraIndex = SearchDevice();
if (cameraIndex == -1){
//未匹配到相机
qDebug() << "[Waring] " << "Hd_CameraModule_HIK not match --CameraName:" << QString::fromStdString(Username);
lastError = "找不到相机" + QString::fromStdString(Username);
return false;
}
2025-11-27 00:36:14 +08:00
std::lock_guard<std::mutex> lock(handleMutex);
2025-11-16 19:29:29 +08:00
if (handle != NULL)
{
MV_CC_StopGrabbing(handle);
MV_CC_CloseDevice(handle);
MV_CC_DestroyHandle(handle);
2025-11-27 00:36:14 +08:00
handle = NULL;
2025-11-16 19:29:29 +08:00
}
//打开相机
MV_CC_CreateHandle(&handle, m_stDevList.pDeviceInfo[cameraIndex]);
int nRet = MV_CC_OpenDevice(handle);
////触发模式
if(triggerMode == SOFTWARE)
{
MV_CC_SetEnumValue(handle, "TriggerMode", MV_TRIGGER_MODE_ON);
MV_CC_SetEnumValue(handle, "TriggerSource", MV_TRIGGER_SOURCE_SOFTWARE);
}
else if(triggerMode == HARDWARE)
{
MV_CC_SetEnumValue(handle, "TriggerMode", MV_TRIGGER_MODE_ON);
MV_CC_SetEnumValue(handle, "TriggerSource", MV_TRIGGER_SOURCE_LINE0);
}
// //加载用户集,相机设置应当UserSet1
nRet = MV_CC_SetEnumValueByString(handle, "UserSetSelector", "UserSet1");
if (MV_OK != nRet)qDebug() << "Set UserSetSelector fail! nRet " << nRet;
// ch:注册抓图回调 | en:Register image callback
MV_CC_SetIntValueEx(handle, "GevHeartbeatTimeout", 3000);//心跳日志
nRet = MV_CC_RegisterImageCallBackEx(handle, ImageCallBackEx, handle);//注册回调
if (MV_OK != nRet)return false;
nRet = MV_CC_RegisterExceptionCallBack(handle, ReconnectDevice, NULL);//断线重连
if (MV_OK != nRet)return false;
MV_CC_StartGrabbing(handle);
2025-11-27 00:36:14 +08:00
cameraStatus.store(CONNECTED);
2025-11-16 19:29:29 +08:00
return true;
}
void SetExposureTime(float exposureValue)
{
qDebug() << "[INFO] " << "exposureValue:" << exposureValue;
MV_CC_SetEnumValue(handle, "ExposureAuto", MV_EXPOSURE_AUTO_MODE_OFF);
MV_CC_SetFloatValue(handle, "ExposureTime", exposureValue);
}
void SetGain(float GainValue)
{
qDebug() << "[INFO] " << "GainValue:" << GainValue;
MV_CC_SetEnumValue(handle, "GainAuto", 0);
MV_CC_SetFloatValue(handle, "Gain", GainValue);
}
//断线重连
void __stdcall ReconnectDevice(unsigned int nMsgType, void* pUser){
qDebug() << "[Error] " << " MV camera disconnects!";
2025-11-27 00:36:14 +08:00
if (nMsgType == MV_EXCEPTION_DEV_DISCONNECT && cameraStatus.load() == CONNECTED){
// 防止重复启动重连线程
bool expected = false;
if (!reconnecting.compare_exchange_strong(expected, true)) {
qDebug() << "[Warning] Reconnect already in progress, skipping duplicate request";
return;
}
cameraStatus.store(RECONNECTING);
2025-11-16 19:29:29 +08:00
// 启动后台线程负责重连,避免在回调线程中长时间阻塞
std::thread([]() {
2025-11-27 00:36:14 +08:00
qDebug() << QString::fromStdString(Username) << " start reconnect thread";
2025-11-16 19:29:29 +08:00
// 确保设备资源关闭
CloseDevice();
int times = 0;
while (runFlag)
{
2025-11-27 00:36:14 +08:00
qDebug() << QString::fromStdString(Username) << " reconnect attempt:" << times++;
2025-11-16 19:29:29 +08:00
if (connectDevice())
{
2025-11-27 00:36:14 +08:00
qWarning() << QString::fromStdString(Username) << " reconnect success!";
cameraStatus.store(CONNECTED);
reconnecting.store(false);
2025-11-16 19:29:29 +08:00
return;
}
2025-11-27 00:36:14 +08:00
2026-01-21 09:41:31 +08:00
qDebug() << QString::fromStdString(Username) << " reconnect failed, waiting 500 ms before retry...";
std::this_thread::sleep_for(std::chrono::milliseconds(500));
2025-11-16 19:29:29 +08:00
}
2025-11-27 00:36:14 +08:00
// 如果runFlag为false,线程退出
qDebug() << "[Info] reconnect thread stopped (runFlag=false)";
cameraStatus.store(DISCONNECTED);
reconnecting.store(false);
2025-11-16 19:29:29 +08:00
}).detach();
}
}
//回调函数;
//经测试,相机像素格式Mono8、RGB8是可以正常转换的,YUV422还需要优化;RGB8格式比YUV422帧率低;BGR格式会导致异色问题
void __stdcall ImageCallBackEx(unsigned char* pData0, MV_FRAME_OUT_INFO_EX* pFrameInfo0, void* pUser0)
{
Q_UNUSED(pUser0)
2026-04-26 12:25:13 +08:00
callbackCollectTimeoutTimer.stop();
2025-11-16 19:29:29 +08:00
int frameNum = 0;
double time_Start = (double)clock();
QMutexLocker locker(n_mutex);
cv::Mat srcImage = cv::Mat();
//qDebug() << "[info]" << __FUNCTION__ << " line:" << __LINE__ << "--- start";
if (pFrameInfo0)
{
//获取的是单通道灰度图
if (pFrameInfo0->enPixelType == PixelType_Gvsp_Mono8)//获取当前采集到的图像格式
{
srcImage = cv::Mat(pFrameInfo0->nHeight, pFrameInfo0->nWidth, CV_8UC1, pData0);
}
//获取的是RGB8图
else if (pFrameInfo0->enPixelType == PixelType_Gvsp_BGR8_Packed)
{
srcImage = cv::Mat(pFrameInfo0->nHeight, pFrameInfo0->nWidth, CV_8UC3, pData0);
//cvtColor(srcImage, srcImage, COLOR_RGB2BGR);//导致异色
}
else if (IsColor(pFrameInfo0->enPixelType)) //其它格式彩色图
{
MvGvspPixelType enDstPixelType = PixelType_Gvsp_Undefined;
enDstPixelType = PixelType_Gvsp_BGR8_Packed;
unsigned char* pConvertData = NULL;
pConvertData = (unsigned char*)malloc(pFrameInfo0->nWidth * pFrameInfo0->nHeight * 3);
if (NULL == pConvertData)
{
printf("malloc pConvertData fail!\n");
}
else
{
unsigned int nConvertDataSize = 0;
nConvertDataSize = pFrameInfo0->nWidth * pFrameInfo0->nHeight * 3;
// ch:像素格式转换 | en:Convert pixel format
MV_CC_PIXEL_CONVERT_PARAM stConvertParam = { 0 };
frameNum = pFrameInfo0->nFrameNum; //ch:帧号 | en:frame number
stConvertParam.nWidth = pFrameInfo0->nWidth; //ch:图像宽 | en:image width
stConvertParam.nHeight = pFrameInfo0->nHeight; //ch:图像高 | en:image height
stConvertParam.pSrcData = pData0; //ch:输入数据缓存 | en:input data buffer
stConvertParam.nSrcDataLen = pFrameInfo0->nFrameLen; //ch:输入数据大小 | en:input data size
stConvertParam.enSrcPixelType = pFrameInfo0->enPixelType; //ch:输入像素格式 | en:input pixel format
stConvertParam.enDstPixelType = enDstPixelType; //ch:输出像素格式 | en:output pixel format
stConvertParam.pDstBuffer = pConvertData; //ch:输出数据缓存 | en:output data buffer
stConvertParam.nDstBufferSize = nConvertDataSize; //ch:输出缓存大小 | en:output buffer size
int nRet = MV_CC_ConvertPixelType(handle, &stConvertParam);
Mat mysrcImage = cv::Mat(pFrameInfo0->nHeight, pFrameInfo0->nWidth, CV_8UC3, stConvertParam.pDstBuffer);
mysrcImage.copyTo(srcImage);
free(pConvertData);
pConvertData = NULL;
stConvertParam = { 0 };
}
}
}
if (srcImage.empty())
{
srcImage = cv::Mat(5, 5, CV_8UC1).setTo(0);
}
2026-01-01 00:49:11 +08:00
queuepic.enqueue(srcImage);
2026-04-26 12:25:13 +08:00
callbackCollectTimeoutTimer.start();
2026-01-01 00:49:11 +08:00
if (queuepic.size() > 10)
queuepic.dequeue();
2025-11-16 19:29:29 +08:00
pData0 = { 0 };
double time_End = (double)clock();
qDebug() << "[INFO] " << __FUNCTION__ << " line:" << __LINE__ << " checkImg getImg,Time:" << (time_End - time_Start) << "ms"
<< "--CameraName:" << QString::fromStdString(Username) << " imgIndex:" << imgIndex << "nFrameNum : " << frameNum;
2026-04-26 12:25:13 +08:00
2025-11-16 19:29:29 +08:00
}
//关闭设备
void CloseDevice()
{
2025-11-27 00:36:14 +08:00
std::lock_guard<std::mutex> lock(handleMutex);
2025-11-16 19:29:29 +08:00
// 如果 handle 已经为空,直接返回
if (handle == nullptr)
return;
// 使用 SDK 正确的停止/关闭/销毁流程,避免使用 CloseHandle 等不匹配的 API
2025-11-27 00:36:14 +08:00
int nRet = MV_CC_StopGrabbing(handle);
if (MV_OK != nRet) {
qDebug() << "[Warning] MV_CC_StopGrabbing failed, nRet=" << nRet;
}
nRet = MV_CC_CloseDevice(handle);
if (MV_OK != nRet) {
qDebug() << "[Warning] MV_CC_CloseDevice failed, nRet=" << nRet;
}
nRet = MV_CC_DestroyHandle(handle);
if (MV_OK != nRet) {
qDebug() << "[Warning] MV_CC_DestroyHandle failed, nRet=" << nRet;
}
2025-11-16 19:29:29 +08:00
handle = nullptr;
}
2026-04-26 12:25:13 +08:00
Hd_CameraModule_HIK_General::Hd_CameraModule_HIK_General() {
2025-11-16 19:29:29 +08:00
2026-04-26 12:25:13 +08:00
}
2025-11-16 19:29:29 +08:00
Hd_CameraModule_HIK_General::~Hd_CameraModule_HIK_General()
{
runFlag = false;
2026-04-26 12:25:13 +08:00
// 防止定时线程在资源析构后仍访问队列和互斥锁
callbackCollectTimeoutTimer.stop();
2025-11-27 00:36:14 +08:00
// 等待重连线程结束
int waitCount = 0;
while (reconnecting.load() && waitCount < 50) { // 最多等待5秒
qDebug() << "[Info] Waiting for reconnect thread to finish...";
std::this_thread::sleep_for(std::chrono::milliseconds(100));
waitCount++;
}
2025-11-16 19:29:29 +08:00
CloseDevice();
if (n_mutex)
{
delete n_mutex;
n_mutex = nullptr;
}
qDebug() << "[INFO] " << " delete success!";
}
//从类中读数据到实例对象
bool Hd_CameraModule_HIK_General::readData(std::vector<cv::Mat>& mat, QByteArray& data)
{
if (queuepic.isEmpty())
{
return false;
}
2026-04-26 12:25:13 +08:00
callbackCollectTimeoutTimer.stop();
2025-11-16 19:29:29 +08:00
QMutexLocker locker(n_mutex);
cv::Mat img = queuepic.dequeue();
mat.push_back(img);
qDebug() << "[INFO] " << __FUNCTION__ << " line:" << __LINE__ << " checkImg readData--CameraName:" << QString::fromStdString(Username) << "--imgIndex : " << imgIndex;
return true;
}
//实例对象把数据写入到类;加载模型后会运行此函数
bool Hd_CameraModule_HIK_General::writeData(std::vector<cv::Mat>& mat, QByteArray& data)
{
Q_UNUSED(mat);
if (data.contains("deviceList")) {
auto result = QByteArrayToMV_CC_DeviceInfoList(data.mid(10));
if (result.nDeviceNum != 0) {
m_stDevList = result;
return true;
}
}
2026-01-01 00:49:11 +08:00
if (data == "getStatus") {
data = lastError.toUtf8();
return true;
}
2026-04-26 12:25:13 +08:00
if (data == "clearCache") {
QMutexLocker locker(n_mutex);
queuepic.clear();
return true;
}
2026-01-05 01:05:56 +08:00
if (data.contains("exposure")){
auto commandList = data.split('_');
QMutexLocker locker(n_mutex);
SetExposureTime(commandList[1].toFloat());
return true;
}
2025-11-16 19:29:29 +08:00
return true;
}
//传入initParas函数,格式为:相机key+参数名1#参数值1#参数值2+参数名2#参数值1...
bool Hd_CameraModule_HIK_General::initParas(QByteArray& initData)
{
QByteArrayList valueList = initData.split('#');
if (valueList.size() < 3) {
qDebug() << "[Error] " << " Hd_CameraModule_HIK initParas fail--params error!参数数量不满足";
lastError = "参数数量不满足";
return false;
}
Username = valueList[0];
auto triggerModeStr = valueList[1];
if (triggerModeStr == "HARDWARE")
triggerMode = HARDWARE;
else if (triggerModeStr == "SOFTWARE")
triggerMode = SOFTWARE;
else if (triggerModeStr == "EMPTY")
triggerMode = EMPTY;
else {
qDebug() << "[Error] " << " Hd_CameraModule_HIK initParas fail--params error!触发模式参数错误";
lastError = "触发模式参数错误";
return false;
}
getImageTimeOut = valueList[2].toInt();
imgIndex = 0;
return connectDevice();
}
bool Hd_CameraModule_HIK_General::run()
{
qDebug() << "[INFO] " << " run start" << QString::fromStdString(Username);
if (triggerMode == SOFTWARE)
MV_CC_SetCommandValue(handle, "TriggerSoftware");
try
{
int sleepMs = 10;
int cnt = 0;
int timeoutCnt = getImageTimeOut / sleepMs;
while (true)
{
QMutexLocker locker(n_mutex);
if (queuepic.size() > 0)return true;
if (cnt++ > timeoutCnt) {
queuepic.push_back(cv::Mat(200, 200, CV_8UC1, cv::Scalar(0))); //返回空图
qDebug() << QString::fromStdString(Username) << "timeout" ;
return true;
}
QThread::msleep(sleepMs);
}
}
catch (QString ev)
{
qDebug() << "[Error] " << " ev:" << ev;
}
return true;
}
//相机连接状态:正常连接或断开
bool Hd_CameraModule_HIK_General::checkStatus()
{
//status = moduleStatus;
2026-04-26 12:25:13 +08:00
return cameraStatus == CONNECTED;
2025-11-16 19:29:29 +08:00
}
Hd_CameraModule_HIK_General* create()
{
qDebug() << "[INFO] " << "Hd_CameraModule_HIK create success!";
return new Hd_CameraModule_HIK_General;
}
void destory(Hd_CameraModule_HIK_General* ptr)
{
if (ptr)
{
delete ptr;
ptr = nullptr;
}
qDebug() << "[INFO] " << "Hd_CameraModule_HIK destory success!";
}