feat 添加直线拟合方式

dev_lsy
liusiyang 3 weeks ago
parent 3fb6f4ec4c
commit c4ca12293e

@ -40,6 +40,17 @@
using namespace std;
using namespace cv;
// ============================ Pin 检测方案开关 ============================
// 1 = 新方案(Pin 直接分析, 不做模板对比): 不做仿射变换, 检测图直接按当前外定位结果裁剪;
// 只分析检测Pin: 自动区分两排Pin(考虑图像旋转)并逐排拟合直线,
// 到直线(法线方向)距离超过 pin_y_offset 的判 偏移;
// 相邻两Pin中心连线距离超过 pin_x_offset 的判 两Pin中间缺失一个Pin。
// 0 = 旧方案(模板对比/内定位仿射): 以内定位(无效时回退外定位)仿射变换到模板坐标系后与模板Pin对比。
// 说明: 可通过编译参数 -DPIN_USE_DIRECT_MATCH=0/1 覆盖此处默认值。
#ifndef PIN_USE_DIRECT_MATCH
#define PIN_USE_DIRECT_MATCH 0
#endif
enum AT_THRESHOLD_TYPE_
{
AT_THRESHOLD_TYPE_NULL,
@ -196,6 +207,11 @@ private:
// 边缘缺陷检测
int Pin_Qx_Det(const cv::Mat &img);
#if PIN_USE_DIRECT_MATCH
// 新方案: 不做模板对比, 只分析检测Pin: 自动分两排(考虑旋转) -> 拟合直线判偏移 + 相邻间距判缺失
int Pin_Direct_Check();
#endif
// 把blob 汇总成 检测结果。
int BLobToDetResult();
@ -303,6 +319,8 @@ private:
cv::RotatedRect m_tplInnerRect; // 模板内边缘矩形
std::vector<std::vector<cv::Point>> m_tplPinContours; // 模板 pin blob 轮廓点集
std::vector<std::vector<cv::Point>> m_curPinContours; // 当前 pin blob 轮廓点集
std::vector<std::pair<cv::Point2f, cv::Point2f>> m_pinFitLines; // 新方案: 两排Pin拟合直线段(当前检测图坐标系, 两端点)
std::vector<cv::Point2f> m_pinMissMarks; // 新方案: 缺失Pin位置(相邻Pin中心连线中点)
};
#endif

