From 050dd312407fe9aff7374363948f7e3ebbe68b19 Mon Sep 17 00:00:00 2001 From: liusiyang Date: Fri, 23 Oct 2026 15:15:41 +0800 Subject: [PATCH] =?UTF-8?q?update=20=E4=BC=98=E5=8C=96pin=E6=95=B4?= =?UTF-8?q?=E4=BD=93=E5=81=8F=E7=A7=BB=E8=AE=A1=E7=AE=97=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AlgorithmModule/src/ImgCheckAnalysisy.cpp | 258 ++++++++++++++-------- 1 file changed, 165 insertions(+), 93 deletions(-) diff --git a/AlgorithmModule/src/ImgCheckAnalysisy.cpp b/AlgorithmModule/src/ImgCheckAnalysisy.cpp index e1fc33a..39c18a4 100644 --- a/AlgorithmModule/src/ImgCheckAnalysisy.cpp +++ b/AlgorithmModule/src/ImgCheckAnalysisy.cpp @@ -18,11 +18,24 @@ #include // ===================== Pin 整体偏移估计参数 ===================== -static const double PIN_SHIFT_MIN_SUPPORT_RATIO = 0.80; // 判定整体偏移所需的最小支持度(支持pin数 / 模板pin总数) +static const double PIN_SHIFT_MIN_SUPPORT_RATIO = 0.80; // 判定整体偏移所需的最小支持度(支持pin数 / 该排模板pin数) static const float PIN_SHIFT_BIN_SIZE_PX = 4.0f; // 位移投票的网格尺寸(px), 同时作为峰值邻域半径 static const float PIN_SHIFT_MAX_GAP_RATIO = 0.5f; // 整体位移量级上限 = 相邻pin最小间距 * 该比例 // ====================================================================================== +// 计算点相对旋转矩形“长边轴线”的带符号法向距离(检测图坐标系): +// 取矩形长边方向作为 Pin 排方向, 返回点沿短边(法线)方向相对矩形中心的投影; +// 正负号天然把分布在矩形两侧的 Pin 分成两组(上下两排分别估计整体偏移)。 +static float PinRowSideOfRect(const cv::RotatedRect &rr, const cv::Point2f &pt) +{ + const float th = static_cast(rr.angle * CV_PI / 180.0); + // OpenCV RotatedRect(图像坐标系, y 轴向下): 宽度方向 (cos, -sin), 高度方向 (sin, cos) + const cv::Point2f widthDir(std::cos(th), -std::sin(th)); + const cv::Point2f heightDir(std::sin(th), std::cos(th)); + const cv::Point2f normalDir = (rr.size.width >= rr.size.height) ? heightDir : widthDir; + return (pt - rr.center).dot(normalDir); +} + // 分类映射配置文件(每行:label索引:缺陷名称;冒号后名称为该缺陷类型名称,行的顺序即缺陷类型索引) static const std::string CLS_LABEL_CONFIG_FILE = "/home/aidlux/BOE/UseModel_ZB/KK_cls.txt"; @@ -2349,124 +2362,183 @@ int ImgCheckAnalysisy::Pin_Qx_Det(const cv::Mat &img) std::vector curUsed(nCur, false); // 整体偏移估计与消除(阈值见文件顶部 PIN_SHIFT_* 常量): - // 若足够多的模板 pin 都具有方向一致、量级接近的位移, 则把模板 pin 轮廓整体平移到与检测 pin 一致的位置, - // 后续匹配/偏移判据均基于平移后的模板 pin { - // 相邻 pin 最小间距: 用于限制整体偏移的量级(整体偏移不应接近一个 pin 间距, 否则无法与"错配一列"区分) - float minGap = 0.0f; - for (int i = 0; i < nTpl; i++) + // inner 定位 ROI 转到 detImg 坐标系(detImg 由 m_outer_roi 裁剪得到) + cv::RotatedRect innerRectDet = m_inner_rroi; + innerRectDet.center.x -= static_cast(m_outer_roi.x); + innerRectDet.center.y -= static_cast(m_outer_roi.y); + + // 1) 用 inner ROI 的长边法线方向区分两组 pin: 法向坐标 < 0 为一排, >= 0 为另一排 + std::vector> shiftGroups; + std::vector shiftGroupNames; + if (m_inner_rroi.size.width > 0 && m_inner_rroi.size.height > 0) { - for (int j = i + 1; j < nTpl; j++) + std::vector> sideIdx(2); + for (int i = 0; i < nTpl; i++) { - float d = static_cast(cv::norm(tplCenters[i] - tplCenters[j])); - if (minGap <= 0.0f || d < minGap) - { - minGap = d; - } + sideIdx[(PinRowSideOfRect(innerRectDet, tplCenters[i]) < 0.0f) ? 0 : 1].push_back(i); + } + // 两排各自至少 2 个 pin 才能独立可靠估计; 否则回退为整体估计 + if (sideIdx[0].size() >= 2 && sideIdx[1].size() >= 2) + { + shiftGroups.push_back(sideIdx[0]); + shiftGroupNames.push_back("upper row"); + shiftGroups.push_back(sideIdx[1]); + shiftGroupNames.push_back("lower row"); } + else + { + m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Pin Check", + "pin row split by inner roi invalid: upper %d lower %d, estimate as a whole", + static_cast(sideIdx[0].size()), static_cast(sideIdx[1].size())); + } + } + else + { + m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Pin Check", + "inner roi invalid, pin offset estimate as a whole"); + } + if (shiftGroups.empty()) + { + // inner ROI 无效或无法分排: 回退为整体估计 + std::vector allIdx(nTpl); + for (int i = 0; i < nTpl; i++) + { + allIdx[i] = i; + } + shiftGroups.push_back(allIdx); + shiftGroupNames.push_back("all"); } + // 2) 逐排(逐组)估计整体位移并消除 if (nCur > 0) { - // 1) 每个模板 pin 取最近检测 pin 的位移作为候选位移(最近检测 pin 必定存在, nCur > 0) - std::vector disp(nTpl); - for (int i = 0; i < nTpl; i++) + for (size_t g = 0; g < shiftGroups.size(); g++) { - int nearestIdx = 0; - double bestD = 1e12; - for (int j = 0; j < nCur; j++) + const std::vector &group = shiftGroups[g]; + const int nGroup = static_cast(group.size()); + if (nGroup <= 0) { - double d = cv::norm(curCenters[j] - tplCenters[i]); - if (d < bestD) + continue; + } + + // 组内相邻 pin 最小间距: 用于限制整体偏移的量级(整体偏移不应接近一个 pin 间距, 否则无法与"错配一列"区分) + float minGap = 0.0f; + for (int a = 0; a < nGroup; a++) + { + for (int b = a + 1; b < nGroup; b++) { - bestD = d; - nearestIdx = j; + float d = static_cast(cv::norm(tplCenters[group[a]] - tplCenters[group[b]])); + if (minGap <= 0.0f || d < minGap) + { + minGap = d; + } } } - disp[i] = curCenters[nearestIdx] - tplCenters[i]; - } - // 2) 网格投票找主位移(峰值并列时优先取量级更小的) - const float binSize = PIN_SHIFT_BIN_SIZE_PX; - std::map, int> hist; - int bestCount = 0; - float bestX = 0.0f; - float bestY = 0.0f; - for (int i = 0; i < nTpl; i++) - { - std::pair key(static_cast(std::lround(disp[i].x / binSize)), - static_cast(std::lround(disp[i].y / binSize))); - int cnt = ++hist[key]; - float cx = key.first * binSize; - float cy = key.second * binSize; - if (cnt > bestCount || - (cnt == bestCount && cx * cx + cy * cy < bestX * bestX + bestY * bestY)) + // 2.1) 组内每个模板 pin 取最近检测 pin 的位移作为候选位移(最近检测 pin 必定存在, nCur > 0) + std::vector disp(nGroup); + for (int a = 0; a < nGroup; a++) + { + int nearestIdx = 0; + double bestD = 1e12; + for (int j = 0; j < nCur; j++) + { + double d = cv::norm(curCenters[j] - tplCenters[group[a]]); + if (d < bestD) + { + bestD = d; + nearestIdx = j; + } + } + disp[a] = curCenters[nearestIdx] - tplCenters[group[a]]; + } + + // 2.2) 网格投票找主位移(峰值并列时优先取量级更小的) + const float binSize = PIN_SHIFT_BIN_SIZE_PX; + std::map, int> hist; + int bestCount = 0; + float bestX = 0.0f; + float bestY = 0.0f; + for (int a = 0; a < nGroup; a++) { - bestCount = cnt; - bestX = cx; - bestY = cy; + std::pair key(static_cast(std::lround(disp[a].x / binSize)), + static_cast(std::lround(disp[a].y / binSize))); + int cnt = ++hist[key]; + float cx = key.first * binSize; + float cy = key.second * binSize; + if (cnt > bestCount || + (cnt == bestCount && cx * cx + cy * cy < bestX * bestX + bestY * bestY)) + { + bestCount = cnt; + bestX = cx; + bestY = cy; + } } - } - // 3) 峰值邻域(±1 档)内的候选位移取均值, 同时统计支持度 - double sumX = 0.0; - double sumY = 0.0; - int sumN = 0; - for (int i = 0; i < nTpl; i++) - { - if (std::fabs(disp[i].x - bestX) <= binSize && - std::fabs(disp[i].y - bestY) <= binSize) + // 2.3) 峰值邻域(±1 档)内的候选位移取均值, 同时统计支持度 + double sumX = 0.0; + double sumY = 0.0; + int sumN = 0; + for (int a = 0; a < nGroup; a++) { - sumX += disp[i].x; - sumY += disp[i].y; - sumN++; + if (std::fabs(disp[a].x - bestX) <= binSize && + std::fabs(disp[a].y - bestY) <= binSize) + { + sumX += disp[a].x; + sumY += disp[a].y; + sumN++; + } } - } - cv::Point2f globalShift(0.0f, 0.0f); - if (sumN > 0) - { - globalShift.x = static_cast(sumX / sumN); - globalShift.y = static_cast(sumY / sumN); - } - else - { - globalShift.x = bestX; - globalShift.y = bestY; - } + cv::Point2f rowShift(0.0f, 0.0f); + if (sumN > 0) + { + rowShift.x = static_cast(sumX / sumN); + rowShift.y = static_cast(sumY / sumN); + } + else + { + rowShift.x = bestX; + rowShift.y = bestY; + } - // 4) 支持度达标 且 位移量级小于 pin 间距的限定比例 -> 判定为整体偏移 - const double supportRatio = static_cast(sumN) / nTpl; - const float shiftLen = static_cast(cv::norm(globalShift)); - const bool bGlobalShift = (supportRatio >= PIN_SHIFT_MIN_SUPPORT_RATIO) && - (minGap <= 0.0f || shiftLen < minGap * PIN_SHIFT_MAX_GAP_RATIO); + // 2.4) 支持度达标 且 位移量级小于 pin 间距的限定比例 -> 判定为该排整体偏移 + const double supportRatio = static_cast(sumN) / nGroup; + const float shiftLen = static_cast(cv::norm(rowShift)); + const bool bRowShift = (supportRatio >= PIN_SHIFT_MIN_SUPPORT_RATIO) && + (minGap <= 0.0f || shiftLen < minGap * PIN_SHIFT_MAX_GAP_RATIO); - if (bGlobalShift) - { - // 模板 pin 轮廓整体平移, 使其与检测 pin 一致 - const int shiftX = cvRound(globalShift.x); - const int shiftY = cvRound(globalShift.y); - for (int i = 0; i < nTpl; i++) + if (bRowShift) { - for (auto &pt : m_tplPinContoursCur[i]) + // 该排模板 pin 轮廓整体平移, 使其与检测 pin 一致 + const int shiftX = cvRound(rowShift.x); + const int shiftY = cvRound(rowShift.y); + for (int a = 0; a < nGroup; a++) { - pt.x += shiftX; - pt.y += shiftY; + const int i = group[a]; + for (auto &pt : m_tplPinContoursCur[i]) + { + pt.x += shiftX; + pt.y += shiftY; + } + // 与前面的统计口径保持一致: 中心/外包框由平移后的轮廓重新计算 + tplCenters[i] = getCenter(m_tplPinContoursCur[i]); + tplRects[i] = cv::boundingRect(m_tplPinContoursCur[i]); } - // 与前面的统计口径保持一致: 中心/外包框由平移后的轮廓重新计算 - tplCenters[i] = getCenter(m_tplPinContoursCur[i]); - tplRects[i] = cv::boundingRect(m_tplPinContoursCur[i]); + m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Check", + "global offset removed(%s): template pins shifted dx %.2f dy %.2f mm (%.1f %.1f px), support %d/%d", + shiftGroupNames[g].c_str(), + rowShift.x * m_fImgage_Scale_X, rowShift.y * m_fImgage_Scale_Y, + rowShift.x, rowShift.y, sumN, nGroup); + } + else if (bestCount > 0) + { + m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Pin Check", + "global offset not applied(%s): dx %.1f dy %.1f px, support %d/%d (ratio %.2f, pin gap %.1f px)", + shiftGroupNames[g].c_str(), + rowShift.x, rowShift.y, sumN, nGroup, supportRatio, minGap); } - m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Check", - "global offset removed: template pins shifted dx %.2f dy %.2f mm (%.1f %.1f px), support %d/%d", - globalShift.x * m_fImgage_Scale_X, globalShift.y * m_fImgage_Scale_Y, - globalShift.x, globalShift.y, sumN, nTpl); - } - else if (bestCount > 0) - { - m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Pin Check", - "global offset not applied: dx %.1f dy %.1f px, support %d/%d (ratio %.2f, pin gap %.1f px)", - globalShift.x, globalShift.y, sumN, nTpl, supportRatio, minGap); } } }