update 崩边检测使用曲线拟合边缘

dev_lsy
liusiyang 4 weeks ago
parent 22dd824f94
commit 3377bfd34f

@ -173,6 +173,7 @@ public:
std::vector<cv::Point> Det_region;
bool bSaveResultImg;
std::shared_ptr<DetLog> detlog;
DetConfigResult()
{
Init();
@ -186,6 +187,7 @@ public:
alginResult.Init();
Det_region.clear();
bSaveResultImg = false;
detlog = nullptr;
}
};

@ -31,7 +31,9 @@ float pointToLineDistance(const cv::Point &pt, const cv::Vec4f &line)
float x = pt.x, y = pt.y;
return std::abs(vy * x - vx * y + (vx * y0 - vy * x0)) / std::sqrt(vx * vx + vy * vy);
}
bool FitLineWithOutlierRemoval(const std::vector<cv::Point> &inputPoints, cv::Vec4f &outputLine, int ransacIters = 100, float inlierThresh = 10)
bool FitLineWithOutlierRemoval(const std::vector<cv::Point> &inputPoints, cv::Vec4f &outputLine,
int ransacIters = 100, float inlierThresh = 10,
std::vector<cv::Point> *outInliers = nullptr)
{
if (inputPoints.size() < 2)
return false;
@ -80,10 +82,62 @@ bool FitLineWithOutlierRemoval(const std::vector<cv::Point> &inputPoints, cv::Ve
if (bestInlierPoints.size() < 2)
return false;
// 输出 inlier 点集,供后续曲线拟合使用
if (outInliers)
*outInliers = bestInlierPoints;
cv::fitLine(bestInlierPoints, outputLine, cv::DIST_L2, 0, 0.01, 0.01);
return true;
}
// 二次曲线拟合(最小二乘法)
// isHorizontal=true: 拟合 y = a*x² + b*x + ccoeffs = [a, b, c]
// isHorizontal=false: 拟合 x = a*y² + b*y + ccoeffs = [a, b, c]
bool FitQuadraticCurve(const std::vector<cv::Point> &points, cv::Vec3f &coeffs, bool isHorizontal)
{
if (points.size() < 3)
return false;
int n = (int)points.size();
cv::Mat A(n, 3, CV_64F);
cv::Mat B(n, 1, CV_64F);
for (int i = 0; i < n; i++)
{
double t = isHorizontal ? points[i].x : points[i].y;
double v = isHorizontal ? points[i].y : points[i].x;
A.at<double>(i, 0) = t * t;
A.at<double>(i, 1) = t;
A.at<double>(i, 2) = 1.0;
B.at<double>(i, 0) = v;
}
cv::Mat X;
if (!cv::solve(A, B, X, cv::DECOMP_SVD))
return false;
coeffs[0] = (float)X.at<double>(0, 0);
coeffs[1] = (float)X.at<double>(1, 0);
coeffs[2] = (float)X.at<double>(2, 0);
return true;
}
// 将点投影到二次曲线上(垂直投影:保持 x 不变求 y或保持 y 不变求 x
cv::Point ProjectPointToCurve(const cv::Point &pt, const cv::Vec3f &coeffs, bool isHorizontal)
{
float a = coeffs[0], b = coeffs[1], c = coeffs[2];
if (isHorizontal)
{
float y = a * pt.x * pt.x + b * pt.x + c;
return cv::Point(pt.x, cvRound(y));
}
else
{
float x = a * pt.y * pt.y + b * pt.y + c;
return cv::Point(cvRound(x), pt.y);
}
}
void drawFittedLine(cv::Mat &image, const cv::Vec4f &line, const cv::Scalar &color, int thickness = 2)
{
double scale = std::max(image.cols, image.rows) * 2.0;
@ -312,8 +366,130 @@ int Edge_QX_Det::Detect(const cv::Mat &img, DetConfigResult *pDetConfig)
else
return 1;
// 用RANSAC剔除崩边凹陷的异常点后拟合二次曲线使ROI贴合产品边缘的自然弧度
// 水平边缘(上下): 拟合 y = a*x² + b*x + c
// 垂直边缘(左右): 拟合 x = a*y² + b*y + c
// 若曲线拟合失败(点数不足), 则回退到直线投影
// 上边缘RANSAC获取inlier → 二次曲线拟合 → 投影到曲线
{
cv::Vec4f fittedLine_up;
std::vector<cv::Point> inliers_up;
cv::Vec3f curveCoeffs_up;
bool useCurve = false;
if (FitLineWithOutlierRemoval(up_edge, fittedLine_up, 100, 15, &inliers_up))
{
useCurve = FitQuadraticCurve(inliers_up, curveCoeffs_up, true);
}
for (auto& line : Up_line)
{
if (useCurve)
{
line.p1 = ProjectPointToCurve(line.p1, curveCoeffs_up, true);
line.p2 = ProjectPointToCurve(line.p2, curveCoeffs_up, true);
}
else
{
float vx = fittedLine_up[0], vy = fittedLine_up[1], x0 = fittedLine_up[2], y0 = fittedLine_up[3];
float denom = vx * vx + vy * vy;
float t1 = ((line.p1.x - x0) * vx + (line.p1.y - y0) * vy) / denom;
float t2 = ((line.p2.x - x0) * vx + (line.p2.y - y0) * vy) / denom;
line.p1 = cv::Point(cvRound(x0 + t1 * vx), cvRound(y0 + t1 * vy));
line.p2 = cv::Point(cvRound(x0 + t2 * vx), cvRound(y0 + t2 * vy));
}
}
}
// 下边缘
{
cv::Vec4f fittedLine_down;
std::vector<cv::Point> inliers_down;
cv::Vec3f curveCoeffs_down;
bool useCurve = false;
if (FitLineWithOutlierRemoval(down_edge, fittedLine_down, 100, 15, &inliers_down))
{
useCurve = FitQuadraticCurve(inliers_down, curveCoeffs_down, true);
}
for (auto& line : down_line)
{
if (useCurve)
{
line.p1 = ProjectPointToCurve(line.p1, curveCoeffs_down, true);
line.p2 = ProjectPointToCurve(line.p2, curveCoeffs_down, true);
}
else
{
float vx = fittedLine_down[0], vy = fittedLine_down[1], x0 = fittedLine_down[2], y0 = fittedLine_down[3];
float denom = vx * vx + vy * vy;
float t1 = ((line.p1.x - x0) * vx + (line.p1.y - y0) * vy) / denom;
float t2 = ((line.p2.x - x0) * vx + (line.p2.y - y0) * vy) / denom;
line.p1 = cv::Point(cvRound(x0 + t1 * vx), cvRound(y0 + t1 * vy));
line.p2 = cv::Point(cvRound(x0 + t2 * vx), cvRound(y0 + t2 * vy));
}
}
}
// 左边缘
{
cv::Vec4f fittedLine_left;
std::vector<cv::Point> inliers_left;
cv::Vec3f curveCoeffs_left;
bool useCurve = false;
if (FitLineWithOutlierRemoval(left_edge, fittedLine_left, 100, 15, &inliers_left))
{
useCurve = FitQuadraticCurve(inliers_left, curveCoeffs_left, false);
}
for (auto& line : left_line)
{
if (useCurve)
{
line.p1 = ProjectPointToCurve(line.p1, curveCoeffs_left, false);
line.p2 = ProjectPointToCurve(line.p2, curveCoeffs_left, false);
}
else
{
float vx = fittedLine_left[0], vy = fittedLine_left[1], x0 = fittedLine_left[2], y0 = fittedLine_left[3];
float denom = vx * vx + vy * vy;
float t1 = ((line.p1.x - x0) * vx + (line.p1.y - y0) * vy) / denom;
float t2 = ((line.p2.x - x0) * vx + (line.p2.y - y0) * vy) / denom;
line.p1 = cv::Point(cvRound(x0 + t1 * vx), cvRound(y0 + t1 * vy));
line.p2 = cv::Point(cvRound(x0 + t2 * vx), cvRound(y0 + t2 * vy));
}
}
}
// 右边缘
{
cv::Vec4f fittedLine_right;
std::vector<cv::Point> inliers_right;
cv::Vec3f curveCoeffs_right;
bool useCurve = false;
if (FitLineWithOutlierRemoval(right_edge, fittedLine_right, 100, 15, &inliers_right))
{
useCurve = FitQuadraticCurve(inliers_right, curveCoeffs_right, false);
}
for (auto& line : right_line)
{
if (useCurve)
{
line.p1 = ProjectPointToCurve(line.p1, curveCoeffs_right, false);
line.p2 = ProjectPointToCurve(line.p2, curveCoeffs_right, false);
}
else
{
float vx = fittedLine_right[0], vy = fittedLine_right[1], x0 = fittedLine_right[2], y0 = fittedLine_right[3];
float denom = vx * vx + vy * vy;
float t1 = ((line.p1.x - x0) * vx + (line.p1.y - y0) * vy) / denom;
float t2 = ((line.p2.x - x0) * vx + (line.p2.y - y0) * vy) / denom;
line.p1 = cv::Point(cvRound(x0 + t1 * vx), cvRound(y0 + t1 * vy));
line.p2 = cv::Point(cvRound(x0 + t2 * vx), cvRound(y0 + t2 * vy));
}
}
}
// 生成 检测的 roi。
int roi_wh = pDetConfig->pBaseCheckFunction->edgeDet.Det_Range;
cv::Rect imgBounds(0, 0, img.cols, img.rows); // 图像边界,用于裁剪越界 ROI
std::vector<Det_ROI_Config> up_det_roi;
std::vector<Det_ROI_Config> down_det_roi;
@ -327,7 +503,8 @@ int Edge_QX_Det::Detect(const cv::Mat &img, DetConfigResult *pDetConfig)
tem.plist.push_back(cv::Point(line.p2.x, line.p2.y + roi_wh));
tem.plist.push_back(cv::Point(line.p1.x, line.p1.y + roi_wh));
tem.roi = cv::boundingRect(tem.plist);
tem.roi = cv::boundingRect(tem.plist) & imgBounds;
if (tem.roi.width <= 0 || tem.roi.height <= 0) continue;
pDetConfig->edge_det_roi.push_back(tem);
up_det_roi.push_back(tem);
}
@ -339,7 +516,8 @@ int Edge_QX_Det::Detect(const cv::Mat &img, DetConfigResult *pDetConfig)
tem.plist.push_back(cv::Point(line.p2.x, line.p2.y - roi_wh));
tem.plist.push_back(cv::Point(line.p1.x, line.p1.y - roi_wh));
tem.roi = cv::boundingRect(tem.plist);
tem.roi = cv::boundingRect(tem.plist) & imgBounds;
if (tem.roi.width <= 0 || tem.roi.height <= 0) continue;
pDetConfig->edge_det_roi.push_back(tem);
down_det_roi.push_back(tem);
}
@ -351,7 +529,8 @@ int Edge_QX_Det::Detect(const cv::Mat &img, DetConfigResult *pDetConfig)
tem.plist.push_back(cv::Point(line.p2.x + roi_wh, line.p2.y));
tem.plist.push_back(cv::Point(line.p1.x + roi_wh, line.p1.y));
tem.roi = cv::boundingRect(tem.plist);
tem.roi = cv::boundingRect(tem.plist) & imgBounds;
if (tem.roi.width <= 0 || tem.roi.height <= 0) continue;
pDetConfig->edge_det_roi.push_back(tem);
left_det_roi.push_back(tem);
}
@ -363,7 +542,8 @@ int Edge_QX_Det::Detect(const cv::Mat &img, DetConfigResult *pDetConfig)
tem.plist.push_back(cv::Point(line.p2.x - roi_wh, line.p2.y));
tem.plist.push_back(cv::Point(line.p1.x - roi_wh, line.p1.y));
tem.roi = cv::boundingRect(tem.plist);
tem.roi = cv::boundingRect(tem.plist) & imgBounds;
if (tem.roi.width <= 0 || tem.roi.height <= 0) continue;
pDetConfig->edge_det_roi.push_back(tem);
right_det_roi.push_back(tem);
}
@ -563,11 +743,6 @@ int Edge_QX_Det::GetEdgePoint(const cv::Mat &img, Edge_Search_Config *pEdge_Sear
// 对每个搜索点进行 y方向搜索
for (int y = sy;; y = y + nCurPoint_Step)
{
if (y < 0 || y >= img.rows)
{
continue;
}
//
if (nCurPoint_Step > 0 && y > ey)
{
@ -577,7 +752,10 @@ int Edge_QX_Det::GetEdgePoint(const cv::Mat &img, Edge_Search_Config *pEdge_Sear
{
break;
}
offt = y * img.cols;
if (y < 0 || y >= img.rows)
{
continue;
}
int range_okNum = 0;
// 对一定范围的点进行判断
for (int k = nrange_start; k < nrange_end; k++)
@ -587,13 +765,13 @@ int Edge_QX_Det::GetEdgePoint(const cv::Mat &img, Edge_Search_Config *pEdge_Sear
{
continue;
}
offt += rangeX;
if (offt < 0 || offt >= img.cols * img.rows)
int cur_offt = y * img.cols + rangeX;
if (cur_offt < 0 || cur_offt >= img.cols * img.rows)
{
printf("rangeX %d nCurPoint_Step %d off %d x %d y %d sy %d ey %d %d %d\n", rangeX, nCurPoint_Step, offt, x, y, sy, ey, img.cols, img.rows);
continue;
}
if (pdata[offt] >= pEdge_Search_Config->nValueThreshold) // 找到
if (pdata[cur_offt] >= pEdge_Search_Config->nValueThreshold) // 找到
{
range_okNum++;
}
@ -648,13 +826,9 @@ int Edge_QX_Det::GetEdgePoint(const cv::Mat &img, Edge_Search_Config *pEdge_Sear
int cur_y = y;
bool bSucc = false;
// 对每个搜索点进行 y方向搜索
// 对每个搜索点进行 x方向搜索
for (int x = sx;; x = x + nCurPoint_Step)
{
if (x < 0 || x >= img.cols)
{
continue;
}
//
if (nCurPoint_Step > 0 && x > ex)
{
@ -664,6 +838,10 @@ int Edge_QX_Det::GetEdgePoint(const cv::Mat &img, Edge_Search_Config *pEdge_Sear
{
break;
}
if (x < 0 || x >= img.cols)
{
continue;
}
int range_okNum = 0;
// 对一定范围的点进行判断
@ -674,12 +852,12 @@ int Edge_QX_Det::GetEdgePoint(const cv::Mat &img, Edge_Search_Config *pEdge_Sear
{
continue;
}
offt = rangey * img.cols + x;
if (offt < 0 || offt >= img.cols * img.rows)
int cur_offt = rangey * img.cols + x;
if (cur_offt < 0 || cur_offt >= img.cols * img.rows)
{
printf("rangey %d off %d x %d y %d ey %d %d %d\n", rangey, offt, x, y, ey, img.cols, img.rows);
continue;
}
if (pdata[offt] >= pEdge_Search_Config->nValueThreshold) // 找到
if (pdata[cur_offt] >= pEdge_Search_Config->nValueThreshold) // 找到
{
range_okNum++;
}
@ -933,12 +1111,14 @@ int Edge_QX_Det::Det_qx(const cv::Mat &img, std::vector<Det_ROI_Config> roilist,
// 进行开操作(先腐蚀后膨胀)可以去除小白点
cv::morphologyEx(roiMask, roiMask, cv::MORPH_OPEN, kernel);
// getchar();
// cv::imwrite("detimg.png", img(DetRoi));
// cv::imwrite("detimg_mask.png", roiMask);
// getchar();
bool jiao_f_1 = false;
bool jiao_f_2 = false;
string jiao_str_1;
string jiao_str_2;
cv::Point jiao_p_1;
cv::Point jiao_p_2;
if (type == Det_ROI_Type_UP)
@ -947,6 +1127,8 @@ int Edge_QX_Det::Det_qx(const cv::Mat &img, std::vector<Det_ROI_Config> roilist,
jiao_f_2 = pDetConfig->pBaseCheckFunction->edgeDet.queJiao_RU_Open;
jiao_p_1 = cv::Point(0, 0);
jiao_p_2 = cv::Point(roiMask.cols, 0);
jiao_str_1 = "queJiao_LU";
jiao_str_2 = "queJiao_RU";
}
else if (type == Det_ROI_Type_DOWN)
{
@ -954,49 +1136,105 @@ int Edge_QX_Det::Det_qx(const cv::Mat &img, std::vector<Det_ROI_Config> roilist,
jiao_f_2 = pDetConfig->pBaseCheckFunction->edgeDet.queJiao_RD_Open;
jiao_p_1 = cv::Point(0, roiMask.rows);
jiao_p_2 = cv::Point(roiMask.cols, roiMask.rows);
jiao_str_1 = "queJiao_LD";
jiao_str_2 = "queJiao_RD";
}
else if (type == Det_ROI_Type_LEFT)
{
jiao_f_1 = pDetConfig->pBaseCheckFunction->edgeDet.queJiao_LU_Open;
jiao_f_2 = pDetConfig->pBaseCheckFunction->edgeDet.queJiao_LD_Open;
jiao_p_1 = cv::Point(0, 0);
jiao_p_2 = cv::Point(0, roiMask.rows);
jiao_str_1 = "queJiao_LU";
jiao_str_2 = "queJiao_LD";
}
else if (type == Det_ROI_Type_RIGHT)
{
jiao_f_1 = pDetConfig->pBaseCheckFunction->edgeDet.queJiao_RU_Open;
jiao_f_2 = pDetConfig->pBaseCheckFunction->edgeDet.queJiao_RU_Open;
jiao_f_2 = pDetConfig->pBaseCheckFunction->edgeDet.queJiao_RD_Open;
jiao_p_1 = cv::Point(roiMask.cols, 0);
jiao_p_2 = cv::Point(roiMask.cols, roiMask.rows);
jiao_str_1 = "queJiao_RU";
jiao_str_2 = "queJiao_RD";
}
// 寻找轮廓
vector<vector<Point>> contours;
cv::findContours(roiMask, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
// 找到最大面积的轮廓
double maxArea = -1;
int maxAreaIdx = -1;
// 缺角过滤配置日志
if (pDetConfig->detlog)
{
pDetConfig->detlog->AddCheckstr(PrintLevel_1, "EdgeQX_Det", "type=%d contours=%zu %s=%d %s=%d thres(w=%d,h=%d)",
type, contours.size(),
jiao_str_1.c_str(), jiao_f_1,
jiao_str_2.c_str(), jiao_f_2,
pDetConfig->pBaseCheckFunction->edgeDet.queJiao_width,
pDetConfig->pBaseCheckFunction->edgeDet.queJiao_height);
}
int jiao_filter_count = 0;
for (size_t i = 0; i < contours.size(); ++i)
{
cv::Rect rect = cv::boundingRect(contours[i]);
cv::Point pc(rect.x + rect.width / 2, rect.y + rect.height / 2);
if (jiao_f_1)
{
// printf("1=%d =========== %d %d\n\n", type, abs(pc.x - jiao_p_1.x), abs(pc.y - jiao_p_1.y));
if (abs(pc.x - jiao_p_1.x) < pDetConfig->pBaseCheckFunction->edgeDet.queJiao_width && abs(pc.y - jiao_p_1.y) < pDetConfig->pBaseCheckFunction->edgeDet.queJiao_height)
int dx1 = abs(pc.x - jiao_p_1.x);
int dy1 = abs(pc.y - jiao_p_1.y);
if (dx1 < pDetConfig->pBaseCheckFunction->edgeDet.queJiao_width && dy1 < pDetConfig->pBaseCheckFunction->edgeDet.queJiao_height)
{
if (pDetConfig->detlog)
{
pDetConfig->detlog->AddCheckstr(PrintLevel_2, "EdgeQX_Det", "type=%d %s contour[%zu] center=(%d,%d) coor=(%d,%d) dx=%d dy=%d thres(w=%d,h=%d) --> succ",
type, jiao_str_1.c_str(), i, pc.x, pc.y, jiao_p_1.x, jiao_p_1.y,
dx1, dy1,
pDetConfig->pBaseCheckFunction->edgeDet.queJiao_width,
pDetConfig->pBaseCheckFunction->edgeDet.queJiao_height);
}
jiao_filter_count++;
continue;
}
else
{
if (pDetConfig->detlog)
{
pDetConfig->detlog->AddCheckstr(PrintLevel_2, "EdgeQX_Det", "type=%d %s contour[%zu] center=(%d,%d) coor=(%d,%d) dx=%d dy=%d thres(w=%d,h=%d) --> fail",
type, jiao_str_1.c_str(), i, pc.x, pc.y, jiao_p_1.x, jiao_p_1.y,
dx1, dy1,
pDetConfig->pBaseCheckFunction->edgeDet.queJiao_width,
pDetConfig->pBaseCheckFunction->edgeDet.queJiao_height);
}
}
}
if (jiao_f_2)
{
// printf("2=%d============ %d %d\n\n", type, abs(pc.x - jiao_p_2.x), abs(pc.y - jiao_p_2.y));
if (abs(pc.x - jiao_p_2.x) < pDetConfig->pBaseCheckFunction->edgeDet.queJiao_width && abs(pc.y - jiao_p_2.y) < pDetConfig->pBaseCheckFunction->edgeDet.queJiao_height)
int dx2 = abs(pc.x - jiao_p_2.x);
int dy2 = abs(pc.y - jiao_p_2.y);
if (dx2 < pDetConfig->pBaseCheckFunction->edgeDet.queJiao_width && dy2 < pDetConfig->pBaseCheckFunction->edgeDet.queJiao_height)
{
if (pDetConfig->detlog)
{
pDetConfig->detlog->AddCheckstr(PrintLevel_2, "EdgeQX_Det", "type=%d %s contour[%zu] center=(%d,%d) coor=(%d,%d) dx=%d dy=%d thres(w=%d,h=%d) --> succ",
type, jiao_str_2.c_str(), i, pc.x, pc.y, jiao_p_2.x, jiao_p_2.y,
dx2, dy2,
pDetConfig->pBaseCheckFunction->edgeDet.queJiao_width,
pDetConfig->pBaseCheckFunction->edgeDet.queJiao_height);
}
jiao_filter_count++;
continue;
}
else
{
if (pDetConfig->detlog)
{
pDetConfig->detlog->AddCheckstr(PrintLevel_2, "EdgeQX_Det", "type=%d %s contour[%zu] center=(%d,%d) coor=(%d,%d) dx=%d dy=%d thres(w=%d,h=%d) --> fail",
type, jiao_str_2.c_str(), i, pc.x, pc.y, jiao_p_2.x, jiao_p_2.y,
dx2, dy2,
pDetConfig->pBaseCheckFunction->edgeDet.queJiao_width,
pDetConfig->pBaseCheckFunction->edgeDet.queJiao_height);
}
}
}
if (rect.width >= pedgeDet->QX_Widht_min && rect.width <= pedgeDet->QX_Widht_max &&
@ -1021,18 +1259,25 @@ int Edge_QX_Det::Det_qx(const cv::Mat &img, std::vector<Det_ROI_Config> roilist,
int Edge_QX_Det::applyMaskInROI(const cv::Mat &grayImg, const Det_ROI_Config &config, cv::Mat &result, int threshold)
{
// 0. 裁剪 ROI防止 RANSAC 投影后越界
cv::Rect safeRoi = config.roi & cv::Rect(0, 0, grayImg.cols, grayImg.rows);
if (safeRoi.width <= 0 || safeRoi.height <= 0)
{
result = cv::Mat();
return -1;
}
// 1. 获取 ROI 区域图像(不 clone只引用
cv::Mat roiGray = grayImg(config.roi);
cv::Mat roiGray = grayImg(safeRoi);
// cv::imwrite("roiGray.png", roiGray);
// 2. 二值化(用 compare 更快)
cv::Mat binary;
cv::compare(roiGray, threshold, binary, cv::CMP_LT); // binary = roiGray > 128 ? 255 : 0
// cv::imwrite("binary.png", binary);
// 3. 构建局部坐标的多边形(避免每次 new
// 3. 构建局部坐标的多边形(相对于裁剪后的 safeRoi
std::vector<cv::Point> localPts;
localPts.reserve(config.plist.size());
for (const auto &pt : config.plist)
localPts.emplace_back(pt.x - config.roi.x, pt.y - config.roi.y);
localPts.emplace_back(pt.x - safeRoi.x, pt.y - safeRoi.y);
// 4. 快速创建 mask 并填充
cv::Mat mask = cv::Mat::zeros(roiGray.size(), CV_8UC1);

Loading…
Cancel
Save