From 13afe246d54f14697eb29b136bd704385ec825bd Mon Sep 17 00:00:00 2001 From: liusiyang Date: Mon, 19 Oct 2026 10:53:52 +0800 Subject: [PATCH] =?UTF-8?q?update=20=E4=B8=8D=E4=BB=BF=E5=B0=84=E5=8E=9F?= =?UTF-8?q?=E5=9B=BE=EF=BC=8C=E5=AF=B9=E6=A8=A1=E6=9D=BFPin=E8=BF=9B?= =?UTF-8?q?=E8=A1=8C=E4=BB=BF=E5=B0=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AlgorithmModule/include/ImgCheckAnalysisy.hpp | 13 +- AlgorithmModule/src/ImgCheckAnalysisy.cpp | 170 +++++++++++++----- 2 files changed, 134 insertions(+), 49 deletions(-) diff --git a/AlgorithmModule/include/ImgCheckAnalysisy.hpp b/AlgorithmModule/include/ImgCheckAnalysisy.hpp index ff03a84..e927065 100644 --- a/AlgorithmModule/include/ImgCheckAnalysisy.hpp +++ b/AlgorithmModule/include/ImgCheckAnalysisy.hpp @@ -218,8 +218,11 @@ private: // 获取指定 残点(blob) 是由哪个(哪些)检测模型检出的 std::string GetBlobModelName(const cv::Rect &roi); - //自适应参数区域更新:仿射变换到模板坐标系(基准矩形可为内定位/外定位) - int Adapt_Config(cv::RotatedRect tplRefRect, cv::RotatedRect curRefRect, cv::Mat& det_img); + // 计算 当前图 -> 模板图 / 模板图 -> 当前图 的仿射矩阵(基准矩形可为内定位/外定位) + // 注意: 不再对原图做 warpAffine, 只做坐标变换 + int CalAffineMats(const cv::RotatedRect &tplRefRect, const cv::RotatedRect &curRefRect); + // 把模板 Pin 轮廓(pinImg 裁剪坐标系)反变换到当前检测图(detImg)坐标系 + int TransformTplPinContoursToCur(); std::shared_ptr AI_Factory; @@ -316,8 +319,12 @@ private: cv::RotatedRect m_outer_rroi; cv::RotatedRect m_inner_rroi; cv::RotatedRect m_tplInnerRect; // 模板内边缘矩形 - std::vector> m_tplPinContours; // 模板 pin blob 轮廓点集 + std::vector> m_tplPinContours; // 模板 pin blob 轮廓点集(pinImg 裁剪坐标系) + std::vector> m_tplPinContoursCur; // 模板 pin 轮廓(反变换到当前检测图坐标系) std::vector> m_curPinContours; // 当前 pin blob 轮廓点集 + cv::Mat m_affine_cur2tpl; // 当前图 -> 模板图 + cv::Mat m_affine_tpl2cur; // 模板图 -> 当前图 + bool m_bAffineValid = false; // 仿射矩阵是否有效 std::vector> m_pinFitLines; // 新方案: 两排Pin拟合直线段(当前检测图坐标系, 两端点) std::vector m_pinMissMarks; // 新方案: 缺失Pin位置(相邻Pin中心连线中点) }; diff --git a/AlgorithmModule/src/ImgCheckAnalysisy.cpp b/AlgorithmModule/src/ImgCheckAnalysisy.cpp index a55dbab..18e8685 100644 --- a/AlgorithmModule/src/ImgCheckAnalysisy.cpp +++ b/AlgorithmModule/src/ImgCheckAnalysisy.cpp @@ -544,46 +544,101 @@ cv::Scalar ImgCheckAnalysisy::calc_blob_info_withstats(cv::Mat &img, const cv::M return cv::Scalar(worb, energy, hj); } -// 仿射变换到模板坐标系:把当前基准矩形(内定位/外定位)的顶点映射到模板对应基准矩形上 -int ImgCheckAnalysisy::Adapt_Config(RotatedRect tplRefRect, RotatedRect curRefRect, Mat& det_img){ +// 计算仿射矩阵(不做图像 warp): +// 基准点用当前基准矩形(内定位优先/外定位回退)的三个角点 -> 模板对应基准矩形的三个角点 +// m_affine_cur2tpl: 当前图坐标 -> 模板图坐标 +// m_affine_tpl2cur: 模板图坐标 -> 当前图坐标 +int ImgCheckAnalysisy::CalAffineMats(const RotatedRect &tplRefRect, const RotatedRect &curRefRect) +{ + m_affine_cur2tpl.release(); + m_affine_tpl2cur.release(); + m_bAffineValid = false; + + if (tplRefRect.size.width <= 0 || tplRefRect.size.height <= 0 || + curRefRect.size.width <= 0 || curRefRect.size.height <= 0) + { + m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "CalAffineMats: invalid ref rect tpl[%f %f] cur[%f %f]", + tplRefRect.size.width, tplRefRect.size.height, + curRefRect.size.width, curRefRect.size.height); + return 1; + } + vector cur_vertices = CheckUtil::sort_vertices(curRefRect); vector tpl_vertices = CheckUtil::sort_vertices(tplRefRect); - - cv::Point2f src_pts[3] = { - cur_vertices[0], // 对应左上 - cur_vertices[1], // 对应右上 - cur_vertices[2] // 对应左下 + + cv::Point2f cur_pts[3] = { + cur_vertices[0], // 左上 + cur_vertices[1], // 右上 + cur_vertices[2] // 左下 }; - cv::Point2f dst_pts[3] = { + cv::Point2f tpl_pts[3] = { tpl_vertices[0], // 左上 tpl_vertices[1], // 右上 tpl_vertices[2] // 左下 }; - cv::Mat affine_mat = cv::getAffineTransform(src_pts, dst_pts); - // 仿射变换 - cv::warpAffine(det_img, det_img, affine_mat, det_img.size()); + m_affine_cur2tpl = cv::getAffineTransform(cur_pts, tpl_pts); + m_affine_tpl2cur = cv::getAffineTransform(tpl_pts, cur_pts); + m_bAffineValid = (!m_affine_cur2tpl.empty() && !m_affine_tpl2cur.empty()); - // 将m_outer_rroi和m_inner_rroi变换到模板图像坐标系下 - auto transformRotatedRect = [&affine_mat](cv::RotatedRect &rrect) + if (!m_bAffineValid) { - const double *M = affine_mat.ptr(0); + m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "CalAffineMats: getAffineTransform failed"); + return 1; + } - cv::Point2f pts[4]; - rrect.points(pts); + // 仅用于日志: 分解当前->模板的旋转/缩放, 便于排查 + const double *M = m_affine_cur2tpl.ptr(0); + double fScaleX = std::sqrt(M[0] * M[0] + M[3] * M[3]); + double fScaleY = std::sqrt(M[1] * M[1] + M[4] * M[4]); + double fAngle = std::atan2(M[3], M[0]) * 180.0 / CV_PI; + m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "CalAffineMats", + "scaleX %.4f scaleY %.4f angle %.2f deg t(%.2f %.2f)", + fScaleX, fScaleY, fAngle, M[2], M[5]); + return 0; +} - std::vector transformed_pts(4); - for (int i = 0; i < 4; i++) +// 把模板 Pin 轮廓反变换到当前检测图(detImg)坐标系: +// m_tplPinContours 在 pinImg(模板外框裁剪图)坐标系 +// -> 加模板外框原点得到模板图坐标 +// -> m_affine_tpl2cur 得到当前图坐标 +// -> 减 m_outer_roi 原点得到 detImg 坐标 +int ImgCheckAnalysisy::TransformTplPinContoursToCur() +{ + m_tplPinContoursCur.clear(); + + if (m_tplPinContours.empty()) + { + return 0; + } + if (!m_bAffineValid || m_affine_tpl2cur.empty()) + { + m_pdetlog->AddCheckstr(PrintLevel_0, "Error", + "TransformTplPinContoursToCur: affine invalid, template pin contours skipped"); + return 1; + } + + const cv::Rect tplBound = m_tplOuterRect.boundingRect(); + const double *M = m_affine_tpl2cur.ptr(0); + + m_tplPinContoursCur.reserve(m_tplPinContours.size()); + for (const auto &contour : m_tplPinContours) + { + std::vector mapped; + mapped.reserve(contour.size()); + for (const auto &pt : contour) { - float x = static_cast(M[0] * pts[i].x + M[1] * pts[i].y + M[2]); - float y = static_cast(M[3] * pts[i].x + M[4] * pts[i].y + M[5]); - transformed_pts[i] = cv::Point2f(x, y); + // pinImg 坐标 -> 模板图坐标 + const double tplX = static_cast(pt.x) + tplBound.x; + const double tplY = static_cast(pt.y) + tplBound.y; + // 模板图坐标 -> 当前图坐标 + const double curX = M[0] * tplX + M[1] * tplY + M[2]; + const double curY = M[3] * tplX + M[4] * tplY + M[5]; + // 当前图坐标 -> detImg 坐标 + mapped.emplace_back(cvRound(curX - m_outer_roi.x), cvRound(curY - m_outer_roi.y)); } - rrect = cv::minAreaRect(transformed_pts); - }; - - transformRotatedRect(m_outer_rroi); - transformRotatedRect(m_inner_rroi); + m_tplPinContoursCur.push_back(std::move(mapped)); + } return 0; } @@ -701,13 +756,13 @@ int ImgCheckAnalysisy::CheckRun() QX_ERROR_INFO_ alignErr; alignErr.Idx = static_cast(m_pDetResult->pQx_ErrorList->size()); - // roi 用整张检测图区域(detImg 坐标系) - cv::Rect tplRect = m_tplOuterRect.boundingRect(); - alignErr.roi = cv::Rect(0, 0, tplRect.width, tplRect.height); + // roi 用整张检测图区域(detImg 坐标系); detImg 现在取当前外定位包围框, 尺寸与之一致 + cv::Rect curBound = m_outer_rroi.boundingRect(); + alignErr.roi = cv::Rect(0, 0, curBound.width, curBound.height); alignErr.area = alignErr.roi.width * alignErr.roi.height; alignErr.JudgArea = alignErr.area * m_fImgage_Scale_X * m_fImgage_Scale_Y; - alignErr.flen = tplRect.width * m_fImgage_Scale_X; - alignErr.fbreadth = tplRect.height * m_fImgage_Scale_Y; + alignErr.flen = curBound.width * m_fImgage_Scale_X; + alignErr.fbreadth = curBound.height * m_fImgage_Scale_Y; // 缺陷列表中原 other(其他)已移除;内外边缘对齐超差暂按 C型卡口异常 上报(待确认) alignErr.nconfig_qx_type = CONFIG_QX_NAME_HunLiao; @@ -763,21 +818,44 @@ int ImgCheckAnalysisy::CheckRun() } #else // 检测PIN偏移/缺失对位置基准最敏感:PIN 位置的真实基准是产品内腔(内定位), - // 因此优先用内定位做仿射变换(当前内边缘 -> 模板内边缘); - // 模板内定位/当前内定位缺失或无效时,回退到外定位(与旧行为一致) + // 因此优先用内定位计算仿射(当前内边缘 -> 模板内边缘);模板/当前内定位无效时回退外定位。 + // 注意: 这里不再对原图做 warpAffine, 只计算矩阵, 并把模板 Pin 轮廓反变换到当前图坐标系。 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) { - m_pdetlog->AddCheckstr(PrintLevel_0, "2.1、pre detect", "Affine align basis: Inner(inner_rroi -> tplInnerRect)"); - Adapt_Config(m_tplInnerRect, m_inner_rroi, m_CheckResult_shareP->in_shareImage->img); + m_pdetlog->AddCheckstr(PrintLevel_0, "2.1、pre detect", + "Affine align basis: Inner(inner_rroi -> tplInnerRect), template pins warp only, image NOT warped"); + CalAffineMats(m_tplInnerRect, m_inner_rroi); } else { - m_pdetlog->AddCheckstr(PrintLevel_0, "2.1、pre detect", "Affine align basis: Outer(outer_rroi -> tplOuterRect)"); - Adapt_Config(m_tplOuterRect, m_outer_rroi, m_CheckResult_shareP->in_shareImage->img); + m_pdetlog->AddCheckstr(PrintLevel_0, "2.1、pre detect", + "Affine align basis: Outer(outer_rroi -> tplOuterRect), template pins warp only, image NOT warped"); + CalAffineMats(m_tplOuterRect, m_outer_rroi); } - m_outer_roi = m_tplOuterRect.boundingRect(); + + // 后续所有检测/裁剪都在当前图坐标系: 裁剪框取当前外定位的包围框(原图不 warp) + { + 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); + m_outer_roi = curOuterBound & curImgBound; + if (m_outer_roi.width <= 0 || m_outer_roi.height <= 0) + { + m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "Affine mode: current outer roi invalid [%d %d %d %d]", + curOuterBound.x, curOuterBound.y, curOuterBound.width, curOuterBound.height); + m_nErrorCode = CHECK_ERROR_Config_cutRoi; + m_nCheckResultErrorCode = m_nErrorCode; + return m_nErrorCode; + } + } + + // 模板 Pin(pinImg 坐标) -> 模板图坐标 -> 当前图坐标 -> detImg 坐标 + TransformTplPinContoursToCur(); + m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "2.1、pre detect", + "tpl pin contours -> current frame: %ld (affineValid %d)", + static_cast(m_tplPinContoursCur.size()), static_cast(m_bAffineValid)); #endif m_Crop_Roi_paramImg = m_outer_roi; m_pImageAllResult->pDetResult->CutRoi = m_outer_roi; @@ -2060,7 +2138,7 @@ int ImgCheckAnalysisy::DrawResult_Step_1() cv::Point(cvRound(mark.x), cvRound(mark.y + 6.0f))}); } #else - const std::vector> &tplDrawPinContours = m_tplPinContours; + const std::vector> &tplDrawPinContours = m_tplPinContoursCur; #endif for (const auto &contour : tplDrawPinContours) { @@ -2204,18 +2282,18 @@ int ImgCheckAnalysisy::Pin_Qx_Det(const cv::Mat &img) return cv::Point2f(r.x + r.width * 0.5f, r.y + r.height * 0.5f); }; - const int nTpl = static_cast(m_tplPinContours.size()); + const int nTpl = static_cast(m_tplPinContoursCur.size()); const int nCur = static_cast(m_curPinContours.size()); if (nTpl > 0) { - // 1、统计模板 pin 中心与外包矩形 + // 1、统计模板 pin 中心与外包矩形(已反变换到当前检测图坐标系) std::vector tplCenters(nTpl); std::vector tplRects(nTpl); for (int i = 0; i < nTpl; i++) { - tplCenters[i] = getCenter(m_tplPinContours[i]); - tplRects[i] = cv::boundingRect(m_tplPinContours[i]); + tplCenters[i] = getCenter(m_tplPinContoursCur[i]); + tplRects[i] = cv::boundingRect(m_tplPinContoursCur[i]); } // 2、当前 pin 中心与外包矩形 @@ -2242,9 +2320,9 @@ int ImgCheckAnalysisy::Pin_Qx_Det(const cv::Mat &img) { QX_ERROR_INFO_ pinErr; pinErr.Idx = static_cast(m_pDetResult->pQx_ErrorList->size()); - cv::Rect r = cv::boundingRect(m_tplPinContours[pinIdx]); + cv::Rect r = cv::boundingRect(m_tplPinContoursCur[pinIdx]); pinErr.roi = r; - pinErr.area = static_cast(cv::contourArea(m_tplPinContours[pinIdx])); + pinErr.area = static_cast(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; @@ -2358,7 +2436,7 @@ int ImgCheckAnalysisy::Pin_Qx_Det(const cv::Mat &img) 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); + cv::drawContours(showimg, m_tplPinContoursCur, -1, CheckUtil::GetDrawColor(showimg, cv::Scalar(0, 255, 0)), 1); #endif imwrite("pin_contours.png", showimg); }