diff --git a/AlgorithmModule/include/CheckErrorCodeDefine.hpp b/AlgorithmModule/include/CheckErrorCodeDefine.hpp index e94e96d..aa6364a 100644 --- a/AlgorithmModule/include/CheckErrorCodeDefine.hpp +++ b/AlgorithmModule/include/CheckErrorCodeDefine.hpp @@ -223,6 +223,31 @@ struct Detect_ROI_Config roiList_Src.push_back(adjustedPolygon); roiList_Show.push_back(adjustedPolygon_size); } + // 用 2x3 仿射矩阵(参数/原图 -> 检测图)更新多边形;点变换后已在检测图坐标系,只做缩放 + void Update_Mat(const std::vector &list, const cv::Mat &matParamToDet, float fScale_x, float fScale_y) + { + std::vector adjustedPolygon; + std::vector adjustedPolygon_size; + for (const auto &point : list) + { + cv::Point2f pt = CheckUtil::transformPoint(cv::Point2f((float)point.x, (float)point.y), matParamToDet); + cv::Point adjustedPoint(cvRound(pt.x), cvRound(pt.y)); + if (adjustedPoint.y < 0) + { + adjustedPoint.y = 0; + } + + cv::Point sizep; + sizep.x = adjustedPoint.x * fScale_x; + sizep.y = adjustedPoint.y * fScale_y; + adjustedPolygon.push_back(adjustedPoint); + adjustedPolygon_size.push_back(sizep); + } + + // 将调整后的多边形添加到roiList_Src + roiList_Src.push_back(adjustedPolygon); + roiList_Show.push_back(adjustedPolygon_size); + } // 判断点是否在指定idx的ROI内 bool isPointInROI(int idx, const cv::Point &point) { diff --git a/AlgorithmModule/include/ImageDetConfig.h b/AlgorithmModule/include/ImageDetConfig.h index 3e9acd1..679709f 100644 --- a/AlgorithmModule/include/ImageDetConfig.h +++ b/AlgorithmModule/include/ImageDetConfig.h @@ -92,6 +92,7 @@ struct One_Image_CheckResult_ cv::Rect CutRoi; // 当前检测图片 对应的裁切区域 cv::Rect Param_CropRoi; // 参数模版 对应的裁切区域 std::shared_ptr> pQx_ErrorList; // 缺陷错误信息 + cv::Mat detToSrcMat; // 检测图坐标 -> 原图坐标 的 2x3 仿射矩阵(检测图摆正时有效; 空表示按 CutRoi 平移映射) void print(std::string str = "") { std::cout << str << ": " diff --git a/AlgorithmModule/include/ImgCheckAnalysisy.hpp b/AlgorithmModule/include/ImgCheckAnalysisy.hpp index ac7d56f..842c7fe 100644 --- a/AlgorithmModule/include/ImgCheckAnalysisy.hpp +++ b/AlgorithmModule/include/ImgCheckAnalysisy.hpp @@ -276,6 +276,12 @@ private: cv::Rect m_inner_roi; cv::Rect m_Crop_Roi_paramImg; // 裁切在参数模板图上的 位置。 + // 检测图摆正(去旋转)相关:用 m_outer_rroi 建立 原图 -> 检测图 的仿射映射 + cv::Mat m_matSrc2Det; // 原图 -> 检测图(摆正) 2x3 仿射矩阵 + cv::Mat m_matDet2Src; // 检测图(摆正) -> 原图 2x3 仿射矩阵 + cv::Mat m_matParam2Det; // 参数(模板)坐标 -> 检测图(摆正) 2x3 仿射矩阵 + cv::Rect m_deskewPadRoi; // 摆正时原图上的裁剪区域(即 warp 的输入范围) + std::string m_strCurDetChannel; // 当前处理的图片通道 // // 检测控制参数 diff --git a/AlgorithmModule/src/CheckResultJson.cpp b/AlgorithmModule/src/CheckResultJson.cpp index 3ffd12d..84d3fb4 100644 --- a/AlgorithmModule/src/CheckResultJson.cpp +++ b/AlgorithmModule/src/CheckResultJson.cpp @@ -18,6 +18,18 @@ Json::Value CheckResultJson::toJsonValue() root["Param_CropRoi"]["width"] = m_pOneImgDetResult->Param_CropRoi.width; root["Param_CropRoi"]["height"] = m_pOneImgDetResult->Param_CropRoi.height; } + // 检测图(摆正) -> 原图 的 2x3 仿射矩阵(用于复测/外部分析还原缺陷小图) + if (!m_pOneImgDetResult->detToSrcMat.empty() && + m_pOneImgDetResult->detToSrcMat.rows == 2 && m_pOneImgDetResult->detToSrcMat.cols == 3) + { + const double *m = m_pOneImgDetResult->detToSrcMat.ptr(0); + root["detToSrc"]["m00"] = m[0]; + root["detToSrc"]["m01"] = m[1]; + root["detToSrc"]["m02"] = m[2]; + root["detToSrc"]["m10"] = m[3]; + root["detToSrc"]["m11"] = m[4]; + root["detToSrc"]["m12"] = m[5]; + } for (int i = 0; i < m_pOneImgDetResult->pQx_ErrorList->size(); i++) { QX_ERROR_INFO_ *p = &m_pOneImgDetResult->pQx_ErrorList->at(i); @@ -84,6 +96,15 @@ void CheckResultJson::toObjectFromValue(Json::Value root) m_pOneImgDetResult->Param_CropRoi.height = root["Param_CropRoi"]["height"].asInt(); } } + // 检测图(摆正) -> 原图 的 2x3 仿射矩阵(旧 JSON 没有该字段时按 CutRoi 平移兼容) + { + auto value = root["detToSrc"]; + if (value.isObject() && value.isMember("m00")) + { + m_pOneImgDetResult->detToSrcMat = (cv::Mat_(2, 3) << value["m00"].asDouble(), value["m01"].asDouble(), value["m02"].asDouble(), + value["m10"].asDouble(), value["m11"].asDouble(), value["m12"].asDouble()); + } + } { if (root.isMember("Qx")) { diff --git a/AlgorithmModule/src/ImageResultJudge.cpp b/AlgorithmModule/src/ImageResultJudge.cpp index 9a0a8de..cabf432 100644 --- a/AlgorithmModule/src/ImageResultJudge.cpp +++ b/AlgorithmModule/src/ImageResultJudge.cpp @@ -1,6 +1,72 @@ #include "ImageResultJudge.h" +// 检测图坐标 -> 原图坐标(检测图摆正时 detToSrcMat 非空; 否则退回 CutRoi 平移) +static cv::Rect DetRoiToSrcRoi(const cv::Mat &detToSrcMat, cv::Rect detRoi, const cv::Rect &cutRoi, const cv::Size &srcSize) +{ + if (detToSrcMat.empty() || detToSrcMat.rows != 2 || detToSrcMat.cols != 3) + { + cv::Rect srcRoi = detRoi; + srcRoi.x += cutRoi.x; + srcRoi.y += cutRoi.y; + return srcRoi; + } + + const double *m = detToSrcMat.ptr(0); + cv::Point2f pts[4] = { + cv::Point2f((float)detRoi.x, (float)detRoi.y), + cv::Point2f((float)(detRoi.x + detRoi.width), (float)detRoi.y), + cv::Point2f((float)(detRoi.x + detRoi.width), (float)(detRoi.y + detRoi.height)), + cv::Point2f((float)detRoi.x, (float)(detRoi.y + detRoi.height))}; + float fminX = 1e18f, fmaxX = -1e18f, fminY = 1e18f, fmaxY = -1e18f; + for (int i = 0; i < 4; i++) + { + float x = (float)(m[0] * pts[i].x + m[1] * pts[i].y + m[2]); + float y = (float)(m[3] * pts[i].x + m[4] * pts[i].y + m[5]); + if (x < fminX) + fminX = x; + if (x > fmaxX) + fmaxX = x; + if (y < fminY) + fminY = y; + if (y > fmaxY) + fmaxY = y; + } + cv::Rect srcRoi(cvFloor(fminX), cvFloor(fminY), cvCeil(fmaxX) - cvFloor(fminX), cvCeil(fmaxY) - cvFloor(fminY)); + if (srcSize.width > 0 && srcSize.height > 0) + { + srcRoi &= cv::Rect(0, 0, srcSize.width, srcSize.height); + } + return srcRoi; +} + +// 从原图取缺陷小图:detCutRoi 为检测图坐标下的截取框;输出保持与检测图一致的摆正方向(原图通道) +static bool WarpSrcCutImg(const cv::Mat &srcImg, const cv::Mat &detToSrcMat, const cv::Rect &detCutRoi, cv::Mat &outPatch) +{ + outPatch.release(); + if (srcImg.empty() || detToSrcMat.empty() || detToSrcMat.rows != 2 || detToSrcMat.cols != 3 || + detCutRoi.width <= 0 || detCutRoi.height <= 0) + { + return false; + } + + // detCutRoi 四角映射回原图,得到需要取的原图范围 + cv::Rect srcBBox = DetRoiToSrcRoi(detToSrcMat, detCutRoi, cv::Rect(0, 0, 0, 0), srcImg.size()); + if (srcBBox.width <= 0 || srcBBox.height <= 0) + { + return false; + } + + // dst 为截取框局部坐标, src 为原图坐标(再平移到 srcBBox 内): + // src = detToSrcMat * (dst + detCutRoi.tl()) - srcBBox.tl() + cv::Mat mat = detToSrcMat.clone(); + mat.at(0, 2) += mat.at(0, 0) * detCutRoi.x + mat.at(0, 1) * detCutRoi.y - srcBBox.x; + mat.at(1, 2) += mat.at(1, 0) * detCutRoi.x + mat.at(1, 1) * detCutRoi.y - srcBBox.y; + + cv::warpAffine(srcImg(srcBBox), outPatch, mat, detCutRoi.size(), cv::INTER_LINEAR, cv::BORDER_REPLICATE); + return !outPatch.empty(); +} + ImageResultJudge::ImageResultJudge(/* args */) { m_pCommonAnalysisyConfig = NULL; @@ -510,19 +576,28 @@ int ImageResultJudge::ResultJudge(std::shared_ptr pImageResult) // CheckUtil::printROI(CutRoi, "CutRoi"); cv::Size sz = cv::Size(QX_SAMLLIMG_WIDTH, QX_SAMLLIMG_HEIGHT); - // 缺陷小图直接取自原图裁剪区(保持原图通道 -> 真实彩色),ReJson/异常时退回检测图 + // 缺陷小图直接取自原图裁剪区(保持原图通道 -> 真实彩色);检测图摆正时按仿射取回(保持摆正方向), + // ReJson/异常时退回检测图 { const cv::Mat &srcImg = pImageResult->result->in_shareImage->img; - int nOffsetX = (pImageResult->result->in_shareImage->Det_Mode != DET_MODE_ReJson) ? pDetResult->CutRoi.x : 0; - int nOffsetY = (pImageResult->result->in_shareImage->Det_Mode != DET_MODE_ReJson) ? pDetResult->CutRoi.y : 0; - cv::Rect srcCanvasRoi(CutRoi.x + nOffsetX, CutRoi.y + nOffsetY, CutRoi.width, CutRoi.height); - if (!srcImg.empty() && CheckUtil::RoiInImg(srcCanvasRoi, srcImg)) + bool bGot = false; + if (pImageResult->result->in_shareImage->Det_Mode != DET_MODE_ReJson) { - tem.srcImg = srcImg(srcCanvasRoi).clone(); + bGot = WarpSrcCutImg(srcImg, pDetResult->detToSrcMat, CutRoi, tem.srcImg); } - else + if (!bGot) { - tem.srcImg = pImageResult->detImg(CutRoi).clone(); + int nOffsetX = (pImageResult->result->in_shareImage->Det_Mode != DET_MODE_ReJson) ? pDetResult->CutRoi.x : 0; + int nOffsetY = (pImageResult->result->in_shareImage->Det_Mode != DET_MODE_ReJson) ? pDetResult->CutRoi.y : 0; + cv::Rect srcCanvasRoi(CutRoi.x + nOffsetX, CutRoi.y + nOffsetY, CutRoi.width, CutRoi.height); + if (!srcImg.empty() && CheckUtil::RoiInImg(srcCanvasRoi, srcImg)) + { + tem.srcImg = srcImg(srcCanvasRoi).clone(); + } + else + { + tem.srcImg = pImageResult->detImg(CutRoi).clone(); + } } } cv::resize(tem.srcImg, tem.resizeImg, sz); @@ -535,9 +610,12 @@ int ImageResultJudge::ResultJudge(std::shared_ptr pImageResult) tem.max_v = maxValue; tem.strTypeName = QX_Result_Names[nqx_type]; tem.qx_Code = QX_Result_Code[nqx_type]; - tem.srcImgroi = roi; - tem.srcImgroi.x += pDetResult->CutRoi.x; - tem.srcImgroi.y += pDetResult->CutRoi.y; + // 缺陷 roi 映射回原图(摆正时按仿射, 否则按 CutRoi 平移) + { + const cv::Mat &srcSizeImg = pImageResult->result->in_shareImage->img; + tem.srcImgroi = DetRoiToSrcRoi(pDetResult->detToSrcMat, roi, pDetResult->CutRoi, + srcSizeImg.empty() ? pImageResult->detImg.size() : srcSizeImg.size()); + } tem.len = flen; tem.breadth = fbreadth; tem.qx_type = 0; @@ -634,19 +712,28 @@ int ImageResultJudge::ResultJudge(std::shared_ptr pImageResult) // CheckUtil::printROI(CutRoi, "CutRoi"); cv::Size sz = cv::Size(QX_SAMLLIMG_WIDTH, QX_SAMLLIMG_HEIGHT); - // 缺陷小图直接取自原图裁剪区(保持原图通道 -> 真实彩色),ReJson/异常时退回检测图 + // 缺陷小图直接取自原图裁剪区(保持原图通道 -> 真实彩色);检测图摆正时按仿射取回(保持摆正方向), + // ReJson/异常时退回检测图 { const cv::Mat &srcImg = pImageResult->result->in_shareImage->img; - int nOffsetX = (pImageResult->result->in_shareImage->Det_Mode != DET_MODE_ReJson) ? pDetResult->CutRoi.x : 0; - int nOffsetY = (pImageResult->result->in_shareImage->Det_Mode != DET_MODE_ReJson) ? pDetResult->CutRoi.y : 0; - cv::Rect srcCanvasRoi(CutRoi.x + nOffsetX, CutRoi.y + nOffsetY, CutRoi.width, CutRoi.height); - if (!srcImg.empty() && CheckUtil::RoiInImg(srcCanvasRoi, srcImg)) + bool bGot = false; + if (pImageResult->result->in_shareImage->Det_Mode != DET_MODE_ReJson) { - tem.srcImg = srcImg(srcCanvasRoi).clone(); + bGot = WarpSrcCutImg(srcImg, pDetResult->detToSrcMat, CutRoi, tem.srcImg); } - else + if (!bGot) { - tem.srcImg = pImageResult->detImg(CutRoi).clone(); + int nOffsetX = (pImageResult->result->in_shareImage->Det_Mode != DET_MODE_ReJson) ? pDetResult->CutRoi.x : 0; + int nOffsetY = (pImageResult->result->in_shareImage->Det_Mode != DET_MODE_ReJson) ? pDetResult->CutRoi.y : 0; + cv::Rect srcCanvasRoi(CutRoi.x + nOffsetX, CutRoi.y + nOffsetY, CutRoi.width, CutRoi.height); + if (!srcImg.empty() && CheckUtil::RoiInImg(srcCanvasRoi, srcImg)) + { + tem.srcImg = srcImg(srcCanvasRoi).clone(); + } + else + { + tem.srcImg = pImageResult->detImg(CutRoi).clone(); + } } } cv::resize(tem.srcImg, tem.resizeImg, sz); @@ -659,9 +746,12 @@ int ImageResultJudge::ResultJudge(std::shared_ptr pImageResult) tem.max_v = maxValue; tem.strTypeName = QX_Result_Names[nqx_type]; tem.qx_Code = QX_Result_Code[nqx_type]; - tem.srcImgroi = roi; - tem.srcImgroi.x += pDetResult->CutRoi.x; - tem.srcImgroi.y += pDetResult->CutRoi.y; + // 缺陷 roi 映射回原图(摆正时按仿射, 否则按 CutRoi 平移) + { + const cv::Mat &srcSizeImg = pImageResult->result->in_shareImage->img; + tem.srcImgroi = DetRoiToSrcRoi(pDetResult->detToSrcMat, roi, pDetResult->CutRoi, + srcSizeImg.empty() ? pImageResult->detImg.size() : srcSizeImg.size()); + } tem.len = flen; tem.breadth = fbreadth; tem.qx_type = 0; diff --git a/AlgorithmModule/src/ImgCheckAnalysisy.cpp b/AlgorithmModule/src/ImgCheckAnalysisy.cpp index 1b60250..9e195b4 100644 --- a/AlgorithmModule/src/ImgCheckAnalysisy.cpp +++ b/AlgorithmModule/src/ImgCheckAnalysisy.cpp @@ -28,6 +28,66 @@ static bool compareContourAreas(const vector &contour1, const vector j); } + +// 2x3 仿射矩阵小工具:变换点、矩形外接、组合矩阵(检测图摆正用) +static cv::Point2f MatAffinePoint(const cv::Mat &mat32, const cv::Point2f &pt) +{ + if (mat32.empty() || mat32.rows != 2 || mat32.cols != 3) + { + return pt; + } + const double *m = mat32.ptr(0); + return cv::Point2f((float)(m[0] * pt.x + m[1] * pt.y + m[2]), + (float)(m[3] * pt.x + m[4] * pt.y + m[5])); +} + +// 矩形四角经仿射变换后取外接矩形 +static cv::Rect MatAffineRect(const cv::Mat &mat32, const cv::Rect &roi) +{ + std::vector pts(4); + pts[0] = MatAffinePoint(mat32, cv::Point2f((float)roi.x, (float)roi.y)); + pts[1] = MatAffinePoint(mat32, cv::Point2f((float)(roi.x + roi.width), (float)roi.y)); + pts[2] = MatAffinePoint(mat32, cv::Point2f((float)(roi.x + roi.width), (float)(roi.y + roi.height))); + pts[3] = MatAffinePoint(mat32, cv::Point2f((float)roi.x, (float)(roi.y + roi.height))); + + float fminX = pts[0].x, fmaxX = pts[0].x, fminY = pts[0].y, fmaxY = pts[0].y; + for (int i = 1; i < 4; i++) + { + if (pts[i].x < fminX) + fminX = pts[i].x; + if (pts[i].x > fmaxX) + fmaxX = pts[i].x; + if (pts[i].y < fminY) + fminY = pts[i].y; + if (pts[i].y > fmaxY) + fmaxY = pts[i].y; + } + int x0 = cvFloor(fminX); + int y0 = cvFloor(fminY); + int x1 = cvCeil(fmaxX); + int y1 = cvCeil(fmaxY); + return cv::Rect(x0, y0, x1 - x0, y1 - y0); +} + +// 组合仿射:先应用 mB 再应用 mA,即 mA ∘ mB,结果仍为 2x3 +static cv::Mat MatAffineCompose(const cv::Mat &mA, const cv::Mat &mB) +{ + if (mA.empty() || mA.rows != 2 || mA.cols != 3) + { + return mB.clone(); + } + if (mB.empty() || mB.rows != 2 || mB.cols != 3) + { + return mA.clone(); + } + cv::Mat hA = cv::Mat::eye(3, 3, CV_64F); + cv::Mat hB = cv::Mat::eye(3, 3, CV_64F); + mA.copyTo(hA(cv::Rect(0, 0, 3, 2))); + mB.copyTo(hB(cv::Rect(0, 0, 3, 2))); + cv::Mat hC = hA * hB; + return hC(cv::Rect(0, 0, 3, 2)).clone(); +} + ImgCheckAnalysisy::ImgCheckAnalysisy() : m_pAI_Edge_Algin(m_pdetlog) { @@ -492,32 +552,86 @@ int ImgCheckAnalysisy::CheckRun() long time_edge_e = CheckUtil::getcurTime(); m_pdetlog->AddCheckstr(PrintLevel_0, "2、pre detect", "-------------------------AI_Edge------------succ---time %ld----\n", time_edge_e - time_edge_s); - /*投影对齐模板*/ - // Adapt_Config(m_tplOuterRect, m_outer_rroi, m_CheckResult_shareP->in_shareImage->img); - m_outer_roi = m_outer_rroi.boundingRect(); - // 旋转矩形 boundingRect 在图像边缘可能越界(如 x=-1),裁剪到图像范围内,避免 image(roi) 越界崩溃 - m_outer_roi &= cv::Rect(0, 0, m_CheckResult_shareP->in_shareImage->img.cols, m_CheckResult_shareP->in_shareImage->img.rows); - m_Crop_Roi_paramImg = m_outer_roi; - m_pImageAllResult->pDetResult->CutRoi = m_outer_roi; - m_pImageAllResult->pDetResult->Param_CropRoi = m_Crop_Roi_paramImg; - long time_calan_e = CheckUtil::getcurTime(); - m_pdetlog->AddCheckstr(PrintLevel_0, "2.1、pre detect", "-------------------------Adapt_Config------------succ---time %ld----\n", time_calan_e - time_edge_s); + /*生成检测图:用 m_outer_rroi(旋转矩形)确定产品区域,并把该区域旋转摆正后输出; + 原图坐标 -> 检测图坐标 的映射存入 m_matSrc2Det / m_matDet2Src,后续坐标换算都走这对矩阵*/ + cv::Mat image = m_CheckResult_shareP->in_shareImage->img; + cv::Rect imgRect(0, 0, image.cols, image.rows); + + // 1、旋转矩形四角 -> 轴对齐矩形四角 的仿射(把产品摆正;用顶点计算,避开 RotatedRect 角度约定的歧义) + std::vector vtx = CheckUtil::sort_vertices(m_outer_rroi); // 近似 左上/右上/左下/右下 + int detW = std::max(1, cvRound(cv::norm(vtx[1] - vtx[0]))); + int detH = std::max(1, cvRound(cv::norm(vtx[2] - vtx[0]))); + cv::Point2f srcTri[3] = {vtx[0], vtx[1], vtx[2]}; + cv::Point2f dstTri[3] = {cv::Point2f(0, 0), cv::Point2f((float)detW, 0), cv::Point2f(0, (float)detH)}; + m_matSrc2Det = cv::getAffineTransform(srcTri, dstTri); + cv::invertAffineTransform(m_matSrc2Det, m_matDet2Src); + + // 2、只对旋转矩形附近区域做 warp,提高效率;先裁到该区域,warp 矩阵同步做平移补偿 + cv::Rect padRoi = m_outer_rroi.boundingRect() & imgRect; + if (padRoi.width <= 0 || padRoi.height <= 0) + { + m_pdetlog->AddCheckstr(PrintLevel_1, "Error", "deskew roi error [%d %d %d %d]", padRoi.x, padRoi.y, padRoi.width, padRoi.height); + m_nErrorCode = CHECK_ERROR_CheckImg_Empty; + m_nCheckResultErrorCode = m_nErrorCode; + return m_nErrorCode; + } + cv::Mat matPad = m_matSrc2Det.clone(); + matPad.at(0, 2) += matPad.at(0, 0) * padRoi.x + matPad.at(0, 1) * padRoi.y; + matPad.at(1, 2) += matPad.at(1, 0) * padRoi.x + matPad.at(1, 1) * padRoi.y; - /*生成 检测的图片(检测用图固定为单通道灰度;彩色原图按 BGR 顺序转灰度,与 imread(path,0) 的亮度一致)*/ - cv::Mat image; - image = m_CheckResult_shareP->in_shareImage->img; - if (image.channels() == 3) + // 3、检测图:单通道灰度(彩色原图按 BGR 顺序转灰度,与 imread(path,0) 的亮度一致),按摆正矩阵重采样; + // 彩色结果图在 ResizeImg 里按显示尺寸直接 warp(比全分辨率彩色 warp 省很多) + m_deskewPadRoi = padRoi; + cv::Mat colorPatch = image(padRoi); + cv::Mat grayPatch; + if (colorPatch.channels() == 1) { - cv::cvtColor(image(m_outer_roi), m_pImageAllResult->detImg, cv::COLOR_BGR2GRAY); + grayPatch = colorPatch; } - else if (image.channels() == 4) + else if (colorPatch.channels() == 3) { - cv::cvtColor(image(m_outer_roi), m_pImageAllResult->detImg, cv::COLOR_BGRA2GRAY); + cv::cvtColor(colorPatch, grayPatch, cv::COLOR_BGR2GRAY); } else { - m_pImageAllResult->detImg = image(m_outer_roi).clone(); + cv::cvtColor(colorPatch, grayPatch, cv::COLOR_BGRA2GRAY); } + cv::warpAffine(grayPatch, m_pImageAllResult->detImg, matPad, cv::Size(detW, detH), cv::INTER_LINEAR, cv::BORDER_REPLICATE); + + // 4、检测图坐标系下 m_outer_roi 即整张检测图;原图坐标系下 CutRoi 仍取旋转矩形外接区域(兼容旧语义/JSON) + m_outer_roi = cv::Rect(0, 0, detW, detH); + m_Crop_Roi_paramImg = m_outer_rroi.boundingRect() & imgRect; + m_pImageAllResult->pDetResult->CutRoi = m_Crop_Roi_paramImg; + m_pImageAllResult->pDetResult->Param_CropRoi = m_Crop_Roi_paramImg; + // 检测图坐标 -> 原图坐标 的映射,供结果解析时取回缺陷小图/换算坐标 + m_pImageAllResult->pDetResult->detToSrcMat = m_matDet2Src.clone(); + + // 5、参数(模板)坐标 -> 检测图坐标 的映射: + // 有对齐时先把参数点映射到原图再摆正;无对齐时参数按产品坐标系处理(与摆正后的检测图同向), + // 直接按旋转矩形中心平移(θ=0 时与旧的裁剪平移完全一致,旋转/平移变化时区域跟随产品) + { + cv::RotatedRect &rroi = m_outer_rroi; + if (m_pEdge_Align_Result && m_pEdge_Align_Result->buseOfft) + { + if (!m_pEdge_Align_Result->H.empty()) + { + m_matParam2Det = MatAffineCompose(m_matSrc2Det, m_pEdge_Align_Result->H); + } + else + { + cv::Mat matOfft = (cv::Mat_(2, 3) << 1, 0, (double)m_pEdge_Align_Result->offt_x, 0, 1, (double)m_pEdge_Align_Result->offt_y); + m_matParam2Det = MatAffineCompose(m_matSrc2Det, matOfft); + } + } + else + { + m_matParam2Det = (cv::Mat_(2, 3) << 1, 0, -rroi.center.x + detW * 0.5, 0, 1, -rroi.center.y + detH * 0.5); + } + } + long time_calan_e = CheckUtil::getcurTime(); + m_pdetlog->AddCheckstr(PrintLevel_0, "2.1、pre detect", "-------------------------deskew--------------succ--- det [%d %d] src bbox [%d %d %d %d] time %ld----\n", + detW, detH, m_Crop_Roi_paramImg.x, m_Crop_Roi_paramImg.y, m_Crop_Roi_paramImg.width, m_Crop_Roi_paramImg.height, + time_calan_e - time_edge_s); /*多线程开启 AI 推理检测*/ long time_AI_s = CheckUtil::getcurTime(); m_AItask = std::make_shared(); @@ -560,10 +674,8 @@ int ImgCheckAnalysisy::CheckRun() QX_ERROR_INFO_ temerror; temerror.Idx = m_pDetResult->pQx_ErrorList->size(); - // roi 在原图坐标系,转换为检测图(detImg)坐标系 - cv::Rect roi = inner.boundingRect(); - roi.x -= m_outer_roi.x; - roi.y -= m_outer_roi.y; + // roi 在原图坐标系,按摆正仿射转换到检测图(detImg)坐标系 + cv::Rect roi = MatAffineRect(m_matSrc2Det, inner.boundingRect()); roi &= cv::Rect(0, 0, m_pImageAllResult->detImg.cols, m_pImageAllResult->detImg.rows); temerror.roi = roi; temerror.area = roi.width * roi.height; @@ -623,10 +735,8 @@ int ImgCheckAnalysisy::CheckRun() continue; } - // 上报该把手区域的位置:原图坐标 -> 检测图(detImg)坐标 - cv::Rect roi = regionResult.boxRegion; - roi.x -= m_outer_roi.x; - roi.y -= m_outer_roi.y; + // 上报该把手区域的位置:参数(模板)坐标 -> 检测图(detImg)坐标 + cv::Rect roi = MatAffineRect(m_matParam2Det, regionResult.boxRegion); roi &= cv::Rect(0, 0, m_pImageAllResult->detImg.cols, m_pImageAllResult->detImg.rows); if (roi.width <= 0 || roi.height <= 0) { @@ -678,10 +788,8 @@ int ImgCheckAnalysisy::CheckRun() { for (const auto &tagRoi : m_tag_roiList) { - // tag roi 在原图坐标系,转换为检测图(detImg)坐标系 - cv::Rect roi = tagRoi.boundingRect(); - roi.x -= m_outer_roi.x; - roi.y -= m_outer_roi.y; + // tag roi 在原图坐标系,按摆正仿射转换到检测图(detImg)坐标系 + cv::Rect roi = MatAffineRect(m_matSrc2Det, tagRoi.boundingRect()); roi &= cv::Rect(0, 0, m_pImageAllResult->detImg.cols, m_pImageAllResult->detImg.rows); if (roi.width <= 0 || roi.height <= 0) { @@ -1786,21 +1894,32 @@ int ImgCheckAnalysisy::ResizeImg() float fw = RESIZE_IMAGE_WIDTH * 1.0f / m_pImageAllResult->detImg.cols; sz.height = int(m_pImageAllResult->detImg.rows * fw); - // 结果图画布取自"原图裁剪区",保持原图通道(彩色原图 -> 真实彩色,不再灰度转假彩); - // ReJson 复测模式下 in_shareImage->img 本身就是裁剪图,直接用检测图兜底 - cv::Mat srcCanvas; + // 结果图画布在显示尺寸上按摆正矩阵直接从原图重采样(保持原图通道 -> 真实彩色,不再灰度转假彩); + // 异常情况(如未走摆正流程)直接用检测图兜底 const cv::Mat &srcImg = m_CheckResult_shareP->in_shareImage->img; if (m_CheckResult_shareP->in_shareImage->Det_Mode != DET_MODE_ReJson && - !srcImg.empty() && CheckUtil::RoiInImg(m_outer_roi, srcImg)) - { - srcCanvas = srcImg(m_outer_roi); + !srcImg.empty() && !m_matSrc2Det.empty() && + m_deskewPadRoi.width > 0 && m_deskewPadRoi.height > 0 && + CheckUtil::RoiInImg(m_deskewPadRoi, srcImg)) + { + // M_disp = 显示缩放 ∘ M_src2det,并把输入平移到原图裁剪区(padRoi)的局部坐标 + cv::Mat matDisp = m_matSrc2Det.clone(); + double fsx = sz.width * 1.0 / m_pImageAllResult->detImg.cols; + double fsy = sz.height * 1.0 / m_pImageAllResult->detImg.rows; + matDisp.at(0, 0) *= fsx; + matDisp.at(0, 1) *= fsx; + matDisp.at(0, 2) *= fsx; + matDisp.at(1, 0) *= fsy; + matDisp.at(1, 1) *= fsy; + matDisp.at(1, 2) *= fsy; + matDisp.at(0, 2) += matDisp.at(0, 0) * m_deskewPadRoi.x + matDisp.at(0, 1) * m_deskewPadRoi.y; + matDisp.at(1, 2) += matDisp.at(1, 0) * m_deskewPadRoi.x + matDisp.at(1, 1) * m_deskewPadRoi.y; + cv::warpAffine(srcImg(m_deskewPadRoi), m_pImageAllResult->resultImg, matDisp, sz, cv::INTER_LINEAR, cv::BORDER_REPLICATE); } else { - srcCanvas = m_pImageAllResult->detImg; + cv::resize(m_pImageAllResult->detImg, m_pImageAllResult->resultImg, sz); } - - cv::resize(srcCanvas, m_pImageAllResult->resultImg, sz); m_CheckResult_shareP->resultimg = m_pImageAllResult->resultImg; m_pdetlog->AddCheckstr(PrintLevel_1, "showImg size", " [w %d,h %d]-> show img [w %d,h %d] ", m_outer_roi.width, m_outer_roi.height, sz.width, sz.height); @@ -1844,34 +1963,10 @@ int ImgCheckAnalysisy::Update_DetRoiList() float fx = m_pImageAllResult->fscale_detToresult_x; float fy = m_pImageAllResult->fscale_detToresult_y; - int num = 0; - cv::Rect offsetroi = m_Crop_Roi_paramImg; - - if (m_pEdge_Align_Result && m_pEdge_Align_Result->buseOfft) - { - for (const auto ®ion : m_pCommonAnalysisyConfig->regionConfigArr) - { - - if (m_pEdge_Align_Result->H.empty()) - { - - m_DetRoiList.Update_1(region.basicInfo.pointArry, offsetroi, m_pEdge_Align_Result->offt_x, m_pEdge_Align_Result->offt_y, fx, fy); - } - else - { - - m_DetRoiList.Update_Marit(region.basicInfo.pointArry, offsetroi, m_pEdge_Align_Result->H, fx, fy); - } - - // m_DetRoiList.Update_1(region.basicInfo.pointArry, offsetroi, m_pEdge_Align_Result->offt_x, m_pEdge_Align_Result->offt_y, fx, fy); - } - } - else + // 参数多边形 -> 检测图(摆正坐标系) 的映射在摆正时已算好(m_matParam2Det) + for (const auto ®ion : m_pCommonAnalysisyConfig->regionConfigArr) { - for (const auto ®ion : m_pCommonAnalysisyConfig->regionConfigArr) - { - m_DetRoiList.Update(region.basicInfo.pointArry, m_Crop_Roi_paramImg, fx, fy); - } + m_DetRoiList.Update_Mat(region.basicInfo.pointArry, m_matParam2Det, fx, fy); } m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "info", "roiList_Src roi num %ld", m_DetRoiList.roiList_Src.size()); @@ -1911,7 +2006,7 @@ int ImgCheckAnalysisy::DrawResult_Step_1() float fs_resize_y = m_pImageAllResult->resultImg.rows * 1.0f / m_pImageAllResult->detImg.rows; - // 绘制m_inner_rroi(蓝色) + // 绘制m_inner_rroi(蓝色),原图坐标 -> 检测图(摆正) -> 结果图 { cv::Point2f vertices[4]; m_inner_rroi.points(vertices); @@ -1919,8 +2014,9 @@ int ImgCheckAnalysisy::DrawResult_Step_1() draw_pts.reserve(4); for (int i = 0; i < 4; i++) { - draw_pts.emplace_back(cvRound((vertices[i].x - m_Crop_Roi_paramImg.x) * fs_resize_x), - cvRound((vertices[i].y - m_Crop_Roi_paramImg.y) * fs_resize_y)); + cv::Point2f detPt = MatAffinePoint(m_matSrc2Det, vertices[i]); + draw_pts.emplace_back(cvRound(detPt.x * fs_resize_x), + cvRound(detPt.y * fs_resize_y)); } cv::polylines(m_CheckResult_shareP->resultimg, draw_pts, true, CheckUtil::GetDrawColor(m_CheckResult_shareP->resultimg, cv::Scalar(255, 0, 0)), 1); @@ -1931,9 +2027,11 @@ int ImgCheckAnalysisy::DrawResult_Step_1() std::vector product_roi_show; for (const auto &pt : m_pbaseCheckFunction->markLine.region) { + // 参数(模板)坐标 -> 检测图(摆正) -> 结果图 + cv::Point2f detPt = MatAffinePoint(m_matParam2Det, cv::Point2f((float)pt.x, (float)pt.y)); cv::Point draw_pt; - draw_pt.x = (pt.x - m_Crop_Roi_paramImg.x) * fs_resize_x; - draw_pt.y = (pt.y - m_Crop_Roi_paramImg.y) * fs_resize_y; + draw_pt.x = cvRound(detPt.x * fs_resize_x); + draw_pt.y = cvRound(detPt.y * fs_resize_y); product_roi_show.push_back(draw_pt); } @@ -1953,9 +2051,11 @@ int ImgCheckAnalysisy::DrawResult_Step_1() { // if (m_pEdge_Align_Result->markresulList[i].status == Mark_Result_Status_OK) { + const cv::Point &markPt = m_pEdge_Align_Result->markresulList[i].det_Local_DetImg; + cv::Point2f detPt = MatAffinePoint(m_matSrc2Det, cv::Point2f((float)markPt.x, (float)markPt.y)); cv::Point p; - p.x = m_pEdge_Align_Result->markresulList[i].det_Local_DetImg.x * fs_resize_x; - p.y = m_pEdge_Align_Result->markresulList[i].det_Local_DetImg.y * fs_resize_y; + p.x = cvRound(detPt.x * fs_resize_x); + p.y = cvRound(detPt.y * fs_resize_y); cv::circle(m_CheckResult_shareP->resultimg, p, 5, CheckUtil::GetDrawColor(m_CheckResult_shareP->resultimg, cv::Scalar(0, 255, 0))); }