@ -10,6 +10,8 @@
#include "CheckUtil.hpp"
#include "Define.h"
#include <omp.h>
#include <map>
#include <algorithm>
#include "AICommonDefine.h"
// 用于排序轮廓的比较函数
static bool compareContourAreas(const vector<Point> &contour1, const vector<Point> &contour2)
@ -557,6 +559,27 @@ int ImgCheckAnalysisy::CheckRun()
}
/*投影对齐模板*/
#if PIN_USE_DIRECT_MATCH
// 新方案(开关 PIN_USE_DIRECT_MATCH = 1): 不做仿射变换, 检测图直接按当前外定位结果裁剪;
// Pin分析在 Pin_Qx_Det -> Pin_Direct_Check 中完成(只分析检测Pin, 不做模板对比)
m_pdetlog->AddCheckstr(PrintLevel_0, "2.1、pre detect", "Align mode: Direct Pin Check (no affine)");
{
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", "Direct Pin Match: 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
// 检测PIN偏移/缺失对位置基准最敏感:PIN 位置的真实基准是产品内腔(内定位),
// 因此优先用内定位做仿射变换(当前内边缘 -> 模板内边缘);
// 模板内定位/当前内定位缺失或无效时,回退到外定位(与旧行为一致)
@ -573,6 +596,7 @@ int ImgCheckAnalysisy::CheckRun()
Adapt_Config(m_tplOuterRect, m_outer_rroi, m_CheckResult_shareP->in_shareImage->img);
}
m_outer_roi = m_tplOuterRect.boundingRect();
#endif
m_Crop_Roi_paramImg = m_outer_roi;
m_pImageAllResult->pDetResult->CutRoi = m_outer_roi;
m_pImageAllResult->pDetResult->Param_CropRoi = m_Crop_Roi_paramImg;
@ -1850,7 +1874,27 @@ int ImgCheckAnalysisy::DrawResult_Step_1()
float fs_resize_y = m_pImageAllResult->resultImg.rows * 1.0f / m_pImageAllResult->detImg.rows;
// 绘制模板 pin 轮廓(绿色)与当前 pin 轮廓(红色)
for (const auto &contour : m_tplPinContours)
#if PIN_USE_DIRECT_MATCH
// 新方案: 模板pin已配准到当前检测图坐标系
// 新方案: 不做模板对比; 将两排Pin拟合直线段与缺失标记组织成 2点"轮廓", 复用下方循环缩放绘制(绿色)
std::vector<std::vector<cv::Point>> tplDrawPinContours;
for (const auto &seg : m_pinFitLines)
{
tplDrawPinContours.push_back({cv::Point(cvRound(seg.first.x), cvRound(seg.first.y)),
cv::Point(cvRound(seg.second.x), cvRound(seg.second.y))});
}
for (const auto &mark : m_pinMissMarks)
{
// 缺失位置: 十字标记(两条短线)
tplDrawPinContours.push_back({cv::Point(cvRound(mark.x - 6.0f), cvRound(mark.y)),
cv::Point(cvRound(mark.x + 6.0f), cvRound(mark.y))});
tplDrawPinContours.push_back({cv::Point(cvRound(mark.x), cvRound(mark.y - 6.0f)),
cv::Point(cvRound(mark.x), cvRound(mark.y + 6.0f))});
}
#else
const std::vector<std::vector<cv::Point>> &tplDrawPinContours = m_tplPinContours;
#endif
for (const auto &contour : tplDrawPinContours)
{
std::vector<cv::Point> draw_pts;
draw_pts.reserve(contour.size());
@ -1978,6 +2022,10 @@ int ImgCheckAnalysisy::Pin_Qx_Det(const cv::Mat &img)
m_curPinContours.push_back(mapped);
}
#if PIN_USE_DIRECT_MATCH
// 新方案: 不做模板对比, 只分析检测Pin(自动分两排 -> 逐排拟合直线判偏移 + 相邻间距判缺失)
Pin_Direct_Check();
#else
// 根据模板Pin和当前Pin轮廓进行对比,判断当前有无缺失和偏移
{
// 计算轮廓中心点(外接矩形中心)
@ -2110,6 +2158,7 @@ int ImgCheckAnalysisy::Pin_Qx_Det(const cv::Mat &img)
m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Check", "tpl %d cur %d miss %d offset %d", nTpl, nCur, nMiss, nOffset);
}
}
#endif
if (m_Edge_DetConfig.bSaveResultImg)
{
@ -2130,13 +2179,597 @@ int ImgCheckAnalysisy::Pin_Qx_Det(const cv::Mat &img)
showimg = img.clone();
}
cv::drawContours(showimg, m_curPinContours, -1, CheckUtil::GetDrawColor(showimg, cv::Scalar(0, 0, 255)), 1);
#if PIN_USE_DIRECT_MATCH
// 新方案: 绘制两排Pin的拟合直线(绿); 缺失位置以黄色十字标记
for (const auto &seg : m_pinFitLines)
{
cv::line(showimg, cv::Point(cvRound(seg.first.x), cvRound(seg.first.y)),
cv::Point(cvRound(seg.second.x), cvRound(seg.second.y)),
CheckUtil::GetDrawColor(showimg, cv::Scalar(0, 255, 0)), 1);
}
for (const auto &mark : m_pinMissMarks)
{
cv::drawMarker(showimg, cv::Point(cvRound(mark.x), cvRound(mark.y)),
CheckUtil::GetDrawColor(showimg, cv::Scalar(0, 255, 255)), cv::MARKER_CROSS, 16, 1);
}
#else
cv::drawContours(showimg, m_tplPinContours, -1, CheckUtil::GetDrawColor(showimg, cv::Scalar(0, 255, 0)), 1);
#endif
imwrite("pin_contours.png", showimg);
}
return 0;
}
#if PIN_USE_DIRECT_MATCH
#if 0 // ----- 旧配准匹配方案(Pin_Direct_Match)已停用, 仅保留代码参考; 如需启用需恢复 m_tplPinContoursCur 成员 -----
// 新方案(开关 PIN_USE_DIRECT_MATCH = 1): 不做仿射变换, 直接对模板Pin与检测Pin做配准后对比
// 输入: m_tplPinContours(模板裁剪坐标系) / m_curPinContours(当前检测图坐标系)
// 输出: m_tplPinContoursCur(配准到当前检测图坐标系的模板Pin轮廓) + 缺失/偏移缺陷上报
// 思路:
// 1) 平移投票: 统计所有 "检测pin中心 - 模板pin中心" 的位移, 取出现次数最多的位移作为全局平移,
// 即让最多数量Pin对重合的平移(对缺失pin/误检pin鲁棒, 最大程度一一对应);
// 2) 由近到远贪心做一对一匹配, 并用匹配对的位移中值迭代细化全局平移(抗异常);
// 3) 配不上检测pin的模板pin -> 缺失; 配上但 x/y 偏移超阈值的 -> 偏移。
int ImgCheckAnalysisy::Pin_Direct_Match()
{
// 计算轮廓中心点(外接矩形中心)
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());
m_tplPinContoursCur.clear();
if (nTpl <= 0)
{
return 0;
}
std::vector<cv::Point2f> tplCenters(nTpl);
for (int i = 0; i < nTpl; i++)
{
tplCenters[i] = getCenter(m_tplPinContours[i]);
}
std::vector<cv::Point2f> curCenters(nCur);
for (int i = 0; i < nCur; i++)
{
curCenters[i] = getCenter(m_curPinContours[i]);
}
// 匹配半径: 取模板pin相邻最小间距的一半, 避免相邻pin被错配; pin数不足时用默认值
float minGap = 0.0f;
for (int i = 0; i < nTpl; i++)
{
for (int j = i + 1; j < nTpl; j++)
{
float d = static_cast<float>(cv::norm(tplCenters[i] - tplCenters[j]));
if (minGap <= 0.0f || d < minGap)
{
minGap = d;
}
}
}
float matchRadius = (minGap > 0.0f) ? (minGap * 0.5f) : 20.0f;
if (matchRadius < 2.0f)
{
matchRadius = 2.0f;
}
// 1、平移投票: 统计出现次数最多的 "检测pin - 模板pin" 位移(4px 一档, 取峰值邻域均值细化)
cv::Point2f T(0.0f, 0.0f);
if (nCur > 0)
{
const float binSize = 4.0f;
std::map<std::pair<int, int>, int> hist;
int bestCount = 0;
float bestX = 0.0f;
float bestY = 0.0f;
for (int i = 0; i < nTpl; i++)
{
for (int j = 0; j < nCur; j++)
{
float dx = curCenters[j].x - tplCenters[i].x;
float dy = curCenters[j].y - tplCenters[i].y;
std::pair<int, int> key(static_cast<int>(std::lround(dx / binSize)),
static_cast<int>(std::lround(dy / binSize)));
int cnt = ++hist[key];
float cx = key.first * binSize;
float cy = key.second * binSize;
// 峰值并列时优先取位移更小的(两图裁剪都以产品为中心,真实位移通常较小)
if (cnt > bestCount ||
(cnt == bestCount && cx * cx + cy * cy < bestX * bestX + bestY * bestY))
{
bestCount = cnt;
bestX = cx;
bestY = cy;
}
}
}
// 用峰值邻域(±1档)的所有位移取均值, 得到更精确的全局平移
double sumX = 0.0;
double sumY = 0.0;
int sumN = 0;
for (int i = 0; i < nTpl; i++)
{
for (int j = 0; j < nCur; j++)
{
float dx = curCenters[j].x - tplCenters[i].x;
float dy = curCenters[j].y - tplCenters[i].y;
if (std::fabs(dx - bestX) <= binSize && std::fabs(dy - bestY) <= binSize)
{
sumX += dx;
sumY += dy;
sumN++;
}
}
}
if (sumN > 0)
{
T.x = static_cast<float>(sumX / sumN);
T.y = static_cast<float>(sumY / sumN);
}
else
{
T.x = bestX;
T.y = bestY;
}
}
// 2、由近到远贪心做一对一匹配, 并用匹配对的位移中值迭代细化全局平移
std::vector<int> matchOfTpl(nTpl, -1);
for (int iter = 0; iter < 3; iter++)
{
// 收集满足匹配半径的候选对, 按距离升序
std::vector<std::pair<double, std::pair<int, int>>> cands; // (距离, (模板idx, 检测idx))
cands.reserve(static_cast<size_t>(nTpl) * static_cast<size_t>(nCur));
for (int i = 0; i < nTpl; i++)
{
cv::Point2f pred = tplCenters[i] + T; // 配准后模板pin在当前检测图中的预测位置
for (int j = 0; j < nCur; j++)
{
double d = cv::norm(curCenters[j] - pred);
if (d <= matchRadius)
{
cands.push_back(std::make_pair(d, std::make_pair(i, j)));
}
}
}
std::sort(cands.begin(), cands.end());
std::vector<char> tplUsed(nTpl, 0);
std::vector<char> curUsed(nCur, 0);
matchOfTpl.assign(nTpl, -1);
std::vector<float> resDisX;
std::vector<float> resDisY;
for (size_t k = 0; k < cands.size(); k++)
{
int i = cands[k].second.first;
int j = cands[k].second.second;
if (tplUsed[i] || curUsed[j])
{
continue;
}
tplUsed[i] = 1;
curUsed[j] = 1;
matchOfTpl[i] = j;
resDisX.push_back(curCenters[j].x - (tplCenters[i].x + T.x));
resDisY.push_back(curCenters[j].y - (tplCenters[i].y + T.y));
}
if (resDisX.empty())
{
break;
}
// 用位移中值(抗异常)更新全局平移, 使匹配对最大程度重合
std::sort(resDisX.begin(), resDisX.end());
std::sort(resDisY.begin(), resDisY.end());
float mx = resDisX[resDisX.size() / 2];
float my = resDisY[resDisY.size() / 2];
T.x += mx;
T.y += my;
if (std::fabs(mx) < 0.5f && std::fabs(my) < 0.5f)
{
break;
}
}
// 3、把模板pin轮廓按最终平移映射到当前检测图坐标系, 供匹配/绘制/上报使用
m_tplPinContoursCur.reserve(nTpl);
for (int i = 0; i < nTpl; i++)
{
std::vector<cv::Point> mapped;
mapped.reserve(m_tplPinContours[i].size());
for (const auto &pt : m_tplPinContours[i])
{
mapped.emplace_back(cvRound(pt.x + T.x), cvRound(pt.y + T.y));
}
m_tplPinContoursCur.push_back(mapped);
}
// 4、偏移阈值由基础参数 pin_x_offset / pin_y_offset (mm) 换算为像素
float offsetThX = 0.0f;
float offsetThY = 0.0f;
if (m_pbaseCheckFunction != NULL)
{
if (m_fImgage_Scale_X > 0.0f)
{
offsetThX = m_pbaseCheckFunction->pinDet.pin_x_offset / m_fImgage_Scale_X;
}
if (m_fImgage_Scale_Y > 0.0f)
{
offsetThY = m_pbaseCheckFunction->pinDet.pin_y_offset / m_fImgage_Scale_Y;
}
}
// 上报缺失/偏移缺陷(pin 位置取配准后的模板pin)
auto reportPinError = [&](int pinIdx, const cv::Point2f &tplCenter, const std::string &reason, int nconfig_qx_type)
{
QX_ERROR_INFO_ pinErr;
pinErr.Idx = static_cast<int>(m_pDetResult->pQx_ErrorList->size());
cv::Rect r = cv::boundingRect(m_tplPinContoursCur[pinIdx]);
pinErr.roi = r;
pinErr.area = static_cast<int>(cv::contourArea(m_tplPinContoursCur[pinIdx]));
pinErr.JudgArea = pinErr.area * m_fImgage_Scale_X * m_fImgage_Scale_Y;
int longSide = (r.width > r.height) ? r.width : r.height;
int shortSide = (r.width < r.height) ? r.width : r.height;
pinErr.flen = longSide * m_fImgage_Scale_X;
pinErr.fbreadth = shortSide * m_fImgage_Scale_Y;
pinErr.nconfig_qx_type = nconfig_qx_type;
pinErr.qx_name = CONFIG_QX_NAME_Names[nconfig_qx_type];
pinErr.detRegionidxList.push_back(0);
pinErr.detlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Pin Check",
"pin %d center(%d,%d) %s",
pinIdx, static_cast<int>(tplCenter.x), static_cast<int>(tplCenter.y),
reason.c_str());
m_pDetResult->pQx_ErrorList->push_back(pinErr);
};
int nMiss = 0;
int nOffset = 0;
for (int i = 0; i < nTpl; i++)
{
// 配不上检测pin的模板pin -> 缺失
if (matchOfTpl[i] < 0)
{
nMiss++;
reportPinError(i, tplCenters[i] + T, "miss", CONFIG_QX_NAME_PIN_QueShi);
m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Check", "pin %d MISS", i);
continue;
}
// x 或 y 方向偏移超过配置阈值 -> 偏移
int j = matchOfTpl[i];
float dx = curCenters[j].x - (tplCenters[i].x + T.x);
float dy = curCenters[j].y - (tplCenters[i].y + T.y);
float absDx = std::fabs(dx);
float absDy = std::fabs(dy);
if (absDx > offsetThX || absDy > offsetThY)
{
nOffset++;
char buf[64];
snprintf(buf, sizeof(buf), "offset dx %.1f dy %.1f", absDx, absDy);
reportPinError(i, tplCenters[i] + T, buf, CONFIG_QX_NAME_PIN_PianYi);
m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Check", "pin %d OFFSET dx %.1f dy %.1f", i, absDx, absDy);
}
}
m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Check",
"direct match: shift(%.1f, %.1f) tpl %d cur %d miss %d offset %d",
T.x, T.y, nTpl, nCur, nMiss, nOffset);
return 0;
}
#endif
// 新方案(开关 PIN_USE_DIRECT_MATCH = 1): 不做模板对比, 只针对检测Pin分析
// 1) 方向扫描(0~180°, 步长1°): 取 "两排Pin沿法线投影组内方差之和" 最小的角度作为Pin排方向(对图像旋转不敏感),
// 再沿该法线投影, 在最大间隙处把Pin分成两排(两排插口; Pin不足4个则按一排处理);
// 2) 每排做最小二乘直线拟合(cv::fitLine): pin中心到直线(沿法线方向)距离超过 pin_y_offset(换算像素) 判 偏移;
// 3) 每排内沿排方向排序: 相邻两pin中心连线距离超过 pin_x_offset(换算像素) 判 两pin中间缺失一个pin。
int ImgCheckAnalysisy::Pin_Direct_Check()
{
// 计算轮廓中心点(外接矩形中心)
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 nCur = static_cast<int>(m_curPinContours.size());
m_pinFitLines.clear();
m_pinMissMarks.clear();
if (nCur <= 0)
{
return 0;
}
std::vector<cv::Point2f> curCenters(nCur);
std::vector<cv::Rect> curRects(nCur);
for (int i = 0; i < nCur; i++)
{
curCenters[i] = getCenter(m_curPinContours[i]);
curRects[i] = cv::boundingRect(m_curPinContours[i]);
}
if (nCur < 2)
{
m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Check", "direct check: pin %d < 2, skip", nCur);
return 0;
}
// 阈值(mm 参数换算为像素): pin_x_offset -> 相邻pin间距(缺失)阈值; pin_y_offset -> 到拟合直线距离(偏移)阈值; <=0 时不判该项
float offsetThX = 0.0f;
float offsetThY = 0.0f;
if (m_pbaseCheckFunction != NULL)
{
if (m_fImgage_Scale_X > 0.0f)
{
offsetThX = m_pbaseCheckFunction->pinDet.pin_x_offset / m_fImgage_Scale_X;
}
if (m_fImgage_Scale_Y > 0.0f)
{
offsetThY = m_pbaseCheckFunction->pinDet.pin_y_offset / m_fImgage_Scale_Y;
}
}
// 沿给定方向投影排序, 在最大间隙处切分成两排(order[0..splitK] 一排, order[splitK+1..] 另一排)
// 两排各至少2个pin; pin不足4个时无法可靠分排, 返回 -1(按一排整体处理)
auto splitByNormal = [&](const cv::Point2f &nor, std::vector<float> &proj, std::vector<int> &order) -> int
{
order.resize(nCur);
proj.resize(nCur);
for (int i = 0; i < nCur; i++)
{
order[i] = i;
proj[i] = curCenters[i].dot(nor);
}
std::sort(order.begin(), order.end(), [&](int a, int b) { return proj[a] < proj[b]; });
int splitK = -1;
if (nCur >= 4)
{
float bestGap = -1.0f;
for (int k = 1; k <= nCur - 3; k++)
{
float gap = proj[order[k + 1]] - proj[order[k]];
if (gap > bestGap)
{
bestGap = gap;
splitK = k;
}
}
}
return splitK;
};
// 组内投影方差之和: 正确排方向上两排pin各自近似共线, 沿法线投影最集中, 该值最小
auto scoreOfRowDir = [&](const std::vector<float> &proj, const std::vector<int> &order, int splitK) -> double
{
double score = 0.0;
const int nGroup = (splitK >= 0) ? 2 : 1;
for (int g = 0; g < nGroup; g++)
{
int b = (g == 0) ? 0 : (splitK + 1);
int e = (g == 0) ? (splitK + 1) : nCur;
double m = 0.0;
for (int i = b; i < e; i++)
{
m += proj[order[i]];
}
m /= (e - b);
for (int i = b; i < e; i++)
{
double d = proj[order[i]] - m;
score += d * d;
}
}
return score;
};
// 1、方向扫描 0~180°(1°一档), 找到让"两排沿法线投影组内方差之和"最小的排方向(考虑图像旋转)
float bestTheta = 0.0f;
if (nCur >= 4)
{
double bestScore = -1.0;
std::vector<float> proj;
std::vector<int> order;
for (int deg = 0; deg < 180; deg++)
{
float th = static_cast<float>(deg * CV_PI / 180.0);
cv::Point2f nor(-std::sin(th), std::cos(th));
int splitK = splitByNormal(nor, proj, order);
double score = scoreOfRowDir(proj, order, splitK);
if (bestScore < 0.0 || score < bestScore)
{
bestScore = score;
bestTheta = th;
}
}
}
else
{
// pin 太少无法分排, 用PCA主方向兜底(按一排处理)
cv::Point2f meanPt(0.0f, 0.0f);
for (int i = 0; i < nCur; i++)
{
meanPt += curCenters[i];
}
meanPt.x /= nCur;
meanPt.y /= nCur;
double sxx = 0.0, sxy = 0.0, syy = 0.0;
for (int i = 0; i < nCur; i++)
{
double dx = curCenters[i].x - meanPt.x;
double dy = curCenters[i].y - meanPt.y;
sxx += dx * dx;
sxy += dx * dy;
syy += dy * dy;
}
bestTheta = static_cast<float>(0.5 * std::atan2(2.0 * sxy, sxx - syy));
}
// 用最优排方向重新投影, 把Pin分成两排
cv::Point2f dirRow(static_cast<float>(std::cos(bestTheta)), static_cast<float>(std::sin(bestTheta)));
cv::Point2f dirNor(-dirRow.y, dirRow.x);
std::vector<float> proj;
std::vector<int> order;
int splitK = splitByNormal(dirNor, proj, order);
std::vector<std::vector<int>> rows;
if (splitK >= 0)
{
rows.push_back(std::vector<int>(order.begin(), order.begin() + splitK + 1));
rows.push_back(std::vector<int>(order.begin() + splitK + 1, order.end()));
}
else
{
rows.push_back(order);
}
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Pin Check",
"row dir %.1f deg rows %d th[gap %.1f line %.1f] px",
bestTheta * 180.0f / static_cast<float>(CV_PI),
static_cast<int>(rows.size()), offsetThX, offsetThY);
// 上报缺陷(roi/area 由调用处给出; grayDis 记录对应度量: 到直线距离 / 相邻间距, 单位像素)
auto reportPinError = [&](const cv::Rect &roi, int area, double grayDis, int nconfig_qx_type, const std::string &detail)
{
QX_ERROR_INFO_ pinErr;
pinErr.Idx = static_cast<int>(m_pDetResult->pQx_ErrorList->size());
pinErr.roi = roi;
pinErr.area = area;
pinErr.JudgArea = pinErr.area * m_fImgage_Scale_X * m_fImgage_Scale_Y;
int longSide = (roi.width > roi.height) ? roi.width : roi.height;
int shortSide = (roi.width < roi.height) ? roi.width : roi.height;
pinErr.flen = longSide * m_fImgage_Scale_X;
pinErr.fbreadth = shortSide * m_fImgage_Scale_Y;
pinErr.nconfig_qx_type = nconfig_qx_type;
pinErr.qx_name = CONFIG_QX_NAME_Names[nconfig_qx_type];
pinErr.grayDis = grayDis;
pinErr.detRegionidxList.push_back(0);
pinErr.detlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Pin Check", "%s", detail.c_str());
m_pDetResult->pQx_ErrorList->push_back(pinErr);
};
int nOffset = 0;
int nMiss = 0;
// 2、3、逐排: 拟合直线 -> 判偏移; 沿排排序 -> 判相邻缺失
for (size_t r = 0; r < rows.size(); r++)
{
const std::vector<int> &rowIdx = rows[r];
const int nRow = static_cast<int>(rowIdx.size());
if (nRow < 2)
{
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Pin Check",
"row %d pin num %d < 2, skip", static_cast<int>(r), nRow);
continue;
}
std::vector<cv::Point2f> rowPts(nRow);
for (int i = 0; i < nRow; i++)
{
rowPts[i] = curCenters[rowIdx[i]];
}
// 最小二乘直线拟合: lineDir 单位方向向量, lineP0 直线上一点, lineNor 单位法线
cv::Vec4f linePara;
cv::fitLine(rowPts, linePara, cv::DIST_L2, 0, 0.01, 0.01);
cv::Point2f lineDir(linePara[0], linePara[1]);
cv::Point2f lineP0(linePara[2], linePara[3]);
cv::Point2f lineNor(-lineDir.y, lineDir.x);
// 记录拟合线段(沿排方向延伸到该排两端, 供绘制/回溯)
float tMin = 0.0f;
float tMax = 0.0f;
for (int i = 0; i < nRow; i++)
{
float t = (rowPts[i] - lineP0).dot(lineDir);
if (i == 0 || t < tMin)
{
tMin = t;
}
if (i == 0 || t > tMax)
{
tMax = t;
}
}
m_pinFitLines.push_back(std::make_pair(lineP0 + lineDir * tMin, lineP0 + lineDir * tMax));
// 2、pin中心到直线(沿法线方向)距离超过 pin_y_offset -> 偏移
if (offsetThY > 0.0f)
{
for (int i = 0; i < nRow; i++)
{
float absDist = std::fabs((rowPts[i] - lineP0).dot(lineNor));
if (absDist > offsetThY)
{
int idx = rowIdx[i];
nOffset++;
char buf[96];
snprintf(buf, sizeof(buf), "pin %d offset to row line %.1f px > %.1f px", idx, absDist, offsetThY);
reportPinError(curRects[idx], static_cast<int>(cv::contourArea(m_curPinContours[idx])),
absDist, CONFIG_QX_NAME_PIN_PianYi, buf);
m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Check", "row %d pin %d OFFSET %.1f px > %.1f px",
static_cast<int>(r), idx, absDist, offsetThY);
}
}
}
// 3、沿排方向排序, 相邻两pin中心连线距离超过 pin_x_offset -> 两pin中间缺失一个pin
if (offsetThX > 0.0f)
{
std::vector<std::pair<float, int>> tOrder;
tOrder.reserve(nRow);
for (int i = 0; i < nRow; i++)
{
tOrder.push_back(std::make_pair((rowPts[i] - lineP0).dot(lineDir), i));
}
std::sort(tOrder.begin(), tOrder.end());
for (int i = 0; i + 1 < nRow; i++)
{
int ia = tOrder[i].second;
int ib = tOrder[i + 1].second;
float gap = static_cast<float>(cv::norm(rowPts[ib] - rowPts[ia]));
if (gap > offsetThX)
{
int idxa = rowIdx[ia];
int idxb = rowIdx[ib];
cv::Point2f mid = (rowPts[ia] + rowPts[ib]) * 0.5f;
m_pinMissMarks.push_back(mid);
// 缺失位置按两个相邻pin的平均尺寸上报
cv::Rect roiAvg = curRects[idxa];
roiAvg.width = (curRects[idxa].width + curRects[idxb].width + 1) / 2;
roiAvg.height = (curRects[idxa].height + curRects[idxb].height + 1) / 2;
cv::Rect roiMiss(cvRound(mid.x - roiAvg.width * 0.5f), cvRound(mid.y - roiAvg.height * 0.5f),
roiAvg.width, roiAvg.height);
int areaAvg = (static_cast<int>(cv::contourArea(m_curPinContours[idxa])) +
static_cast<int>(cv::contourArea(m_curPinContours[idxb]))) / 2;
nMiss++;
char buf[96];
snprintf(buf, sizeof(buf), "miss between pin %d-%d gap %.1f px > %.1f px", idxa, idxb, gap, offsetThX);
reportPinError(roiMiss, areaAvg, gap, CONFIG_QX_NAME_PIN_QueShi, buf);
m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Check", "row %d pin %d-%d MISS gap %.1f px > %.1f px",
static_cast<int>(r), idxa, idxb, gap, offsetThX);
}
}
}
}
m_pdetlog->AddCheckstr(PrintLevel_0, "Pin Check",
"direct check: pin %d rows %d miss %d offset %d",
nCur, static_cast<int>(rows.size()), nMiss, nOffset);
return 0;
}
#endif // PIN_USE_DIRECT_MATCH
// 获取指定 残点(blob) 是由哪个(哪些)检测模型检出的
// 做法:对各模型自己输出的全图 mask,在 blob 的 roi 内统计非零像素;
// 非零则说明该模型在该残点区域有检出。多个模型都检出时用 "+" 连接。

Loading…
Cancel
Save