diff --git a/AlgorithmModule/include/ImageResultJudge.h b/AlgorithmModule/include/ImageResultJudge.h index 9cca6c6..d48586b 100644 --- a/AlgorithmModule/include/ImageResultJudge.h +++ b/AlgorithmModule/include/ImageResultJudge.h @@ -107,10 +107,15 @@ public: int UpdateConfig(std::string strcameraName); int DrawResult(std::shared_ptr pImageResult); - // 盲失联合判断:主通道(ms_main_channel)检出的盲失(Other)若在过滤通道(ms_filter_channels)同位置存在缺陷, + // 盲失联合判断:主通道(ms_main_channel)检出的盲失(Other)若在过滤通道(ms_filter_channels)同位置存在盲失, // 则不算盲失(撤销NG)。同位置判断参考暗点联合分析:双方按 ms_expand 扩张后计算 IOU, 大于 ms_iou 认为同位置 int FilterMsBlindNG(std::shared_ptr pCheck_Result); + // cell黑白点联合判断:主通道(cellbw_main_channel)检出的cell黑白点在过滤通道(cellbw_filter_channels) + // 同位置存在cell黑白点(白点/黑点均算)时不算cell黑白点(撤销NG) + // 同位置判断参考暗点联合分析:双方按 cellbw_expand 扩张后计算 IOU, 大于 cellbw_iou 认为同位置 + int FilterCellBwNG(std::shared_ptr pCheck_Result); + private: // 获取 缺陷在参数列表的位置 int GetParamidx(); @@ -142,13 +147,33 @@ private: // 撤销单个缺陷的NG结果:恢复为OK,移除对应NG小图并回退计数 int UndoNGResult(std::shared_ptr pImageResult, int qxidx, QX_ERROR_INFO_ *QX_info); - // 判断盲失(产品坐标系ROI)在配置的过滤通道是否存在同位置缺陷(双方按 expand 扩张后计算IOU) + // 跨通道同位置判断:当前缺陷(产品坐标系ROI)在过滤通道是否存在指定类型的同位置缺陷(双方按 expand 扩张后计算IOU) + bool IsOverlapQxInFilterChannels(const std::vector> &imgResults, + const std::string &cur_channel_name, + const std::vector &filter_channels, + const std::vector &match_qx_types, + const cv::Rect &qx_product_roi, + int roi_expand, float iou_threshold, + const std::string &strTag, + std::shared_ptr plog); + + // 判断盲失(产品坐标系ROI)在配置的过滤通道是否存在同位置盲失缺陷(双方按 expand 扩张后计算IOU) bool IsOverlapQxInMsFilterChannel(const std::vector> &imgResults, const std::string &cur_channel_name, const cv::Rect &qx_product_roi, int roi_expand, float iou_threshold, std::shared_ptr plog); + // 跨通道联合判断(通用):主通道中指定类型的NG缺陷,若在过滤通道同位置存在指定类型的缺陷,则撤销该NG + int FilterCrossChannelNG(std::shared_ptr pCheck_Result, + const std::vector &main_channels, + const std::vector &filter_channels, + const std::vector &main_qx_types, + const std::vector &match_qx_types, + int roi_expand, float iou_threshold, + const std::string &strTag, + const std::string &strReason); + // 关联过滤源过滤:撤销与关联通道过滤源缺陷重叠的暗点NG结果(含单图判定的单点NG) int FilterCorrelatedDirtyNG(std::shared_ptr pImageResult); diff --git a/AlgorithmModule/src/CameraCheckAnalysisy.cpp b/AlgorithmModule/src/CameraCheckAnalysisy.cpp index dd46f6a..770bf0a 100644 --- a/AlgorithmModule/src/CameraCheckAnalysisy.cpp +++ b/AlgorithmModule/src/CameraCheckAnalysisy.cpp @@ -351,6 +351,16 @@ int CameraCheckAnalysisy::ResultParamJudge() } } + // cell黑白点联合判断:主通道(cellbw_main_channel)检出的cell黑白点若在过滤通道(cellbw_filter_channels)同位置 + // 存在cell黑白点(白点/黑点均算),则不算cell黑白点(撤销NG) + { + int nCellBwFilterNum = m_imageResultJudge.FilterCellBwNG(m_pCheck_Result); + if (nCellBwFilterNum > 0) + { + m_pdetlog->AddCheckstr(PrintLevel_1, "ResultParamJudge", "cell黑白点联合判断: 过滤通道同位置存在cell黑白点, 过滤NG %d 个", nCellBwFilterNum); + } + } + m_pQX_Merge_Analysis->setSendStatus(m_strcameraName, QX_Merge_Analysis::RUN_STATUS_READY); m_pdetlog->AddCheckstr(PrintLevel_1, "ResultParamJudge", "===============QX_Merge_Analysis===start %s", CheckUtil::getCurTimeHMS().c_str()); diff --git a/AlgorithmModule/src/ImageResultJudge.cpp b/AlgorithmModule/src/ImageResultJudge.cpp index 28b3c08..492e253 100644 --- a/AlgorithmModule/src/ImageResultJudge.cpp +++ b/AlgorithmModule/src/ImageResultJudge.cpp @@ -1145,6 +1145,247 @@ int ImageResultJudge::FilterMsBlindNG(std::shared_ptr pCheck_Resul return nFilterNum; } +/** + * @brief 跨通道同位置判断:判断当前缺陷(产品坐标系ROI)在过滤通道是否存在指定类型的同位置缺陷 + * 同位置判断方式参考暗点联合分析:当前ROI与过滤通道缺陷ROI双方均按 expand 扩张后计算 IOU + * @param imgResults 当前相机所有通道的检测结果 + * @param cur_channel_name 当前缺陷所属通道(主通道) + * @param filter_channels 需要比较的过滤通道 + * @param match_qx_types 过滤通道中参与比较的缺陷类型(CONFIG_QX_NAME_) + * @param qx_product_roi 当前缺陷的产品坐标系ROI + * @param roi_expand 扩张像素 + * @param iou_threshold IOU阈值 + * @param strTag 日志标签 + * @param plog 日志 + * @return bool 过滤通道同位置存在指定类型缺陷返回true + */ +bool ImageResultJudge::IsOverlapQxInFilterChannels(const std::vector> &imgResults, + const std::string &cur_channel_name, + const std::vector &filter_channels, + const std::vector &match_qx_types, + const cv::Rect &qx_product_roi, + int roi_expand, float iou_threshold, + const std::string &strTag, + std::shared_ptr plog) +{ + // 防御:ROI非法/未配置过滤通道/未指定匹配类型时不判断,避免无效计算与除零 + if (qx_product_roi.width <= 0 || qx_product_roi.height <= 0) + { + return false; + } + if (filter_channels.empty() || match_qx_types.empty()) + { + return false; + } + if (m_pAnalysisyConfig == nullptr) + { + return false; + } + float fproduct_offx_mm = m_pAnalysisyConfig->commonCheckConfig.baseConfig.fProduct_Off_X_mm; + int fproduct_offx_piexl = int(fproduct_offx_mm / m_fImgage_Scale_X); + + // 与暗点联合分析保持一致:双方均按 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 imgidx = 0; imgidx < (int)imgResults.size(); imgidx++) + { + std::shared_ptr pFilterImage = imgResults.at(imgidx); + if (pFilterImage == nullptr) + { + continue; + } + // 只与配置的过滤通道比较;当前通道不参与,避免自身重叠导致误过滤 + if (pFilterImage->strChannel == cur_channel_name) + { + continue; + } + if (std::find(filter_channels.begin(), filter_channels.end(), pFilterImage->strChannel) == filter_channels.end()) + { + continue; + } + std::shared_ptr pFilterDetResult = pFilterImage->pDetResult; + if (pFilterDetResult == nullptr || pFilterDetResult->pQx_ErrorList == nullptr) + { + continue; + } + + // 只统计过滤通道内指定类型的缺陷,与具体判定结果无关 + for (int j = 0; j < (int)pFilterDetResult->pQx_ErrorList->size(); j++) + { + const QX_ERROR_INFO_ &filter_qx = pFilterDetResult->pQx_ErrorList->at(j); + if (std::find(match_qx_types.begin(), match_qx_types.end(), filter_qx.nconfig_qx_type) == match_qx_types.end()) + { + continue; + } + cv::Rect filter_roi = filter_qx.roi; + filter_roi.x += fproduct_offx_piexl; + cv::Rect filter_expanded(filter_roi.x - roi_expand, filter_roi.y - roi_expand, + filter_roi.width + roi_expand * 2, filter_roi.height + roi_expand * 2); + + float fiou = CheckUtil::CalIoU(roi_expanded, filter_expanded); + if (plog) + { + plog->AddCheckstr(PrintLevel_3, DET_LOG_LEVEL_3, strTag, + "main channel %s roi %s <-> filter channel %s qx %d roi %s expand %d iou %f", + cur_channel_name.c_str(), CheckUtil::GetRectString(qx_product_roi).c_str(), + pFilterImage->strChannel.c_str(), j, + CheckUtil::GetRectString(filter_roi).c_str(), roi_expand, fiou); + } + // IOU 为 NaN(ROI 非法导致的除零)时比较结果为 false,不会误过滤 + if (fiou > iou_threshold) + { + if (plog) + { + plog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, strTag, + "main channel %s roi %s overlap filter channel %s qx %d roi %s expand %d iou %f > %f", + cur_channel_name.c_str(), CheckUtil::GetRectString(qx_product_roi).c_str(), + pFilterImage->strChannel.c_str(), j, + CheckUtil::GetRectString(filter_roi).c_str(), roi_expand, fiou, iou_threshold); + } + return true; + } + } + } + + return false; +} + +/** + * @brief 跨通道联合判断(通用):主通道中指定类型的NG缺陷,若在过滤通道同位置存在指定类型的缺陷,则撤销该NG + * 同位置判断方式参考暗点联合分析:双方按 expand 扩张后计算 IOU, 大于 iou_threshold 认为同位置 + * @param pCheck_Result 当前相机检测结果(包含该相机所有通道的检测结果) + * @param main_channels 主通道 + * @param filter_channels 过滤通道 + * @param main_qx_types 参与判断的主通道缺陷类型(CONFIG_QX_NAME_) + * @param match_qx_types 过滤通道中参与比较的缺陷类型(CONFIG_QX_NAME_) + * @param roi_expand 扩张像素 + * @param iou_threshold IOU阈值 + * @param strTag 日志标签 + * @param strReason 过滤原因(日志) + * @return int 被过滤的NG个数 + */ +int ImageResultJudge::FilterCrossChannelNG(std::shared_ptr pCheck_Result, + const std::vector &main_channels, + const std::vector &filter_channels, + const std::vector &main_qx_types, + const std::vector &match_qx_types, + int roi_expand, float iou_threshold, + const std::string &strTag, + const std::string &strReason) +{ + if (pCheck_Result == nullptr || m_pAnalysisyConfig == nullptr) + { + return 0; + } + // 未配置主通道/过滤通道/判断类型时不需要判断 + if (main_channels.empty() || filter_channels.empty() || main_qx_types.empty()) + { + return 0; + } + float fproduct_offx_mm = m_pAnalysisyConfig->commonCheckConfig.baseConfig.fProduct_Off_X_mm; + int fproduct_offx_piexl = int(fproduct_offx_mm / m_fImgage_Scale_X); + + int nFilterNum = 0; + for (int imgidx = 0; imgidx < (int)pCheck_Result->ImageALLDetResultList.size(); imgidx++) + { + std::shared_ptr pImageResult = pCheck_Result->ImageALLDetResultList.at(imgidx); + if (pImageResult == nullptr) + { + continue; + } + // 只处理配置的主通道 + if (std::find(main_channels.begin(), main_channels.end(), pImageResult->strChannel) == main_channels.end()) + { + continue; + } + std::shared_ptr pDetResult = pImageResult->pDetResult; // 检测结果 + if (pDetResult == nullptr || pDetResult->pQx_ErrorList == nullptr || pImageResult->result == nullptr) + { + continue; + } + std::shared_ptr pImageLog = pImageResult->detlog; + + for (int qxidx = 0; qxidx < (int)pDetResult->pQx_ErrorList->size(); qxidx++) + { + QX_ERROR_INFO_ *QX_info = &pDetResult->pQx_ErrorList->at(qxidx); + // 只处理指定类型的 NG 结果(YS 不降级) + if (QX_info->result != QX_RESULT_TYPE_NG) + { + continue; + } + if (std::find(main_qx_types.begin(), main_qx_types.end(), QX_info->nconfig_qx_type) == main_qx_types.end()) + { + continue; + } + + cv::Rect product_roi = QX_info->roi; + product_roi.x += fproduct_offx_piexl; + // 过滤通道同位置不存在匹配类型的缺陷时,保留该NG + if (!IsOverlapQxInFilterChannels(pCheck_Result->ImageALLDetResultList, + pImageResult->strChannel, + filter_channels, match_qx_types, + product_roi, + roi_expand, iou_threshold, + strTag, pImageLog)) + { + continue; + } + + // 撤销NG结果 + UndoNGResult(pImageResult, qxidx, QX_info); + nFilterNum++; + + if (pImageLog) + { + pImageLog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, strTag, + "img %s cam %s blob %d qx %s roi %s : %s", + pImageResult->strChannel.c_str(), pImageResult->cameraBaseResult->strCameraName.c_str(), + qxidx, QX_info->qx_name.c_str(), CheckUtil::GetRectString(product_roi).c_str(), + strReason.c_str()); + } + if (m_pQX_Merge_Analysis != nullptr && m_pQX_Merge_Analysis->m_pMergedetlog) + { + m_pQX_Merge_Analysis->m_pMergedetlog->AddCheckstr(PrintLevel_1, 3, strTag, + "img %s cam %s blob %d qx %s roi %s : %s", + pImageResult->strChannel.c_str(), pImageResult->cameraBaseResult->strCameraName.c_str(), + qxidx, QX_info->qx_name.c_str(), CheckUtil::GetRectString(product_roi).c_str(), + strReason.c_str()); + } + } + } + + return nFilterNum; +} + +/** + * @brief cell黑白点联合判断:主通道(cellbw_main_channel)检出的cell黑白点在过滤通道(cellbw_filter_channels) + * 同位置存在cell黑白点(白点/黑点均算)时不算cell黑白点(撤销NG) + * 同位置判断方式参考暗点联合分析:双方按 cellbw_expand 扩张后计算 IOU, 大于 cellbw_iou 认为同位置 + * @param pCheck_Result 当前相机检测结果(包含该相机所有通道的检测结果) + * @return int 被过滤的cell黑白点NG个数 + */ +int ImageResultJudge::FilterCellBwNG(std::shared_ptr pCheck_Result) +{ + if (m_pbaseCheckFunction == nullptr) + { + return 0; + } + Base_Function_CellBW_Check *pCellBwParam = &m_pbaseCheckFunction->cellbw_check; + // 未开启cell黑白点分析时不需要判断(主通道/过滤通道为空时由通用逻辑直接返回) + if (!pCellBwParam->bOpen) + { + return 0; + } + // 白点/黑点通用:主通道与过滤通道均按 cell 黑白点匹配(白↔白、黑↔黑、白↔黑都算) + const std::vector qxTypes = {CONFIG_QX_NAME_white_Cell, CONFIG_QX_NAME_black_Cell}; + return FilterCrossChannelNG(pCheck_Result, + pCellBwParam->cellbw_main_channel, pCellBwParam->cellbw_filter_channels, + qxTypes, qxTypes, + (int)(round)(pCellBwParam->CellBW_Expand), pCellBwParam->CellBW_IOU, + "CellBw", "过滤通道同位置存在cell黑白点, 不算cell黑白点"); +} + int ImageResultJudge::MergeResult(std::shared_ptr pImageResult, QX_Analysis_Result_List *ptemre) {