|
|
|
@ -10,6 +10,7 @@
|
|
|
|
#include "CheckUtil.hpp"
|
|
|
|
#include "CheckUtil.hpp"
|
|
|
|
#include "Define.h"
|
|
|
|
#include "Define.h"
|
|
|
|
#include <algorithm>
|
|
|
|
#include <algorithm>
|
|
|
|
|
|
|
|
#include <cmath>
|
|
|
|
|
|
|
|
|
|
|
|
CameraCheckAnalysisy::CameraCheckAnalysisy()
|
|
|
|
CameraCheckAnalysisy::CameraCheckAnalysisy()
|
|
|
|
{
|
|
|
|
{
|
|
|
|
@ -168,7 +169,7 @@ int CameraCheckAnalysisy::Detect_Pre()
|
|
|
|
// pImageResult->result->in_shareImage->strCameraName, pImageResult->result->in_shareImage->strChannel);
|
|
|
|
// pImageResult->result->in_shareImage->strCameraName, pImageResult->result->in_shareImage->strChannel);
|
|
|
|
|
|
|
|
|
|
|
|
// 简单硬拼接:img 在左,img_B 在右,拼接结果写回 img
|
|
|
|
// 简单硬拼接:img 在左,img_B 在右,拼接结果写回 img
|
|
|
|
// 水平方向有overlap,拼接时固定去掉左图最右边overlap区域
|
|
|
|
// 先去掉左图最右边overlap区域,再做首行判断与上下对齐,最后水平拼接
|
|
|
|
if (pImageResult->result->in_shareImage->img.size() != pImageResult->result->in_shareImage->img_B.size() ||
|
|
|
|
if (pImageResult->result->in_shareImage->img.size() != pImageResult->result->in_shareImage->img_B.size() ||
|
|
|
|
pImageResult->result->in_shareImage->img.type() != pImageResult->result->in_shareImage->img_B.type())
|
|
|
|
pImageResult->result->in_shareImage->img.type() != pImageResult->result->in_shareImage->img_B.type())
|
|
|
|
{
|
|
|
|
{
|
|
|
|
@ -178,50 +179,79 @@ int CameraCheckAnalysisy::Detect_Pre()
|
|
|
|
cv::Mat &img = pImageResult->result->in_shareImage->img;
|
|
|
|
cv::Mat &img = pImageResult->result->in_shareImage->img;
|
|
|
|
cv::Mat &img_B = pImageResult->result->in_shareImage->img_B;
|
|
|
|
cv::Mat &img_B = pImageResult->result->in_shareImage->img_B;
|
|
|
|
|
|
|
|
|
|
|
|
// 左右图存在上下错位: 找到左右图产品所在的第一行, 进行简单上下对齐
|
|
|
|
// 1、先裁剪左图: 固定去掉左图最右侧与右图重叠的 overlap 区域
|
|
|
|
|
|
|
|
int overlap = cvRound(m_pbaseCheckFunction->markLine.hoverlap);
|
|
|
|
|
|
|
|
if (img.cols <= overlap)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
int rowLeft = -1;
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_1, "Detect_Pre", "Cam %s ==================img.cols <= overlap %d ", strBasic.c_str(), overlap);
|
|
|
|
int rowRight = -1;
|
|
|
|
return 1;
|
|
|
|
if (GetProductFirstRow(img, rowLeft) == 0 && GetProductFirstRow(img_B, rowRight) == 0)
|
|
|
|
}
|
|
|
|
|
|
|
|
img = img(cv::Rect(0, 0, img.cols - overlap, img.rows));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 拼接缝: 左图右边界在拼接图中的 x
|
|
|
|
|
|
|
|
const int seamX = img.cols;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 2、裁剪后做顶边对齐: 分别拟合左右图产品顶边直线, 用直线在拼接缝处的行坐标做上下平移。
|
|
|
|
|
|
|
|
// 只用单一坐标值(首行)相减误差大, 产品倾斜时会拼接不准; 用拟合出的直线取值更稳定。
|
|
|
|
|
|
|
|
// 这里只做上下平移, 不旋转图像。
|
|
|
|
|
|
|
|
bool bFitOK = false; // 顶边拟合是否成功
|
|
|
|
|
|
|
|
cv::Point2f pL1, pL2; // 左图顶边线段(端点)
|
|
|
|
|
|
|
|
cv::Point2f pR1, pR2; // 右图顶边线段(端点)
|
|
|
|
|
|
|
|
int dyAlign = 0; // 右图的上下平移量(>0 向下, <0 向上)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
// 以较高的顶边为基准, 把较低的那张图向上平移, 使两图顶边对齐
|
|
|
|
float kL = 0.0f, bL = 0.0f; // 左图顶边直线: y = kL * x + bL
|
|
|
|
int refRow = std::min(rowLeft, rowRight);
|
|
|
|
float kR = 0.0f, bR = 0.0f; // 右图顶边直线: y = kR * x + bR
|
|
|
|
int shiftLeft = refRow - rowLeft; // <= 0, 负值表示向上平移
|
|
|
|
|
|
|
|
int shiftRight = refRow - rowRight; // <= 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_1, "Detect_Pre", "Cam %s ==================align rowLeft %d rowRight %d shiftLeft %d shiftRight %d",
|
|
|
|
int reL = FitProductTopLine(img, kL, bL, pL1, pL2);
|
|
|
|
strBasic.c_str(), rowLeft, rowRight, shiftLeft, shiftRight);
|
|
|
|
int reR = FitProductTopLine(img_B, kR, bR, pR1, pR2);
|
|
|
|
|
|
|
|
|
|
|
|
if (shiftLeft < 0)
|
|
|
|
if (reL == 0 && reR == 0)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
// 内容向上平移 |shiftLeft| 行,底部补黑。
|
|
|
|
// 左图顶边直线在拼接缝处(x = seamX)的行坐标 = 左图线段右端点的高度
|
|
|
|
int dy = -shiftLeft;
|
|
|
|
const double seamY = kL * seamX + bL;
|
|
|
|
cv::Mat shifted = cv::Mat::zeros(img.size(), img.type());
|
|
|
|
// 右图顶边直线在其左边界(x = 0)处的行坐标 = 右图线段左端点的高度
|
|
|
|
img(cv::Rect(0, dy, img.cols, img.rows - dy)).copyTo(shifted(cv::Rect(0, 0, img.cols, img.rows - dy)));
|
|
|
|
const double rightY = bR;
|
|
|
|
img = shifted;
|
|
|
|
|
|
|
|
}
|
|
|
|
// 让两条线段的连接端点在拼接缝处相接: 右图上下平移 seamY - rightY 行
|
|
|
|
if (shiftRight < 0)
|
|
|
|
dyAlign = (int)cvRound(seamY - rightY);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_1, "Detect_Pre",
|
|
|
|
|
|
|
|
"Cam %s ==================topline L(k %.5f b %.1f) R(k %.5f b %.1f) seamY %.1f rightY %.1f dy %d",
|
|
|
|
|
|
|
|
strBasic.c_str(), kL, bL, kR, bR, seamY, rightY, dyAlign);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 上下平移右图, 空出的部分补黑
|
|
|
|
|
|
|
|
if (dyAlign != 0 && dyAlign > -img_B.rows && dyAlign < img_B.rows)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
int dy = -shiftRight;
|
|
|
|
|
|
|
|
cv::Mat shifted = cv::Mat::zeros(img_B.size(), img_B.type());
|
|
|
|
cv::Mat shifted = cv::Mat::zeros(img_B.size(), img_B.type());
|
|
|
|
img_B(cv::Rect(0, dy, img_B.cols, img_B.rows - dy)).copyTo(shifted(cv::Rect(0, 0, img_B.cols, img_B.rows - dy)));
|
|
|
|
if (dyAlign > 0)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
// 内容向下平移 dyAlign 行, 顶部补黑
|
|
|
|
|
|
|
|
img_B(cv::Rect(0, 0, img_B.cols, img_B.rows - dyAlign))
|
|
|
|
|
|
|
|
.copyTo(shifted(cv::Rect(0, dyAlign, img_B.cols, img_B.rows - dyAlign)));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
else
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
// 内容向上平移 |dyAlign| 行, 底部补黑
|
|
|
|
|
|
|
|
const int d = -dyAlign;
|
|
|
|
|
|
|
|
img_B(cv::Rect(0, d, img_B.cols, img_B.rows - d))
|
|
|
|
|
|
|
|
.copyTo(shifted(cv::Rect(0, 0, img_B.cols, img_B.rows - d)));
|
|
|
|
|
|
|
|
}
|
|
|
|
img_B = shifted;
|
|
|
|
img_B = shifted;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bFitOK = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
else
|
|
|
|
{
|
|
|
|
{
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_1, "Detect_Pre", "Cam %s ==================GetProductFirstRow fail, skip align", strBasic.c_str());
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_1, "Detect_Pre", "Cam %s ==================FitProductTopLine fail(L %d R %d), skip align", strBasic.c_str(), reL, reR);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
int overlap = cvRound(m_pbaseCheckFunction->markLine.hoverlap);
|
|
|
|
// 3、上下对齐后水平拼接
|
|
|
|
if (img.cols <= overlap)
|
|
|
|
|
|
|
|
{
|
|
|
|
{
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_1, "Detect_Pre", "Cam %s ==================img.cols <= overlap %d ", strBasic.c_str(), overlap);
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
cv::Mat merge_img;
|
|
|
|
cv::Mat merge_img;
|
|
|
|
cv::hconcat(img(cv::Rect(0, 0, img.cols - overlap, img.rows)), img_B, merge_img);
|
|
|
|
cv::hconcat(img, img_B, merge_img);
|
|
|
|
img = merge_img;
|
|
|
|
img = merge_img;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 拼接完成后 img_B 不再需要,立即释放内存
|
|
|
|
// 拼接完成后 img_B 不再需要,立即释放内存
|
|
|
|
img_B.release();
|
|
|
|
img_B.release();
|
|
|
|
@ -229,6 +259,45 @@ int CameraCheckAnalysisy::Detect_Pre()
|
|
|
|
if (pImageResult->result->in_shareImage->Det_Mode == DET_MODE_MergeImg)
|
|
|
|
if (pImageResult->result->in_shareImage->Det_Mode == DET_MODE_MergeImg)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
cv::imwrite(pImageResult->result->in_shareImage->strCameraName + "_MergeImg.jpg", pImageResult->result->in_shareImage->img);
|
|
|
|
cv::imwrite(pImageResult->result->in_shareImage->strCameraName + "_MergeImg.jpg", pImageResult->result->in_shareImage->img);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 保存绘制了顶边线段的图(绿: 左图顶边线段, 红: 右图顶边线段, 黄/青点: 两条线段的连接端点)
|
|
|
|
|
|
|
|
if (bFitOK)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
// 先缩放到可读尺寸再画线, 避免在超大图上做彩色转换(内存/耗时)
|
|
|
|
|
|
|
|
const cv::Mat &mergeImg = pImageResult->result->in_shareImage->img;
|
|
|
|
|
|
|
|
const int dbgW = 4096;
|
|
|
|
|
|
|
|
const double sc = std::min(1.0, (double)dbgW / mergeImg.cols);
|
|
|
|
|
|
|
|
cv::Mat small;
|
|
|
|
|
|
|
|
cv::resize(mergeImg, small, cv::Size(), sc, sc, cv::INTER_AREA);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cv::Mat dbgLineImg;
|
|
|
|
|
|
|
|
if (small.channels() == 1)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
cv::cvtColor(small, dbgLineImg, cv::COLOR_GRAY2BGR);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
else
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
dbgLineImg = small.clone();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 左图顶边线段(拼接图左侧)
|
|
|
|
|
|
|
|
const cv::Point2f a1((float)(pL1.x * sc), (float)(pL1.y * sc));
|
|
|
|
|
|
|
|
const cv::Point2f a2((float)(pL2.x * sc), (float)(pL2.y * sc));
|
|
|
|
|
|
|
|
// 右图顶边线段(拼接图右侧, 并叠加本次的上下平移量)
|
|
|
|
|
|
|
|
const cv::Point2f b1((float)((seamX + pR1.x) * sc), (float)((pR1.y + dyAlign) * sc));
|
|
|
|
|
|
|
|
const cv::Point2f b2((float)((seamX + pR2.x) * sc), (float)((pR2.y + dyAlign) * sc));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const int thick = std::max(2, (int)cvRound(9 * sc));
|
|
|
|
|
|
|
|
cv::line(dbgLineImg, a1, a2, cv::Scalar(0, 255, 0), thick); // 绿: 左图顶边线段
|
|
|
|
|
|
|
|
cv::line(dbgLineImg, b1, b2, cv::Scalar(0, 0, 255), thick); // 红: 右图顶边线段
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 两条线段的连接端点(平移对齐后应重合在拼接缝处)
|
|
|
|
|
|
|
|
const int r = std::max(4, (int)cvRound(14 * sc));
|
|
|
|
|
|
|
|
cv::circle(dbgLineImg, a2, r, cv::Scalar(0, 255, 255), -1);
|
|
|
|
|
|
|
|
cv::circle(dbgLineImg, b1, r, cv::Scalar(255, 255, 0), -1);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cv::imwrite(pImageResult->result->in_shareImage->strCameraName + "_MergeImg_Line.jpg", dbgLineImg);
|
|
|
|
|
|
|
|
}
|
|
|
|
return 2;
|
|
|
|
return 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -1633,3 +1702,168 @@ int CameraCheckAnalysisy::GetProductFirstRow(const cv::Mat &img, int &outRow)
|
|
|
|
|
|
|
|
|
|
|
|
return 2; // 理论上不会走到这里
|
|
|
|
return 2; // 理论上不会走到这里
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 拟合图像中产品(亮色区域)的顶边直线段: 对顶边上的点做最小二乘拟合, 得到 y = k * x + b
|
|
|
|
|
|
|
|
// 产品倾斜时顶边在每一列的高度不同, 用逐列顶边点拟合出的直线才能真正反映倾斜,
|
|
|
|
|
|
|
|
// 左右两张图各拟合一条线段后, 就可以让它们在拼接缝处首尾相连。
|
|
|
|
|
|
|
|
// 返回 0 成功; 非 0 失败(未找到顶边或有效点太少)
|
|
|
|
|
|
|
|
int CameraCheckAnalysisy::FitProductTopLine(const cv::Mat &img, float &outK, float &outB,
|
|
|
|
|
|
|
|
cv::Point2f &outP1, cv::Point2f &outP2)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
outK = 0.0f;
|
|
|
|
|
|
|
|
outB = 0.0f;
|
|
|
|
|
|
|
|
outP1 = cv::Point2f(0.0f, 0.0f);
|
|
|
|
|
|
|
|
outP2 = cv::Point2f(0.0f, 0.0f);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (img.empty())
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cv::Mat gray;
|
|
|
|
|
|
|
|
if (img.channels() == 1)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
gray = img;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
else if (img.channels() == 3)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
else
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const uchar brightThreshold = 45; // 亮像素灰度阈值(与背景判断阈值保持一致)
|
|
|
|
|
|
|
|
const int needBrightCount = std::max(1, (int)(gray.cols * 0.1)); // 该行亮像素数量占比超过 20% 认为是产品所在行
|
|
|
|
|
|
|
|
const int rowStep = 16; // 跳行扫描步长
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 判断某一行是否为产品所在行(亮像素数量达到阈值)
|
|
|
|
|
|
|
|
auto isProductRow = [&](int row) -> bool
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
const uchar *p = gray.ptr<uchar>(row);
|
|
|
|
|
|
|
|
int brightCount = 0;
|
|
|
|
|
|
|
|
for (int col = 0; col < gray.cols; ++col)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (p[col] >= brightThreshold)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (++brightCount >= needBrightCount)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 1、跳行粗扫: 定位产品顶边的大致位置
|
|
|
|
|
|
|
|
int rowFound = -1;
|
|
|
|
|
|
|
|
for (int row = 0; row < gray.rows; row += rowStep)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (isProductRow(row))
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
rowFound = row;
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (rowFound < 0)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return 2; // 未找到产品顶边
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 2、逐列定位顶边点: 在粗定位行附近的范围内, 从上往下找到第一段连续亮像素
|
|
|
|
|
|
|
|
// 产品倾斜时顶边在每列的行坐标不同, 这些点连起来才是真实的顶边线段
|
|
|
|
|
|
|
|
const int yMargin = std::max(rowStep * 4, 60); // 纵向搜索范围(要能覆盖倾斜造成的高度差)
|
|
|
|
|
|
|
|
const int yStart = std::max(0, rowFound - yMargin);
|
|
|
|
|
|
|
|
const int yEnd = std::min(gray.rows - 1, rowFound + yMargin);
|
|
|
|
|
|
|
|
const int colStep = std::max(1, gray.cols / 200); // 采样列步长, 避免逐列遍历超大图
|
|
|
|
|
|
|
|
const int minRunLen = 3; // 连续亮像素个数, 用于过滤噪点
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
std::vector<cv::Point2f> points;
|
|
|
|
|
|
|
|
points.reserve(gray.cols / colStep + 1);
|
|
|
|
|
|
|
|
for (int col = 0; col < gray.cols; col += colStep)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
for (int row = yStart; row <= yEnd; ++row)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
int runLen = 0;
|
|
|
|
|
|
|
|
for (int k = 0; k < minRunLen && row + k <= yEnd; ++k)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (gray.at<uchar>(row + k, col) >= brightThreshold)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
runLen++;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
else
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (runLen >= minRunLen)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
points.push_back(cv::Point2f((float)col, (float)row));
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (points.size() < 10)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return 3; // 顶边点太少, 无法拟合
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 3、迭代最小二乘拟合 y = k * x + b, 并用残差剔除离群点
|
|
|
|
|
|
|
|
std::vector<cv::Point2f> fitPts = points;
|
|
|
|
|
|
|
|
float k = 0.0f;
|
|
|
|
|
|
|
|
float b = 0.0f;
|
|
|
|
|
|
|
|
for (int iter = 0; iter < 3; ++iter)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
const int n = (int)fitPts.size();
|
|
|
|
|
|
|
|
if (n < 2)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
double sx = 0.0, sy = 0.0, sxx = 0.0, sxy = 0.0;
|
|
|
|
|
|
|
|
for (const auto &p : fitPts)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
sx += p.x;
|
|
|
|
|
|
|
|
sy += p.y;
|
|
|
|
|
|
|
|
sxx += (double)p.x * p.x;
|
|
|
|
|
|
|
|
sxy += (double)p.x * p.y;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
const double denom = n * sxx - sx * sx;
|
|
|
|
|
|
|
|
if (std::abs(denom) < 1e-6)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
k = 0.0f; // 所有点几乎在同一列, 无法确定斜率
|
|
|
|
|
|
|
|
b = (float)(sy / n);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
else
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
k = (float)((n * sxy - sx * sy) / denom);
|
|
|
|
|
|
|
|
b = (float)((sy - k * sx) / n);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 残差剔除: 偏离拟合直线过多的点不参与下一次拟合
|
|
|
|
|
|
|
|
std::vector<cv::Point2f> inliers;
|
|
|
|
|
|
|
|
inliers.reserve(fitPts.size());
|
|
|
|
|
|
|
|
for (const auto &p : fitPts)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (std::abs(p.y - (k * p.x + b)) <= 3.0f)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
inliers.push_back(p);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (inliers.size() == fitPts.size() || inliers.size() < 10)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
fitPts.swap(inliers);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
outK = k;
|
|
|
|
|
|
|
|
outB = b;
|
|
|
|
|
|
|
|
// 线段端点: 取图像左右边界上的点
|
|
|
|
|
|
|
|
outP1 = cv::Point2f(0.0f, b);
|
|
|
|
|
|
|
|
outP2 = cv::Point2f((float)(gray.cols - 1), k * (gray.cols - 1) + b);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|