From 211296fc2003d0a579bfb6ba27431d3d1c9476ab Mon Sep 17 00:00:00 2001 From: liusiyang Date: Fri, 23 Oct 2026 14:44:07 +0800 Subject: [PATCH] =?UTF-8?q?feat=20=E6=B7=BB=E5=8A=A0=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E9=85=8D=E7=BD=AEpin=E8=A7=92=E6=96=B9=E6=B3=95=E9=80=89?= =?UTF-8?q?=E6=8B=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AlgorithmModule/include/ImgCheckAnalysisy.hpp | 16 +- AlgorithmModule/src/ImgCheckAnalysisy.cpp | 215 ++++++++++-------- ConfigModule/include/CheckConfigDefine.h | 13 +- ConfigModule/src/JsonConfig.cpp | 4 + 4 files changed, 140 insertions(+), 108 deletions(-) diff --git a/AlgorithmModule/include/ImgCheckAnalysisy.hpp b/AlgorithmModule/include/ImgCheckAnalysisy.hpp index e927065..7467fa0 100644 --- a/AlgorithmModule/include/ImgCheckAnalysisy.hpp +++ b/AlgorithmModule/include/ImgCheckAnalysisy.hpp @@ -40,17 +40,6 @@ using namespace std; using namespace cv; -// ============================ Pin 检测方案开关 ============================ -// 1 = 新方案(Pin 直接分析, 不做模板对比): 不做仿射变换, 检测图直接按当前外定位结果裁剪; -// 只分析检测Pin: 自动区分两排Pin(考虑图像旋转)并逐排拟合直线, -// 到直线(法线方向)距离超过 pin_y_offset 的判 偏移; -// 相邻两Pin中心连线距离超过 pin_x_offset 的判 两Pin中间缺失一个Pin。 -// 0 = 旧方案(模板对比/内定位仿射): 以内定位(无效时回退外定位)仿射变换到模板坐标系后与模板Pin对比。 -// 说明: 可通过编译参数 -DPIN_USE_DIRECT_MATCH=0/1 覆盖此处默认值。 -#ifndef PIN_USE_DIRECT_MATCH -#define PIN_USE_DIRECT_MATCH 0 -#endif - enum AT_THRESHOLD_TYPE_ { AT_THRESHOLD_TYPE_NULL, @@ -207,10 +196,11 @@ private: // 边缘缺陷检测 int Pin_Qx_Det(const cv::Mat &img); -#if PIN_USE_DIRECT_MATCH + // 当前 Pin 检测方案是否为"直接分析"(参数 pinDet.Det_Method == "line_fitting") + bool IsDirectPinMethod() const; + // 新方案: 不做模板对比, 只分析检测Pin: 自动分两排(考虑旋转) -> 拟合直线判偏移 + 相邻间距判缺失 int Pin_Direct_Check(); -#endif // 把blob 汇总成 检测结果。 int BLobToDetResult(); diff --git a/AlgorithmModule/src/ImgCheckAnalysisy.cpp b/AlgorithmModule/src/ImgCheckAnalysisy.cpp index 29c541b..e1fc33a 100644 --- a/AlgorithmModule/src/ImgCheckAnalysisy.cpp +++ b/AlgorithmModule/src/ImgCheckAnalysisy.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include "AICommonDefine.h" #include #include @@ -805,65 +806,69 @@ int ImgCheckAnalysisy::CheckRun() } /*投影对齐模板*/ -#if PIN_USE_DIRECT_MATCH - // 新方案(开关 PIN_USE_DIRECT_MATCH = 1): 不做仿射变换, 检测图直接按当前外定位结果裁剪; - // Pin分析在 Pin_Qx_Det -> Pin_Direct_Check 中完成(只分析检测Pin, 不做模板对比) - m_pdetlog->AddCheckstr(PrintLevel_0, "2.1、pre detect", "Align mode: Direct Pin Check (no affine)"); - { - cv::Rect curOuterBound = m_outer_rroi.boundingRect(); - cv::Rect curImgBound(0, 0, m_CheckResult_shareP->in_shareImage->img.cols, - m_CheckResult_shareP->in_shareImage->img.rows); - curOuterBound &= curImgBound; - if (curOuterBound.width <= 0 || curOuterBound.height <= 0) - { - cv::Rect rawBound = m_outer_rroi.boundingRect(); - m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "Direct Pin Match: current outer roi invalid [%d %d %d %d]", - rawBound.x, rawBound.y, rawBound.width, rawBound.height); - m_nErrorCode = CHECK_ERROR_Config_cutRoi; - m_nCheckResultErrorCode = m_nErrorCode; - return m_nErrorCode; + // Pin 检测方案由参数 Det_Method 运行时选择(替代原 PIN_USE_DIRECT_MATCH 编译宏) + if (IsDirectPinMethod()) + { + // 新方案(Det_Method == "line_fitting"): 不做仿射变换, 检测图直接按当前外定位结果裁剪; + // Pin分析在 Pin_Qx_Det -> Pin_Direct_Check 中完成(只分析检测Pin, 不做模板对比) + m_pdetlog->AddCheckstr(PrintLevel_0, "2.1、pre detect", "Align mode: Direct Pin Check (no affine)"); + { + cv::Rect curOuterBound = m_outer_rroi.boundingRect(); + cv::Rect curImgBound(0, 0, m_CheckResult_shareP->in_shareImage->img.cols, + m_CheckResult_shareP->in_shareImage->img.rows); + curOuterBound &= curImgBound; + if (curOuterBound.width <= 0 || curOuterBound.height <= 0) + { + cv::Rect rawBound = m_outer_rroi.boundingRect(); + m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "Direct Pin Match: current outer roi invalid [%d %d %d %d]", + rawBound.x, rawBound.y, rawBound.width, rawBound.height); + m_nErrorCode = CHECK_ERROR_Config_cutRoi; + m_nCheckResultErrorCode = m_nErrorCode; + return m_nErrorCode; + } + m_outer_roi = curOuterBound; } - m_outer_roi = curOuterBound; - } -#else - // 不对原图做 warpAffine, 只计算矩阵, 并把模板 Pin 轮廓反变换到当前图坐标系。 - bool bUseInnerAlign = (m_tplInnerRect.size.width > 0 && m_tplInnerRect.size.height > 0 && - m_inner_rroi.size.width > 0 && m_inner_rroi.size.height > 0); - if (bUseInnerAlign) - { - m_pdetlog->AddCheckstr(PrintLevel_0, "2.1、pre detect", - "Affine align basis: Inner(inner_rroi -> tplInnerRect), template pins warp only, image NOT warped"); - CalAffineMats(m_tplInnerRect, m_inner_rroi); } else { - m_pdetlog->AddCheckstr(PrintLevel_0, "2.1、pre detect", - "Affine align basis: Outer(outer_rroi -> tplOuterRect), template pins warp only, image NOT warped"); - CalAffineMats(m_tplOuterRect, m_outer_rroi); - } + // 旧方案(Det_Method == "BTB_Marix" 或未配置): 不对原图做 warpAffine, 只计算矩阵, 并把模板 Pin 轮廓反变换到当前图坐标系。 + bool bUseInnerAlign = (m_tplInnerRect.size.width > 0 && m_tplInnerRect.size.height > 0 && + m_inner_rroi.size.width > 0 && m_inner_rroi.size.height > 0); + if (bUseInnerAlign) + { + m_pdetlog->AddCheckstr(PrintLevel_0, "2.1、pre detect", + "Affine align basis: Inner(inner_rroi -> tplInnerRect), template pins warp only, image NOT warped"); + CalAffineMats(m_tplInnerRect, m_inner_rroi); + } + else + { + m_pdetlog->AddCheckstr(PrintLevel_0, "2.1、pre detect", + "Affine align basis: Outer(outer_rroi -> tplOuterRect), template pins warp only, image NOT warped"); + CalAffineMats(m_tplOuterRect, m_outer_rroi); + } - // 后续所有检测/裁剪都在当前图坐标系: 裁剪框取当前外定位的包围框(原图不 warp) - { - cv::Rect curOuterBound = m_outer_rroi.boundingRect(); - cv::Rect curImgBound(0, 0, m_CheckResult_shareP->in_shareImage->img.cols, - m_CheckResult_shareP->in_shareImage->img.rows); - m_outer_roi = curOuterBound & curImgBound; - if (m_outer_roi.width <= 0 || m_outer_roi.height <= 0) + // 后续所有检测/裁剪都在当前图坐标系: 裁剪框取当前外定位的包围框(原图不 warp) { - m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "Affine mode: current outer roi invalid [%d %d %d %d]", - curOuterBound.x, curOuterBound.y, curOuterBound.width, curOuterBound.height); - m_nErrorCode = CHECK_ERROR_Config_cutRoi; - m_nCheckResultErrorCode = m_nErrorCode; - return m_nErrorCode; + cv::Rect curOuterBound = m_outer_rroi.boundingRect(); + cv::Rect curImgBound(0, 0, m_CheckResult_shareP->in_shareImage->img.cols, + m_CheckResult_shareP->in_shareImage->img.rows); + m_outer_roi = curOuterBound & curImgBound; + if (m_outer_roi.width <= 0 || m_outer_roi.height <= 0) + { + m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "Affine mode: current outer roi invalid [%d %d %d %d]", + curOuterBound.x, curOuterBound.y, curOuterBound.width, curOuterBound.height); + m_nErrorCode = CHECK_ERROR_Config_cutRoi; + m_nCheckResultErrorCode = m_nErrorCode; + return m_nErrorCode; + } } - } - // 模板 Pin(pinImg 坐标) -> 模板图坐标 -> 当前图坐标 -> detImg 坐标 - TransformTplPinContoursToCur(); - m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "2.1、pre detect", - "tpl pin contours -> current frame: %ld (affineValid %d)", - static_cast(m_tplPinContoursCur.size()), static_cast(m_bAffineValid)); -#endif + // 模板 Pin(pinImg 坐标) -> 模板图坐标 -> 当前图坐标 -> detImg 坐标 + TransformTplPinContoursToCur(); + m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "2.1、pre detect", + "tpl pin contours -> current frame: %ld (affineValid %d)", + static_cast(m_tplPinContoursCur.size()), static_cast(m_bAffineValid)); + } m_Crop_Roi_paramImg = m_outer_roi; m_pImageAllResult->pDetResult->CutRoi = m_outer_roi; m_pImageAllResult->pDetResult->Param_CropRoi = m_Crop_Roi_paramImg; @@ -2127,27 +2132,28 @@ int ImgCheckAnalysisy::DrawResult_Step_1() float fs_resize_y = m_pImageAllResult->resultImg.rows * 1.0f / m_pImageAllResult->detImg.rows; // 绘制模板 pin 轮廓(绿色)与当前 pin 轮廓(红色) -#if PIN_USE_DIRECT_MATCH - // 新方案: 模板pin已配准到当前检测图坐标系 - // 新方案: 不做模板对比; 将两排Pin拟合直线段与缺失标记组织成 2点"轮廓", 复用下方循环缩放绘制(绿色) - std::vector> tplDrawPinContours; - for (const auto &seg : m_pinFitLines) - { - tplDrawPinContours.push_back({cv::Point(cvRound(seg.first.x), cvRound(seg.first.y)), - cv::Point(cvRound(seg.second.x), cvRound(seg.second.y))}); - } - for (const auto &mark : m_pinMissMarks) + // Pin 绘制内容按当前检测方案选择: 新方案绘制拟合直线/缺失标记, 旧方案绘制反变换后的模板Pin + std::vector> directDrawPinContours; + const std::vector> *pTplDrawPinContours = &m_tplPinContoursCur; + if (IsDirectPinMethod()) { - // 缺失位置: 十字标记(两条短线) - tplDrawPinContours.push_back({cv::Point(cvRound(mark.x - 6.0f), cvRound(mark.y)), - cv::Point(cvRound(mark.x + 6.0f), cvRound(mark.y))}); - tplDrawPinContours.push_back({cv::Point(cvRound(mark.x), cvRound(mark.y - 6.0f)), - cv::Point(cvRound(mark.x), cvRound(mark.y + 6.0f))}); + // 新方案: 不做模板对比; 将两排Pin拟合直线段与缺失标记组织成 2点"轮廓", 复用下方循环缩放绘制(绿色) + for (const auto &seg : m_pinFitLines) + { + directDrawPinContours.push_back({cv::Point(cvRound(seg.first.x), cvRound(seg.first.y)), + cv::Point(cvRound(seg.second.x), cvRound(seg.second.y))}); + } + for (const auto &mark : m_pinMissMarks) + { + // 缺失位置: 十字标记(两条短线) + directDrawPinContours.push_back({cv::Point(cvRound(mark.x - 6.0f), cvRound(mark.y)), + cv::Point(cvRound(mark.x + 6.0f), cvRound(mark.y))}); + directDrawPinContours.push_back({cv::Point(cvRound(mark.x), cvRound(mark.y - 6.0f)), + cv::Point(cvRound(mark.x), cvRound(mark.y + 6.0f))}); + } + pTplDrawPinContours = &directDrawPinContours; } -#else - const std::vector> &tplDrawPinContours = m_tplPinContoursCur; -#endif - for (const auto &contour : tplDrawPinContours) + for (const auto &contour : *pTplDrawPinContours) { std::vector draw_pts; draw_pts.reserve(contour.size()); @@ -2240,6 +2246,31 @@ int ImgCheckAnalysisy::UpdateImgageScale() return 0; } +// 当前 Pin 检测方案是否为"直接分析"(新方案): 由参数 pinDet.Det_Method 在运行时决定 +// "line_fitting" -> 新方案(不做模板对比); 其它/未配置 -> 旧方案(模板对比) +bool ImgCheckAnalysisy::IsDirectPinMethod() const +{ + if (m_pbaseCheckFunction == NULL) + { + return false; + } + + std::string method = m_pbaseCheckFunction->pinDet.Det_Method; + // 去掉首尾空白并转小写, 兼容大小写/带空格的配置值 + size_t begin = method.find_first_not_of(" \t\r\n"); + if (begin == std::string::npos) + { + return false; + } + size_t end = method.find_last_not_of(" \t\r\n"); + method = method.substr(begin, end - begin + 1); + std::transform(method.begin(), method.end(), method.begin(), + [](unsigned char c) + { return static_cast(std::tolower(c)); }); + + return (method == "line_fitting"); +} + int ImgCheckAnalysisy::Pin_Qx_Det(const cv::Mat &img) { @@ -2278,10 +2309,12 @@ int ImgCheckAnalysisy::Pin_Qx_Det(const cv::Mat &img) m_curPinContours.push_back(mapped); } -#if PIN_USE_DIRECT_MATCH - // 新方案: 不做模板对比, 只分析检测Pin(自动分两排 -> 逐排拟合直线判偏移 + 相邻间距判缺失) - Pin_Direct_Check(); -#else + if (IsDirectPinMethod()) + { + // 新方案: 不做模板对比, 只分析检测Pin(自动分两排 -> 逐排拟合直线判偏移 + 相邻间距判缺失) + Pin_Direct_Check(); + } + else // 根据模板Pin和当前Pin轮廓进行对比,判断当前有无缺失和偏移 { // 计算轮廓中心点(外接矩形中心) @@ -2533,8 +2566,7 @@ int ImgCheckAnalysisy::Pin_Qx_Det(const cv::Mat &img) m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Check", "tpl %d cur %d miss %d offset %d", nTpl, nCur, nMiss, nOffset); } } -#endif - + if (m_Edge_DetConfig.bSaveResultImg) { // 画布优先用原图裁剪区(保持原图通道),ReJson 或异常时退回检测图 @@ -2554,29 +2586,31 @@ int ImgCheckAnalysisy::Pin_Qx_Det(const cv::Mat &img) showimg = img.clone(); } cv::drawContours(showimg, m_curPinContours, -1, CheckUtil::GetDrawColor(showimg, cv::Scalar(0, 0, 255)), 1); -#if PIN_USE_DIRECT_MATCH - // 新方案: 绘制两排Pin的拟合直线(绿); 缺失位置以黄色十字标记 - for (const auto &seg : m_pinFitLines) + if (IsDirectPinMethod()) { - cv::line(showimg, cv::Point(cvRound(seg.first.x), cvRound(seg.first.y)), - cv::Point(cvRound(seg.second.x), cvRound(seg.second.y)), - CheckUtil::GetDrawColor(showimg, cv::Scalar(0, 255, 0)), 1); + // 新方案: 绘制两排Pin的拟合直线(绿); 缺失位置以黄色十字标记 + for (const auto &seg : m_pinFitLines) + { + cv::line(showimg, cv::Point(cvRound(seg.first.x), cvRound(seg.first.y)), + cv::Point(cvRound(seg.second.x), cvRound(seg.second.y)), + CheckUtil::GetDrawColor(showimg, cv::Scalar(0, 255, 0)), 1); + } + for (const auto &mark : m_pinMissMarks) + { + cv::drawMarker(showimg, cv::Point(cvRound(mark.x), cvRound(mark.y)), + CheckUtil::GetDrawColor(showimg, cv::Scalar(0, 255, 255)), cv::MARKER_CROSS, 16, 1); + } } - for (const auto &mark : m_pinMissMarks) + else { - cv::drawMarker(showimg, cv::Point(cvRound(mark.x), cvRound(mark.y)), - CheckUtil::GetDrawColor(showimg, cv::Scalar(0, 255, 255)), cv::MARKER_CROSS, 16, 1); + cv::drawContours(showimg, m_tplPinContoursCur, -1, CheckUtil::GetDrawColor(showimg, cv::Scalar(0, 255, 0)), 1); } -#else - cv::drawContours(showimg, m_tplPinContoursCur, -1, CheckUtil::GetDrawColor(showimg, cv::Scalar(0, 255, 0)), 1); -#endif imwrite("pin_contours.png", showimg); } return 0; } -#if PIN_USE_DIRECT_MATCH #if 0 // ----- 旧配准匹配方案(Pin_Direct_Match)已停用, 仅保留代码参考; 如需启用需恢复 m_tplPinContoursCur 成员 ----- // 新方案(开关 PIN_USE_DIRECT_MATCH = 1): 不做仿射变换, 直接对模板Pin与检测Pin做配准后对比 // 输入: m_tplPinContours(模板裁剪坐标系) / m_curPinContours(当前检测图坐标系) @@ -2842,7 +2876,7 @@ int ImgCheckAnalysisy::Pin_Direct_Match() } #endif -// 新方案(开关 PIN_USE_DIRECT_MATCH = 1): 不做模板对比, 只针对检测Pin分析 +// 新方案(Det_Method == "line_fitting"): 不做模板对比, 只针对检测Pin分析 // 1) 方向扫描(0~180°, 步长1°): 取 "两排Pin沿法线投影组内方差之和" 最小的角度作为Pin排方向(对图像旋转不敏感), // 再沿该法线投影, 在最大间隙处把Pin分成两排(两排插口; Pin不足4个则按一排处理); // 2) 每排做最小二乘直线拟合(cv::fitLine): pin中心到直线(沿法线方向)距离按成像精度换算为 mm, 超过 pin_y_offset(mm) 判 偏移; @@ -3147,7 +3181,6 @@ int ImgCheckAnalysisy::Pin_Direct_Check() nCur, static_cast(rows.size()), nMiss, nOffset); return 0; } -#endif // PIN_USE_DIRECT_MATCH // 获取指定 残点(blob) 是由哪个(哪些)检测模型检出的 // 做法:对各模型自己输出的全图 mask,在 blob 的 roi 内统计非零像素; diff --git a/ConfigModule/include/CheckConfigDefine.h b/ConfigModule/include/CheckConfigDefine.h index 4ea37f1..7002cdc 100644 --- a/ConfigModule/include/CheckConfigDefine.h +++ b/ConfigModule/include/CheckConfigDefine.h @@ -1247,6 +1247,9 @@ struct Base_Function_Pin_Det bool bOpen; // 是否开启 float pin_x_offset; // pin x 偏移 float pin_y_offset; // pin y 偏移 + // "line_fitting" = 新方案(直接分析检测Pin: 分两排拟合直线判偏移 + 相邻间距判缺失, 不做模板对比) + // "BTB_Marix" = 旧方案(内定位仿射 + 模板Pin对比) + std::string Det_Method; // pin 检测方案 bool bDrawResult; // 是否绘制结果 Base_Function_Pin_Det() @@ -1258,6 +1261,7 @@ struct Base_Function_Pin_Det bOpen = false; pin_x_offset = 0; pin_y_offset = 0; + Det_Method = "BTB_Marix"; bDrawResult = false; } @@ -1266,18 +1270,19 @@ struct Base_Function_Pin_Det this->bOpen = tem.bOpen; this->pin_x_offset = tem.pin_x_offset; this->pin_y_offset = tem.pin_y_offset; + this->Det_Method = tem.Det_Method; this->bDrawResult = tem.bDrawResult; } void print(std::string str) { - printf("%s>>bOpen %d pin_x_offset %f pin_y_offset %f bDrawResult %d\n", str.c_str(), - bOpen, pin_x_offset, pin_y_offset, bDrawResult); + printf("%s>>bOpen %d pin_x_offset %f pin_y_offset %f Det_Method %s bDrawResult %d\n", str.c_str(), + bOpen, pin_x_offset, pin_y_offset, Det_Method.c_str(), bDrawResult); } std::string GetInfo(std::string str) { char buffer[256]; - sprintf(buffer, "%s>>bOpen %d pin_x_offset %f pin_y_offset %f bDrawResult %d\n", str.c_str(), - bOpen, pin_x_offset, pin_y_offset, bDrawResult); + sprintf(buffer, "%s>>bOpen %d pin_x_offset %f pin_y_offset %f Det_Method %s bDrawResult %d\n", str.c_str(), + bOpen, pin_x_offset, pin_y_offset, Det_Method.c_str(), bDrawResult); std::string str123 = buffer; return str123; } diff --git a/ConfigModule/src/JsonConfig.cpp b/ConfigModule/src/JsonConfig.cpp index eb9e047..fcd9905 100644 --- a/ConfigModule/src/JsonConfig.cpp +++ b/ConfigModule/src/JsonConfig.cpp @@ -822,6 +822,10 @@ int BaseFuntonConfigJson::GetFunction(Json::Value value) if (_config.pinDet.bOpen) { + if (value_f["form"]["Det_Config"]["Det_Method"]) + { + _config.pinDet.Det_Method = value_f["form"]["Det_Config"]["Det_Method"].asString(); + } if (value_f["form"]["Det_Config"]["pin_x_offset"]) { _config.pinDet.pin_x_offset = value_f["form"]["Det_Config"]["pin_x_offset"].asFloat();