diff --git a/AlgorithmModule/include/CheckUtil.hpp b/AlgorithmModule/include/CheckUtil.hpp index c438d17..1ee0dc7 100644 --- a/AlgorithmModule/include/CheckUtil.hpp +++ b/AlgorithmModule/include/CheckUtil.hpp @@ -80,8 +80,18 @@ public: // 抠出掩膜四周的凸起:主体近似矩形,凸起在该边表现为边界值的一次突变(进入)与一次反向突变(离开) // binMask:掩膜(非零为前景,兼容 0/1 与 0/255);protrusions:输出的凸起列表 - // nJumpThresh:边界突变阈值(输入图像素);nMinRun:凸起最小长度(输入图像素);nMinDepth:凸起最小深度(输入图像素) - // nMaxRun:凸起最大长度(输入图像素),<=0 时取 掩膜短边/3 + // 参数调整影响(nJumpThresh/nMinRun/nMinDepth 为输入图像素,内部按降采样比例 nScale(1/2/4) 缩小后使用;nMaxRun 不换算,直接用工作图像素): + // nJumpThresh:边界突变阈值,相邻行/列边界值一次变化超过它才算"进入/离开"台阶; + // 调大:只认陡峭台阶,圆角/噪声/锯齿被过滤,过大时边缘圆滑的真把手会漏检; + // 调小:敏感,噪声/斜边易触发假配对(可能形成跨度很大的假凸起)。 + // nMinRun:凸起最小长度("离开"突变搜索从 s+nMinRun 开始;profile 比它短的边直接跳过); + // 调大:短凸起被排除,若大于真把手长度,真"离开"突变被跳过、会配到更远突变 → 出更大的框; + // 调小:小抖动/碎框变多。 + // nMinDepth:凸起最小深度,区间内 ≥80% 采样点相对首尾连线基线外偏 ≥ 它(且最大外偏 ≥ 它)才保留(滤斜边/圆角); + // 调大:只保留明显凸出的,浅凸起漏检,可挡"浅而长"的假配对; + // 调小:浅波纹/边缘抖动也过检,假凸起变多。 + // nMaxRun:凸起最大长度(框沿边长度的硬上限);<=0 时取 max(100, 掩膜短边/3)(工作图像素); + // 调大:允许远距离突变配对 → 会出"覆盖整边"的大框;调小:框被截短,小于真把手长度时真把手漏检。 // 返回:0 成功、1 掩膜为空、2 无有效前景 static int GetMaskProtrusions(const cv::Mat &binMask, std::vector &protrusions, int nJumpThresh = 40, int nMinRun = 40, int nMinDepth = 40, int nMaxRun = 0); diff --git a/AlgorithmModule/include/ImgCheckAnalysisy.hpp b/AlgorithmModule/include/ImgCheckAnalysisy.hpp index 4f0096a..f977569 100644 --- a/AlgorithmModule/include/ImgCheckAnalysisy.hpp +++ b/AlgorithmModule/include/ImgCheckAnalysisy.hpp @@ -79,6 +79,45 @@ struct Handle_Region_Result_ } }; +// 支架偏移检测的绘制信息(CheckRun 支架块计算 -> DrawResult_Step_1 绘制) +// 坐标均为原图坐标,绘制时经 m_matSrc2Det 映射到检测图后再缩放到结果图 +struct Support_Draw_Info_ +{ + // 参与判定的单个方向:实测/标准距离 + 是否超标准(超标准画红色) + struct Edge_Draw_ + { + std::string strEdge; // left/right/up/down + float fDist; // 实测距离(原图像素) + float fStandard; // 标准距离(原图像素) + bool bExceed; // |fDist-fStandard| 超限差(x_offset) -> 红色 + Edge_Draw_() + { + fDist = 0.0f; + fStandard = 0.0f; + bExceed = false; + } + }; + bool bValid; // 本次是否有效计算出支架偏移几何 + bool bOffset; // 是否判偏移 NG(十字颜色:红=NG) + cv::Rect rpRect; // 产品区域外接矩形(原图坐标) + cv::Rect rsRect; // 支架区域外接矩形(原图坐标) + cv::Point2f ptCenter; // 支架中心(原图坐标) + std::vector edgeList; // 各参与判定方向的绘制信息 + Support_Draw_Info_() + { + Reset(); + } + void Reset() + { + bValid = false; + bOffset = false; + rpRect = cv::Rect(0, 0, 0, 0); + rsRect = cv::Rect(0, 0, 0, 0); + ptCenter = cv::Point2f(0.0f, 0.0f); + edgeList.clear(); + } +}; + class ImgCheckAnalysisy : public ImgCheckBase { @@ -348,6 +387,8 @@ private: bool m_bHandleDetSucc; // 本次是否完成把手检测 bool m_bHandleFound; // 所有非空把手区域是否都找到了把手 bool m_bHandleExpect; // 是否开启了把手缺失检测(且有非空把手区域) + // 支架偏移检测的绘制信息(CheckRun 支架块填充,DrawResult_Step_1 绘制) + Support_Draw_Info_ m_SupportDrawInfo; }; #endif \ No newline at end of file diff --git a/AlgorithmModule/src/ImageResultJudge.cpp b/AlgorithmModule/src/ImageResultJudge.cpp index 6c01787..c1b0d27 100644 --- a/AlgorithmModule/src/ImageResultJudge.cpp +++ b/AlgorithmModule/src/ImageResultJudge.cpp @@ -892,32 +892,9 @@ int ImageResultJudge::DrawResult(std::shared_ptr pImageResult) draw_str->txtP = cv::Point(draw_qx->cp.x + r, draw_qx->cp.y - r); draw_str->txtsize = 0.5; draw_str->txtbackgroundk = txtbackgroundk; - cv::Rect srcroi = pqx->srcImgroi; - cv::Point srccp = cv::Point(srcroi.x + srcroi.width * .5, srcroi.y + srcroi.height * 0.5); - - char buffer[128]; - { - std::string text = std::to_string(pqx->idx) + ":" + pqx->strTypeName; - draw_str->strlist.push_back(text); - } - { - sprintf(buffer, " center:%d %d", srccp.x, srccp.y); - std::string text = buffer; - draw_str->strlist.push_back(text); - } - { - sprintf(buffer, " len %0.2f A: %0.2f ", pqx->len, pqx->area); - std::string text = buffer; - draw_str->strlist.push_back(text); - } - { - sprintf(buffer, " breadth %0.2f A: %0.2f ", pqx->breadth, pqx->area); - std::string text = buffer; - draw_str->strlist.push_back(text); - } { - sprintf(buffer, " E:%0.2f hj %0.2f", pqx->energy, pqx->hj); - std::string text = buffer; + // 只绘制 id:缺陷code (缺陷名可能中文乱码, 用code替代) + std::string text = std::to_string(pqx->idx) + ":" + pqx->qx_Code; draw_str->strlist.push_back(text); } sendTask(draw_str); diff --git a/AlgorithmModule/src/ImgCheckAnalysisy.cpp b/AlgorithmModule/src/ImgCheckAnalysisy.cpp index 23cf0d0..57de6e6 100644 --- a/AlgorithmModule/src/ImgCheckAnalysisy.cpp +++ b/AlgorithmModule/src/ImgCheckAnalysisy.cpp @@ -891,6 +891,7 @@ int ImgCheckAnalysisy::CheckRun() 4、计算值与标准值(x_standard/y_standard)做差取平均,平均差超过 offset(x_offset) → 偏移 */ { Function_Support_Det &supportDet = m_pbaseCheckFunction->supportDet; + m_SupportDrawInfo.Reset(); // 清空本帧绘制信息(未开启/计算无效时不绘制) if (supportDet.bDetSupport) { const cv::Mat &bigmask = m_pEdge_Align_Result->bigmask; @@ -1008,6 +1009,15 @@ int ImgCheckAnalysisy::CheckRun() float fDiff = fabs(fDist - fStandard); fSumDiff += fDiff; nDiffNum++; + // 记录该方向绘制信息:|实测-标准|超限差(x_offset)则绘制为红色 + { + Support_Draw_Info_::Edge_Draw_ edgeDraw; + edgeDraw.strEdge = strEdge; + edgeDraw.fDist = fDist; + edgeDraw.fStandard = fStandard; + edgeDraw.bExceed = (fDiff > supportDet.x_offset); + m_SupportDrawInfo.edgeList.push_back(edgeDraw); + } sprintf(strTmp, "%s(%.1f/%.1f) ", strEdge.c_str(), fDist, fStandard); strSelect += strTmp; sprintf(strTmp, "%.1f ", fDiff); @@ -1017,6 +1027,13 @@ int ImgCheckAnalysisy::CheckRun() float fAvgDiff = (nDiffNum > 0) ? (fSumDiff / nDiffNum) : 0.0f; bool bOffset = (nDiffNum > 0) && (fAvgDiff > supportDet.x_offset); + // 记录绘制信息(DrawResult_Step_1 用):支架中心十字 + 各方向距离 + m_SupportDrawInfo.bValid = true; + m_SupportDrawInfo.bOffset = bOffset; + m_SupportDrawInfo.rpRect = rpRect; + m_SupportDrawInfo.rsRect = rsRect; + 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", rpRect.x, rpRect.y, rpRect.width, rpRect.height, @@ -2343,21 +2360,7 @@ int ImgCheckAnalysisy::DrawResult_Step_1() float fs_resize_y = m_pImageAllResult->resultImg.rows * 1.0f / m_pImageAllResult->detImg.rows; - // 绘制m_inner_rroi(蓝色),原图坐标 -> 检测图(裁剪) -> 结果图 - { - cv::Point2f vertices[4]; - m_inner_rroi.points(vertices); - std::vector draw_pts; - draw_pts.reserve(4); - for (int i = 0; i < 4; i++) - { - 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); - } + // 注:不再绘制 inner 定位蓝色框(m_inner_rroi 为大轮廓裁剪子图局部坐标,直接映射会缺裁剪原点偏移) if (m_pbaseCheckFunction && m_pbaseCheckFunction->markLine.region.size() > 1) { @@ -2400,6 +2403,63 @@ int ImgCheckAnalysisy::DrawResult_Step_1() } } + // 支架偏移检测绘制:支架中心十字(绿=正常/红=偏移NG)+ 支架到边缘的距离(绿=正常/红=超标准) + if (m_SupportDrawInfo.bValid && !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)); + + // 支架中心十字:NG 红色 / 正常绿色 + 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); + + // 支架到边缘的距离:中心 -> 产品区域对应边缘连线 + 实测/标准(像素)文字 + 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::Scalar colorEdge = edgeDraw.bExceed ? cv::Scalar(0, 0, 255) : cv::Scalar(0, 255, 0); + colorEdge = CheckUtil::GetDrawColor(m_CheckResult_shareP->resultimg, colorEdge); + cv::line(m_CheckResult_shareP->resultimg, pCenter, pEdge, colorEdge, 2); + + // 距离文字:实测/标准(原图像素) + 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), + cv::FONT_HERSHEY_SIMPLEX, 0.8, colorEdge, 2); + } + } + // cv::imwrite("sss.png", m_CheckResult_shareP->resultimg); return 0; }