|
|
|
|
@ -51,13 +51,18 @@
|
|
|
|
|
#define HANDLE_EDGE_MARGIN_STEP_COEF 3
|
|
|
|
|
// 把手缺失检测开关见 Base层 Function_Support_Det::bDetHandle(CheckConfigDefine.h)
|
|
|
|
|
|
|
|
|
|
// 支架边缘原图精细化默认参数(JSON: Support_Detect.form.support_param 下的 edge_refine_search / edge_refine_step):
|
|
|
|
|
// 仅对 edge_select 选中的边缘生效:沿边缘按步长采样,从边缘向产品内侧逐像素找第一个 255 像素作为新边缘点,
|
|
|
|
|
// 支架边缘原图精细化默认参数(JSON: Support_Detect.form.support_param 下的
|
|
|
|
|
// edge_refine_search / edge_refine_step / edge_refine_gray):
|
|
|
|
|
// 仅对 edge_select 选中的边缘生效:沿边缘按步长采样,从边缘向产品内侧逐像素找第一个灰度 >= 阈值的像素作为新边缘点,
|
|
|
|
|
// 有效点足够时重新拟合边缘线(找不到的采样点没有新信息,不参与拟合,相当于沿用原边缘点)。
|
|
|
|
|
// edge_refine_search:最多向内搜索的像素数。搜不到 → 该采样点无贡献;调大更灵敏但可能搜到内部亮区
|
|
|
|
|
#define EDGE_REFINE_SEARCH_DEFAULT 80
|
|
|
|
|
// edge_refine_step:沿边采样步长(像素)。不需要每个点都找,调大可省时间;调小边缘拟合更稳
|
|
|
|
|
#define EDGE_REFINE_STEP_DEFAULT 40
|
|
|
|
|
// edge_refine_gray:产品边缘灰度阈值(0~255)。产品区域比背景亮,扫描时灰度 >= 该值视为已经进入产品:
|
|
|
|
|
// 调低:更灵敏(背景较亮/产品边缘偏暗时能搜到),但可能把暗部纹理/脏污当成边缘,边缘偏小;
|
|
|
|
|
// 调高:更严格(避开大部分杂散亮块),但产品边缘不够亮时搜不到(该采样点不参与拟合)
|
|
|
|
|
#define EDGE_REFINE_GRAY_THRESHOLD 240
|
|
|
|
|
|
|
|
|
|
// ===== 支架方向精细化默认参数 =====
|
|
|
|
|
// (JSON: Support_Detect.form.support_param 下的
|
|
|
|
|
@ -65,14 +70,13 @@
|
|
|
|
|
// 背景:支架由 Align_Inner 模型(smallmask,1696×800)检出,上采样回原图后边缘量化误差约 4px,
|
|
|
|
|
// 且支架区域接近方形时 minAreaRect 的"长轴"方向不稳定,用它求旋转角误差大。
|
|
|
|
|
// 改为用模型支架边缘做初值,在原图(detImg 灰度)上拟合支架长边(朝产品内侧的边)求方向角。
|
|
|
|
|
// support_refine_search:取模型支架外接矩形最外侧多少列作为边缘搜索带(未配置默认 30 列)。
|
|
|
|
|
// 右边缘 → 取外接矩形最右 nStrip 列,从右往左逐像素找灰度梯度最大的点;
|
|
|
|
|
// 左边缘 → 取最左 nStrip 列,从左往右找(相反逻辑)
|
|
|
|
|
// 调大:覆盖更大的模型误差/边缘更靠内,但可能扫到支架内部结构;
|
|
|
|
|
// 调小:更严格,模型边缘偏差大时找不到边缘(回退 minAreaRect 方案)
|
|
|
|
|
#define SUPPORT_REFINE_STRIP_DEFAULT 30
|
|
|
|
|
// support_refine_search:取 outer 定位后的 ROI 原图(detImg)最外侧多少列作为边缘搜索带(未配置默认 50 列)。
|
|
|
|
|
// 注意:搜索带位置只按 outer ROi 原图取(右边缘取最右 N 列、左边缘取最左 N 列),不再按 smallmask 的边界取:
|
|
|
|
|
// 模型 smallmask 上采样回原图后边界不准,按它取带会把真实支架边缘切到带外(边缘点被 mask 掉)。
|
|
|
|
|
// 调大:覆盖更大的范围,但可能扫到支架内部结构;调小:更严格,但 mask 偏差大时仍可能漏掉边缘
|
|
|
|
|
#define SUPPORT_REFINE_STRIP_DEFAULT 50
|
|
|
|
|
// support_refine_step:沿支架长边采样的步长(像素)。调大可省时间;调小拟合更稳
|
|
|
|
|
#define SUPPORT_REFINE_STEP_DEFAULT 20
|
|
|
|
|
#define SUPPORT_REFINE_STEP_DEFAULT 10
|
|
|
|
|
// support_refine_segments:把采样点沿边分成 n 段,每段稳健拟合一条直线,取各段方向角的中位数。
|
|
|
|
|
// 支架边缘有凸起时:凸起处采样点成为所在段的离群点(MAD 剔除);凸起外边缘与支架边缘平行,
|
|
|
|
|
// 即使拟合到凸起外边缘也不影响方向角
|
|
|
|
|
@ -1119,25 +1123,72 @@ int ImgCheckAnalysisy::CheckRun()
|
|
|
|
|
|
|
|
|
|
// 3、检测图:单通道灰度(彩色原图按 BGR 顺序转灰度,与 imread(path,0) 的亮度一致),直接裁剪;
|
|
|
|
|
// 彩色原图时再输出一张同样裁剪的彩色检测图,供裁剪图(_RE_CutImg)保存时保留原图通道
|
|
|
|
|
// (检测算法仍只用灰度 detImg)
|
|
|
|
|
// 注:这里先只做"裁剪",灰度结果存 detImgNoMask;是否叠加 mask 见 3.1
|
|
|
|
|
m_deskewPadRoi = padRoi;
|
|
|
|
|
cv::Mat colorPatch = image(padRoi);
|
|
|
|
|
if (colorPatch.channels() == 1)
|
|
|
|
|
{
|
|
|
|
|
colorPatch.copyTo(m_pImageAllResult->detImg);
|
|
|
|
|
colorPatch.copyTo(m_pImageAllResult->detImgNoMask);
|
|
|
|
|
m_pImageAllResult->detColorImg.release();
|
|
|
|
|
}
|
|
|
|
|
else if (colorPatch.channels() == 3)
|
|
|
|
|
{
|
|
|
|
|
cv::cvtColor(colorPatch, m_pImageAllResult->detImg, cv::COLOR_BGR2GRAY);
|
|
|
|
|
cv::cvtColor(colorPatch, m_pImageAllResult->detImgNoMask, cv::COLOR_BGR2GRAY);
|
|
|
|
|
colorPatch.copyTo(m_pImageAllResult->detColorImg);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
cv::cvtColor(colorPatch, m_pImageAllResult->detImg, cv::COLOR_BGRA2GRAY);
|
|
|
|
|
cv::cvtColor(colorPatch, m_pImageAllResult->detImgNoMask, cv::COLOR_BGRA2GRAY);
|
|
|
|
|
colorPatch.copyTo(m_pImageAllResult->detColorImg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3.1、两胀检测图
|
|
|
|
|
// ① detImgNoMask:只做"裁剪"、不叠加任何 mask 的 ROI 灰度图——支架搜边用它(外侧保留真实灰度台阶)
|
|
|
|
|
// ② detImg :detImgNoMask 再叠加 outer 定位 mask(bigmask) 之外置 0——缺陷检测/AI 推理用它
|
|
|
|
|
{
|
|
|
|
|
const cv::Mat &bigmask = m_pEdge_Align_Result->bigmask;
|
|
|
|
|
bool bMaskApplied = false;
|
|
|
|
|
int nMaskFg = -1;
|
|
|
|
|
m_pImageAllResult->detImg = m_pImageAllResult->detImgNoMask.clone();
|
|
|
|
|
if (!bigmask.empty() && bigmask.size() == image.size())
|
|
|
|
|
{
|
|
|
|
|
cv::Mat maskPatch = bigmask(padRoi);
|
|
|
|
|
// 注意:必须先 clone,否则 threshold(原地) 会写回 bigmask
|
|
|
|
|
cv::Mat binMask;
|
|
|
|
|
if (maskPatch.channels() != 1)
|
|
|
|
|
{
|
|
|
|
|
cv::cvtColor(maskPatch, binMask, cv::COLOR_BGR2GRAY);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
binMask = maskPatch.clone();
|
|
|
|
|
}
|
|
|
|
|
double dMin = 0, dMax = 0;
|
|
|
|
|
cv::minMaxLoc(binMask, &dMin, &dMax);
|
|
|
|
|
// 兼容 0/1 与 0/255 掩膜
|
|
|
|
|
cv::threshold(binMask, binMask, dMax > 1 ? dMax * 0.5 : 0, 255, cv::THRESH_BINARY);
|
|
|
|
|
m_pImageAllResult->detImg.setTo(0, binMask == 0); // mask 之外置 0
|
|
|
|
|
nMaskFg = cv::countNonZero(binMask);
|
|
|
|
|
bMaskApplied = true;
|
|
|
|
|
}
|
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "2.2、pre detect",
|
|
|
|
|
"det img crop [%d %d], mask(bigmask) applied %s, fg %d",
|
|
|
|
|
m_pImageAllResult->detImgNoMask.cols, m_pImageAllResult->detImgNoMask.rows,
|
|
|
|
|
bMaskApplied ? "yes" : "no(mask empty or size mismatch)", nMaskFg);
|
|
|
|
|
|
|
|
|
|
// -s:把两张裁剪图都存下来,便于核对外侧是否被 mask 覆盖
|
|
|
|
|
// 文件名带相机名,避免明/暗场同名通道互相覆盖
|
|
|
|
|
if (DetImgInfo_shareP->bsaveProcessImg)
|
|
|
|
|
{
|
|
|
|
|
const std::string strCropBase = DetImgInfo_shareP->strCameraName + "_" + DetImgInfo_shareP->strChannel;
|
|
|
|
|
cv::imwrite(strCropBase + "_det_crop_used.png", m_pImageAllResult->detImg);
|
|
|
|
|
cv::imwrite(strCropBase + "_det_crop_nomask.png", m_pImageAllResult->detImgNoMask);
|
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "2.2、pre detect",
|
|
|
|
|
"det crop images saved: %s_det_crop_used.png / %s_det_crop_nomask.png",
|
|
|
|
|
strCropBase.c_str(), strCropBase.c_str());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 4、检测图坐标系下 m_outer_roi 即整张检测图;原图坐标系下 CutRoi 仍取旋转矩形外接区域(兼容旧语义/JSON)
|
|
|
|
|
m_outer_roi = cv::Rect(0, 0, detW, detH);
|
|
|
|
|
m_Crop_Roi_paramImg = padRoi;
|
|
|
|
|
@ -1192,7 +1243,7 @@ int ImgCheckAnalysisy::CheckRun()
|
|
|
|
|
(CheckHandleByBigMask -> GetMaskProtrusions;边缘上的凸起(把手等)不会把边缘带偏),
|
|
|
|
|
拟合不可用/无效时回退 bigmask 最大连通域外接矩形 rp(近似矩形,四周可能有凸起)
|
|
|
|
|
3.1、对 edge_select 选中的边缘再结合原图(detImg 灰度)精细化:沿边缘按步长采样,从边缘向产品内侧
|
|
|
|
|
找第一个 255 像素作为新边缘点(最多向内 edge_refine_search 像素,找不到沿用原边缘点),
|
|
|
|
|
找第一个灰度 >= 阈值(edge_refine_gray,默认 240)的像素作为新边缘点(最多向内 edge_refine_search 像素,找不到沿用原边缘点),
|
|
|
|
|
有效点足够时稳健拟合新的边缘线;采样步长 edge_refine_step(像素)
|
|
|
|
|
4、按 edge_select 指定方向(left/right/up/down)计算 rs 中心到产品对应边缘的距离
|
|
|
|
|
5、计算值与标准值(x_standard/y_standard)做差取平均,平均差超过 offset(x_offset) → 偏移
|
|
|
|
|
@ -1410,7 +1461,8 @@ int ImgCheckAnalysisy::CheckRun()
|
|
|
|
|
|
|
|
|
|
// 3.2、结合原图精细化检测边缘(只处理 edge_select 选中的方向):
|
|
|
|
|
// 在"所计算边缘"(拟合四边形边/回退外接矩形边)上按步长采样,从边缘向产品内侧逐像素扫描原图
|
|
|
|
|
// (detImg 灰度,未做其它处理;产品区域为饱和白),找到的第一个 255 像素作为该采样点的新边缘点;
|
|
|
|
|
// (detImg 灰度,未做其它处理;产品区域为饱和白),找到的第一个灰度 >= 阈值的像素作为该采样点的新边缘点;
|
|
|
|
|
// 阈值优先取 edge_refine_gray(JSON,默认见 EDGE_REFINE_GRAY_THRESHOLD);
|
|
|
|
|
// 最多向内搜索 edge_refine_search 像素(JSON,默认见 EDGE_REFINE_SEARCH_DEFAULT),
|
|
|
|
|
// 找不到的采样点没有新边缘信息,不参与拟合(相当于沿用原边缘点);
|
|
|
|
|
// edge_refine_step(JSON,默认见 EDGE_REFINE_STEP_DEFAULT)为沿边采样步长,不需要每个点都找;
|
|
|
|
|
@ -1419,6 +1471,7 @@ int ImgCheckAnalysisy::CheckRun()
|
|
|
|
|
const cv::Mat &grayEdgeImg = m_pImageAllResult->detImg;
|
|
|
|
|
int nRefineSearch = EDGE_REFINE_SEARCH_DEFAULT;
|
|
|
|
|
int nRefineStep = EDGE_REFINE_STEP_DEFAULT;
|
|
|
|
|
int nRefineGray = EDGE_REFINE_GRAY_THRESHOLD;
|
|
|
|
|
if (supportDet.edge_refine_search > 0)
|
|
|
|
|
{
|
|
|
|
|
nRefineSearch = (int)(supportDet.edge_refine_search + 0.5f);
|
|
|
|
|
@ -1427,12 +1480,28 @@ int ImgCheckAnalysisy::CheckRun()
|
|
|
|
|
{
|
|
|
|
|
nRefineStep = (int)(supportDet.edge_refine_step + 0.5f);
|
|
|
|
|
}
|
|
|
|
|
if (supportDet.edge_refine_gray > 0)
|
|
|
|
|
{
|
|
|
|
|
nRefineGray = (int)(supportDet.edge_refine_gray + 0.5f);
|
|
|
|
|
}
|
|
|
|
|
nRefineSearch = std::max(1, nRefineSearch);
|
|
|
|
|
nRefineStep = std::max(4, nRefineStep);
|
|
|
|
|
nRefineGray = std::min(255, std::max(0, nRefineGray));
|
|
|
|
|
|
|
|
|
|
bool bEdgeRefined[4] = {false, false, false, false}; // edge_select 配重复时只处理一次
|
|
|
|
|
if (grayEdgeImg.type() == CV_8UC1 && grayEdgeImg.cols > 0 && grayEdgeImg.rows > 0)
|
|
|
|
|
{
|
|
|
|
|
// -s 存过程图:在检测图(灰度转 BGR)上绘制
|
|
|
|
|
// 绿线=精细化前的边缘线 蓝点=向产品内侧找到的 255 边缘点 红线=稳健拟合后的边缘线
|
|
|
|
|
const bool bSaveRefineImg = (DetImgInfo_shareP != nullptr && DetImgInfo_shareP->bsaveProcessImg);
|
|
|
|
|
cv::Mat showRefine;
|
|
|
|
|
int nRefineDrawPts = 0; // 已绘制的蓝点数量
|
|
|
|
|
int nRefineDrawEdge = 0; // 已绘制的边缘条数
|
|
|
|
|
if (bSaveRefineImg)
|
|
|
|
|
{
|
|
|
|
|
cv::cvtColor(grayEdgeImg, showRefine, cv::COLOR_GRAY2BGR);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (size_t iselect = 0; iselect < supportDet.edge_select.size(); iselect++)
|
|
|
|
|
{
|
|
|
|
|
const std::string &strEdge = supportDet.edge_select[iselect];
|
|
|
|
|
@ -1484,6 +1553,15 @@ int ImgCheckAnalysisy::CheckRun()
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// -s 调试图:精细化前的边缘线(绿),便于和蓝点/红线对比
|
|
|
|
|
if (bSaveRefineImg && !showRefine.empty())
|
|
|
|
|
{
|
|
|
|
|
const cv::Point2f ptA = MatAffinePoint(m_matSrc2Det, ptSegA);
|
|
|
|
|
const cv::Point2f ptB = MatAffinePoint(m_matSrc2Det, ptSegB);
|
|
|
|
|
cv::line(showRefine, cv::Point(cvRound(ptA.x), cvRound(ptA.y)),
|
|
|
|
|
cv::Point(cvRound(ptB.x), cvRound(ptB.y)), cv::Scalar(0, 255, 0), 4);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<cv::Point2f> vecRefinePts; // 有效的新边缘点(t, v):左右 t=y/v=x,上下 t=x/v=y
|
|
|
|
|
int nSampleNum = 0;
|
|
|
|
|
for (double fT = fTStart; fT <= fTEnd; fT += nRefineStep)
|
|
|
|
|
@ -1492,13 +1570,13 @@ int ImgCheckAnalysisy::CheckRun()
|
|
|
|
|
const double fEdgePos = fKOld * fT + fBOld;
|
|
|
|
|
const cv::Point2f ptSrc = bVertEdge ? cv::Point2f((float)fEdgePos, (float)fT)
|
|
|
|
|
: cv::Point2f((float)fT, (float)fEdgePos);
|
|
|
|
|
// 原图坐标 -> 检测图(detImg)坐标(纯平移);在检测图灰度上找第一个 255
|
|
|
|
|
// 原图坐标 -> 检测图(detImg)坐标(纯平移);在检测图灰度上找第一个灰度 >= 阈值的像素
|
|
|
|
|
const cv::Point2f ptDet = MatAffinePoint(m_matSrc2Det, ptSrc);
|
|
|
|
|
const int nFix = bVertEdge ? cvRound(ptDet.y) : cvRound(ptDet.x); // 固定坐标(行/列)
|
|
|
|
|
if ((bVertEdge && (nFix < 0 || nFix >= grayEdgeImg.rows)) ||
|
|
|
|
|
(!bVertEdge && (nFix < 0 || nFix >= grayEdgeImg.cols)))
|
|
|
|
|
{
|
|
|
|
|
continue; // 固定坐标越界,该采样点无法搜索
|
|
|
|
|
continue; // 固定坐标越界,该采样点无法搜索s
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool bFound = false;
|
|
|
|
|
@ -1509,7 +1587,7 @@ int ImgCheckAnalysisy::CheckRun()
|
|
|
|
|
const int nXEnd = std::min(grayEdgeImg.cols - 1, nX0 + nRefineSearch);
|
|
|
|
|
for (int x = std::max(0, nX0); x <= nXEnd; x++)
|
|
|
|
|
{
|
|
|
|
|
if (grayEdgeImg.at<uchar>(nFix, x) == 255)
|
|
|
|
|
if (grayEdgeImg.at<uchar>(nFix, x) >= nRefineGray)
|
|
|
|
|
{
|
|
|
|
|
const cv::Point2f ptNew = MatAffinePoint(m_matDet2Src, cv::Point2f((float)x, ptDet.y));
|
|
|
|
|
fNewPos = ptNew.x;
|
|
|
|
|
@ -1524,7 +1602,7 @@ int ImgCheckAnalysisy::CheckRun()
|
|
|
|
|
const int nXEnd = std::max(0, nX0 - nRefineSearch);
|
|
|
|
|
for (int x = std::min(grayEdgeImg.cols - 1, nX0); x >= nXEnd && x >= 0; x--)
|
|
|
|
|
{
|
|
|
|
|
if (grayEdgeImg.at<uchar>(nFix, x) == 255)
|
|
|
|
|
if (grayEdgeImg.at<uchar>(nFix, x) >= nRefineGray)
|
|
|
|
|
{
|
|
|
|
|
const cv::Point2f ptNew = MatAffinePoint(m_matDet2Src, cv::Point2f((float)x, ptDet.y));
|
|
|
|
|
fNewPos = ptNew.x;
|
|
|
|
|
@ -1539,7 +1617,7 @@ int ImgCheckAnalysisy::CheckRun()
|
|
|
|
|
const int nYEnd = std::min(grayEdgeImg.rows - 1, nY0 + nRefineSearch);
|
|
|
|
|
for (int y = std::max(0, nY0); y <= nYEnd; y++)
|
|
|
|
|
{
|
|
|
|
|
if (grayEdgeImg.at<uchar>(y, nFix) == 255)
|
|
|
|
|
if (grayEdgeImg.at<uchar>(y, nFix) >= nRefineGray)
|
|
|
|
|
{
|
|
|
|
|
const cv::Point2f ptNew = MatAffinePoint(m_matDet2Src, cv::Point2f(ptDet.x, (float)y));
|
|
|
|
|
fNewPos = ptNew.y;
|
|
|
|
|
@ -1554,7 +1632,7 @@ int ImgCheckAnalysisy::CheckRun()
|
|
|
|
|
const int nYEnd = std::max(0, nY0 - nRefineSearch);
|
|
|
|
|
for (int y = std::min(grayEdgeImg.rows - 1, nY0); y >= nYEnd && y >= 0; y--)
|
|
|
|
|
{
|
|
|
|
|
if (grayEdgeImg.at<uchar>(y, nFix) == 255)
|
|
|
|
|
if (grayEdgeImg.at<uchar>(y, nFix) >= nRefineGray)
|
|
|
|
|
{
|
|
|
|
|
const cv::Point2f ptNew = MatAffinePoint(m_matDet2Src, cv::Point2f(ptDet.x, (float)y));
|
|
|
|
|
fNewPos = ptNew.y;
|
|
|
|
|
@ -1566,6 +1644,16 @@ int ImgCheckAnalysisy::CheckRun()
|
|
|
|
|
if (bFound)
|
|
|
|
|
{
|
|
|
|
|
vecRefinePts.push_back(cv::Point2f((float)fT, (float)fNewPos));
|
|
|
|
|
// -s 调试图:蓝点=找到的新边缘像素(换回检测图坐标绘制)
|
|
|
|
|
if (bSaveRefineImg && !showRefine.empty())
|
|
|
|
|
{
|
|
|
|
|
const cv::Point2f ptFoundSrc = bVertEdge ? cv::Point2f((float)fNewPos, (float)fT)
|
|
|
|
|
: cv::Point2f((float)fT, (float)fNewPos);
|
|
|
|
|
const cv::Point2f ptFoundDet = MatAffinePoint(m_matSrc2Det, ptFoundSrc);
|
|
|
|
|
cv::circle(showRefine, cv::Point(cvRound(ptFoundDet.x), cvRound(ptFoundDet.y)), 10,
|
|
|
|
|
cv::Scalar(255, 0, 0), -1);
|
|
|
|
|
nRefineDrawPts++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 找不到:该采样点没有新边缘信息,不参与拟合(相当于沿用原边缘点)
|
|
|
|
|
}
|
|
|
|
|
@ -1631,11 +1719,38 @@ int ImgCheckAnalysisy::CheckRun()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// -s 调试图:红线=稳健拟合后的边缘线
|
|
|
|
|
if (bSaveRefineImg && !showRefine.empty())
|
|
|
|
|
{
|
|
|
|
|
const cv::Point2f ptA = MatAffinePoint(m_matSrc2Det, ptSegAArr[gi]);
|
|
|
|
|
const cv::Point2f ptB = MatAffinePoint(m_matSrc2Det, ptSegBArr[gi]);
|
|
|
|
|
cv::line(showRefine, cv::Point(cvRound(ptA.x), cvRound(ptA.y)),
|
|
|
|
|
cv::Point(cvRound(ptB.x), cvRound(ptB.y)), cv::Scalar(0, 0, 255), 4);
|
|
|
|
|
nRefineDrawEdge++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "支架检测",
|
|
|
|
|
"edge refine %s: sample %d found %ld center %.1f -> %.1f shift %.1f",
|
|
|
|
|
strEdge.c_str(), nSampleNum, (long)vecRefinePts.size(),
|
|
|
|
|
fOldCenterPos, fNewCenterPos, fNewCenterPos - fOldCenterPos);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// -s 存图:边缘点调试图(绿=原边缘线 蓝=找到的边缘点 红=拟合后边缘线)
|
|
|
|
|
if (bSaveRefineImg && !showRefine.empty() && nRefineDrawPts > 0)
|
|
|
|
|
{
|
|
|
|
|
char strDbg[256];
|
|
|
|
|
snprintf(strDbg, sizeof(strDbg), "edge refine: refined edges %d, found points %d, gray thr %d", nRefineDrawEdge, nRefineDrawPts, nRefineGray);
|
|
|
|
|
cv::putText(showRefine, strDbg, cv::Point(20, 70), cv::FONT_HERSHEY_SIMPLEX, 2.0, cv::Scalar(0, 255, 255), 4);
|
|
|
|
|
cv::putText(showRefine, "green=old edge blue=found edge point red=refined edge", cv::Point(20, 140),
|
|
|
|
|
cv::FONT_HERSHEY_SIMPLEX, 1.6, cv::Scalar(0, 255, 255), 4);
|
|
|
|
|
// 调试图按检测图原分辨率保存(不再缩小到 1/4):边缘点/拟合线位置可逐像素核对,
|
|
|
|
|
// 存图耗时/文件大小靠 edge_refine_step 控制采样点数量,而不是靠缩小图像
|
|
|
|
|
std::vector<int> paramJpg = {cv::IMWRITE_JPEG_QUALITY, 95};
|
|
|
|
|
cv::imwrite(DetImgInfo_shareP->strChannel + "_edge_refine.jpg", showRefine, paramJpg);
|
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "支架检测",
|
|
|
|
|
"edge refine debug image saved: %s_edge_refine.jpg (refined edges %d, found pts %d)",
|
|
|
|
|
DetImgInfo_shareP->strChannel.c_str(), nRefineDrawEdge, nRefineDrawPts);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (!supportDet.edge_select.empty())
|
|
|
|
|
{
|
|
|
|
|
@ -1668,21 +1783,27 @@ int ImgCheckAnalysisy::CheckRun()
|
|
|
|
|
// 模型 smallmask 分辨率低(1696×800 上采样回原图,量化误差约 4px),且支架区域接近方形时
|
|
|
|
|
// minAreaRect 的"长轴"方向不稳定,直接用它求旋转角误差大;这里用模型支架边缘做初值,
|
|
|
|
|
// 在 detImg 灰度原图上重新拟合支架长边方向:
|
|
|
|
|
// 1、把 smallmask 最大轮廓映射回原图,得到支架外接矩形(rsRect);
|
|
|
|
|
// 取外接矩形最外侧 nStrip 列(默认 30 列)作为该侧边缘的搜索带
|
|
|
|
|
// 1、把 smallmask 最大轮廓映射回原图,得到支架外接矩形(rsRect),只用它的 y 范围(采样行);
|
|
|
|
|
// 列带不使用 mask 边界,而是直接取 outer 定位后的 ROI 原图(detImg)最外侧 nStrip 列,
|
|
|
|
|
// 避免 smallmask 精度低时把真实边缘切到带外(边缘点被 mask 掉)
|
|
|
|
|
// 2、结合 edge_select:选 left → 拟合支架右边缘;选 right → 拟合支架左边缘
|
|
|
|
|
// (up/down 对应支架上下短边,水平的线不好拟合,不处理,仍用 minAreaRect 结果)
|
|
|
|
|
// 3、沿外接矩形高度按 step 采样;每行在搜索带内逐像素找灰度梯度最大的位置作为边缘点:
|
|
|
|
|
// 右边缘取最右 nStrip 列、从右往左找;左边缘取最左 nStrip 列、从左往右找;
|
|
|
|
|
// 3、沿支架区域的 y 范围按 step 采样;每行在搜索带内逐像素找灰度梯度最大的位置作为边缘点:
|
|
|
|
|
// 右边缘取 ROI 最右 nStrip 列、从右往左找;左边缘取 ROI 最左 nStrip 列、从左往右找;
|
|
|
|
|
// 梯度不足(该侧没有真实边缘)的行不参与拟合
|
|
|
|
|
// 4、采样点沿 y 分成 n 段,每段用 FitLineRobustTV 稳健拟合直线,取各段方向角的中位数:
|
|
|
|
|
// 支架边缘的凸起是矩形,凸起外边缘与支架长边平行,拟合到凸起处也不影响方向角;
|
|
|
|
|
// 凸起侧边/内部纹理等离群点由 MAD 剔除
|
|
|
|
|
// 失败(有效采样/分段不足、角度离散过大)时保留下面的 minAreaRect 长轴方案
|
|
|
|
|
if (!m_pImageAllResult->detImg.empty() && m_pImageAllResult->detImg.type() == CV_8UC1)
|
|
|
|
|
// 搜边输入用 detImgNoMask(只裁剪、未叠加 mask 的检测图):detImg 被 mask 置 0 后外侧是硬切,
|
|
|
|
|
// 用 detImg 找边相当于在测 mask 边界;未生成时(异常)回退 detImg
|
|
|
|
|
const cv::Mat &graySupportImg = (!m_pImageAllResult->detImgNoMask.empty() && m_pImageAllResult->detImgNoMask.type() == CV_8UC1)
|
|
|
|
|
? m_pImageAllResult->detImgNoMask
|
|
|
|
|
: m_pImageAllResult->detImg;
|
|
|
|
|
if (!graySupportImg.empty() && graySupportImg.type() == CV_8UC1)
|
|
|
|
|
{
|
|
|
|
|
const cv::Mat &grayRef = m_pImageAllResult->detImg;
|
|
|
|
|
// 边缘搜索带宽度(列):取模型支架外接矩形最外侧多少列;未配置默认 30
|
|
|
|
|
const cv::Mat &grayRef = graySupportImg;
|
|
|
|
|
// 边缘搜索带宽度(列):取 outer ROI 原图最外侧多少列;未配置默认 50
|
|
|
|
|
int nStrip = (supportDet.support_refine_search > 0)
|
|
|
|
|
? cvRound(supportDet.support_refine_search)
|
|
|
|
|
: SUPPORT_REFINE_STRIP_DEFAULT;
|
|
|
|
|
@ -1733,20 +1854,20 @@ int ImgCheckAnalysisy::CheckRun()
|
|
|
|
|
vRef = -vRef;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ===== 取模型支架外接矩形(rsRect)最外侧 nStrip 列,逐行找边缘点 =====
|
|
|
|
|
// 右边缘:取外接矩形最右 nStrip 列,从右往左扫;左边缘:取最左 nStrip 列,从左往右扫。
|
|
|
|
|
// 每行按灰度梯度找边缘点;梯度不足(该侧没有真实边缘)的行不参与拟合
|
|
|
|
|
int nXStart = 0; // 外侧起始列(原图坐标)
|
|
|
|
|
int nXEnd = 0; // 内侧结束列(原图坐标)
|
|
|
|
|
// ===== 取 outer 定位后的 ROI 原图(detImg)最外侧 nStrip 列,逐行找边缘点 =====
|
|
|
|
|
// 右边缘:取 ROI 最右 nStrip 列,从右往左扫;左边缘:取 ROI 最左 nStrip 列,从左往右扫。
|
|
|
|
|
// 搜索带只依赖 outer ROI 原图,不依赖 smallmask 边界(mask 上采样精度低,会把真实边缘切到带外)
|
|
|
|
|
int nXStart = 0; // 外侧起始列(detImg 坐标)
|
|
|
|
|
int nXEnd = 0; // 内侧结束列(detImg 坐标)
|
|
|
|
|
if (nSide == 0)
|
|
|
|
|
{
|
|
|
|
|
nXStart = rsRect.x;
|
|
|
|
|
nXEnd = rsRect.x + nStrip - 1;
|
|
|
|
|
nXStart = 0;
|
|
|
|
|
nXEnd = std::min(grayRef.cols - 1, nStrip - 1);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
nXStart = rsRect.x + rsRect.width - 1;
|
|
|
|
|
nXEnd = rsRect.x + rsRect.width - nStrip;
|
|
|
|
|
nXStart = grayRef.cols - 1;
|
|
|
|
|
nXEnd = std::max(0, grayRef.cols - nStrip);
|
|
|
|
|
}
|
|
|
|
|
const int nStepX = (nXEnd >= nXStart) ? 1 : -1;
|
|
|
|
|
|
|
|
|
|
@ -1766,16 +1887,15 @@ int ImgCheckAnalysisy::CheckRun()
|
|
|
|
|
|
|
|
|
|
for (double fT = fY0; fT <= fY1; fT += nStep)
|
|
|
|
|
{
|
|
|
|
|
// 原图 -> 检测图(detImg) 为纯平移;列带两端换算到检测图坐标
|
|
|
|
|
cv::Point2f ptDetStart = MatAffinePoint(m_matSrc2Det, cv::Point2f((float)nXStart, (float)fT));
|
|
|
|
|
cv::Point2f ptDetEnd = MatAffinePoint(m_matSrc2Det, cv::Point2f((float)nXEnd, (float)fT));
|
|
|
|
|
const int nYDet = cvRound(ptDetStart.y);
|
|
|
|
|
// 行号由原图坐标(t)换算到 detImg;列带已经是 detImg 坐标
|
|
|
|
|
cv::Point2f ptDetRow = MatAffinePoint(m_matSrc2Det, cv::Point2f(0.0f, (float)fT));
|
|
|
|
|
const int nYDet = cvRound(ptDetRow.y);
|
|
|
|
|
if (nYDet < 2 || nYDet >= grayRef.rows - 2)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
const int nX0 = cvRound(ptDetStart.x); // 外侧端
|
|
|
|
|
const int nX1 = cvRound(ptDetEnd.x); // 内侧端
|
|
|
|
|
const int nX0 = nXStart; // 外侧端
|
|
|
|
|
const int nX1 = nXEnd; // 内侧端
|
|
|
|
|
if ((nStepX > 0 && nX1 <= nX0) || (nStepX < 0 && nX1 >= nX0))
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
@ -1805,8 +1925,51 @@ int ImgCheckAnalysisy::CheckRun()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "支架检测",
|
|
|
|
|
"support edge diag(side %d): strip %d cols [%d,%d], pts %d",
|
|
|
|
|
nSide, nStrip, nXStart, nXEnd, (int)vecEdgePts.size());
|
|
|
|
|
"support edge diag(side %d): strip %d cols (ROI) [%d,%d], pts %d",
|
|
|
|
|
nSide, nStrip, std::min(nXStart, nXEnd), std::max(nXStart, nXEnd),
|
|
|
|
|
(int)vecEdgePts.size());
|
|
|
|
|
// -s 存图:把本侧搜边实际使用的条带(outer ROI 原图最外侧 nStrip 列)裁出来,
|
|
|
|
|
// 并把找到的边缘点(黄点)画上去,便于确认条带是否覆盖到真实支架边缘、边缘点是否准确
|
|
|
|
|
if (DetImgInfo_shareP->bsaveProcessImg && !vecEdgePts.empty())
|
|
|
|
|
{
|
|
|
|
|
const char *strSide = (nSide == 0) ? "left" : "right";
|
|
|
|
|
const int nStripX0 = std::min(nXStart, nXEnd);
|
|
|
|
|
const int nStripW = std::abs(nXEnd - nXStart) + 1;
|
|
|
|
|
// 列范围直接用 detImg 坐标;行范围取支架区域(rsRect)的 y 范围,换算到 detImg
|
|
|
|
|
cv::Point2f ptDetY0 = MatAffinePoint(m_matSrc2Det, cv::Point2f(0.0f, (float)rsRect.y));
|
|
|
|
|
cv::Point2f ptDetY1 = MatAffinePoint(m_matSrc2Det, cv::Point2f(0.0f, (float)(rsRect.y + rsRect.height)));
|
|
|
|
|
cv::Rect stripRoi(nStripX0, cvRound(ptDetY0.y), nStripW, cvRound(ptDetY1.y - ptDetY0.y));
|
|
|
|
|
stripRoi &= cv::Rect(0, 0, grayRef.cols, grayRef.rows);
|
|
|
|
|
if (stripRoi.width > 0 && stripRoi.height > 0)
|
|
|
|
|
{
|
|
|
|
|
cv::Mat showStrip;
|
|
|
|
|
if (grayRef.channels() == 1)
|
|
|
|
|
{
|
|
|
|
|
cv::cvtColor(grayRef(stripRoi), showStrip, cv::COLOR_GRAY2BGR);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
showStrip = grayRef(stripRoi).clone();
|
|
|
|
|
}
|
|
|
|
|
for (size_t i = 0; i < vecEdgePts.size(); i++)
|
|
|
|
|
{
|
|
|
|
|
// vecEdgePts 存 (t,v)=(yDet,xDet),转成条带内局部坐标
|
|
|
|
|
const int nPx = cvRound(vecEdgePts[i].y) - stripRoi.x;
|
|
|
|
|
const int nPy = cvRound(vecEdgePts[i].x) - stripRoi.y;
|
|
|
|
|
if (nPx < 0 || nPx >= showStrip.cols || nPy < 0 || nPy >= showStrip.rows)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
cv::circle(showStrip, cv::Point(nPx, nPy), 3, cv::Scalar(0, 255, 255), -1);
|
|
|
|
|
}
|
|
|
|
|
std::string strStripPath = DetImgInfo_shareP->strChannel + "_support_edge_strip_" + strSide + ".png";
|
|
|
|
|
cv::imwrite(strStripPath, showStrip);
|
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "支架检测",
|
|
|
|
|
"support edge strip saved: %s (side %s strip %d cols [%d,%d] roi [%d %d %d %d] pts %d)",
|
|
|
|
|
strStripPath.c_str(), strSide, nStripW, nStripX0, nStripX0 + nStripW - 1,
|
|
|
|
|
stripRoi.x, stripRoi.y, stripRoi.width, stripRoi.height, (int)vecEdgePts.size());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nSampleOut = (int)vecEdgePts.size();
|
|
|
|
|
if (nSampleOut < 8)
|
|
|
|
|
@ -2022,10 +2185,10 @@ int ImgCheckAnalysisy::CheckRun()
|
|
|
|
|
cv::polylines(show, vecRsPoly, true, cv::Scalar(255, 0, 0), 4);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 采样边缘点(黄)
|
|
|
|
|
// 采样边缘点(黄):放大到半径 8,1/4 缩略后仍清晰可见
|
|
|
|
|
for (size_t i = 0; i < vecBestSamples.size(); i++)
|
|
|
|
|
{
|
|
|
|
|
cv::circle(show, cv::Point(cvRound(vecBestSamples[i].x), cvRound(vecBestSamples[i].y)), 4,
|
|
|
|
|
cv::circle(show, cv::Point(cvRound(vecBestSamples[i].x), cvRound(vecBestSamples[i].y)), 8,
|
|
|
|
|
cv::Scalar(0, 255, 255), -1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -2092,11 +2255,13 @@ int ImgCheckAnalysisy::CheckRun()
|
|
|
|
|
fSupportAngle, fBestEdgeVert,
|
|
|
|
|
(float)(std::atan2((double)vRefDbg.x, (double)vRefDbg.y) * 180.0 / CV_PI));
|
|
|
|
|
cv::putText(show, strDbg, cv::Point(20, 140), cv::FONT_HERSHEY_SIMPLEX, 2.0, cv::Scalar(0, 255, 255), 4);
|
|
|
|
|
cv::putText(show, "blue=support contour yellow=edge points green=seg fit red=dir magenta=ref edge",
|
|
|
|
|
cv::Point(20, 210), cv::FONT_HERSHEY_SIMPLEX, 1.6, cv::Scalar(0, 255, 255), 4);
|
|
|
|
|
|
|
|
|
|
cv::Mat showSmall;
|
|
|
|
|
cv::resize(show, showSmall, cv::Size(show.cols / 4, show.rows / 4));
|
|
|
|
|
std::vector<int> paramJpg = {cv::IMWRITE_JPEG_QUALITY, 90};
|
|
|
|
|
cv::imwrite(DetImgInfo_shareP->strChannel + "_support_angle.jpg", showSmall, paramJpg);
|
|
|
|
|
// 调试图按检测图原分辨率保存(不再缩小到 1/4):边缘点/拟合线位置可逐像素核对,
|
|
|
|
|
// 存图耗时/文件大小靠 support_refine_step 控制采样点数量,而不是靠缩小图像
|
|
|
|
|
std::vector<int> paramJpg = {cv::IMWRITE_JPEG_QUALITY, 95};
|
|
|
|
|
cv::imwrite(DetImgInfo_shareP->strChannel + "_support_angle.jpg", show, paramJpg);
|
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "支架检测",
|
|
|
|
|
"support angle debug image saved: %s_support_angle.jpg", DetImgInfo_shareP->strChannel.c_str());
|
|
|
|
|
}
|
|
|
|
|
|