From 6099c864ee7b35c2f27710c8c1ebcac61810ace0 Mon Sep 17 00:00:00 2001 From: liusiyang Date: Fri, 18 Sep 2026 15:05:04 +0800 Subject: [PATCH] =?UTF-8?q?update=20=E4=BC=98=E5=8C=96=E7=89=A9=E7=90=86?= =?UTF-8?q?=E5=9D=90=E6=A0=87=E8=AE=A1=E7=AE=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AlgorithmModule/include/ImageAllResult.h | 11 ++++ AlgorithmModule/include/ImageDetConfig.h | 8 ++- AlgorithmModule/include/ImgCheckAnalysisy.hpp | 3 + AlgorithmModule/src/ImageAllResult.cpp | 52 +++++++++++++++ AlgorithmModule/src/ImageResultJudge.cpp | 50 ++++++++++---- AlgorithmModule/src/ImgCheckAnalysisy.cpp | 66 +++++++++++++++---- 6 files changed, 163 insertions(+), 27 deletions(-) diff --git a/AlgorithmModule/include/ImageAllResult.h b/AlgorithmModule/include/ImageAllResult.h index 87195ef..3858ad1 100644 --- a/AlgorithmModule/include/ImageAllResult.h +++ b/AlgorithmModule/include/ImageAllResult.h @@ -71,8 +71,19 @@ public: float fscale_detToresult_y = 0; // 产品左上角(rot_cur_roi.boundingRect)在检测图 detImg 中的偏移,缺陷坐标转换到产品坐标系时需减去该间隙 + // 注意:这是轴对齐的近似换算,仅在无旋转矩形(vProductQuadInDet 为空)时作为退化路径使用 cv::Point ptRoiOffsetInDet = cv::Point(0, 0); + // 产品旋转外接矩形四角在 detImg 坐标系下的坐标,顺序固定为 0左上 1右上 2左下 3右下 + // 非空时用「缺陷点到两条边线段的距离」把点换算成产品坐标,可抗产品倾斜 + std::vector vProductQuadInDet; + + // 缺陷点(detImg 坐标) -> 产品坐标(像素) + // x = 到左边线段(左下->左上)的垂距,y = 到顶边线段(左上->右上)的垂距,落在矩形内为正 + // 这样同一物理位置在产品任意倾斜角下都能得到相同的产品坐标(轴对齐相减做不到) + // vProductQuadInDet 无效时退化为 ptRoiOffsetInDet 轴对齐平移(旧行为) + cv::Point2f DetPointToProductCoord(const cv::Point2f &ptDet) const; + int NG_num = 0; int YS_num = 0; int Ok_num = 0; diff --git a/AlgorithmModule/include/ImageDetConfig.h b/AlgorithmModule/include/ImageDetConfig.h index 3e9acd1..8493ab6 100644 --- a/AlgorithmModule/include/ImageDetConfig.h +++ b/AlgorithmModule/include/ImageDetConfig.h @@ -18,7 +18,9 @@ struct QX_ERROR_INFO_ int Idx; int result; std::string result_name; - cv::Rect roi; + cv::Rect roi; // 缺陷框(产品坐标系,抗倾斜;roi.x/y 为框左上角) + cv::Rect roiDet; // 缺陷框(检测图 detImg 坐标系,小图裁剪/定位等内部用途) + cv::Point2f ptProduct = cv::Point2f(0.f, 0.f); // 缺陷中心的产品坐标(像素,抗倾斜,float 精度) int area; int energy; float JudgArea; @@ -59,7 +61,9 @@ struct QX_ERROR_INFO_ result = 0; result_name = "OK"; density = 0; - cv::Rect roi = cv::Rect(0, 0, 0, 0); + roi = cv::Rect(0, 0, 0, 0); + roiDet = cv::Rect(0, 0, 0, 0); + ptProduct = cv::Point2f(0.f, 0.f); detLogList.erase(detLogList.begin(), detLogList.end()); detLogList.clear(); detRegionidxList.clear(); diff --git a/AlgorithmModule/include/ImgCheckAnalysisy.hpp b/AlgorithmModule/include/ImgCheckAnalysisy.hpp index 7e49e6b..24ca1e0 100644 --- a/AlgorithmModule/include/ImgCheckAnalysisy.hpp +++ b/AlgorithmModule/include/ImgCheckAnalysisy.hpp @@ -196,6 +196,9 @@ private: // 边缘缺陷检测 int Edge_Qx_Det(const cv::Mat &img); + // 缺陷框(detImg 坐标) -> 产品坐标(抗倾斜),并记录 detImg 坐标框 + int ConvertDetRoiToProduct(QX_ERROR_INFO_ &qxerror); + // 把blob 汇总成 检测结果。 int BLobToDetResult(); diff --git a/AlgorithmModule/src/ImageAllResult.cpp b/AlgorithmModule/src/ImageAllResult.cpp index 392a266..df72fd0 100644 --- a/AlgorithmModule/src/ImageAllResult.cpp +++ b/AlgorithmModule/src/ImageAllResult.cpp @@ -8,6 +8,9 @@ */ #include "ImageAllResult.h" +#include +#include + ImageAllResult::ImageAllResult() { detlog = std::make_shared(); @@ -44,6 +47,55 @@ bool ImageAllResult::IsNotDet() return false; } +// 点到「线段所在直线」的有符号垂距,法线 n 指向的一侧为正 +static bool SignedPerpDistToEdge(const cv::Point2f &p, const cv::Point2f &a, const cv::Point2f &b, float &fDist) +{ + const cv::Point2f d = b - a; + const float len = std::sqrt(d.x * d.x + d.y * d.y); + if (len < 1e-6f) + { + return false; + } + // 顺时针顶点序(左上->右上->右下->左下)下,内侧法线 = (-dy, dx)/|d| + const cv::Point2f n(-d.y / len, d.x / len); + fDist = (p.x - a.x) * n.x + (p.y - a.y) * n.y; + return true; +} + +cv::Point2f ImageAllResult::DetPointToProductCoord(const cv::Point2f &ptDet) const +{ + if (vProductQuadInDet.size() != 4) + { + // 没有旋转矩形:退化为轴对齐平移(旧行为) + return cv::Point2f(ptDet.x - ptRoiOffsetInDet.x, ptDet.y - ptRoiOffsetInDet.y); + } + + // 固定四角顺序:0左上 1右上 2左下 3右下(先按 y 再按 x 排序) + std::vector v = vProductQuadInDet; + std::sort(v.begin(), v.end(), [](const cv::Point2f &a, const cv::Point2f &b) { return a.y < b.y; }); + if (v[0].x > v[1].x) + { + std::swap(v[0], v[1]); + } + if (v[2].x > v[3].x) + { + std::swap(v[2], v[3]); + } + const cv::Point2f &pLT = v[0]; + const cv::Point2f &pRT = v[1]; + const cv::Point2f &pLB = v[2]; + + float fX = 0.f; + float fY = 0.f; + // x: 到左边线段(左下->左上)的垂距;y: 到顶边线段(左上->右上)的垂距 + if (!SignedPerpDistToEdge(ptDet, pLB, pLT, fX) || + !SignedPerpDistToEdge(ptDet, pLT, pRT, fY)) + { + return cv::Point2f(ptDet.x - ptRoiOffsetInDet.x, ptDet.y - ptRoiOffsetInDet.y); + } + return cv::Point2f(fX, fY); +} + int ImageAllResult::AddDetImage(std::shared_ptr p) { { diff --git a/AlgorithmModule/src/ImageResultJudge.cpp b/AlgorithmModule/src/ImageResultJudge.cpp index 999aaf6..44441b7 100644 --- a/AlgorithmModule/src/ImageResultJudge.cpp +++ b/AlgorithmModule/src/ImageResultJudge.cpp @@ -119,11 +119,16 @@ int ImageResultJudge::GetAIDetImg(std::shared_ptr pImageResult, QXImageResult ImageResultJudge::BuildDefectImage(std::shared_ptr pImageResult, QX_ERROR_INFO_ *QX_info, int qxidx, cv::Point pCenter, float fs_resize_x, float fs_resize_y) { - cv::Rect roi = QX_info->roi; // 缺陷坐标(产品坐标系,已减去裁剪框与产品左上角的间隙) - // 还原检测图(detImg)坐标,用于缺陷小图裁剪等内部用途 - cv::Rect roi_det = roi; - roi_det.x += pImageResult->ptRoiOffsetInDet.x; - roi_det.y += pImageResult->ptRoiOffsetInDet.y; + cv::Rect roi = QX_info->roi; // 缺陷坐标(产品坐标系,抗倾斜) + // 检测图(detImg)坐标框,用于缺陷小图裁剪等内部用途。 + // 优先用 ImgCheckAnalysisy 记录的真实 detImg 框:产品倾斜时无法再由产品坐标加回偏移还原 + cv::Rect roi_det = QX_info->roiDet; + if (roi_det.empty()) + { + roi_det = roi; + roi_det.x += pImageResult->ptRoiOffsetInDet.x; + roi_det.y += pImageResult->ptRoiOffsetInDet.y; + } float JudgArea = QX_info->JudgArea; float flen = QX_info->flen; float fbreadth = QX_info->fbreadth; @@ -167,9 +172,18 @@ QXImageResult ImageResultJudge::BuildDefectImage(std::shared_ptr tem.resizeImgroi.y = roi_det.y * fs_resize_y; tem.resizeImgroi.height = roi_det.height * fs_resize_y; - // 缺陷中心:产品坐标系(像素/物理),相对产品左上角 - tem.x_pixel = roi.x + roi.width * 0.5; - tem.y_pixel = roi.y + roi.height * 0.5; + // 缺陷中心:产品坐标系(像素/物理),相对产品左边/顶边 + if (!QX_info->roiDet.empty()) + { + // 抗倾斜路径:直接用点到左边/顶边线段的垂距(float 精度,不受框取整影响) + tem.x_pixel = QX_info->ptProduct.x; + tem.y_pixel = QX_info->ptProduct.y; + } + else + { + tem.x_pixel = roi.x + roi.width * 0.5; + tem.y_pixel = roi.y + roi.height * 0.5; + } tem.x_mm = tem.x_pixel * m_fImgage_Scale_X; tem.y_mm = tem.y_pixel * m_fImgage_Scale_Y; @@ -241,13 +255,25 @@ int ImageResultJudge::ResultJudge(std::shared_ptr pImageResult) std::string qx_wb = WHITE_BLCAK_Names[QX_whiteBLACK]; pQxLog->AddCheckstr(PrintLevel_1, - "", "qxidx: %d roi[%d %d %d %d] qx %d %s %s JudgArea %f hj %f len %f breadth %f energy %d region num %ld", - qxidx, roi.x, roi.y, roi.width, roi.height, config_qx_type, qx_name.c_str(), qx_wb.c_str(), + "", "qxidx: %d roi[%d %d %d %d] roidet[%d %d %d %d] qx %d %s %s JudgArea %f hj %f len %f breadth %f energy %d region num %ld", + qxidx, roi.x, roi.y, roi.width, roi.height, + QX_info->roiDet.x, QX_info->roiDet.y, QX_info->roiDet.width, QX_info->roiDet.height, + config_qx_type, qx_name.c_str(), qx_wb.c_str(), JudgArea, grayDis, flen, fbreadth, energy, QX_info->detRegionidxList.size()); + // 中心点用 detImg 坐标:检测区域(roiList_Src)与结果图绘制都在该坐标系下 + // (产品坐标会整体平移一个常量,不影响两两距离,但绘制会偏) cv::Point pCenter; - pCenter.x = roi.x + roi.width * 0.5; - pCenter.y = roi.y + roi.height * 0.5; + if (!QX_info->roiDet.empty()) + { + pCenter.x = QX_info->roiDet.x + QX_info->roiDet.width * 0.5; + pCenter.y = QX_info->roiDet.y + QX_info->roiDet.height * 0.5; + } + else + { + pCenter.x = roi.x + roi.width * 0.5; + pCenter.y = roi.y + roi.height * 0.5; + } bool bNG_Status = false; bool bYS_Status = false; diff --git a/AlgorithmModule/src/ImgCheckAnalysisy.cpp b/AlgorithmModule/src/ImgCheckAnalysisy.cpp index e448b82..7368aef 100644 --- a/AlgorithmModule/src/ImgCheckAnalysisy.cpp +++ b/AlgorithmModule/src/ImgCheckAnalysisy.cpp @@ -881,17 +881,36 @@ int ImgCheckAnalysisy::CheckRun() m_pdetlog->AddCheckstr(PrintLevel_0, "2、pre detect", "-------------------------AI_Edge------------succ---time %ld----\n", time_edge_e - time_edge_s); m_Crop_Roi_paramImg = m_CutRoi; - // 计算产品左上角在检测图(detImg)中的偏移(间隙),用于缺陷坐标转换到产品坐标系 + // 产品坐标换算所需的参考几何: + // 1) ptRoiOffsetInDet:产品左上角顶点在 detImg 中的偏移(轴对齐,仅作退化路径) + // 2) vProductQuadInDet:产品旋转外接矩形四角在 detImg 中的坐标(0左上 1右上 2左下 3右下), + // 用于「缺陷点到两边线段的距离」的抗倾斜产品坐标换算 if (m_bRotCurRoiValid) { // 取旋转矩形实际的左上角顶点(非轴对齐外接框 boundingRect,后者在带旋转角时会偏大) std::vector rroi_vertices = sort_vertices(m_rot_cur_roi); m_pImageAllResult->ptRoiOffsetInDet.x = cvRound(rroi_vertices[0].x) - m_CutRoi.x; m_pImageAllResult->ptRoiOffsetInDet.y = cvRound(rroi_vertices[0].y) - m_CutRoi.y; + + m_pImageAllResult->vProductQuadInDet.clear(); + m_pImageAllResult->vProductQuadInDet.reserve(4); + for (int i = 0; i < 4; i++) + { + // 原图坐标 -> detImg 坐标 + m_pImageAllResult->vProductQuadInDet.push_back( + cv::Point2f(rroi_vertices[i].x - m_CutRoi.x, rroi_vertices[i].y - m_CutRoi.y)); + } + m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Convert", + "product quad in det [(%d,%d) (%d,%d) (%d,%d) (%d,%d)]", + cvRound(rroi_vertices[0].x - m_CutRoi.x), cvRound(rroi_vertices[0].y - m_CutRoi.y), + cvRound(rroi_vertices[1].x - m_CutRoi.x), cvRound(rroi_vertices[1].y - m_CutRoi.y), + cvRound(rroi_vertices[2].x - m_CutRoi.x), cvRound(rroi_vertices[2].y - m_CutRoi.y), + cvRound(rroi_vertices[3].x - m_CutRoi.x), cvRound(rroi_vertices[3].y - m_CutRoi.y)); } else { m_pImageAllResult->ptRoiOffsetInDet = cv::Point(0, 0); + m_pImageAllResult->vProductQuadInDet.clear(); } m_pImageAllResult->pDetResult->CutRoi = m_CutRoi; m_pImageAllResult->pDetResult->Param_CropRoi = m_Crop_Roi_paramImg; @@ -2512,6 +2531,35 @@ int ImgCheckAnalysisy::UpdateImgageScale() return 0; } +// 缺陷框(detImg 坐标) -> 产品坐标 +// 旧实现:roi = roiDet - 产品左上角顶点,只做了轴对齐平移,产品倾斜时同一物理位置的 +// 坐标会随倾斜角漂移(沿产品长/宽方向产生 offset*sin(θ) 量级的误差)。 +// 新实现:缺陷中心点到旋转矩形左边线段的垂距作为 x,到顶边线段的垂距作为 y, +// 任意倾斜角下同一物理位置得到相同坐标;再用中心反推产品坐标系下的框左上角, +// 保持 roi 原有的「框左上角 + 框尺寸」语义。roiDet 保留 detImg 坐标供内部裁剪使用。 +int ImgCheckAnalysisy::ConvertDetRoiToProduct(QX_ERROR_INFO_ &qxerror) +{ + // 1) 记录 detImg 坐标下的缺陷框(小图裁剪等内部用途,不受倾斜影响) + qxerror.roiDet = qxerror.roi; + + if (m_pImageAllResult == nullptr) + { + return 1; + } + + // 2) 缺陷中心 -> 产品坐标(点到左边/顶边线段的垂距) + const cv::Point2f pcDet(qxerror.roiDet.x + qxerror.roiDet.width * 0.5f, + qxerror.roiDet.y + qxerror.roiDet.height * 0.5f); + const cv::Point2f pcProduct = m_pImageAllResult->DetPointToProductCoord(pcDet); + qxerror.ptProduct = pcProduct; + + // 3) 用中心反推框左上角 + qxerror.roi.x = cvRound(pcProduct.x - qxerror.roiDet.width * 0.5f); + qxerror.roi.y = cvRound(pcProduct.y - qxerror.roiDet.height * 0.5f); + + return 0; +} + int ImgCheckAnalysisy::Edge_Qx_Det(const cv::Mat &img) { @@ -2603,12 +2651,8 @@ int ImgCheckAnalysisy::Edge_Qx_Det(const cv::Mat &img) // } // } - // 缺陷坐标转换到产品坐标系:减去裁剪框与产品左上角的间隙 - if (m_bRotCurRoiValid) - { - temerror.roi.x -= m_pImageAllResult->ptRoiOffsetInDet.x; - temerror.roi.y -= m_pImageAllResult->ptRoiOffsetInDet.y; - } + // 缺陷坐标转换到产品坐标系(点到左边/顶边线段的垂距,抗倾斜) + ConvertDetRoiToProduct(temerror); m_pDetResult->pQx_ErrorList->push_back(temerror); } @@ -2681,12 +2725,8 @@ int ImgCheckAnalysisy::BLobToDetResult() { QX_ERROR_INFO_ temerror; temerror.roi = roi; - // 缺陷坐标转换到产品坐标系:减去裁剪框与产品左上角的间隙 - if (m_bRotCurRoiValid) - { - temerror.roi.x -= m_pImageAllResult->ptRoiOffsetInDet.x; - temerror.roi.y -= m_pImageAllResult->ptRoiOffsetInDet.y; - } + // 缺陷坐标转换到产品坐标系(点到左边/顶边线段的垂距,抗倾斜) + ConvertDetRoiToProduct(temerror); temerror.Idx = m_pDetResult->pQx_ErrorList->size(); temerror.area = blobs.blobTab[i].area; temerror.JudgArea = JudgArea;