|
|
|
|
@ -722,24 +722,30 @@ cv::Point2f CheckUtil::transformPoint(const cv::Point2f &point, const cv::Mat &t
|
|
|
|
|
return cv::Point2f(result_mat.at<double>(0), result_mat.at<double>(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<cv::Point2f> &pts, double fTol, double fMaxSlope, double &fK, double &fB)
|
|
|
|
|
{
|
|
|
|
|
if (pts.empty())
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// 常量线兜底:值取中位数
|
|
|
|
|
// 常量线兜底:值取中位数(点太少或拟合结果不合理时使用)
|
|
|
|
|
fK = 0.0;
|
|
|
|
|
{
|
|
|
|
|
std::vector<float> valList;
|
|
|
|
|
@ -751,88 +757,62 @@ static bool FitEdgeLine(const std::vector<cv::Point2f> &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<size_t>(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<cv::Point2f> inl;
|
|
|
|
|
inl.reserve(pts.size());
|
|
|
|
|
std::vector<cv::Point2f> 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<cv::Point2f> &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::vector<Mask_Protr
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 5、拟合四条边缘直线:左/右 x=f(y)、上/下 y=f(x)
|
|
|
|
|
// 直边判定:|k|<=0.6(近轴对齐)且带内(±fTol)点数最多的直线;fTol 覆盖量化台阶+网格误差
|
|
|
|
|
// 稳健拟合(先用 cv::fitLine(L1) 定初值,再迭代裁剪 + cv::fitLine(L2) 精修),|k|>0.6(非近轴)时退回常量线
|
|
|
|
|
const int nFitStep = std::max(1, nMinDepthWork / 2); // 量化台阶估计(工作图)
|
|
|
|
|
// nExp:边缘外扩量(工作图像素)。显式给定(nEdgeMargin>0)时把边缘线整体向外推该距离,
|
|
|
|
|
// 等效于减小凸起区域;未给定时按最小深度的一半自动取值
|
|
|
|
|
|