diff --git a/AlgorithmModule/include/ImgCheckAnalysisy.hpp b/AlgorithmModule/include/ImgCheckAnalysisy.hpp index 623a223..07359a5 100644 --- a/AlgorithmModule/include/ImgCheckAnalysisy.hpp +++ b/AlgorithmModule/include/ImgCheckAnalysisy.hpp @@ -77,11 +77,17 @@ struct Support_Draw_Info_ float fDist; // 实测距离(原图像素) float fStandard; // 标准距离(原图像素) bool bExceed; // |fDist-fStandard| 超限差(x_offset) -> 红色 + cv::Point2f ptEdge; // 连线端点:所计算边缘上、支架中心行/列处的点(原图坐标) + cv::Point2f ptEdgeA; // 所计算边缘线段端点A(原图坐标) + cv::Point2f ptEdgeB; // 所计算边缘线段端点B(原图坐标) Edge_Draw_() { fDist = 0.0f; fStandard = 0.0f; bExceed = false; + ptEdge = cv::Point2f(0.0f, 0.0f); + ptEdgeA = cv::Point2f(0.0f, 0.0f); + ptEdgeB = cv::Point2f(0.0f, 0.0f); } }; bool bValid; // 本次是否有效计算出支架偏移几何 @@ -377,6 +383,7 @@ private: // 把手检测结果(从 outer 定位 mask 分离的凸起区域 + 各把手区域判定结果) std::vector m_HandleProtrusionList; // 分离出的凸起区域(把手候选:roi/质心/深度/就近角等) std::vector m_HandleRegionResultList; // 各把手区域(最多4个)的判定结果 + std::vector m_HandleBodyQuad; // 拟合出的主体四边形(TL/TR/BR/BL,原图坐标;支架偏移检测复用其四边抗凸起) bool m_bHandleDetSucc; // 本次是否完成把手检测 bool m_bHandleFound; // 所有非空把手区域是否都找到了把手 bool m_bHandleExpect; // 是否开启了把手缺失检测(且有非空把手区域) diff --git a/AlgorithmModule/src/ImgCheckAnalysisy.cpp b/AlgorithmModule/src/ImgCheckAnalysisy.cpp index 76af4f5..fe64869 100644 --- a/AlgorithmModule/src/ImgCheckAnalysisy.cpp +++ b/AlgorithmModule/src/ImgCheckAnalysisy.cpp @@ -1070,9 +1070,11 @@ int ImgCheckAnalysisy::CheckRun() /* 检测支架偏移(bDetSupport 开关关闭时不做支架判定) 不使用参数绘制区域,结合两次定位的 mask 计算: - 1、bigmask 最大连通域 = 产品区域 rp(近似矩形,四周可能有凸起) + 1、产品边缘:优先复用把手提取对 bigmask 做的"四边抗凸起拟合"主体四边形 + (CheckHandleByBigMask -> GetMaskProtrusions;边缘上的凸起(把手等)不会把边缘带偏), + 拟合不可用/无效时回退 bigmask 最大连通域外接矩形 rp(近似矩形,四周可能有凸起) 2、smallmask 最大连通域 = 支架区域 rs(按定位裁剪区 m_outer_rroi 外接矩形映射回原图坐标) - 3、按 edge_select 指定方向(left/right/up/down)计算 rs 中心到 rp 对应边缘的距离 + 3、按 edge_select 指定方向(left/right/up/down)计算 rs 中心到产品对应边缘的距离 4、计算值与标准值(x_standard/y_standard)做差取平均,平均差超过 offset(x_offset) → 偏移 */ { Function_Support_Det &supportDet = m_pbaseCheckFunction->supportDet; @@ -1132,7 +1134,7 @@ int ImgCheckAnalysisy::CheckRun() } else { - // rp 边缘:产品最大连通域的外接矩形(含凸起) + // rp 边缘(拟合不可用时的回退):产品最大连通域的外接矩形(含凸起) cv::Rect rpRect = cv::boundingRect(rpContour); // rs 映射回原图坐标:smallmask 对应定位时裁剪的大轮廓区域(m_outer_rroi 外接矩形),按比例映射 @@ -1146,18 +1148,87 @@ int ImgCheckAnalysisy::CheckRun() cvRound(rsSmallRect.width * fx), cvRound(rsSmallRect.height * fy)); - // 3、rs 中心到 rp 上下左右边缘的距离 + // 3、产品边缘:优先复用把手提取对 bigmask 做的"四边抗凸起拟合"主体四边形 + // (CheckHandleByBigMask -> GetMaskProtrusions 输出):边缘上的凸起(把手等)不会把边缘带偏; + // 拟合不可用/无效时回退到产品最大连通域外接矩形 rpRect(含凸起) + bool bFitQuad = false; // 是否用上了拟合主体四边形 + float fDL = 0, fDR = 0, fDT = 0, fDB = 0; // 支架中心到产品左/右/上/下边缘的距离(原图像素,后乘缩放系数) + cv::Point2f ptEdgeArr[4]; // 连线端点(边缘线上):0 left 1 right 2 up 3 down + cv::Point2f ptSegAArr[4]; // 边缘线段端点A + cv::Point2f ptSegBArr[4]; // 边缘线段端点B + float fCx = rsRect.x + rsRect.width * 0.5f; float fCy = rsRect.y + rsRect.height * 0.5f; - float fDL = fCx - rpRect.x; // 到左边缘 - float fDR = (rpRect.x + rpRect.width) - fCx; // 到右边缘 - float fDT = fCy - rpRect.y; // 到上边缘 - float fDB = (rpRect.y + rpRect.height) - fCy; // 到下边缘 + if (m_HandleBodyQuad.size() == 4) + { + // 四边形角点顺序:左上/右上/右下/左下;左/右边 x=k*y+b、上/下边 y=k*x+b(与 GetMaskProtrusions 约定一致); + // 边长过小说明拟合退化,不用 + const cv::Point2f &ptTL = m_HandleBodyQuad[0]; + const cv::Point2f &ptTR = m_HandleBodyQuad[1]; + const cv::Point2f &ptBR = m_HandleBodyQuad[2]; + const cv::Point2f &ptBL = m_HandleBodyQuad[3]; + if (fabs(ptBL.y - ptTL.y) > 2.0f && fabs(ptBR.y - ptTR.y) > 2.0f && + fabs(ptTR.x - ptTL.x) > 2.0f && fabs(ptBR.x - ptBL.x) > 2.0f) + { + float fKL = (ptBL.x - ptTL.x) / (ptBL.y - ptTL.y); // 左边 x = k*y + b + float fBL = ptTL.x - fKL * ptTL.y; + float fKR = (ptBR.x - ptTR.x) / (ptBR.y - ptTR.y); // 右边 x = k*y + b + float fBR = ptTR.x - fKR * ptTR.y; + float fKT = (ptTR.y - ptTL.y) / (ptTR.x - ptTL.x); // 上边 y = k*x + b + float fBT = ptTL.y - fKT * ptTL.x; + float fKB = (ptBR.y - ptBL.y) / (ptBR.x - ptBL.x); // 下边 y = k*x + b + float fBB = ptBL.y - fKB * ptBL.x; + // 连线端点:取边缘线在支架中心行/列上的点;长度即中心到边缘的水平/垂直距离 + float fXEdgeL = fKL * fCy + fBL; + float fXEdgeR = fKR * fCy + fBR; + float fYEdgeT = fKT * fCx + fBT; + float fYEdgeB = fKB * fCx + fBB; + fDL = fCx - fXEdgeL; + fDR = fXEdgeR - fCx; + fDT = fCy - fYEdgeT; + fDB = fYEdgeB - fCy; + // 拟合有效性:支架中心应位于四条拟合边内侧(四个方向距离均为正),否则放弃拟合 + if (fDL > 0 && fDR > 0 && fDT > 0 && fDB > 0) + { + ptEdgeArr[0] = cv::Point2f(fXEdgeL, fCy); + ptEdgeArr[1] = cv::Point2f(fXEdgeR, fCy); + ptEdgeArr[2] = cv::Point2f(fCx, fYEdgeT); + ptEdgeArr[3] = cv::Point2f(fCx, fYEdgeB); + ptSegAArr[0] = ptTL; ptSegBArr[0] = ptBL; // left:整条拟合左边 + ptSegAArr[1] = ptTR; ptSegBArr[1] = ptBR; // right:整条拟合右边 + ptSegAArr[2] = ptTL; ptSegBArr[2] = ptTR; // up:整条拟合上边 + ptSegAArr[3] = ptBL; ptSegBArr[3] = ptBR; // down:整条拟合下边 + bFitQuad = true; + } + } + } + if (!bFitQuad) + { + // 回退:产品区域外接矩形(含凸起)的四条边 + fDL = fCx - rpRect.x; + fDR = (rpRect.x + rpRect.width) - fCx; + fDT = fCy - rpRect.y; + fDB = (rpRect.y + rpRect.height) - fCy; + ptEdgeArr[0] = cv::Point2f((float)rpRect.x, fCy); + ptEdgeArr[1] = cv::Point2f((float)(rpRect.x + rpRect.width), fCy); + ptEdgeArr[2] = cv::Point2f(fCx, (float)rpRect.y); + ptEdgeArr[3] = cv::Point2f(fCx, (float)(rpRect.y + rpRect.height)); + ptSegAArr[0] = cv::Point2f((float)rpRect.x, (float)rpRect.y); + ptSegBArr[0] = cv::Point2f((float)rpRect.x, (float)(rpRect.y + rpRect.height)); + ptSegAArr[1] = cv::Point2f((float)(rpRect.x + rpRect.width), (float)rpRect.y); + ptSegBArr[1] = cv::Point2f((float)(rpRect.x + rpRect.width), (float)(rpRect.y + rpRect.height)); + ptSegAArr[2] = cv::Point2f((float)rpRect.x, (float)rpRect.y); + ptSegBArr[2] = cv::Point2f((float)(rpRect.x + rpRect.width), (float)rpRect.y); + ptSegAArr[3] = cv::Point2f((float)rpRect.x, (float)(rpRect.y + rpRect.height)); + ptSegBArr[3] = cv::Point2f((float)(rpRect.x + rpRect.width), (float)(rpRect.y + rpRect.height)); + } + // 像素 -> mm fDL *= m_fImgage_Scale_X; fDR *= m_fImgage_Scale_X; fDT *= m_fImgage_Scale_Y; fDB *= m_fImgage_Scale_Y; + float fDistArr[4] = {fDL, fDR, fDT, fDB}; // 顺序固定:0 left 1 right 2 up 3 down // 4、按 edge_select 指定方向(left/right/up/down)取计算值,与标准值做差取平均,平均差超过 offset → 偏移 // left/right 用 x_standard,up/down 用 y_standard;offset 用 x_offset @@ -1169,26 +1240,26 @@ int ImgCheckAnalysisy::CheckRun() for (size_t iselect = 0; iselect < supportDet.edge_select.size(); iselect++) { const std::string &strEdge = supportDet.edge_select[iselect]; - float fDist = 0; + int gi = -1; // 方向索引:0 left 1 right 2 up 3 down float fStandard = 0; if (strEdge == "left") { - fDist = fDL; + gi = 0; fStandard = supportDet.x_standard; } else if (strEdge == "right") { - fDist = fDR; + gi = 1; fStandard = supportDet.x_standard; } else if (strEdge == "up") { - fDist = fDT; + gi = 2; fStandard = supportDet.y_standard; } else if (strEdge == "down") { - fDist = fDB; + gi = 3; fStandard = supportDet.y_standard; } else @@ -1196,6 +1267,7 @@ int ImgCheckAnalysisy::CheckRun() continue; } + float fDist = fDistArr[gi]; float fDiff = fabs(fDist - fStandard); fSumDiff += fDiff; nDiffNum++; @@ -1206,6 +1278,9 @@ int ImgCheckAnalysisy::CheckRun() edgeDraw.fDist = fDist; edgeDraw.fStandard = fStandard; edgeDraw.bExceed = (fDiff > supportDet.x_offset); + edgeDraw.ptEdge = ptEdgeArr[gi]; + edgeDraw.ptEdgeA = ptSegAArr[gi]; + edgeDraw.ptEdgeB = ptSegBArr[gi]; m_SupportDrawInfo.edgeList.push_back(edgeDraw); } sprintf(strTmp, "%s(%.1f/%.1f) ", strEdge.c_str(), fDist, fStandard); @@ -1225,10 +1300,10 @@ int ImgCheckAnalysisy::CheckRun() m_SupportDrawInfo.ptCenter = cv::Point2f(fCx, fCy); m_pdetlog->AddCheckstr(PrintLevel_0, "支架检测", - "rp [%d %d %d %d] rs [%d %d %d %d] scale (%.4f %.4f) center (%.1f %.1f) dL %.1f dR %.1f dT %.1f dB %.1f | select %s diff %s avgDiff %.1f limit %.1f ng %d", + "rp [%d %d %d %d] rs [%d %d %d %d] scale (%.4f %.4f) center (%.1f %.1f) edge %s quad %ld dL %.1f dR %.1f dT %.1f dB %.1f | select %s diff %s avgDiff %.1f limit %.1f ng %d", rpRect.x, rpRect.y, rpRect.width, rpRect.height, rsRect.x, rsRect.y, rsRect.width, rsRect.height, - fx, fy, fCx, fCy, fDL, fDR, fDT, fDB, + fx, fy, fCx, fCy, bFitQuad ? "fitquad" : "rpbox", (long)m_HandleBodyQuad.size(), fDL, fDR, fDT, fDB, strSelect.c_str(), strDiff.c_str(), fAvgDiff, supportDet.x_offset, bOffset); if (bOffset) @@ -2618,56 +2693,47 @@ int ImgCheckAnalysisy::DrawResult_Step_1() } } - // 支架偏移检测绘制(受参数 bDrawSupport 控制,默认关闭):支架中心十字(绿=正常/红=偏移NG)+ 支架到边缘的距离(绿=正常/红=超标准) + // 支架偏移检测绘制(受参数 bDrawSupport 控制,默认关闭): + // 支架中心十字(绿=正常/红=偏移NG)+ 各方向"所计算的产品边缘"线段(抗凸起拟合直边,见 CheckRun 支架块) + // + 支架中心到该边缘的连线(连线长度=支架中心到所计算边缘的距离;绿=正常/红=超标准)+ 实测/标准(mm)文字 if (m_SupportDrawInfo.bValid && m_pbaseCheckFunction->supportDet.bDrawSupport && !m_CheckResult_shareP->resultimg.empty()) { - // 支架中心:原图坐标 -> 检测图(裁剪) -> 结果图 - cv::Point2f detPt = MatAffinePoint(m_matSrc2Det, m_SupportDrawInfo.ptCenter); - cv::Point pCenter(cvRound(detPt.x * fs_resize_x), cvRound(detPt.y * fs_resize_y)); + // 原图坐标 -> 检测图(裁剪) -> 结果图 + auto srcToResult = [&](const cv::Point2f &pt) -> cv::Point { + cv::Point2f detPt = MatAffinePoint(m_matSrc2Det, pt); + return cv::Point(cvRound(detPt.x * fs_resize_x), cvRound(detPt.y * fs_resize_y)); + }; // 支架中心十字:NG 红色 / 正常绿色 + cv::Point pCenter = srcToResult(m_SupportDrawInfo.ptCenter); cv::Scalar colorCross = m_SupportDrawInfo.bOffset ? cv::Scalar(0, 0, 255) : cv::Scalar(0, 255, 0); colorCross = CheckUtil::GetDrawColor(m_CheckResult_shareP->resultimg, colorCross); const int nCrossLen = 40; // 结果图上十字半长(px) cv::line(m_CheckResult_shareP->resultimg, cv::Point(pCenter.x - nCrossLen, pCenter.y), cv::Point(pCenter.x + nCrossLen, pCenter.y), colorCross, 2); cv::line(m_CheckResult_shareP->resultimg, cv::Point(pCenter.x, pCenter.y - nCrossLen), cv::Point(pCenter.x, pCenter.y + nCrossLen), colorCross, 2); - // 支架到边缘的距离:中心 -> 产品区域对应边缘连线 + 实测/标准(像素)文字 + // 各方向:所计算边缘线段(抗凸起拟合直边/回退外接矩形边) + 中心到边缘点的连线(长度=点到所计算边缘) + 实测/标准文字 + // 几何(边缘点/线段两端点)由 CheckRun 支架块算好存入 edgeDraw,此处只做坐标映射与绘制 char strTxt[64]; for (size_t i = 0; i < m_SupportDrawInfo.edgeList.size(); i++) { const Support_Draw_Info_::Edge_Draw_ &edgeDraw = m_SupportDrawInfo.edgeList[i]; - cv::Point2f ptEdge = m_SupportDrawInfo.ptCenter; - if (edgeDraw.strEdge == "left") - { - ptEdge.x = (float)m_SupportDrawInfo.rpRect.x; - } - else if (edgeDraw.strEdge == "right") - { - ptEdge.x = (float)(m_SupportDrawInfo.rpRect.x + m_SupportDrawInfo.rpRect.width); - } - else if (edgeDraw.strEdge == "up") - { - ptEdge.y = (float)m_SupportDrawInfo.rpRect.y; - } - else if (edgeDraw.strEdge == "down") - { - ptEdge.y = (float)(m_SupportDrawInfo.rpRect.y + m_SupportDrawInfo.rpRect.height); - } - else - { - continue; - } - cv::Point2f detEdge = MatAffinePoint(m_matSrc2Det, ptEdge); - cv::Point pEdge(cvRound(detEdge.x * fs_resize_x), cvRound(detEdge.y * fs_resize_y)); + cv::Point pEdge = srcToResult(edgeDraw.ptEdge); + cv::Point pEdgeA = srcToResult(edgeDraw.ptEdgeA); + cv::Point pEdgeB = srcToResult(edgeDraw.ptEdgeB); // 超标准红色 / 正常绿色 cv::Scalar colorEdge = edgeDraw.bExceed ? cv::Scalar(0, 0, 255) : cv::Scalar(0, 255, 0); colorEdge = CheckUtil::GetDrawColor(m_CheckResult_shareP->resultimg, colorEdge); + + // 1、所计算边缘线段:标出参与计算的产品边缘(细线;抗凸起拟合直边/回退边) + cv::line(m_CheckResult_shareP->resultimg, pEdgeA, pEdgeB, colorEdge, 1); + + // 2、中心 -> 边缘点连线:长度即支架中心到所计算边缘的距离 cv::line(m_CheckResult_shareP->resultimg, pCenter, pEdge, colorEdge, 2); - // 距离文字:实测/标准(原图像素) + // 3、距离文字:实测/标准(mm) snprintf(strTxt, sizeof(strTxt), "%s %.0f/%.0f", edgeDraw.strEdge.c_str(), edgeDraw.fDist, edgeDraw.fStandard); cv::putText(m_CheckResult_shareP->resultimg, strTxt, cv::Point((pCenter.x + pEdge.x) / 2 + 6, (pCenter.y + pEdge.y) / 2 - 6), @@ -2864,6 +2930,7 @@ int ImgCheckAnalysisy::AI_Edge(const cv::Mat &img, cv::RotatedRect &outerRoi, cv // 3、凸起本身不算 NG,只有"某个把手区域附近找不到匹配的凸起"才算把手缺失(NG 由 CheckRun 上报) // 4、-s(存过程图)时输出 {通道}_handle_det.jpg:绿色四边形=拟合主体、红框=凸起(带编号/角/深度)、 // 蓝框=参数把手区域、绿框=匹配到的把手,便于从图像观察问题 +// 5、拟合出的主体四边形存 m_HandleBodyQuad:支架偏移检测复用其四边(抗凸起)做测距与绘制 int ImgCheckAnalysisy::CheckHandleByBigMask(const cv::Mat &bigmask) { m_bHandleDetSucc = false; @@ -2871,6 +2938,7 @@ int ImgCheckAnalysisy::CheckHandleByBigMask(const cv::Mat &bigmask) m_bHandleExpect = false; m_HandleProtrusionList.clear(); m_HandleRegionResultList.clear(); + m_HandleBodyQuad.clear(); if (bigmask.empty()) { @@ -2910,8 +2978,8 @@ int ImgCheckAnalysisy::CheckHandleByBigMask(const cv::Mat &bigmask) nEdgeMargin = (int)(fEdgeMarginCoef * nStep + 0.5f); } } - std::vector vecBodyQuad; // 拟合出的主体四边形(仅 -s 存图观察用) - int re = CheckUtil::GetMaskProtrusions(bigmask, m_HandleProtrusionList, nMinDepth, nMinArea, &vecBodyQuad, nEdgeMargin); + // m_HandleBodyQuad:拟合出的主体四边形(左上/右上/右下/左下);-s 存图观察用,支架偏移检测复用其四边做抗凸起测距/绘制 + int re = CheckUtil::GetMaskProtrusions(bigmask, m_HandleProtrusionList, nMinDepth, nMinArea, &m_HandleBodyQuad, nEdgeMargin); if (re != 0) { m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Handle", "GetMaskProtrusions error %d", re); @@ -2992,12 +3060,12 @@ int ImgCheckAnalysisy::CheckHandleByBigMask(const cv::Mat &bigmask) show = bigmask.clone(); } // 主体四边形(拟合出的四条边缘) - if (vecBodyQuad.size() == 4) + if (m_HandleBodyQuad.size() == 4) { std::vector vecQuad; - for (size_t i = 0; i < vecBodyQuad.size(); i++) + for (size_t i = 0; i < m_HandleBodyQuad.size(); i++) { - vecQuad.push_back(cv::Point(cvRound(vecBodyQuad[i].x), cvRound(vecBodyQuad[i].y))); + vecQuad.push_back(cv::Point(cvRound(m_HandleBodyQuad[i].x), cvRound(m_HandleBodyQuad[i].y))); } cv::polylines(show, vecQuad, true, cv::Scalar(0, 255, 0), 8); }