feat 添加pin脚投影方式

dev_lsy
liusiyang 2 weeks ago
parent 050dd31240
commit aa5037e74d

@ -199,9 +199,18 @@ private:
// 当前 Pin 检测方案是否为"直接分析"(参数 pinDet.Det_Method == "line_fitting") // 当前 Pin 检测方案是否为"直接分析"(参数 pinDet.Det_Method == "line_fitting")
bool IsDirectPinMethod() const; bool IsDirectPinMethod() const;
// 当前 Pin 检测方案是否为"Pin 投影矩阵"(参数 pinDet.Det_Method == "Pin_Matrix")
bool IsPinMatrixMethod() const;
// 新方案: 不做模板对比, 只分析检测Pin: 自动分两排(考虑旋转) -> 拟合直线判偏移 + 相邻间距判缺失 // 新方案: 不做模板对比, 只分析检测Pin: 自动分两排(考虑旋转) -> 拟合直线判偏移 + 相邻间距判缺失
int Pin_Direct_Check(); int Pin_Direct_Check();
// Pin_Matrix 方案: 用当前帧 Pin 拟合投影矩阵(上下两排分别计算相似变换), 把模板 Pin 投影到当前检测图坐标系
int ComputePinProjectionMats();
// Pin_Matrix 方案: 过滤当前帧上明显的误检 pin blob(按模板 pin 的包围盒尺寸)
int FilterCurPinBlobsByTplSize();
// 把blob 汇总成 检测结果。 // 把blob 汇总成 检测结果。
int BLobToDetResult(); int BLobToDetResult();

