#include "Hd_CameraModule_HIK_General.h" #include using namespace std; #include #include #include #include #include #include enum statusEnum { CONNECTED, RECONNECTING, DISCONNECTED }; enum triggerModeEnum { SOFTWARE, HARDWARE, EMPTY }; std::atomic cameraStatus(DISCONNECTED); std::atomic reconnecting(false); std::mutex handleMutex; 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 queuepic; QMutex* n_mutex = new QMutex(); 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 runFlag = false; std::atomic interval = 1500; int ontTimeSleepInterval = 10; }; callbackCollectTimeoutTimerClass callbackCollectTimeoutTimer; 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((LPCSTR)name); if (Username == userName) { return i; break; } name = m_stDevList.pDeviceInfo[i]->SpecialInfo.stUsb3VInfo.chSerialNumber; userName = static_cast((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; } std::lock_guard lock(handleMutex); if (handle != NULL) { MV_CC_StopGrabbing(handle); MV_CC_CloseDevice(handle); MV_CC_DestroyHandle(handle); handle = NULL; } //打开相机 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); cameraStatus.store(CONNECTED); 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!"; 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); // 启动后台线程负责重连,避免在回调线程中长时间阻塞 std::thread([]() { qDebug() << QString::fromStdString(Username) << " start reconnect thread"; // 确保设备资源关闭 CloseDevice(); int times = 0; while (runFlag) { qDebug() << QString::fromStdString(Username) << " reconnect attempt:" << times++; if (connectDevice()) { qWarning() << QString::fromStdString(Username) << " reconnect success!"; cameraStatus.store(CONNECTED); reconnecting.store(false); return; } qDebug() << QString::fromStdString(Username) << " reconnect failed, waiting 500 ms before retry..."; std::this_thread::sleep_for(std::chrono::milliseconds(500)); } // 如果runFlag为false,线程退出 qDebug() << "[Info] reconnect thread stopped (runFlag=false)"; cameraStatus.store(DISCONNECTED); reconnecting.store(false); }).detach(); } } //回调函数; //经测试,相机像素格式Mono8、RGB8是可以正常转换的,YUV422还需要优化;RGB8格式比YUV422帧率低;BGR格式会导致异色问题 void __stdcall ImageCallBackEx(unsigned char* pData0, MV_FRAME_OUT_INFO_EX* pFrameInfo0, void* pUser0) { Q_UNUSED(pUser0) callbackCollectTimeoutTimer.stop(); 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); } queuepic.enqueue(srcImage); callbackCollectTimeoutTimer.start(); if (queuepic.size() > 10) queuepic.dequeue(); 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; } //关闭设备 void CloseDevice() { std::lock_guard lock(handleMutex); // 如果 handle 已经为空,直接返回 if (handle == nullptr) return; // 使用 SDK 正确的停止/关闭/销毁流程,避免使用 CloseHandle 等不匹配的 API 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; } handle = nullptr; } Hd_CameraModule_HIK_General::Hd_CameraModule_HIK_General() { } Hd_CameraModule_HIK_General::~Hd_CameraModule_HIK_General() { runFlag = false; // 防止定时线程在资源析构后仍访问队列和互斥锁 callbackCollectTimeoutTimer.stop(); // 等待重连线程结束 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++; } CloseDevice(); if (n_mutex) { delete n_mutex; n_mutex = nullptr; } qDebug() << "[INFO] " << " delete success!"; } //从类中读数据到实例对象 bool Hd_CameraModule_HIK_General::readData(std::vector& mat, QByteArray& data) { if (queuepic.isEmpty()) { return false; } callbackCollectTimeoutTimer.stop(); 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& 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; } } if (data == "getStatus") { data = lastError.toUtf8(); return true; } if (data == "clearCache") { QMutexLocker locker(n_mutex); queuepic.clear(); return true; } if (data.contains("exposure")){ auto commandList = data.split('_'); QMutexLocker locker(n_mutex); SetExposureTime(commandList[1].toFloat()); return true; } 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; return cameraStatus == CONNECTED; } 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!"; }