From ab3abb8fa878ae7a222b34b2832a28039d907245 Mon Sep 17 00:00:00 2001 From: liusiyang Date: Wed, 16 Sep 2026 17:54:47 +0800 Subject: [PATCH] =?UTF-8?q?feat=20=E5=88=9D=E6=AD=A5=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=85=B3=E8=81=94=E9=80=9A=E9=81=93=E5=88=86=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AlgorithmModule/include/QX_Merge_Analysis.h | 44 ++++ AlgorithmModule/src/ImageResultJudge.cpp | 18 ++ AlgorithmModule/src/QX_Merge_Analysis.cpp | 222 ++++++++++++++++++-- 3 files changed, 270 insertions(+), 14 deletions(-) diff --git a/AlgorithmModule/include/QX_Merge_Analysis.h b/AlgorithmModule/include/QX_Merge_Analysis.h index 7962a1f..2614db9 100644 --- a/AlgorithmModule/include/QX_Merge_Analysis.h +++ b/AlgorithmModule/include/QX_Merge_Analysis.h @@ -8,6 +8,8 @@ #include #include #include +#include +#include #include "CheckErrorCodeDefine.hpp" #include "CheckConfigDefine.h" #include "QX_Analysis.h" @@ -126,6 +128,32 @@ public: } }; + // 关联通道缺陷信息(用于判断关联通道是否存在脏污(dirty)重叠缺陷) + struct Correlated_Qx_Info_ + { + std::string channel_name; // 缺陷所属通道 + std::string camera_name; // 缺陷所属相机 + int config_qx_type; // 缺陷配置类型 CONFIG_QX_NAME_ + int result_type; // 缺陷结果类型(仅用于日志/追溯) + cv::Rect product_roi; // 产品坐标系下的缺陷ROI + Correlated_Qx_Info_() + { + channel_name = ""; + camera_name = ""; + config_qx_type = -1; + result_type = -1; + product_roi = cv::Rect(0, 0, 0, 0); + } + std::string GetInfo() + { + char buffer[256]; + snprintf(buffer, sizeof(buffer), "cam:%s channel:%s cfgType:%d resType:%d roi:[%d,%d,%d,%d]", + camera_name.c_str(), channel_name.c_str(), config_qx_type, result_type, + product_roi.x, product_roi.y, product_roi.width, product_roi.height); + return std::string(buffer); + } + }; + public: QX_Merge_Analysis(); ~QX_Merge_Analysis(); @@ -146,6 +174,9 @@ public: bool Idx(int qxidx); bool AddQxInfo(int qxidx, QX_Info qxinfo, std::shared_ptr plog, std::shared_ptr pchannelLog); + // 登记关联通道(非主/次通道)的缺陷信息,如脏污(dirty),供 AD 绑定分析判断重叠缺陷 + bool AddCorrelatedQxInfo(Correlated_Qx_Info_ qxinfo, std::shared_ptr plog); + bool setSendStatus(std::string cameraName, RUN_STATUS_ status); bool waitComplete(int timeoutMs = 15000); @@ -176,6 +207,15 @@ private: int Analysis_AD(ALL_Qx_DataList *pALLTypeqxList, int qx_idx); int Analysis_AD_Bind(ALL_Qx_DataList *pALLTypeqxList, int qx_idx); + // 判断当前缺陷是否与关联通道(correlated_channels)中的脏污缺陷重叠 + bool IsOverlapDirtyInCorrelatedChannel(const std::vector &correlated_channels, + const std::string &cur_channel_name, + const std::string &cur_camera_name, + const cv::Rect &qx_product_roi, + int roi_expand, + float iou_threshold, + std::shared_ptr plog); + ChannelCheckFunction *GetChannelFuntion(std::string strChannelName); double DistanceBetweenRectCenters(const cv::Rect &rect1, const cv::Rect &rect2, float fx, float fy); @@ -201,6 +241,10 @@ private: std::unordered_map> AD_list_bind; + // 关联通道缺陷缓存:channel_name -> 缺陷列表(产品坐标系) + std::unordered_map> m_correlatedQxMap; + std::mutex mtx_correlatedQx; + private: }; diff --git a/AlgorithmModule/src/ImageResultJudge.cpp b/AlgorithmModule/src/ImageResultJudge.cpp index a0bb5ec..018d247 100644 --- a/AlgorithmModule/src/ImageResultJudge.cpp +++ b/AlgorithmModule/src/ImageResultJudge.cpp @@ -746,6 +746,24 @@ int ImageResultJudge::ResultJudge(std::shared_ptr pImageResult) pQxLog->AddCheckstr(PrintLevel_2, DET_LOG_LEVEL_3, " Use Num And Dis Judge ", "qx %s ,Add %s %s", qx_name.c_str(), BOOL_TO_STR_Error(bad), CheckUtil::getCurTimeHMS().c_str()); } } + + // 关联通道分析:登记脏污(dirty)缺陷位置,供 AD 绑定分析判断关联通道是否存在重叠脏污缺陷 + if (config_qx_type == CONFIG_QX_NAME_Dirty_L0 || + config_qx_type == CONFIG_QX_NAME_Dirty_L1 || + config_qx_type == CONFIG_QX_NAME_Dirty_L2) + { + QX_Merge_Analysis::Correlated_Qx_Info_ corr_qx; + corr_qx.channel_name = pImageResult->strChannel; + corr_qx.camera_name = pImageResult->cameraBaseResult->strCameraName; + corr_qx.config_qx_type = config_qx_type; + corr_qx.result_type = ConfigTypeToResultType(config_qx_type); + corr_qx.product_roi = roi; + corr_qx.product_roi.x += fproduct_offx_piexl; + + bool bAddCorr = m_pQX_Merge_Analysis->AddCorrelatedQxInfo(corr_qx, pQxLog); + pQxLog->AddCheckstr(PrintLevel_2, DET_LOG_LEVEL_3, " Use Num And Dis Judge ", "qx %s ,Add Correlated %s %s", + qx_name.c_str(), BOOL_TO_STR_Error(bAddCorr), corr_qx.GetInfo().c_str()); + } } pQxLog->AddCheckstr(PrintLevel_2, DET_LOG_LEVEL_3, " Cur Result ", "qx %s ,result = %s", qx_name.c_str(), resultType.c_str()); diff --git a/AlgorithmModule/src/QX_Merge_Analysis.cpp b/AlgorithmModule/src/QX_Merge_Analysis.cpp index 6570975..34ac7a4 100644 --- a/AlgorithmModule/src/QX_Merge_Analysis.cpp +++ b/AlgorithmModule/src/QX_Merge_Analysis.cpp @@ -28,6 +28,10 @@ int QX_Merge_Analysis::Clear() { m_pdetlogList.clear(); m_statusList.clear(); + { + std::lock_guard lock(mtx_correlatedQx); + m_correlatedQxMap.clear(); + } return 0; } @@ -45,6 +49,10 @@ int QX_Merge_Analysis::AddCamer(std::string cameraName) int QX_Merge_Analysis::InitData() { m_reultList.Init(); + { + std::lock_guard lock(mtx_correlatedQx); + m_correlatedQxMap.clear(); + } m_pMergedetlog = std::make_shared(); m_pMergedetlog->AddCheckstr(PrintLevel_2, 3, "QX_Merge_Analysis", "InitData %s",CheckUtil::getCurTimeHMS().c_str()); @@ -259,6 +267,163 @@ bool QX_Merge_Analysis::setSendStatus(std::string cameraName, RUN_STATUS_ status return false; } +/** + * @brief 判断缺陷配置类型是否为脏污(dirty) + * @param nconfigType 缺陷配置类型 CONFIG_QX_NAME_ + * @return bool 是脏污返回true + */ +static bool IsDirtyConfigType(int nconfigType) +{ + return nconfigType == CONFIG_QX_NAME_Dirty_L0 || + nconfigType == CONFIG_QX_NAME_Dirty_L1 || + nconfigType == CONFIG_QX_NAME_Dirty_L2; +} + +/** + * @brief 判断关联通道名称是否需要参与判断(为空或NONE则不参与) + * @param strChannelName 关联通道名称 + * @return bool 需要判断返回true + */ +static bool IsValidCorrelatedChannel(const std::string &strChannelName) +{ + if (strChannelName == "") + { + return false; + } + if (CheckUtil::compareIgnoreCase(strChannelName, "NONE")) + { + return false; + } + return true; +} + +bool QX_Merge_Analysis::AddCorrelatedQxInfo(Correlated_Qx_Info_ qxinfo, std::shared_ptr plog) +{ + // 防御:通道为空或ROI非法时不登记,避免无效数据参与后续判断 + if (qxinfo.channel_name == "" || qxinfo.product_roi.width <= 0 || qxinfo.product_roi.height <= 0) + { + return false; + } + + { + std::lock_guard lock(mtx_correlatedQx); + std::vector &qxList = m_correlatedQxMap[qxinfo.channel_name]; + // 同一相机同一通道同一位置只登记一次,避免同一缺陷被重复登记 + for (int i = 0; i < (int)qxList.size(); i++) + { + if (qxList.at(i).camera_name == qxinfo.camera_name && + qxList.at(i).config_qx_type == qxinfo.config_qx_type && + qxList.at(i).product_roi == qxinfo.product_roi) + { + return true; + } + } + qxList.push_back(qxinfo); + } + + if (plog) + { + plog->AddCheckstr(PrintLevel_3, 3, "AddCorrelatedQxInfo", "add %s", qxinfo.GetInfo().c_str()); + } + if (m_pMergedetlog) + { + m_pMergedetlog->AddCheckstr(PrintLevel_3, 3, "AddCorrelatedQxInfo", "add %s", qxinfo.GetInfo().c_str()); + } + return true; +} + +bool QX_Merge_Analysis::IsOverlapDirtyInCorrelatedChannel(const std::vector &correlated_channels, + const std::string &cur_channel_name, + const std::string &cur_camera_name, + const cv::Rect &qx_product_roi, + int roi_expand, + float iou_threshold, + std::shared_ptr plog) +{ + // 未配置关联通道(NONE/空)时不需要判断 + if (correlated_channels.empty()) + { + return false; + } + // 防御:ROI非法时不判断,避免无效计算与除零 + if (qx_product_roi.width <= 0 || qx_product_roi.height <= 0) + { + return false; + } + + // 拷贝出需要比较的脏污缺陷列表,避免在锁内做IoU计算 + std::vector dirtyList; + { + std::lock_guard lock(mtx_correlatedQx); + for (int i = 0; i < (int)correlated_channels.size(); i++) + { + const std::string &channel_name = correlated_channels.at(i); + if (!IsValidCorrelatedChannel(channel_name)) + { + continue; + } + auto it = m_correlatedQxMap.find(channel_name); + if (it == m_correlatedQxMap.end()) + { + continue; + } + for (int j = 0; j < (int)it->second.size(); j++) + { + if (IsDirtyConfigType(it->second.at(j).config_qx_type)) + { + dirtyList.push_back(it->second.at(j)); + } + } + } + } + + if (dirtyList.empty()) + { + return false; + } + + // 与 Analysis_AD_Bind 中主次通道判断保持一致:双方均按 AD_Expand 扩张后再计算 IOU + cv::Rect roi_expanded(qx_product_roi.x - roi_expand, qx_product_roi.y - roi_expand, + qx_product_roi.width + roi_expand * 2, qx_product_roi.height + roi_expand * 2); + + for (int i = 0; i < (int)dirtyList.size(); i++) + { + cv::Rect dirty_roi = dirtyList.at(i).product_roi; + cv::Rect dirty_expanded(dirty_roi.x - roi_expand, dirty_roi.y - roi_expand, + dirty_roi.width + roi_expand * 2, dirty_roi.height + roi_expand * 2); + + float fiou = CheckUtil::CalIoU(roi_expanded, dirty_expanded); + + if (plog) + { + plog->AddCheckstr(PrintLevel_3, 3, "CorrelatedDirty", + "cur %s cam %s roi %s <-> dirty %s roi %s expand %d iou %f", + cur_channel_name.c_str(), cur_camera_name.c_str(), + CheckUtil::GetRectString(qx_product_roi).c_str(), + dirtyList.at(i).channel_name.c_str(), + CheckUtil::GetRectString(dirty_roi).c_str(), + roi_expand, fiou); + } + // IOU 为 NaN(ROI 非法导致的除零)时比较结果为 false,不会误抑制 NG + if (fiou > iou_threshold) + { + if (plog) + { + plog->AddCheckstr(PrintLevel_1, 3, "CorrelatedDirty", + "cur %s cam %s roi %s overlap dirty %s cam %s roi %s expand %d iou %f > %f", + cur_channel_name.c_str(), cur_camera_name.c_str(), + CheckUtil::GetRectString(qx_product_roi).c_str(), + dirtyList.at(i).channel_name.c_str(), dirtyList.at(i).camera_name.c_str(), + CheckUtil::GetRectString(dirty_roi).c_str(), + roi_expand, fiou, iou_threshold); + } + return true; + } + } + + return false; +} + /** * @brief 等待所有任务完成 * @param timeoutMs 超时时间(毫秒) @@ -1509,26 +1674,55 @@ int QX_Merge_Analysis::Analysis_AD_Bind(ALL_Qx_DataList *pALLTypeqxList, int qx_ // 已经NG if (bNG) { + // 注意:按缺陷(blob)粒度逐条判断,避免把同一通道内与脏污无关的缺陷也一并放过 + const int roi_expand = (int)(round)(pAd_checkParma->analysis_iou.AD_Expand); + const float iou_threshold = pAd_checkParma->analysis_iou.AD_IOU; + QX_channel_List *pqxList = pQXChannelList; for (int idx = 0; idx < (int)pqxList->qxList.size(); idx++) { - if (pqxList->qxList.at(idx).result == 0) + if (pqxList->qxList.at(idx).result != 0) { - pqxList->qxList.at(idx).result = 1; - - QX_RESULT tem; - tem.blobIdx = pqxList->qxList.at(idx).blobIdx; - tem.mindis = pqxList->qxList.at(idx).fmindis; - tem.flen = pqxList->qxList.at(idx).length; - tem.error_Type = pqxList->qxList.at(idx).nqx_type; - tem.camera_name = pqxList->qxList.at(idx).camera_name; - tem.channel_name = pqxList->qxList.at(idx).channel_name; - pqxList->pChannelDetlog->AddCheckstr(PrintLevel_4, 3, "result = NG", "Add New qx,Blob idx %d type %d num %d dis %f len %f ", tem.blobIdx, tem.error_Type, tem.qx_Num, tem.mindis, tem.flen); - m_pMergedetlog->AddCheckstr(PrintLevel_4, 3, "result = NG", "%s Add New qx,Blob idx %d type %d num %d dis %f len %f ", - tem.channel_name.c_str(), tem.blobIdx, tem.error_Type, tem.qx_Num, tem.mindis, tem.flen); + continue; + } - m_reultList.resultList.push_back(tem); + // 关联通道存在与当前缺陷重叠的脏污缺陷时,该缺陷不记录NG结果 + if (IsOverlapDirtyInCorrelatedChannel(correlated_channels, + pqxList->qxList.at(idx).channel_name, + pqxList->qxList.at(idx).camera_name, + pqxList->qxList.at(idx).product_roi, + roi_expand, iou_threshold, + pqxList->pChannelDetlog)) + { + pqxList->pChannelDetlog->AddCheckstr(PrintLevel_1, 3, "result = NG skip", + "channel %s cam %s blob %d roi %s : 关联通道存在重叠, 不记录NG结果", + pqxList->qxList.at(idx).channel_name.c_str(), + pqxList->qxList.at(idx).camera_name.c_str(), + pqxList->qxList.at(idx).blobIdx, + CheckUtil::GetRectString(pqxList->qxList.at(idx).product_roi).c_str()); + m_pMergedetlog->AddCheckstr(PrintLevel_1, 3, "result = NG skip", + "channel %s cam %s blob %d roi %s : 关联通道存在重叠, 不记录NG结果", + pqxList->qxList.at(idx).channel_name.c_str(), + pqxList->qxList.at(idx).camera_name.c_str(), + pqxList->qxList.at(idx).blobIdx, + CheckUtil::GetRectString(pqxList->qxList.at(idx).product_roi).c_str()); + continue; } + + pqxList->qxList.at(idx).result = 1; + + QX_RESULT tem; + tem.blobIdx = pqxList->qxList.at(idx).blobIdx; + tem.mindis = pqxList->qxList.at(idx).fmindis; + tem.flen = pqxList->qxList.at(idx).length; + tem.error_Type = pqxList->qxList.at(idx).nqx_type; + tem.camera_name = pqxList->qxList.at(idx).camera_name; + tem.channel_name = pqxList->qxList.at(idx).channel_name; + pqxList->pChannelDetlog->AddCheckstr(PrintLevel_4, 3, "result = NG", "Add New qx,Blob idx %d type %d num %d dis %f len %f ", tem.blobIdx, tem.error_Type, tem.qx_Num, tem.mindis, tem.flen); + m_pMergedetlog->AddCheckstr(PrintLevel_4, 3, "result = NG", "%s Add New qx,Blob idx %d type %d num %d dis %f len %f ", + tem.channel_name.c_str(), tem.blobIdx, tem.error_Type, tem.qx_Num, tem.mindis, tem.flen); + + m_reultList.resultList.push_back(tem); } } }