@ -36,6 +36,91 @@ static float PinRowSideOfRect(const cv::RotatedRect &rr, const cv::Point2f &pt)
return (pt - rr.center).dot(normalDir); return (pt - rr.center).dot(normalDir);
} }
// 规范化 Pin 检测方案字符串: 去掉首尾空白并转小写(兼容配置里的大小写/空格差异)
static std::string NormalizeDetMethodStr(const std::string &method)
{
size_t begin = method.find_first_not_of(" \t\r\n");
if (begin == std::string::npos)
{
return "";
}
size_t end = method.find_last_not_of(" \t\r\n");
std::string ret = method.substr(begin, end - begin + 1);
std::transform(ret.begin(), ret.end(), ret.begin(),
[](unsigned char c)
{ return static_cast<char>(std::tolower(c)); });
return ret;
}
// 应用 2x3 仿射矩阵(CV_64F): 模板图坐标 -> 目标图坐标
static cv::Point2f ApplyAffine2D(const cv::Mat &M, const cv::Point2f &pt)
{
return cv::Point2f(static_cast<float>(M.at<double>(0, 0) * pt.x + M.at<double>(0, 1) * pt.y + M.at<double>(0, 2)),
static_cast<float>(M.at<double>(1, 0) * pt.x + M.at<double>(1, 1) * pt.y + M.at<double>(1, 2)));
}
// 把 Pin 中心按"排"分组(最多两排), 用于内定位法线无法分排时的兜底:
// 用 minAreaRect 求 Pin 群主方向, 沿短边(法线)投影, 在最大间隙处切分;
// 间隙过小(单排)或切分后某组不足 2 个 Pin 时按一排处理。
static std::vector<std::vector<int>> SplitPinsIntoRows(const std::vector<cv::Point2f> &pts)
{
std::vector<std::vector<int>> rows;
const int n = static_cast<int>(pts.size());
std::vector<int> all(n);
for (int i = 0; i < n; i++)
{
all[i] = i;
}
if (n < 4)
{
rows.push_back(all);
return rows;
}
cv::RotatedRect rr = cv::minAreaRect(pts);
const double th = rr.angle * CV_PI / 180.0;
const cv::Point2f widthDir(static_cast<float>(std::cos(th)), static_cast<float>(-std::sin(th)));
const cv::Point2f heightDir(static_cast<float>(std::sin(th)), static_cast<float>(std::cos(th)));
cv::Point2f nor = (rr.size.width <= rr.size.height) ? widthDir : heightDir;
// 统一法线朝向, 避免 ±180 度只是翻转上下排的先后顺序
if (nor.y < 0.0f || (nor.y == 0.0f && nor.x < 0.0f))
{
nor = -nor;
}
std::vector<float> proj(n);
std::vector<int> order(n);
for (int i = 0; i < n; i++)
{
order[i] = i;
proj[i] = pts[i].dot(nor);
}
std::sort(order.begin(), order.end(), [&](int a, int b)
{ return proj[a] < proj[b]; });
int splitK = -1;
float bestGap = -1.0f;
for (int k = 1; k <= n - 3; k++)
{
float gap = proj[order[k + 1]] - proj[order[k]];
if (gap > bestGap)
{
bestGap = gap;
splitK = k;
}
}
// 两排间距不明显(单排)或切分后某组不足 2 个 Pin -> 按一排处理
if (splitK < 1 || splitK > n - 3 || bestGap < 5.0f)
{
rows.push_back(all);
return rows;
}
rows.push_back(std::vector<int>(order.begin(), order.begin() + splitK + 1));
rows.push_back(std::vector<int>(order.begin() + splitK + 1, order.end()));
return rows;
}
// 分类映射配置文件(每行:label索引:缺陷名称;冒号后名称为该缺陷类型名称,行的顺序即缺陷类型索引) // 分类映射配置文件(每行:label索引:缺陷名称;冒号后名称为该缺陷类型名称,行的顺序即缺陷类型索引)
static const std::string CLS_LABEL_CONFIG_FILE = "/home/aidlux/BOE/UseModel_ZB/KK_cls.txt"; static const std::string CLS_LABEL_CONFIG_FILE = "/home/aidlux/BOE/UseModel_ZB/KK_cls.txt";
@ -842,6 +927,29 @@ int ImgCheckAnalysisy::CheckRun()
m_outer_roi = curOuterBound; m_outer_roi = curOuterBound;
} }
} }
else if (IsPinMatrixMethod())
{
// 方案三(Det_Method == "Pin_Matrix"): 投影矩阵在 Pin_Qx_Det 中由当前帧 Pin 拟合(上下两排分别计算);
// 此处只需要用当前外定位包围框作为检测图裁剪区域(原图不 warp), 不依赖内/外定位仿射
m_pdetlog->AddCheckstr(PrintLevel_0, "2.1、pre detect",
"Align mode: Pin Matrix (projection matrix fitted by pins, upper/lower rows separately, image NOT warped)");
{
cv::Rect curOuterBound = m_outer_rroi.boundingRect();
cv::Rect curImgBound(0, 0, m_CheckResult_shareP->in_shareImage->img.cols,
m_CheckResult_shareP->in_shareImage->img.rows);
curOuterBound &= curImgBound;
if (curOuterBound.width <= 0 || curOuterBound.height <= 0)
{
cv::Rect rawBound = m_outer_rroi.boundingRect();
m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "Pin Matrix: current outer roi invalid [%d %d %d %d]",
rawBound.x, rawBound.y, rawBound.width, rawBound.height);
m_nErrorCode = CHECK_ERROR_Config_cutRoi;
m_nCheckResultErrorCode = m_nErrorCode;
return m_nErrorCode;
}
m_outer_roi = curOuterBound;
}
}
else else
{ {
// 旧方案(Det_Method == "BTB_Marix" 或未配置): 不对原图做 warpAffine, 只计算矩阵, 并把模板 Pin 轮廓反变换到当前图坐标系。 // 旧方案(Det_Method == "BTB_Marix" 或未配置): 不对原图做 warpAffine, 只计算矩阵, 并把模板 Pin 轮廓反变换到当前图坐标系。
@ -2260,28 +2368,560 @@ int ImgCheckAnalysisy::UpdateImgageScale()
} }
// 当前 Pin 检测方案是否为"直接分析"(新方案): 由参数 pinDet.Det_Method 在运行时决定 // 当前 Pin 检测方案是否为"直接分析"(新方案): 由参数 pinDet.Det_Method 在运行时决定
// "line_fitting" -> 新方案(不做模板对比); 其它/未配置 -> 旧方案(模板对比) // "line_fitting" -> 新方案(不做模板对比); 其它/未配置 -> 模板对比方案
bool ImgCheckAnalysisy::IsDirectPinMethod() const bool ImgCheckAnalysisy::IsDirectPinMethod() const
{ {
if (m_pbaseCheckFunction == NULL) if (m_pbaseCheckFunction == NULL)
{ {
return false; return false;
} }
return NormalizeDetMethodStr(m_pbaseCheckFunction->pinDet.Det_Method) == "line_fitting";
}
std::string method = m_pbaseCheckFunction->pinDet.Det_Method; // 当前 Pin 检测方案是否为"Pin 投影矩阵"方案: pinDet.Det_Method == "Pin_Matrix"(忽略大小写/空白)
// 去掉首尾空白并转小写, 兼容大小写/带空格的配置值 // 该方案与 BTB_Marix 一样做模板 Pin 对比, 区别只在投影矩阵由 Pin 自身拟合(上下两排分别计算)
size_t begin = method.find_first_not_of(" \t\r\n"); bool ImgCheckAnalysisy::IsPinMatrixMethod() const
if (begin == std::string::npos) {
if (m_pbaseCheckFunction == NULL)
{ {
return false; return false;
} }
size_t end = method.find_last_not_of(" \t\r\n"); return NormalizeDetMethodStr(m_pbaseCheckFunction->pinDet.Det_Method) == "pin_matrix";
method = method.substr(begin, end - begin + 1); }
std::transform(method.begin(), method.end(), method.begin(),
[](unsigned char c) // Pin_Matrix 方案: 过滤当前帧上明显的误检 blob(按模板 Pin 的包围框尺寸)
{ return static_cast<char>(std::tolower(c)); }); // 以模板 Pin 外接矩形的中位宽/高为基准(按当前/模板外框面积比缩放), 当前 blob 的宽/高需落在 [0.4, 2.5] 倍范围内;
// 模板 Pin 少于 3 个时不做过滤。
int ImgCheckAnalysisy::FilterCurPinBlobsByTplSize()
{
if (m_curPinContours.empty() || m_tplPinContours.size() < 3)
{
return 0;
}
std::vector<int> ws;
std::vector<int> hs;
ws.reserve(m_tplPinContours.size());
hs.reserve(m_tplPinContours.size());
for (size_t i = 0; i < m_tplPinContours.size(); i++)
{
cv::Rect r = cv::boundingRect(m_tplPinContours[i]);
if (r.width > 0 && r.height > 0)
{
ws.push_back(r.width);
hs.push_back(r.height);
}
}
if (ws.size() < 3)
{
return 0;
}
std::sort(ws.begin(), ws.end());
std::sort(hs.begin(), hs.end());
float medW = static_cast<float>(ws[ws.size() / 2]);
float medH = static_cast<float>(hs[hs.size() / 2]);
// 模板/当前外框面积比 -> 尺寸基准缩放(同一相机一般约为 1)
const double tplArea = static_cast<double>(m_tplOuterRect.size.width) * m_tplOuterRect.size.height;
const double curArea = static_cast<double>(m_outer_rroi.size.width) * m_outer_rroi.size.height;
if (tplArea > 0.0 && curArea > 0.0)
{
const float scaleRef = static_cast<float>(std::sqrt(curArea / tplArea));
medW *= scaleRef;
medH *= scaleRef;
}
const float kMinRatio = 0.4f; // 尺寸下限比例
const float kMaxRatio = 2.5f; // 尺寸上限比例
const size_t nBefore = m_curPinContours.size();
for (size_t i = m_curPinContours.size(); i-- > 0;)
{
cv::Rect r = cv::boundingRect(m_curPinContours[i]);
const bool bSizeOk = (r.width > 0 && r.height > 0) &&
(r.width >= medW * kMinRatio) && (r.width <= medW * kMaxRatio) &&
(r.height >= medH * kMinRatio) && (r.height <= medH * kMaxRatio);
if (!bSizeOk)
{
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Pin Matrix",
"filter suspicious pin blob idx %d size[%d %d] (tpl median[%.1f %.1f])",
static_cast<int>(i), r.width, r.height, medW, medH);
m_curPinContours.erase(m_curPinContours.begin() + static_cast<long>(i));
}
}
if (m_curPinContours.size() != nBefore)
{
m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Matrix",
"filter pin blobs: %ld -> %ld (tpl median size [%.1f %.1f])",
static_cast<long>(nBefore), static_cast<long>(m_curPinContours.size()), medW, medH);
}
return 0;
}
// Det_Method == "Pin_Matrix": 与 BTB_Marix 一样做"模板 Pin vs 当前 Pin"的逐个比对(相交 -> 最近 -> dx/dy 超限),
// 区别只在投影矩阵的来源: BTB_Marix 用内/外定位矩形求仿射, 本方案直接由 Pin 拟合:
// 1) 内/外定位仿射只作为匹配初值(模板 Pin -> 当前检测图), 最终矩阵由 Pin 决定;
// 2) 模板 Pin 按当前帧内定位矩形的长边法线分成上下两排, 每排独立估计一个相似变换(旋转+等比缩放+平移):
// 一排 Pin 近似共线, 6 自由度全仿射在垂直排方向上病态, 因此用 4 自由度相似变换;
// 3) 估计准则 = "模板 Pin 投影后与当前 Pin 重合的数量最多": 先固定初值的旋转/缩放做平移投票,
// 再按最近邻一对一匹配 + RANSAC 拟合迭代求精, 取重合数最多的解;
// 4) 某排可匹配 Pin 少于 2 个(无法拟合)时, 该排退回内/外定位仿射投影, 保证缺失 Pin 的 roi 仍能落位。
// 输出的 m_tplPinContoursCur 即按每排矩阵投影后的模板 Pin 轮廓(当前检测图坐标系), 供后续比对/绘制使用。
int ImgCheckAnalysisy::ComputePinProjectionMats()
{
m_tplPinContoursCur.clear();
if (m_tplPinContours.empty())
{
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Pin Matrix", "no template pin contours, skip");
return 0;
}
// 0、过滤明显误检 blob(仅本方案; line_fitting / BTB_Marix 行为不变)
FilterCurPinBlobsByTplSize();
const cv::Rect tplBound = m_tplOuterRect.boundingRect();
auto getCenter = [](const std::vector<cv::Point> &contour) -> cv::Point2f
{
cv::Rect r = cv::boundingRect(contour);
return cv::Point2f(r.x + r.width * 0.5f, r.y + r.height * 0.5f);
};
const int nTpl = static_cast<int>(m_tplPinContours.size());
const int nCur = static_cast<int>(m_curPinContours.size());
// 1、匹配初值: 内定位优先/外定位回退(仅用于建立对应关系, 不参与最终判断)
const bool bUseInnerAlign = (m_tplInnerRect.size.width > 0 && m_tplInnerRect.size.height > 0 &&
m_inner_rroi.size.width > 0 && m_inner_rroi.size.height > 0);
if (bUseInnerAlign)
{
CalAffineMats(m_tplInnerRect, m_inner_rroi);
}
else
{
CalAffineMats(m_tplOuterRect, m_outer_rroi);
}
// T0: 模板图坐标 -> detImg 坐标
cv::Mat T0;
if (m_bAffineValid && !m_affine_tpl2cur.empty())
{
T0 = m_affine_tpl2cur.clone();
T0.at<double>(0, 2) -= m_outer_roi.x;
T0.at<double>(1, 2) -= m_outer_roi.y;
}
else
{
// 防御: 定位矩形异常时退化为"模板外框中心 -> 当前外框中心"的平移
const cv::Rect curOuter = m_outer_rroi.boundingRect();
const double tx = (curOuter.x + curOuter.width * 0.5) - (tplBound.x + tplBound.width * 0.5) - m_outer_roi.x;
const double ty = (curOuter.y + curOuter.height * 0.5) - (tplBound.y + tplBound.height * 0.5) - m_outer_roi.y;
T0 = (cv::Mat_<double>(2, 3) << 1.0, 0.0, tx, 0.0, 1.0, ty);
m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Matrix",
"affine init invalid, fallback to bbox translation t(%.1f %.1f) px", tx, ty);
}
// 2、模板 Pin 中心(模板图坐标) + 用初值投影到 detImg(仅用于分排)
std::vector<cv::Point2f> tplCenters(nTpl);
std::vector<cv::Point2f> tplCentersDet(nTpl);
for (int i = 0; i < nTpl; i++)
{
cv::Point2f c = getCenter(m_tplPinContours[i]);
tplCenters[i] = cv::Point2f(c.x + static_cast<float>(tplBound.x), c.y + static_cast<float>(tplBound.y));
tplCentersDet[i] = ApplyAffine2D(T0, tplCenters[i]);
}
std::vector<cv::Point2f> curCenters(nCur);
for (int j = 0; j < nCur; j++)
{
curCenters[j] = getCenter(m_curPinContours[j]);
}
// 3、模板 Pin 分两排: 当前帧内定位矩形的长边法线(与整体偏移估计一致); 内定位无效时按 Pin 自身几何兜底
std::vector<std::vector<int>> rows;
{
cv::RotatedRect innerRectDet = m_inner_rroi;
innerRectDet.center.x -= static_cast<float>(m_outer_roi.x);
innerRectDet.center.y -= static_cast<float>(m_outer_roi.y);
if (innerRectDet.size.width > 0 && innerRectDet.size.height > 0)
{
std::vector<std::vector<int>> sideIdx(2);
for (int i = 0; i < nTpl; i++)
{
sideIdx[(PinRowSideOfRect(innerRectDet, tplCentersDet[i]) < 0.0f) ? 0 : 1].push_back(i);
}
if (!sideIdx[0].empty() && !sideIdx[1].empty())
{
rows.push_back(sideIdx[0]);
rows.push_back(sideIdx[1]);
}
}
if (rows.empty())
{
rows = SplitPinsIntoRows(tplCentersDet);
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Pin Matrix",
"inner rect unavailable, split pins by pin geometry instead");
}
}
// 4、重合判据: pin_x_offset(mm) 换算为像素(pin_x_offset 未配置时用 pin_y_offset)
float matchRadius = 0.0f;
if (m_pbaseCheckFunction != NULL)
{
if (m_pbaseCheckFunction->pinDet.pin_x_offset > 0.0f && m_fImgage_Scale_X > 0.0f)
{
matchRadius = m_pbaseCheckFunction->pinDet.pin_x_offset / m_fImgage_Scale_X;
}
else if (m_pbaseCheckFunction->pinDet.pin_y_offset > 0.0f && m_fImgage_Scale_Y > 0.0f)
{
matchRadius = m_pbaseCheckFunction->pinDet.pin_y_offset / m_fImgage_Scale_Y;
}
}
if (matchRadius <= 0.0f)
{
matchRadius = 20.0f; // 未配置阈值时的默认匹配半径
}
if (matchRadius < 2.0f)
{
matchRadius = 2.0f;
}
// 5、逐排拟合: 每排放到对应的当前 Pin 组里做"平移投票(固定初值旋转/缩放) -> 最近邻一对一匹配 -> RANSAC 相似变换",
// 取"模板 Pin 投影后与当前 Pin 重合数最多"的解; 两排的对应关系取总重合数最大的配对(兼容上下颠倒)
struct PinRowFit
{
cv::Mat M; // 模板图坐标 -> detImg 坐标
int nMatch = 0; // 投影后与当前 Pin 重合的数量
float radius = 0.0f;
bool bFromPins = false;
};
// 当前帧 Pin 也按同一内定位法线分两排; 内定位无效时按 Pin 自身几何兜底
std::vector<std::vector<int>> curRows;
{
cv::RotatedRect innerRectDet = m_inner_rroi;
innerRectDet.center.x -= static_cast<float>(m_outer_roi.x);
innerRectDet.center.y -= static_cast<float>(m_outer_roi.y);
if (innerRectDet.size.width > 0 && innerRectDet.size.height > 0)
{
std::vector<std::vector<int>> sideIdx(2);
for (int j = 0; j < nCur; j++)
{
sideIdx[(PinRowSideOfRect(innerRectDet, curCenters[j]) < 0.0f) ? 0 : 1].push_back(j);
}
if (!sideIdx[0].empty() && !sideIdx[1].empty())
{
curRows = sideIdx;
}
}
if (curRows.empty() && nCur > 0)
{
curRows = SplitPinsIntoRows(curCenters);
}
}
std::vector<int> allCurIdx(nCur);
for (int j = 0; j < nCur; j++)
{
allCurIdx[j] = j;
}
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Pin Matrix",
"pin rows: tpl %d cur %d", static_cast<int>(rows.size()),
static_cast<int>(curRows.size()));
const double initAngle = std::atan2(T0.at<double>(1, 0), T0.at<double>(0, 0));
auto bReasonableMat = [&](const cv::Mat &M) -> bool
{
if (M.empty() || M.rows != 2 || M.cols != 3)
{
return false;
}
const double a = M.at<double>(0, 0), b = M.at<double>(0, 1);
const double c = M.at<double>(1, 0), d = M.at<double>(1, 1);
if (a * d - b * c <= 0.0)
{
return false; // 不允许镜像/退化
}
const double scale = std::sqrt(a * a + c * c);
if (scale < 0.5 || scale > 2.0)
{
return false;
}
double dAng = std::atan2(c, a) - initAngle;
while (dAng > CV_PI)
{
dAng -= 2.0 * CV_PI;
}
while (dAng < -CV_PI)
{
dAng += 2.0 * CV_PI;
}
return std::fabs(dAng) <= 30.0 * CV_PI / 180.0;
};
// 用模板 Pin 组 rowIdx 与当前 Pin 候选组 curIdxList 拟合一个相似变换(模板图坐标 -> detImg 坐标)
auto fitOneRow = [&](const std::vector<int> &rowIdx, const std::vector<int> &curIdxList, PinRowFit &fit)
{
const int nRow = static_cast<int>(rowIdx.size());
const int nCurSub = static_cast<int>(curIdxList.size());
fit = PinRowFit();
fit.M = T0.clone();
fit.radius = matchRadius;
// 匹配半径不能超过同排相邻 Pin 间距的一半, 否则会错配到相邻 Pin
{
float minGap = 0.0f;
for (int a = 0; a < nRow; a++)
{
for (int b = a + 1; b < nRow; b++)
{
float d = static_cast<float>(cv::norm(tplCenters[rowIdx[a]] - tplCenters[rowIdx[b]]));
if (minGap <= 0.0f || d < minGap)
{
minGap = d;
}
}
}
if (minGap > 0.0f && fit.radius > minGap * 0.5f)
{
fit.radius = minGap * 0.5f;
}
}
if (fit.radius < 1.0f)
{
fit.radius = 1.0f;
}
if (nRow < 2 || nCurSub < 2)
{
return;
}
// 5.1) 平移投票: 固定初值的旋转/缩放, 取"投影后重合数最多"的平移
const double l00 = T0.at<double>(0, 0), l01 = T0.at<double>(0, 1);
const double l10 = T0.at<double>(1, 0), l11 = T0.at<double>(1, 1);
const cv::Point2f t0(static_cast<float>(T0.at<double>(0, 2)), static_cast<float>(T0.at<double>(1, 2)));
auto countOverlap = [&](const cv::Mat &M) -> int
{
// 粗略重合计数(允许多对一): 只看"投影点附近是否有当前 Pin", 用于平移投票
int cnt = 0;
for (int a = 0; a < nRow; a++)
{
cv::Point2f p = ApplyAffine2D(M, tplCenters[rowIdx[a]]);
for (int k = 0; k < nCurSub; k++)
{
if (cv::norm(curCenters[curIdxList[k]] - p) <= fit.radius)
{
cnt++;
break;
}
}
}
return cnt;
};
int bestCnt = -1;
double bestTDist = 1e12;
cv::Point2f bestT = t0;
for (int a = 0; a < nRow; a++)
{
const cv::Point2f &tp = tplCenters[rowIdx[a]];
const double lx = l00 * tp.x + l01 * tp.y;
const double ly = l10 * tp.x + l11 * tp.y;
for (int k = 0; k < nCurSub; k++)
{
const cv::Point2f &curPt = curCenters[curIdxList[k]];
const cv::Point2f t(static_cast<float>(curPt.x - lx),
static_cast<float>(curPt.y - ly));
cv::Mat M = T0.clone();
M.at<double>(0, 2) = t.x;
M.at<double>(1, 2) = t.y;
const int cnt = countOverlap(M);
const double tDist = cv::norm(t - t0);
// 重合数最多; 并列时取离初值平移更近的(避免跨排/跨列错位)
if (cnt > bestCnt || (cnt == bestCnt && tDist < bestTDist))
{
bestCnt = cnt;
bestT = t;
bestTDist = tDist;
}
}
}
cv::Mat MCur = T0.clone();
MCur.at<double>(0, 2) = bestT.x;
MCur.at<double>(1, 2) = bestT.y;
// 5.2) 最近邻一对一匹配 + RANSAC 相似变换迭代求精, 取重合数最多的解
auto matchPins = [&](const cv::Mat &M, std::vector<std::pair<int, int>> &matches) -> int
{
std::vector<std::pair<double, std::pair<int, int>>> cands;
cands.reserve(static_cast<size_t>(nRow) * static_cast<size_t>(nCurSub));
for (int a = 0; a < nRow; a++)
{
cv::Point2f p = ApplyAffine2D(M, tplCenters[rowIdx[a]]);
for (int k = 0; k < nCurSub; k++)
{
const int j = curIdxList[k];
const double d = cv::norm(curCenters[j] - p);
if (d <= fit.radius)
{
cands.push_back(std::make_pair(d, std::make_pair(a, j)));
}
}
}
std::sort(cands.begin(), cands.end());
std::vector<char> usedT(nRow, 0);
std::vector<char> usedC(nCur, 0);
matches.clear();
for (size_t k = 0; k < cands.size(); k++)
{
const int a = cands[k].second.first;
const int j = cands[k].second.second;
if (usedT[a] || usedC[j])
{
continue;
}
usedT[a] = 1;
usedC[j] = 1;
matches.push_back(cands[k].second);
}
return static_cast<int>(matches.size());
};
std::vector<std::pair<int, int>> matches;
int bestOverlap = matchPins(MCur, matches);
cv::Mat bestM = MCur.clone();
for (int iter = 0; iter < 5 && bestOverlap < nRow; iter++)
{
if (static_cast<int>(matches.size()) < 2)
{
break;
}
std::vector<cv::Point2f> srcPts;
std::vector<cv::Point2f> dstPts;
srcPts.reserve(matches.size());
dstPts.reserve(matches.size());
for (size_t k = 0; k < matches.size(); k++)
{
srcPts.push_back(tplCenters[rowIdx[matches[k].first]]);
dstPts.push_back(curCenters[matches[k].second]);
}
// 一排 Pin 近似共线, 用 4 自由度相似变换(旋转+等比缩放+平移)避免全仿射病态
cv::Mat MNew = cv::estimateAffinePartial2D(srcPts, dstPts, cv::noArray(), cv::RANSAC, 3.0, 2000, 0.995, 10);
if (!bReasonableMat(MNew))
{
break;
}
const int cnt = matchPins(MNew, matches);
if (cnt > bestOverlap)
{
bestOverlap = cnt;
bestM = MNew.clone();
}
MCur = MNew;
}
if (bestOverlap >= 2 && bReasonableMat(bestM))
{
fit.M = bestM;
fit.nMatch = bestOverlap;
fit.bFromPins = true;
}
};
std::vector<PinRowFit> rowFits(rows.size());
if (rows.size() == 2 && curRows.size() == 2)
{
// 两侧都成功分排: 每排分别与两个当前排各拟合一次, 再取总重合数更大的配对(兼容上下颠倒)
PinRowFit fits[2][2];
for (int a = 0; a < 2; a++)
{
for (int b = 0; b < 2; b++)
{
fitOneRow(rows[a], curRows[b], fits[a][b]);
}
}
const int scoreDirect = fits[0][0].nMatch + fits[1][1].nMatch;
const int scoreSwap = fits[0][1].nMatch + fits[1][0].nMatch;
if (scoreSwap > scoreDirect)
{
rowFits[0] = fits[0][1];
rowFits[1] = fits[1][0];
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Pin Matrix",
"row pairing swapped (direct %d vs swap %d)", scoreDirect, scoreSwap);
}
else
{
rowFits[0] = fits[0][0];
rowFits[1] = fits[1][1];
}
}
else
{
// 无法两侧都分排: 每排与全部当前 Pin 匹配
for (size_t r = 0; r < rows.size(); r++)
{
fitOneRow(rows[r], allCurIdx, rowFits[r]);
}
}
for (size_t r = 0; r < rowFits.size(); r++)
{
if (!rowFits[r].bFromPins)
{
// 该排可匹配 Pin 不足(无法拟合), 退回内/外定位仿射投影, 保证缺失 Pin 的 roi 仍能落位
rowFits[r].M = T0.clone();
rowFits[r].nMatch = 0;
}
}
return (method == "line_fitting"); // 6、按每排矩阵把模板 Pin 轮廓投影到 detImg 坐标系
std::vector<int> rowOfPin(nTpl, -1);
for (size_t r = 0; r < rows.size(); r++)
{
for (size_t k = 0; k < rows[r].size(); k++)
{
rowOfPin[rows[r][k]] = static_cast<int>(r);
}
}
m_tplPinContoursCur.resize(nTpl);
for (int i = 0; i < nTpl; i++)
{
const cv::Mat &M = (rowOfPin[i] >= 0) ? rowFits[rowOfPin[i]].M : T0;
std::vector<cv::Point> mapped;
mapped.reserve(m_tplPinContours[i].size());
for (size_t k = 0; k < m_tplPinContours[i].size(); k++)
{
const cv::Point2f srcPt(static_cast<float>(m_tplPinContours[i][k].x + tplBound.x),
static_cast<float>(m_tplPinContours[i][k].y + tplBound.y));
const cv::Point2f dstPt = ApplyAffine2D(M, srcPt);
mapped.emplace_back(cvRound(dstPt.x), cvRound(dstPt.y));
}
m_tplPinContoursCur[i] = std::move(mapped);
}
// 7、日志: 每排的矩阵(旋转/缩放/平移)与重合数
for (size_t r = 0; r < rows.size(); r++)
{
const cv::Mat &M = rowFits[r].M;
const double scale = std::sqrt(M.at<double>(0, 0) * M.at<double>(0, 0) +
M.at<double>(1, 0) * M.at<double>(1, 0));
const double angle = std::atan2(M.at<double>(1, 0), M.at<double>(0, 0)) * 180.0 / CV_PI;
m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Matrix",
"row %d pins %d matched %d/%d scale %.4f angle %.2f deg t(%.1f %.1f) px %s",
static_cast<int>(r), static_cast<int>(rows[r].size()),
rowFits[r].nMatch, static_cast<int>(rows[r].size()), scale, angle,
M.at<double>(0, 2), M.at<double>(1, 2),
rowFits[r].bFromPins ? "" : "(fallback to align affine)");
}
m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Matrix",
"tpl %d cur %d rows %d match radius %.1f px (pin_x_offset %.2f mm scale_x %.4f)",
nTpl, nCur, static_cast<int>(rows.size()), rowFits.empty() ? matchRadius : rowFits[0].radius,
m_pbaseCheckFunction != NULL ? m_pbaseCheckFunction->pinDet.pin_x_offset : 0.0f,
m_fImgage_Scale_X);
return 0;
} }
int ImgCheckAnalysisy::Pin_Qx_Det(const cv::Mat &img) int ImgCheckAnalysisy::Pin_Qx_Det(const cv::Mat &img)
@ -2330,6 +2970,13 @@ int ImgCheckAnalysisy::Pin_Qx_Det(const cv::Mat &img)
else else
// 根据模板Pin和当前Pin轮廓进行对比,判断当前有无缺失和偏移 // 根据模板Pin和当前Pin轮廓进行对比,判断当前有无缺失和偏移
{ {
if (IsPinMatrixMethod())
{
// Det_Method == "Pin_Matrix": 投影矩阵由当前帧 Pin 拟合(上下两排分别计算),
// 把模板 Pin 投影到当前检测图坐标系; 后续比对与 BTB_Marix 完全一致
ComputePinProjectionMats();
}
// 计算轮廓中心点(外接矩形中心) // 计算轮廓中心点(外接矩形中心)
auto getCenter = [](const std::vector<cv::Point> &contour) -> cv::Point2f auto getCenter = [](const std::vector<cv::Point> &contour) -> cv::Point2f
{ {
@ -2362,6 +3009,8 @@ int ImgCheckAnalysisy::Pin_Qx_Det(const cv::Mat &img)
std::vector<bool> curUsed(nCur, false); std::vector<bool> curUsed(nCur, false);
// 整体偏移估计与消除(阈值见文件顶部 PIN_SHIFT_* 常量): // 整体偏移估计与消除(阈值见文件顶部 PIN_SHIFT_* 常量):
// Pin_Matrix 方案的投影矩阵已由 Pin 自身拟合(上下两排分别计算), 不需要二次偏移矫正
if (!IsPinMatrixMethod())
{ {
// inner 定位 ROI 转到 detImg 坐标系(detImg 由 m_outer_roi 裁剪得到) // inner 定位 ROI 转到 detImg 坐标系(detImg 由 m_outer_roi 裁剪得到)
cv::RotatedRect innerRectDet = m_inner_rroi; cv::RotatedRect innerRectDet = m_inner_rroi;

@ -1249,6 +1249,7 @@ struct Base_Function_Pin_Det
float pin_y_offset; // pin y 偏移 float pin_y_offset; // pin y 偏移
// "line_fitting" = 新方案(直接分析检测Pin: 分两排拟合直线判偏移 + 相邻间距判缺失, 不做模板对比) // "line_fitting" = 新方案(直接分析检测Pin: 分两排拟合直线判偏移 + 相邻间距判缺失, 不做模板对比)
// "BTB_Marix" = 旧方案(内定位仿射 + 模板Pin对比) // "BTB_Marix" = 旧方案(内定位仿射 + 模板Pin对比)
// "Pin_Matrix" = 模板Pin对比方案, 投影矩阵由 Pin 自身拟合(上下两排分别计算, 不做二次偏移矫正)
std::string Det_Method; // pin 检测方案 std::string Det_Method; // pin 检测方案
bool bDrawResult; // 是否绘制结果 bool bDrawResult; // 是否绘制结果

Loading…
Cancel
Save