|
|
|
|
@ -36,6 +36,91 @@ static float PinRowSideOfRect(const cv::RotatedRect &rr, const cv::Point2f &pt)
|
|
|
|
|
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索引:缺陷名称;冒号后名称为该缺陷类型名称,行的顺序即缺陷类型索引)
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
|
{
|
|
|
|
|
// 旧方案(Det_Method == "BTB_Marix" 或未配置): 不对原图做 warpAffine, 只计算矩阵, 并把模板 Pin 轮廓反变换到当前图坐标系。
|
|
|
|
|
@ -2260,28 +2368,560 @@ int ImgCheckAnalysisy::UpdateImgageScale()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 当前 Pin 检测方案是否为"直接分析"(新方案): 由参数 pinDet.Det_Method 在运行时决定
|
|
|
|
|
// "line_fitting" -> 新方案(不做模板对比); 其它/未配置 -> 旧方案(模板对比)
|
|
|
|
|
// "line_fitting" -> 新方案(不做模板对比); 其它/未配置 -> 模板对比方案
|
|
|
|
|
bool ImgCheckAnalysisy::IsDirectPinMethod() const
|
|
|
|
|
{
|
|
|
|
|
if (m_pbaseCheckFunction == NULL)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return NormalizeDetMethodStr(m_pbaseCheckFunction->pinDet.Det_Method) == "line_fitting";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string method = m_pbaseCheckFunction->pinDet.Det_Method;
|
|
|
|
|
// 去掉首尾空白并转小写, 兼容大小写/带空格的配置值
|
|
|
|
|
size_t begin = method.find_first_not_of(" \t\r\n");
|
|
|
|
|
if (begin == std::string::npos)
|
|
|
|
|
// 当前 Pin 检测方案是否为"Pin 投影矩阵"方案: pinDet.Det_Method == "Pin_Matrix"(忽略大小写/空白)
|
|
|
|
|
// 该方案与 BTB_Marix 一样做模板 Pin 对比, 区别只在投影矩阵由 Pin 自身拟合(上下两排分别计算)
|
|
|
|
|
bool ImgCheckAnalysisy::IsPinMatrixMethod() const
|
|
|
|
|
{
|
|
|
|
|
if (m_pbaseCheckFunction == NULL)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
size_t end = method.find_last_not_of(" \t\r\n");
|
|
|
|
|
method = method.substr(begin, end - begin + 1);
|
|
|
|
|
std::transform(method.begin(), method.end(), method.begin(),
|
|
|
|
|
[](unsigned char c)
|
|
|
|
|
{ return static_cast<char>(std::tolower(c)); });
|
|
|
|
|
return NormalizeDetMethodStr(m_pbaseCheckFunction->pinDet.Det_Method) == "pin_matrix";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Pin_Matrix 方案: 过滤当前帧上明显的误检 blob(按模板 Pin 的包围框尺寸)
|
|
|
|
|
// 以模板 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]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (method == "line_fitting");
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
@ -2330,6 +2970,13 @@ int ImgCheckAnalysisy::Pin_Qx_Det(const cv::Mat &img)
|
|
|
|
|
else
|
|
|
|
|
// 根据模板Pin和当前Pin轮廓进行对比,判断当前有无缺失和偏移
|
|
|
|
|
{
|
|
|
|
|
if (IsPinMatrixMethod())
|
|
|
|
|
{
|
|
|
|
|
// Det_Method == "Pin_Matrix": 投影矩阵由当前帧 Pin 拟合(上下两排分别计算),
|
|
|
|
|
// 把模板 Pin 投影到当前检测图坐标系; 后续比对与 BTB_Marix 完全一致
|
|
|
|
|
ComputePinProjectionMats();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 计算轮廓中心点(外接矩形中心)
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
// 整体偏移估计与消除(阈值见文件顶部 PIN_SHIFT_* 常量):
|
|
|
|
|
// Pin_Matrix 方案的投影矩阵已由 Pin 自身拟合(上下两排分别计算), 不需要二次偏移矫正
|
|
|
|
|
if (!IsPinMatrixMethod())
|
|
|
|
|
{
|
|
|
|
|
// inner 定位 ROI 转到 detImg 坐标系(detImg 由 m_outer_roi 裁剪得到)
|
|
|
|
|
cv::RotatedRect innerRectDet = m_inner_rroi;
|
|
|
|
|
|