|
|
|
@ -18,11 +18,16 @@
|
|
|
|
#include <fstream>
|
|
|
|
#include <fstream>
|
|
|
|
|
|
|
|
|
|
|
|
// ===================== Pin 整体偏移估计参数 =====================
|
|
|
|
// ===================== Pin 整体偏移估计参数 =====================
|
|
|
|
static const double PIN_SHIFT_MIN_SUPPORT_RATIO = 0.80; // 判定整体偏移所需的最小支持度(支持pin数 / 该排模板pin数)
|
|
|
|
static const double PIN_SHIFT_MIN_SUPPORT_RATIO = 0.40; // 判定整体偏移所需的最小支持度(支持pin数 / 该排模板pin数)
|
|
|
|
static const float PIN_SHIFT_BIN_SIZE_PX = 4.0f; // 位移投票的网格尺寸(px), 同时作为峰值邻域半径
|
|
|
|
static const float PIN_SHIFT_BIN_SIZE_PX = 4.0f; // 位移投票的网格尺寸(px), 同时作为峰值邻域半径
|
|
|
|
static const float PIN_SHIFT_MAX_GAP_RATIO = 0.5f; // 整体位移量级上限 = 相邻pin最小间距 * 该比例
|
|
|
|
static const float PIN_SHIFT_MAX_GAP_RATIO = 0.8f; // 整体位移量级上限 = 相邻pin最小间距 * 该比例
|
|
|
|
// ======================================================================================
|
|
|
|
// ======================================================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ===================== 图像模糊度(清晰度)检测参数 =====================
|
|
|
|
|
|
|
|
// 计算清晰度前把检测图最长边缩放到该尺寸(只缩小不放大), 使阈值与图像分辨率无关
|
|
|
|
|
|
|
|
static const int BLUR_CHECK_MAX_SIZE = 1000;
|
|
|
|
|
|
|
|
// ======================================================================
|
|
|
|
|
|
|
|
|
|
|
|
// 计算点相对旋转矩形“长边轴线”的带符号法向距离(检测图坐标系):
|
|
|
|
// 计算点相对旋转矩形“长边轴线”的带符号法向距离(检测图坐标系):
|
|
|
|
// 取矩形长边方向作为 Pin 排方向, 返回点沿短边(法线)方向相对矩形中心的投影;
|
|
|
|
// 取矩形长边方向作为 Pin 排方向, 返回点沿短边(法线)方向相对矩形中心的投影;
|
|
|
|
// 正负号天然把分布在矩形两侧的 Pin 分成两组(上下两排分别估计整体偏移)。
|
|
|
|
// 正负号天然把分布在矩形两侧的 Pin 分成两组(上下两排分别估计整体偏移)。
|
|
|
|
@ -59,6 +64,57 @@ static cv::Point2f ApplyAffine2D(const cv::Mat &M, const cv::Point2f &pt)
|
|
|
|
static_cast<float>(M.at<double>(1, 0) * pt.x + M.at<double>(1, 1) * pt.y + M.at<double>(1, 2)));
|
|
|
|
static_cast<float>(M.at<double>(1, 0) * pt.x + M.at<double>(1, 1) * pt.y + M.at<double>(1, 2)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 最小二乘拟合相似变换(旋转+等比缩放+平移): src(模板点) -> dst(当前点), 返回 CV_64F 2x3
|
|
|
|
|
|
|
|
// 用全部匹配点做闭式解(不剔外点), 使投影后的残差均值为 0 —— 即模板 Pin 整体居中于当前 Pin;
|
|
|
|
|
|
|
|
// 注意: 这一步很关键, 只做"硬贴某一个 pin 对"的平移会让整排留下几像素的系统偏移
|
|
|
|
|
|
|
|
static cv::Mat FitSimilarityLS(const std::vector<cv::Point2f> &src, const std::vector<cv::Point2f> &dst)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
const size_t n = src.size();
|
|
|
|
|
|
|
|
if (n < 2 || dst.size() != n)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return cv::Mat();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
double mxs = 0.0, mys = 0.0, mxd = 0.0, myd = 0.0;
|
|
|
|
|
|
|
|
for (size_t i = 0; i < n; i++)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
mxs += src[i].x;
|
|
|
|
|
|
|
|
mys += src[i].y;
|
|
|
|
|
|
|
|
mxd += dst[i].x;
|
|
|
|
|
|
|
|
myd += dst[i].y;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
mxs /= n;
|
|
|
|
|
|
|
|
mys /= n;
|
|
|
|
|
|
|
|
mxd /= n;
|
|
|
|
|
|
|
|
myd /= n;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
double sdp = 0.0; // Σ p·q
|
|
|
|
|
|
|
|
double scp = 0.0; // Σ p×q
|
|
|
|
|
|
|
|
double den = 0.0; // Σ |p|^2
|
|
|
|
|
|
|
|
for (size_t i = 0; i < n; i++)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
const double px = src[i].x - mxs;
|
|
|
|
|
|
|
|
const double py = src[i].y - mys;
|
|
|
|
|
|
|
|
const double qx = dst[i].x - mxd;
|
|
|
|
|
|
|
|
const double qy = dst[i].y - myd;
|
|
|
|
|
|
|
|
sdp += px * qx + py * qy;
|
|
|
|
|
|
|
|
scp += px * qy - py * qx;
|
|
|
|
|
|
|
|
den += px * px + py * py;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (den <= 1e-12)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return cv::Mat();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const double scale = std::sqrt(sdp * sdp + scp * scp) / den;
|
|
|
|
|
|
|
|
const double theta = std::atan2(scp, sdp);
|
|
|
|
|
|
|
|
const double a = scale * std::cos(theta);
|
|
|
|
|
|
|
|
const double b = -scale * std::sin(theta);
|
|
|
|
|
|
|
|
const double tx = mxd - (a * mxs + b * mys);
|
|
|
|
|
|
|
|
const double ty = myd - (-b * mxs + a * mys);
|
|
|
|
|
|
|
|
return (cv::Mat_<double>(2, 3) << a, b, tx, -b, a, ty);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 把 Pin 中心按"排"分组(最多两排), 用于内定位法线无法分排时的兜底:
|
|
|
|
// 把 Pin 中心按"排"分组(最多两排), 用于内定位法线无法分排时的兜底:
|
|
|
|
// 用 minAreaRect 求 Pin 群主方向, 沿短边(法线)投影, 在最大间隙处切分;
|
|
|
|
// 用 minAreaRect 求 Pin 群主方向, 沿短边(法线)投影, 在最大间隙处切分;
|
|
|
|
// 间隙过小(单排)或切分后某组不足 2 个 Pin 时按一排处理。
|
|
|
|
// 间隙过小(单排)或切分后某组不足 2 个 Pin 时按一排处理。
|
|
|
|
@ -689,6 +745,8 @@ int ImgCheckAnalysisy::CalAffineMats(const RotatedRect &tplRefRect, const Rotate
|
|
|
|
m_affine_tpl2cur = cv::getAffineTransform(tpl_pts, cur_pts);
|
|
|
|
m_affine_tpl2cur = cv::getAffineTransform(tpl_pts, cur_pts);
|
|
|
|
m_bAffineValid = (!m_affine_cur2tpl.empty() && !m_affine_tpl2cur.empty());
|
|
|
|
m_bAffineValid = (!m_affine_cur2tpl.empty() && !m_affine_tpl2cur.empty());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
m_pEdge_Align_Result->H = m_affine_tpl2cur;
|
|
|
|
|
|
|
|
|
|
|
|
if (!m_bAffineValid)
|
|
|
|
if (!m_bAffineValid)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "CalAffineMats: getAffineTransform failed");
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "CalAffineMats: getAffineTransform failed");
|
|
|
|
@ -751,6 +809,98 @@ int ImgCheckAnalysisy::TransformTplPinContoursToCur()
|
|
|
|
return 0;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 图像模糊度(清晰度)检测: 必须在 AI 检测之前执行。
|
|
|
|
|
|
|
|
// 1) 检测图 detImg 转灰度(本身就是单通道, 这里做兼容);
|
|
|
|
|
|
|
|
// 2) 最长边缩放到 BLUR_CHECK_MAX_SIZE(只缩小不放大), 保证阈值与图像分辨率无关;
|
|
|
|
|
|
|
|
// 3) 用拉普拉斯算子(CV_64F)响应的方差作为清晰度指标: 方差越小越模糊;
|
|
|
|
|
|
|
|
// 4) 方差 < 阈值(基础参数 blurDet.fThreshold, 对应界面参数 blur_threshold) -> 判为模糊图片,
|
|
|
|
|
|
|
|
// 参数 blurDet.bNG(界面 bNG)打开时直接判 NG。
|
|
|
|
|
|
|
|
// 返回: 1 图像模糊且需要判 NG; 0 正常 / 功能未开启 / 图像无效
|
|
|
|
|
|
|
|
int ImgCheckAnalysisy::BlurCheck(const cv::Mat &img)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (m_pbaseCheckFunction == NULL || !m_pbaseCheckFunction->blurDet.bOpen ||
|
|
|
|
|
|
|
|
m_pbaseCheckFunction->blurDet.fThreshold <= 0.0f)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 每帧重置模糊判定结果
|
|
|
|
|
|
|
|
if (m_pDetResult)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
m_pDetResult->bBlurNG = false;
|
|
|
|
|
|
|
|
m_pDetResult->fBlurScore = 0.0f;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (img.empty() || img.cols < 5 || img.rows < 5)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Blur Check", "input img invalid, skip");
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 1、转灰度(detImg 为单通道, 其他通道情况做兼容)
|
|
|
|
|
|
|
|
cv::Mat grayImg;
|
|
|
|
|
|
|
|
if (1 == img.channels())
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
grayImg = img;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
else if (3 == img.channels())
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
cv::cvtColor(img, grayImg, cv::COLOR_BGR2GRAY);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
else if (4 == img.channels())
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
cv::cvtColor(img, grayImg, cv::COLOR_BGRA2GRAY);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
else
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Blur Check", "unsupported channels %d, skip", img.channels());
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 2、按最长边缩放(只缩小不放大)
|
|
|
|
|
|
|
|
cv::Mat checkImg = grayImg;
|
|
|
|
|
|
|
|
const int nMaxSide = std::max(grayImg.cols, grayImg.rows);
|
|
|
|
|
|
|
|
if (nMaxSide > BLUR_CHECK_MAX_SIZE)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
const double fScale = static_cast<double>(BLUR_CHECK_MAX_SIZE) / static_cast<double>(nMaxSide);
|
|
|
|
|
|
|
|
cv::resize(grayImg, checkImg, cv::Size(), fScale, fScale, cv::INTER_AREA);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 3、拉普拉斯方差(越大越清晰)
|
|
|
|
|
|
|
|
cv::Mat lapImg;
|
|
|
|
|
|
|
|
cv::Laplacian(checkImg, lapImg, CV_64F, 3);
|
|
|
|
|
|
|
|
cv::Scalar meanVal;
|
|
|
|
|
|
|
|
cv::Scalar stdVal;
|
|
|
|
|
|
|
|
cv::meanStdDev(lapImg, meanVal, stdVal);
|
|
|
|
|
|
|
|
const float fScore = static_cast<float>(stdVal[0] * stdVal[0]);
|
|
|
|
|
|
|
|
const float fThreshold = m_pbaseCheckFunction->blurDet.fThreshold;
|
|
|
|
|
|
|
|
const bool bNGOpen = m_pbaseCheckFunction->blurDet.bNG;
|
|
|
|
|
|
|
|
const bool bBlur = (fScore < fThreshold);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (m_pDetResult)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
m_pDetResult->fBlurScore = fScore;
|
|
|
|
|
|
|
|
m_pDetResult->bBlurNG = (bBlur && bNGOpen);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_0, "Blur Check",
|
|
|
|
|
|
|
|
"variance %.2f %s threshold %.2f -> %s (bNG %d, img [%d %d] -> check [%d %d])",
|
|
|
|
|
|
|
|
fScore, bBlur ? "<" : ">=", fThreshold, bBlur ? "BLUR" : "OK",
|
|
|
|
|
|
|
|
static_cast<int>(bNGOpen),
|
|
|
|
|
|
|
|
grayImg.cols, grayImg.rows, checkImg.cols, checkImg.rows);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (bBlur && bNGOpen)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_0, "Blur Check", "image blurred -> judge NG");
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (bBlur)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_0, "Blur Check", "image blurred, but bNG is off -> not NG");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int ImgCheckAnalysisy::CheckRun()
|
|
|
|
int ImgCheckAnalysisy::CheckRun()
|
|
|
|
{
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
@ -1011,6 +1161,15 @@ int ImgCheckAnalysisy::CheckRun()
|
|
|
|
{
|
|
|
|
{
|
|
|
|
m_pImageAllResult->detImg = image(m_outer_roi).clone();
|
|
|
|
m_pImageAllResult->detImg = image(m_outer_roi).clone();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*图像模糊度(清晰度)检测: 必须在 AI 检测之前完成, 模糊图片直接判 NG*/
|
|
|
|
|
|
|
|
long time_blur_s = CheckUtil::getcurTime();
|
|
|
|
|
|
|
|
const bool bBlurNG = (BlurCheck(m_pImageAllResult->detImg) == 1);
|
|
|
|
|
|
|
|
long time_blur_e = CheckUtil::getcurTime();
|
|
|
|
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_0, "2.2、pre detect",
|
|
|
|
|
|
|
|
"-------------------------Blur Check------------%s---time %ld----\n",
|
|
|
|
|
|
|
|
bBlurNG ? "NG" : "OK", time_blur_e - time_blur_s);
|
|
|
|
|
|
|
|
|
|
|
|
/*多线程开启 AI 推理检测*/
|
|
|
|
/*多线程开启 AI 推理检测*/
|
|
|
|
long time_AI_s = CheckUtil::getcurTime();
|
|
|
|
long time_AI_s = CheckUtil::getcurTime();
|
|
|
|
m_AItask = std::make_shared<TaskInfo>();
|
|
|
|
m_AItask = std::make_shared<TaskInfo>();
|
|
|
|
@ -1019,7 +1178,7 @@ int ImgCheckAnalysisy::CheckRun()
|
|
|
|
|
|
|
|
|
|
|
|
ImgPreDet();
|
|
|
|
ImgPreDet();
|
|
|
|
/*更新检测区域*/
|
|
|
|
/*更新检测区域*/
|
|
|
|
// Update_DetRoiList();
|
|
|
|
Update_DetRoiList();
|
|
|
|
|
|
|
|
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_0, "3、pre detect", "-------------------------pre Det--------------- \n");
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_0, "3、pre detect", "-------------------------pre Det--------------- \n");
|
|
|
|
|
|
|
|
|
|
|
|
@ -1033,6 +1192,12 @@ int ImgCheckAnalysisy::CheckRun()
|
|
|
|
|
|
|
|
|
|
|
|
m_CheckResult_shareP->nresult = 0;
|
|
|
|
m_CheckResult_shareP->nresult = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 图像模糊: 在 AI 检测之前已判定, 直接判 NG(nresult=1 与区域参数判断出的 NG 语义一致)
|
|
|
|
|
|
|
|
if (bBlurNG)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
m_CheckResult_shareP->nresult = 1;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*把临时可以绘制的结果都绘制出来。*/
|
|
|
|
/*把临时可以绘制的结果都绘制出来。*/
|
|
|
|
DrawResult_Step_1();
|
|
|
|
DrawResult_Step_1();
|
|
|
|
long time_AI_e;
|
|
|
|
long time_AI_e;
|
|
|
|
@ -1779,7 +1944,6 @@ int ImgCheckAnalysisy::ConfigCheck(cv::Mat img)
|
|
|
|
|
|
|
|
|
|
|
|
int ImgCheckAnalysisy::AI_Detect_Thread(const cv::Mat &img, cv::Mat &ResultImg)
|
|
|
|
int ImgCheckAnalysisy::AI_Detect_Thread(const cv::Mat &img, cv::Mat &ResultImg)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
std::string cur_time_s = CheckUtil::getCurTimeHMS();
|
|
|
|
|
|
|
|
// 依次使用多个检测模型,各模型输出 mask 取并集作为最终 outmask;
|
|
|
|
// 依次使用多个检测模型,各模型输出 mask 取并集作为最终 outmask;
|
|
|
|
// Defect_QueXi 模型是否参与合并由基础参数 Merge_QueXi 开关控制
|
|
|
|
// Defect_QueXi 模型是否参与合并由基础参数 Merge_QueXi 开关控制
|
|
|
|
std::vector<std::shared_ptr<AIModel_Base>> pAIDetList;
|
|
|
|
std::vector<std::shared_ptr<AIModel_Base>> pAIDetList;
|
|
|
|
@ -1909,9 +2073,11 @@ int ImgCheckAnalysisy::AI_Detect_Thread(const cv::Mat &img, cv::Mat &ResultImg)
|
|
|
|
// 目录结构:/home/aidlux/BOE/ZB_SOCKET/AI_Detect/<模型名称>/<产品ID>/
|
|
|
|
// 目录结构:/home/aidlux/BOE/ZB_SOCKET/AI_Detect/<模型名称>/<产品ID>/
|
|
|
|
if (m_pbaseCheckFunction->saveImg.bSaveAIDetImg && !modelMaskFull.empty())
|
|
|
|
if (m_pbaseCheckFunction->saveImg.bSaveAIDetImg && !modelMaskFull.empty())
|
|
|
|
{
|
|
|
|
{
|
|
|
|
std::string strSaveDir = "/home/aidlux/BOE/AI_Detect/" + DetImgInfo_shareP->strImgProductID + "/";
|
|
|
|
// 前缀用日期(年月日): 同一产品当天的所有图前缀一致; 文件已存在时在日期后追加序号, 不覆盖旧图
|
|
|
|
CheckUtil::CreateDir(strSaveDir);
|
|
|
|
std::string strDatePre = CheckUtil::getCurrentDateYMD();
|
|
|
|
std::string strSavePath_out = strSaveDir + cur_time_s + "_" + m_strCurDetChannel + "_" + strModelName + "_out.png";
|
|
|
|
std::string strSaveDir = "/home/aidlux/BOE/AI_Detect/" + DetImgInfo_shareP->strImgProductID + "/KK/";
|
|
|
|
|
|
|
|
std::string strName = m_strCurDetChannel + "_" + strModelName + "_out";
|
|
|
|
|
|
|
|
std::string strSavePath_out = CheckUtil::GetUniqueImgPath(strSaveDir, strDatePre, strName, ".png");
|
|
|
|
CheckUtil::SaveImgAsync(strSavePath_out, modelMaskFull);
|
|
|
|
CheckUtil::SaveImgAsync(strSavePath_out, modelMaskFull);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -1969,9 +2135,11 @@ int ImgCheckAnalysisy::AI_Detect_Thread(const cv::Mat &img, cv::Mat &ResultImg)
|
|
|
|
|
|
|
|
|
|
|
|
if (m_pbaseCheckFunction->saveImg.bSaveAIDetImg )
|
|
|
|
if (m_pbaseCheckFunction->saveImg.bSaveAIDetImg )
|
|
|
|
{
|
|
|
|
{
|
|
|
|
std::string strSaveDir = "/home/aidlux/BOE/AI_Detect/" + DetImgInfo_shareP->strImgProductID + "/";
|
|
|
|
// 前缀用日期(年月日): 同一产品当天的所有图前缀一致; 文件已存在时在日期后追加序号, 不覆盖旧图
|
|
|
|
CheckUtil::CreateDir(strSaveDir);
|
|
|
|
std::string strDatePre = CheckUtil::getCurrentDateYMD();
|
|
|
|
std::string strSavePath_in = strSaveDir + cur_time_s + "_" + m_strCurDetChannel + "_in.png";
|
|
|
|
std::string strSaveDir = "/home/aidlux/BOE/AI_Detect/" + DetImgInfo_shareP->strImgProductID + "/KK/";
|
|
|
|
|
|
|
|
std::string strName = m_strCurDetChannel + "_in";
|
|
|
|
|
|
|
|
std::string strSavePath_in = CheckUtil::GetUniqueImgPath(strSaveDir, strDatePre, strName, ".png");
|
|
|
|
CheckUtil::SaveImgAsync(strSavePath_in, m_CheckResult_shareP->cutSrcimg);
|
|
|
|
CheckUtil::SaveImgAsync(strSavePath_in, m_CheckResult_shareP->cutSrcimg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// 临时存图
|
|
|
|
// 临时存图
|
|
|
|
@ -2658,7 +2826,7 @@ int ImgCheckAnalysisy::ComputePinProjectionMats()
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 用模板 Pin 组 rowIdx 与当前 Pin 候选组 curIdxList 拟合一个相似变换(模板图坐标 -> detImg 坐标)
|
|
|
|
// 用模板 Pin 组 rowIdx 与当前 Pin 候选组 curIdxList 拟合一个相似变换(模板图坐标 -> detImg 坐标)
|
|
|
|
auto fitOneRow = [&](const std::vector<int> &rowIdx, const std::vector<int> &curIdxList, PinRowFit &fit)
|
|
|
|
auto fitOneRow = [&](const std::vector<int> &rowIdx, const std::vector<int> &curIdxList, int rowNo, PinRowFit &fit)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
const int nRow = static_cast<int>(rowIdx.size());
|
|
|
|
const int nRow = static_cast<int>(rowIdx.size());
|
|
|
|
const int nCurSub = static_cast<int>(curIdxList.size());
|
|
|
|
const int nCurSub = static_cast<int>(curIdxList.size());
|
|
|
|
@ -2789,8 +2957,22 @@ int ImgCheckAnalysisy::ComputePinProjectionMats()
|
|
|
|
return static_cast<int>(matches.size());
|
|
|
|
return static_cast<int>(matches.size());
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 匹配残差(平方和): 用于"重合数相同"时挑出真正居中的解
|
|
|
|
|
|
|
|
auto residualOf = [&](const cv::Mat &M, const std::vector<std::pair<int, int>> &matchList) -> double
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
double sum = 0.0;
|
|
|
|
|
|
|
|
for (size_t k = 0; k < matchList.size(); k++)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
const cv::Point2f p = ApplyAffine2D(M, tplCenters[rowIdx[matchList[k].first]]);
|
|
|
|
|
|
|
|
const cv::Point2f d = curCenters[matchList[k].second] - p;
|
|
|
|
|
|
|
|
sum += static_cast<double>(d.x) * d.x + static_cast<double>(d.y) * d.y;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return sum;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
std::vector<std::pair<int, int>> matches;
|
|
|
|
std::vector<std::pair<int, int>> matches;
|
|
|
|
int bestOverlap = matchPins(MCur, matches);
|
|
|
|
int bestOverlap = matchPins(MCur, matches);
|
|
|
|
|
|
|
|
double bestErr = residualOf(MCur, matches);
|
|
|
|
cv::Mat bestM = MCur.clone();
|
|
|
|
cv::Mat bestM = MCur.clone();
|
|
|
|
for (int iter = 0; iter < 5 && bestOverlap < nRow; iter++)
|
|
|
|
for (int iter = 0; iter < 5 && bestOverlap < nRow; iter++)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
@ -2814,19 +2996,67 @@ int ImgCheckAnalysisy::ComputePinProjectionMats()
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const int cnt = matchPins(MNew, matches);
|
|
|
|
const int cnt = matchPins(MNew, matches);
|
|
|
|
if (cnt > bestOverlap)
|
|
|
|
const double err = residualOf(MNew, matches);
|
|
|
|
|
|
|
|
// 重合数更多, 或重合数相同但残差更小 -> 采用(避免停留在"只硬贴某一个 pin 对"的投票解上)
|
|
|
|
|
|
|
|
if (cnt > bestOverlap || (cnt == bestOverlap && err < bestErr))
|
|
|
|
{
|
|
|
|
{
|
|
|
|
bestOverlap = cnt;
|
|
|
|
bestOverlap = cnt;
|
|
|
|
|
|
|
|
bestErr = err;
|
|
|
|
bestM = MNew.clone();
|
|
|
|
bestM = MNew.clone();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
MCur = MNew;
|
|
|
|
MCur = MNew;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 最终用全部重合对再做一次最小二乘(闭式解)相似变换:
|
|
|
|
|
|
|
|
// 让模板投影相对当前 Pin 的残差均值为 0(整体居中), 消除"整排几像素的系统偏移"
|
|
|
|
|
|
|
|
if (bestOverlap >= 2)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
matchPins(bestM, matches);
|
|
|
|
|
|
|
|
if (matches.size() >= 2)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
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]);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
cv::Mat MRefine = FitSimilarityLS(srcPts, dstPts);
|
|
|
|
|
|
|
|
// 重合数不下降才采用(避免把错配点带进最小二乘)
|
|
|
|
|
|
|
|
if (bReasonableMat(MRefine) && matchPins(MRefine, matches) >= bestOverlap)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
bestM = MRefine.clone();
|
|
|
|
|
|
|
|
bestOverlap = static_cast<int>(matches.size());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (bestOverlap >= 2 && bReasonableMat(bestM))
|
|
|
|
if (bestOverlap >= 2 && bReasonableMat(bestM))
|
|
|
|
{
|
|
|
|
{
|
|
|
|
fit.M = bestM;
|
|
|
|
fit.M = bestM;
|
|
|
|
fit.nMatch = bestOverlap;
|
|
|
|
fit.nMatch = bestOverlap;
|
|
|
|
fit.bFromPins = true;
|
|
|
|
fit.bFromPins = true;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 诊断: 模板投影相对当前 Pin 的残差(均值应接近 0, rms 反映重合程度)
|
|
|
|
|
|
|
|
matchPins(bestM, matches);
|
|
|
|
|
|
|
|
double sumX = 0.0, sumY = 0.0, sumSq = 0.0;
|
|
|
|
|
|
|
|
for (size_t k = 0; k < matches.size(); k++)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
const cv::Point2f p = ApplyAffine2D(bestM, tplCenters[rowIdx[matches[k].first]]);
|
|
|
|
|
|
|
|
const cv::Point2f d = curCenters[matches[k].second] - p;
|
|
|
|
|
|
|
|
sumX += d.x;
|
|
|
|
|
|
|
|
sumY += d.y;
|
|
|
|
|
|
|
|
sumSq += static_cast<double>(d.x) * d.x + static_cast<double>(d.y) * d.y;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
const int nM = static_cast<int>(matches.size());
|
|
|
|
|
|
|
|
if (nM > 0)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Pin Matrix",
|
|
|
|
|
|
|
|
"row %d residual: mean(%.2f %.2f) px rms %.2f px n %d",
|
|
|
|
|
|
|
|
rowNo, sumX / nM, sumY / nM, std::sqrt(sumSq / nM), nM);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
@ -2839,7 +3069,7 @@ int ImgCheckAnalysisy::ComputePinProjectionMats()
|
|
|
|
{
|
|
|
|
{
|
|
|
|
for (int b = 0; b < 2; b++)
|
|
|
|
for (int b = 0; b < 2; b++)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
fitOneRow(rows[a], curRows[b], fits[a][b]);
|
|
|
|
fitOneRow(rows[a], curRows[b], a, fits[a][b]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const int scoreDirect = fits[0][0].nMatch + fits[1][1].nMatch;
|
|
|
|
const int scoreDirect = fits[0][0].nMatch + fits[1][1].nMatch;
|
|
|
|
@ -2862,7 +3092,7 @@ int ImgCheckAnalysisy::ComputePinProjectionMats()
|
|
|
|
// 无法两侧都分排: 每排与全部当前 Pin 匹配
|
|
|
|
// 无法两侧都分排: 每排与全部当前 Pin 匹配
|
|
|
|
for (size_t r = 0; r < rows.size(); r++)
|
|
|
|
for (size_t r = 0; r < rows.size(); r++)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
fitOneRow(rows[r], allCurIdx, rowFits[r]);
|
|
|
|
fitOneRow(rows[r], allCurIdx, static_cast<int>(r), rowFits[r]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|