From fd03ebe0ae8ab15be83a6bd04b1368ec3ca926b9 Mon Sep 17 00:00:00 2001 From: liusiyang Date: Wed, 21 Oct 2026 20:51:14 +0800 Subject: [PATCH] =?UTF-8?q?update=20=E4=BC=98=E5=8C=96=E8=BE=B9=E7=BC=98?= =?UTF-8?q?=E6=89=BE=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AlgorithmModule/src/CheckUtil.cpp | 119 +++++++++------------ AlgorithmModule/src/ImgCheckAnalysisy.cpp | 121 +++++++++++----------- 2 files changed, 111 insertions(+), 129 deletions(-) diff --git a/AlgorithmModule/src/CheckUtil.cpp b/AlgorithmModule/src/CheckUtil.cpp index 34b1bcc..5168829 100644 --- a/AlgorithmModule/src/CheckUtil.cpp +++ b/AlgorithmModule/src/CheckUtil.cpp @@ -722,24 +722,30 @@ cv::Point2f CheckUtil::transformPoint(const cv::Point2f &point, const cv::Mat &t return cv::Point2f(result_mat.at(0), result_mat.at(1)); } +// 迭代裁剪轮数:每轮"带内点 cv::fitLine(L2) 精修 + 容差收紧一半",3 轮足够收敛 +#define FIT_EDGE_REFINE_ITERS 3 + // 拟合边缘直线:val ≈ fK * idx + fB(点集为 (扫描序号, 边界值)) -// 直边检测:在 |k| <= fMaxSlope 的斜率范围内搜索“带宽 ±fTol 内点数最多”的直线: -// 主体直边段的点最多且共线 → 被选中;把手段(点数更少)、角部过渡/另一侧边缘 -// (斜率超出限制)等都不会获胜。随后在带内点集上做最小二乘精修(两轮收紧容差)。 +// 做法(简洁版,不再做斜率网格搜索): +// 1) cv::fitLine(DIST_HUBER) 初始化:Huber 是 M 估计,把手凸起/角部过渡等偏离点被自动降权, +// 不会像普通最小二乘那样被拉偏(旧实现靠粗/细斜率网格找带内最多点,代码长且难调); +// 2) 迭代裁剪 + cv::fitLine(DIST_L2) 精修:每轮只保留距当前直线 ±fUseTol 内的点做最小二乘, +// 容差从 fTol 逐轮收紧到 3px(与旧实现“带内点两轮收紧容差”等价,实测四条边结果与旧实现一致); +// 3) |k| > fMaxSlope(不是近轴直边,多半是角部斜过渡/另一侧边缘)→ 常量线兜底。 // 可调参数(应对实际情况): -// fTol:带宽容差(工作图像素)。调大→主体边缘带变宽,浅把手更容易被并入主体(漏检)、 +// fTol:裁剪容差(工作图像素)。调大→浅把手更容易被并入主体(漏检)、 // 但更能压住量化台阶毛刺;调小→台阶噪声可能分裂支撑或拟合到非主体结构。 // 默认由调用方按“量化台阶+2px”传入(见 GetMaskProtrusions 第 5 步)。 -// fMaxSlope:直线搜索斜率上限。调大→允许产品倾斜更大;过大时角部斜过渡段可能 -// 被当成直边;调小→产品倾斜稍大时拟合失败(退回常量线)。 -// 点集为空返回 false;点太少/无有效直线时退回常量线(值取中位数) +// fMaxSlope:斜率上限。调大→允许产品倾斜更大;过大时角部斜过渡段可能被当成直边; +// 调小→产品倾斜稍大时退回常量线。 +// 点集为空返回 false;点太少/拟合结果不合理时退回常量线(值取中位数) static bool FitEdgeLine(const std::vector &pts, double fTol, double fMaxSlope, double &fK, double &fB) { if (pts.empty()) { return false; } - // 常量线兜底:值取中位数 + // 常量线兜底:值取中位数(点太少或拟合结果不合理时使用) fK = 0.0; { std::vector valList; @@ -751,88 +757,62 @@ static bool FitEdgeLine(const std::vector &pts, double fTol, double std::nth_element(valList.begin(), valList.begin() + valList.size() / 2, valList.end()); fB = (double)valList[valList.size() / 2]; } - if (pts.size() < 16) + if (pts.size() < 8) { return true; // 点太少:用常量线兜底 } - // 1、粗搜索:斜率网格 × 抽样锚点,取带内点数最多的直线 - int nBestCnt = -1; - double fBestK = 0.0; - double fBestB = fB; - const size_t nStride = std::max(1, pts.size() / 64); - auto fnCount = [&](double fk, double fb) -> int { - int nCnt = 0; - for (size_t i = 0; i < pts.size(); i++) + // cv::fitLine 的输出(单位方向向量 + 线上一点)换算成 v = k*t + b + auto fnToKB = [](const cv::Vec4f &line, double &k, double &b) -> bool { + if (std::fabs((double)line[0]) < 1e-6) { - if (std::fabs((double)pts[i].y - (fk * pts[i].x + fb)) <= fTol) - { - nCnt++; - } + return false; // 近垂直线:v 随 t 剧烈变化,无法用 k/b 表示 } - return nCnt; + k = (double)line[1] / (double)line[0]; + b = (double)line[3] - k * (double)line[2]; + return true; }; - const int nCoarseN = (int)(fMaxSlope / 0.03 + 0.5); // 斜率粗网格:±nCoarseN×0.03 - for (int nk = -nCoarseN; nk <= nCoarseN; nk++) - { - double fk = nk * 0.03; - for (size_t i = 0; i < pts.size(); i += nStride) - { - double fb = (double)pts[i].y - fk * (double)pts[i].x; - int nCnt = fnCount(fk, fb); - if (nCnt > nBestCnt) - { - nBestCnt = nCnt; - fBestK = fk; - fBestB = fb; - } - } - } - // 2、细搜索:在粗搜索最优斜率附近收紧网格 - for (int nk = -6; nk <= 6; nk++) + + // 1、Huber 稳健初始化(1.345 = 高斯噪声下 ~95% 效率的标准 Huber 系数) + cv::Vec4f line; + cv::fitLine(pts, line, cv::DIST_HUBER, 1.345, 1e-3, 1e-3); + double fKFit = 0, fBFit = 0; + if (!fnToKB(line, fKFit, fBFit) || std::fabs(fKFit) > fMaxSlope) { - double fk = fBestK + nk * 0.005; - for (size_t i = 0; i < pts.size(); i += nStride) - { - double fb = (double)pts[i].y - fk * (double)pts[i].x; - int nCnt = fnCount(fk, fb); - if (nCnt > nBestCnt) - { - nBestCnt = nCnt; - fBestK = fk; - fBestB = fb; - } - } + return true; // 拟不出近轴直边:常量线兜底 } - // 3、带内点最小二乘精修(两轮收紧容差) - fK = fBestK; - fB = fBestB; + // 2、迭代裁剪 + L2 精修(容差逐轮收紧;某轮点不够或结果不合理时保留上一轮结果) double fUseTol = fTol; - for (int nIter = 0; nIter < 2; nIter++) + for (int nIter = 0; nIter < FIT_EDGE_REFINE_ITERS; nIter++) { - std::vector inl; - inl.reserve(pts.size()); + std::vector vecInlier; + vecInlier.reserve(pts.size()); for (size_t i = 0; i < pts.size(); i++) { - if (std::fabs((double)pts[i].y - (fK * pts[i].x + fB)) <= fUseTol) + if (std::fabs((double)pts[i].y - (fKFit * pts[i].x + fBFit)) <= fUseTol) { - inl.push_back(pts[i]); + vecInlier.push_back(pts[i]); } } - if (inl.size() < 8) + if (vecInlier.size() < 8) { - break; + break; // 支撑点太少:保留当前结果,不再收紧 } - cv::Vec4f line; - cv::fitLine(inl, line, cv::DIST_L2, 0, 1e-3, 1e-3); - if (std::fabs((double)line[0]) > 1e-6) + cv::Vec4f lineRefit; + cv::fitLine(vecInlier, lineRefit, cv::DIST_L2, 0, 1e-3, 1e-3); + double fKNew = 0, fBNew = 0; + if (!fnToKB(lineRefit, fKNew, fBNew) || std::fabs(fKNew) > fMaxSlope) { - fK = (double)line[1] / (double)line[0]; - fB = (double)line[3] - fK * (double)line[2]; + break; // 精修结果不合理:保留上一轮 } + fKFit = fKNew; + fBFit = fBNew; fUseTol = std::max(3.0, fUseTol * 0.5); } + + fK = fKFit; + fB = fBFit; return true; } @@ -840,7 +820,8 @@ static bool FitEdgeLine(const std::vector &pts, double fTol, double // 产品主体是圆角矩形、把手基本出现在四角附近,把手在掩膜上表现为超出主体四条边缘的外凸块: // 1、取最大连通域为主体; // 2、逐行/逐列扫描主体边界,得到 左/右(x=f(y))、上/下(y=f(x)) 四条边缘的边界点集; -// 3、对每条边缘做直线拟合(见 FitEdgeLine:取带内点数最多的直线,允许产品小角度倾斜); +// 3、对每条边缘做直线拟合(见 FitEdgeLine:cv::fitLine(Huber) 定初值 + 迭代裁剪 + cv::fitLine(L2) 精修, +// 允许产品小角度倾斜); // 4、掩膜中超出四条边缘(超过容差带)的像素按连通域切分成凸起区域, // 并按质心所在象限给出就近角(0 左上 1 右上 2 左下 3 右下);深度/面积不足的量化毛刺被过滤。 // 可调参数(应对实际情况): @@ -990,7 +971,7 @@ int CheckUtil::GetMaskProtrusions(const cv::Mat &binMask, std::vector0.6(非近轴)时退回常量线 const int nFitStep = std::max(1, nMinDepthWork / 2); // 量化台阶估计(工作图) // nExp:边缘外扩量(工作图像素)。显式给定(nEdgeMargin>0)时把边缘线整体向外推该距离, // 等效于减小凸起区域;未给定时按最小深度的一半自动取值 diff --git a/AlgorithmModule/src/ImgCheckAnalysisy.cpp b/AlgorithmModule/src/ImgCheckAnalysisy.cpp index 4b117e2..4974d72 100644 --- a/AlgorithmModule/src/ImgCheckAnalysisy.cpp +++ b/AlgorithmModule/src/ImgCheckAnalysisy.cpp @@ -84,6 +84,14 @@ // 搜索列带内相邻像素(3 行灰度均值)的灰度差小于该值视为无边缘(极性无关,支架比外侧亮/暗均可) #define SUPPORT_REFINE_MIN_CONTRAST 15.0 +// ===== 稳健直线拟合(FitLineRobustTV)的偏离点剔除参数 ===== +// FIT_LINE_MAD_SIGMA:MAD 倍数阈值。调大→保留更多采样点(噪声大时更稳,但离群点可能残留、直线被轻微拉偏); +// 调小→剔除更狠(凸起/缺口影响更小,但噪声大时可能误删真实边缘点) +#define FIT_LINE_MAD_SIGMA 3.0 +// FIT_LINE_MIN_TOL:最小绝对容差(像素)。剔除阈值 = max(该值, FIT_LINE_MAD_SIGMA×1.4826×MAD), +// 避免边缘本身很直(MAD≈0)时把正常的像素抖动当成偏离点 +#define FIT_LINE_MIN_TOL 2.0 + // 分类映射配置文件(每行:label索引:缺陷名称;冒号后名称为该缺陷类型名称,行的顺序即缺陷类型索引) static const std::string CLS_LABEL_CONFIG_FILE = "/home/aidlux/BOE/UseModel_ZB/BM_cls.txt"; @@ -311,86 +319,79 @@ static cv::Mat MatAffineCompose(const cv::Mat &mA, const cv::Mat &mB) } // 稳健直线拟合:pts 为 (t, v) 采样点,拟合 v = k*t + b(左右边 t=y,v=x;上下边 t=x,v=y) -// 先最小二乘,再按“残差中位数的绝对偏差(MAD)”剔除离群点(把手凸起/边缘缺口/内部亮区等),重新拟合; -// 返回参与拟合的内点数,<4 表示拟合失败(调用方沿用原边缘) +// 三步(逻辑短、参数少,便于调整): +// 1) cv::fitLine(DIST_HUBER) 粗拟合:Huber 是 M 估计,把手凸起/边缘缺口/内部亮区等离群点被自动降权, +// 不会把初值拉偏(旧实现用普通最小二乘求初值,离群点一多整条线都会偏); +// 2) 按残差中位数的绝对偏差(MAD)剔除偏离点:|残差-中位数| > max(FIT_LINE_MIN_TOL, FIT_LINE_MAD_SIGMA×1.4826×MAD) 判为偏离点; +// 3) 内点用 cv::fitLine(DIST_L2) 精修一次。 +// 返回参与最终拟合的点数(已剔除偏离点);0 表示无法拟合(点数<2 或近垂直线,不能写成 v=k*t+b)。 +// 相比旧实现:不再要求“内点≥4 才算出线”,点数够就给结果,减少调用方“拟合失败→沿用旧边缘”的回退。 static int FitLineRobustTV(const std::vector &pts, double &fK, double &fB) { - auto fnFit = [](const std::vector &p, double &k, double &b) -> bool { - if (p.size() < 2) - { - return false; - } - double fSumT = 0, fSumV = 0; - for (size_t i = 0; i < p.size(); i++) - { - fSumT += p[i].x; - fSumV += p[i].y; - } - const double fMeanT = fSumT / p.size(); - const double fMeanV = fSumV / p.size(); - double fSxx = 0, fSxy = 0; - for (size_t i = 0; i < p.size(); i++) - { - const double fDt = p[i].x - fMeanT; - fSxx += fDt * fDt; - fSxy += fDt * (p[i].y - fMeanV); - } - if (fSxx < 1e-6) + if (pts.size() < 2) + { + return 0; + } + + // cv::fitLine 的输出(单位方向向量 + 线上一点)换算成 v = k*t + b + auto fnToKB = [](const cv::Vec4f &line, double &k, double &b) -> bool { + if (std::fabs((double)line[0]) < 1e-6) { - return false; + return false; // 近垂直线:v 随 t 剧烈变化,无法用 k/b 表示 } - k = fSxy / fSxx; - b = fMeanV - k * fMeanT; + k = (double)line[1] / (double)line[0]; + b = (double)line[3] - k * (double)line[2]; return true; }; - if (pts.size() < 4) + // 1、Huber 粗拟合(1.345 = 高斯噪声下 ~95% 效率的标准 Huber 系数;离群点被降权) + cv::Vec4f line; + cv::fitLine(pts, line, cv::DIST_HUBER, 1.345, 1e-3, 1e-3); + double fKFit = 0, fBFit = 0; + if (!fnToKB(line, fKFit, fBFit)) { return 0; } - double fKFit = 0, fBFit = 0; - if (!fnFit(pts, fKFit, fBFit)) + + // 2、按残差 MAD 剔除偏离点 + std::vector vecRes; + vecRes.reserve(pts.size()); + for (size_t i = 0; i < pts.size(); i++) { - return 0; + vecRes.push_back(pts[i].y - (fKFit * pts[i].x + fBFit)); + } + std::vector vecTmp = vecRes; + std::nth_element(vecTmp.begin(), vecTmp.begin() + vecTmp.size() / 2, vecTmp.end()); + const double fMedRes = vecTmp[vecTmp.size() / 2]; + for (size_t i = 0; i < vecTmp.size(); i++) + { + vecTmp[i] = std::fabs(vecTmp[i] - fMedRes); } + std::nth_element(vecTmp.begin(), vecTmp.begin() + vecTmp.size() / 2, vecTmp.end()); + const double fMad = vecTmp[vecTmp.size() / 2]; + const double fTol = std::max((double)FIT_LINE_MIN_TOL, (double)FIT_LINE_MAD_SIGMA * 1.4826 * fMad); std::vector vecInlier; - for (int nIter = 0; nIter < 2; nIter++) + vecInlier.reserve(pts.size()); + for (size_t i = 0; i < pts.size(); i++) { - std::vector vecRes; - vecRes.reserve(pts.size()); - for (size_t i = 0; i < pts.size(); i++) - { - vecRes.push_back(pts[i].y - (fKFit * pts[i].x + fBFit)); - } - std::vector vecAbs = vecRes; - std::nth_element(vecAbs.begin(), vecAbs.begin() + vecAbs.size() / 2, vecAbs.end()); - const double fMed = vecAbs[vecAbs.size() / 2]; - for (size_t i = 0; i < vecAbs.size(); i++) - { - vecAbs[i] = std::fabs(vecAbs[i] - fMed); - } - std::nth_element(vecAbs.begin(), vecAbs.begin() + vecAbs.size() / 2, vecAbs.end()); - const double fMad = vecAbs[vecAbs.size() / 2]; - const double fThr = std::max(2.0, 3.0 * 1.4826 * fMad); - - vecInlier.clear(); - for (size_t i = 0; i < pts.size(); i++) + if (std::fabs(vecRes[i] - fMedRes) <= fTol) { - if (std::fabs(pts[i].y - (fKFit * pts[i].x + fBFit)) <= fThr) - { - vecInlier.push_back(pts[i]); - } - } - if (vecInlier.size() < 4 || !fnFit(vecInlier, fKFit, fBFit)) - { - break; + vecInlier.push_back(pts[i]); } } - if (vecInlier.size() < 4) + if (vecInlier.size() < 2) { - return 0; + // 数据退化成两簇等极端情况:保留 Huber 结果,避免调用方回退到旧边缘 + fK = fKFit; + fB = fBFit; + return (int)vecInlier.size(); } + + // 3、内点最小二乘精修(换算失败时保留 Huber 结果) + cv::fitLine(vecInlier, line, cv::DIST_L2, 0, 1e-3, 1e-3); + fnToKB(line, fKFit, fBFit); + fK = fKFit; fB = fBFit; return (int)vecInlier.size();