From a24995500bb9360bd43635d107abc894866203f5 Mon Sep 17 00:00:00 2001 From: liusiyang Date: Sun, 18 Oct 2026 11:43:46 +0800 Subject: [PATCH] =?UTF-8?q?feat=20=E6=94=AF=E6=9E=B6=E7=BC=BA=E5=A4=B1?= =?UTF-8?q?=EF=BC=8C=E5=81=8F=E7=A7=BB=E6=97=8B=E8=BD=AC=E8=A7=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AlgorithmModule/src/AI_Edge_Algin.cpp | 20 ++- AlgorithmModule/src/ImgCheckAnalysisy.cpp | 176 +++++++++++++++++++--- ConfigModule/include/CheckConfigDefine.h | 13 +- ConfigModule/src/JsonConfig.cpp | 6 + 4 files changed, 185 insertions(+), 30 deletions(-) diff --git a/AlgorithmModule/src/AI_Edge_Algin.cpp b/AlgorithmModule/src/AI_Edge_Algin.cpp index 7812408..4536f15 100644 --- a/AlgorithmModule/src/AI_Edge_Algin.cpp +++ b/AlgorithmModule/src/AI_Edge_Algin.cpp @@ -273,12 +273,24 @@ int AI_Edge_Algin::Detect(const cv::Mat &img, DetConfig *pDetConfig, std::shared } if (re1 != 0) { - m_pdetlog->AddCheckstr(PrintLevel_2, DET_LOG_LEVEL_3, "AI_Edge_Algin ", "AICheck_Edge_Small----error %d ", re1); - if (m_pDetConfig->IsSaveProcessImg()) + // Align_Inner(支架)未检出有效轮廓(4=无轮廓 / 6=轮廓点太少): + // 支架检测开启时不视为定位失败,继续带 smallmask(可能为空/全零) 返回, + // 由后续"支架缺失"判定(support_min_area)上报 ZJ_QueShi NG + bool bSupportDetOn = (pDetConfig->pBaseCheckFunction != NULL && pDetConfig->pBaseCheckFunction->supportDet.bDetSupport); + if (bSupportDetOn && (re1 == 4 || re1 == 6)) { - cv::imwrite(str_error, img); + m_pdetlog->AddCheckstr(PrintLevel_2, DET_LOG_LEVEL_3, "AI_Edge_Algin ", "AICheck_Edge_Small no contour(%d), treat as support missing ", re1); + re1 = 0; + } + else + { + m_pdetlog->AddCheckstr(PrintLevel_2, DET_LOG_LEVEL_3, "AI_Edge_Algin ", "AICheck_Edge_Small----error %d ", re1); + if (m_pDetConfig->IsSaveProcessImg()) + { + cv::imwrite(str_error, img); + } + return re1; } - return re1; } if (re2 != 0) { diff --git a/AlgorithmModule/src/ImgCheckAnalysisy.cpp b/AlgorithmModule/src/ImgCheckAnalysisy.cpp index fe64869..c2bebe5 100644 --- a/AlgorithmModule/src/ImgCheckAnalysisy.cpp +++ b/AlgorithmModule/src/ImgCheckAnalysisy.cpp @@ -1068,14 +1068,17 @@ int ImgCheckAnalysisy::CheckRun() m_pdetlog->AddCheckstr(PrintLevel_0, "3、pre detect", "-------------------------pre Det--------------- \n"); - /* 检测支架偏移(bDetSupport 开关关闭时不做支架判定) + /* 检测支架缺失 + 支架偏移(bDetSupport 开关关闭时不做支架判定) 不使用参数绘制区域,结合两次定位的 mask 计算: - 1、产品边缘:优先复用把手提取对 bigmask 做的"四边抗凸起拟合"主体四边形 + 1、smallmask(=Align_Inner 模型输出)最大连通域 = 支架区域 rs(按定位裁剪区 m_outer_rroi 外接矩形映射回原图坐标) + 2、支架缺失:支架面积(轮廓面积 × 缩放 × 成像精度 X/Y,单位 mm²) < support_min_area → 支架缺失 NG(ZJ_QueShi), + 缺失时不再做偏移判定(几何不可靠,避免重复/误报) + 3、产品边缘:优先复用把手提取对 bigmask 做的"四边抗凸起拟合"主体四边形 (CheckHandleByBigMask -> GetMaskProtrusions;边缘上的凸起(把手等)不会把边缘带偏), 拟合不可用/无效时回退 bigmask 最大连通域外接矩形 rp(近似矩形,四周可能有凸起) - 2、smallmask 最大连通域 = 支架区域 rs(按定位裁剪区 m_outer_rroi 外接矩形映射回原图坐标) - 3、按 edge_select 指定方向(left/right/up/down)计算 rs 中心到产品对应边缘的距离 - 4、计算值与标准值(x_standard/y_standard)做差取平均,平均差超过 offset(x_offset) → 偏移 */ + 4、按 edge_select 指定方向(left/right/up/down)计算 rs 中心到产品对应边缘的距离 + 5、计算值与标准值(x_standard/y_standard)做差取平均,平均差超过 offset(x_offset) → 偏移 + 6、旋转偏移:支架最小外接旋转矩形的长轴(对应左/右边缘)与产品左/右边缘夹角超过 r_offset(度) → 偏移 */ { Function_Support_Det &supportDet = m_pbaseCheckFunction->supportDet; m_SupportDrawInfo.Reset(); // 清空本帧绘制信息(未开启/计算无效时不绘制) @@ -1127,7 +1130,81 @@ int ImgCheckAnalysisy::CheckRun() std::vector rpContour = CheckUtil::getLargestContourROI(bigBin, bFoundRp); bool bFoundRs = false; std::vector rsContour = CheckUtil::getLargestContourROI(smallBin, bFoundRs); - if (!bFoundRp || rpContour.size() < 3 || !bFoundRs || rsContour.size() < 3) + + // smallmask -> 原图坐标的比例:smallmask 对应定位时裁剪的大轮廓区域(m_outer_rroi 外接矩形),按比例映射 + cv::Rect bigRect = m_outer_rroi.boundingRect(); + float fx = smallmask.cols > 0 ? bigRect.width * 1.0f / smallmask.cols : 1.0f; + float fy = smallmask.rows > 0 ? bigRect.height * 1.0f / smallmask.rows : 1.0f; + + // rs 映射回原图坐标(支架轮廓缺失时保持空矩形,面积按 0 处理) + cv::Rect rsRect; + if (bFoundRs && rsContour.size() >= 3) + { + cv::Rect rsSmallRect = cv::boundingRect(rsContour); + rsRect = cv::Rect( + cvRound(bigRect.x + rsSmallRect.x * fx), + cvRound(bigRect.y + rsSmallRect.y * fy), + cvRound(rsSmallRect.width * fx), + cvRound(rsSmallRect.height * fy)); + } + + // 3、支架缺失判定:支架由 Align_Inner 模型(smallmask)检出,支架面积 < support_min_area(单位 mm²) → 缺失 + // 面积换算:smallmask 轮廓面积 × fx × fy = 原图像素面积,再乘成像精度 X/Y(mm/px) + float fScaleX = (m_fImgage_Scale_X > 0) ? m_fImgage_Scale_X : 1.0f; + float fScaleY = (m_fImgage_Scale_Y > 0) ? m_fImgage_Scale_Y : 1.0f; + double dSupportAreaSmall = (bFoundRs && rsContour.size() >= 3) ? cv::contourArea(rsContour) : 0.0; + double dSupportAreaMm2 = dSupportAreaSmall * fx * fy * fScaleX * fScaleY; + bool bSupportMiss = (supportDet.support_min_area > 0) && (dSupportAreaMm2 < supportDet.support_min_area); + m_pdetlog->AddCheckstr(PrintLevel_0, "支架检测", + "support area %.2f mm2 (small px %.1f, scale %.4f %.4f) min_area %.2f miss %d", + dSupportAreaMm2, dSupportAreaSmall, fx * fScaleX, fy * fScaleY, + supportDet.support_min_area, bSupportMiss); + + if (bSupportMiss) + { + // 支架缺失 NG:ROI 取支架区域(有轮廓时)或整个产品区域(无轮廓时),映射到检测图(detImg)坐标 + cv::Rect missRect = (rsRect.width > 0 && rsRect.height > 0) ? rsRect : bigRect; + cv::Rect roi = MatAffineRect(m_matSrc2Det, missRect); + roi &= cv::Rect(0, 0, m_pImageAllResult->detImg.cols, m_pImageAllResult->detImg.rows); + + QX_ERROR_INFO_ temerror; + temerror.Idx = m_pDetResult->pQx_ErrorList->size(); + temerror.roi = roi; + temerror.area = roi.width * roi.height; + temerror.JudgArea = roi.width * fScaleX * roi.height * fScaleY; + temerror.JudgArea_second = temerror.JudgArea; + float w = missRect.width; + float h = missRect.height; + temerror.flen = (w > h ? w : h) * fScaleX; + temerror.fbreadth = (w > h ? h : w) * fScaleY; + temerror.nconfig_qx_type = CONFIG_QX_NAME_cell_ZJ_QueShi; + temerror.qx_name = CONFIG_QX_NAME_Names[temerror.nconfig_qx_type]; + temerror.result = QX_RESULT_TYPE_NG; + temerror.result_name = QX_RESULT_TYPE_Names[QX_RESULT_TYPE_NG]; + + // 计算支架(缺失区域)中心所在的检测区域 + cv::Point pCenter; + pCenter.x = roi.x + roi.width * 0.5; + pCenter.y = roi.y + roi.height * 0.5; + int nmaxregionIdx = 0; + for (int iregion = 0; iregion < m_DetRoiList.roiList_Src.size(); iregion++) + { + const std::vector &polygon = m_DetRoiList.roiList_Src[iregion]; + double result = cv::pointPolygonTest(polygon, pCenter, false); + if (result < 0) + { + continue; + } + nmaxregionIdx = iregion; + } + temerror.detRegionidxList.push_back(nmaxregionIdx); + + m_pDetResult->pQx_ErrorList->push_back(temerror); + m_pdetlog->AddCheckstr(PrintLevel_0, "支架检测", "%s %d support missing: area %.2f mm2 < min_area %.2f roi [%d %d %d %d]", + temerror.qx_name.c_str(), temerror.Idx, dSupportAreaMm2, supportDet.support_min_area, + roi.x, roi.y, roi.width, roi.height); + } + else if (!bFoundRp || rpContour.size() < 3 || !bFoundRs || rsContour.size() < 3) { m_pdetlog->AddCheckstr(PrintLevel_0, "支架检测", "contour invalid (rp %d %ld, rs %d %ld), skip support offset", bFoundRp, rpContour.size(), bFoundRs, rsContour.size()); @@ -1137,17 +1214,6 @@ int ImgCheckAnalysisy::CheckRun() // rp 边缘(拟合不可用时的回退):产品最大连通域的外接矩形(含凸起) cv::Rect rpRect = cv::boundingRect(rpContour); - // rs 映射回原图坐标:smallmask 对应定位时裁剪的大轮廓区域(m_outer_rroi 外接矩形),按比例映射 - cv::Rect bigRect = m_outer_rroi.boundingRect(); - float fx = bigRect.width * 1.0f / smallmask.cols; - float fy = bigRect.height * 1.0f / smallmask.rows; - cv::Rect rsSmallRect = cv::boundingRect(rsContour); - cv::Rect rsRect( - cvRound(bigRect.x + rsSmallRect.x * fx), - cvRound(bigRect.y + rsSmallRect.y * fy), - cvRound(rsSmallRect.width * fx), - cvRound(rsSmallRect.height * fy)); - // 3、产品边缘:优先复用把手提取对 bigmask 做的"四边抗凸起拟合"主体四边形 // (CheckHandleByBigMask -> GetMaskProtrusions 输出):边缘上的凸起(把手等)不会把边缘带偏; // 拟合不可用/无效时回退到产品最大连通域外接矩形 rpRect(含凸起) @@ -1223,6 +1289,70 @@ int ImgCheckAnalysisy::CheckRun() ptSegBArr[3] = cv::Point2f((float)(rpRect.x + rpRect.width), (float)(rpRect.y + rpRect.height)); } + // 3.5、旋转偏移:支架最小外接旋转矩形(长轴对应左/右边缘、短轴对应上/下边缘) + // 长轴与产品左/右边缘的夹角 > r_offset(度) → 偏移;r_offset<=0 不判定(兼容旧参数) + float fSupportAngle = 0; // 支架长轴相对产品左/右边缘的夹角(度,带符号;正=支架长轴相对产品边缘向右倾) + bool bRotOffset = false; + if (supportDet.r_offset > 0 && rsContour.size() >= 3) + { + // 轮廓先映射回原图坐标再拟合旋转矩形(smallmask 的 x/y 缩放可能不同,不还原会带角度误差) + std::vector rsContourSrc; + rsContourSrc.reserve(rsContour.size()); + for (size_t ip = 0; ip < rsContour.size(); ip++) + { + rsContourSrc.emplace_back(rsContour[ip].x * fx, rsContour[ip].y * fy); + } + cv::RotatedRect rsRotRect = cv::minAreaRect(rsContourSrc); + cv::Point2f rpts[4]; + rsRotRect.points(rpts); + cv::Point2f d01 = rpts[1] - rpts[0]; + cv::Point2f d12 = rpts[2] - rpts[1]; + float len01 = (float)cv::norm(d01); + float len12 = (float)cv::norm(d12); + // 长轴方向 = 长边方向(长轴对应左/右边缘),并统一朝下(与左边缘 上->下 方向一致) + cv::Point2f vLong(0, 1); + if (len01 >= len12 && len01 > 1e-3f) + { + vLong = d01 * (1.0f / len01); + } + else if (len12 > 1e-3f) + { + vLong = d12 * (1.0f / len12); + } + if (vLong.y < 0) + { + vLong = -vLong; + } + // 参考方向 = 产品左边缘(ptSegAArr[0]->ptSegBArr[0],拟合边/回退矩形边);退化时用垂直方向 + cv::Point2f vRef = ptSegBArr[0] - ptSegAArr[0]; + float lenRef = (float)cv::norm(vRef); + if (lenRef > 1e-3f) + { + vRef *= (1.0f / lenRef); + } + else + { + vRef = cv::Point2f(0, 1); + } + if (vRef.y < 0) + { + vRef = -vRef; + } + // 有符号夹角(度);长轴是“线”,角度归一化到 [-90, 90] + double dCross = (double)vLong.x * vRef.y - (double)vLong.y * vRef.x; + double dDot = (double)vLong.x * vRef.x + (double)vLong.y * vRef.y; + fSupportAngle = (float)(std::atan2(dCross, dDot) * 180.0 / CV_PI); + if (fSupportAngle > 90.0f) + { + fSupportAngle -= 180.0f; + } + else if (fSupportAngle < -90.0f) + { + fSupportAngle += 180.0f; + } + bRotOffset = (fabs(fSupportAngle) > supportDet.r_offset); + } + // 像素 -> mm fDL *= m_fImgage_Scale_X; fDR *= m_fImgage_Scale_X; @@ -1288,9 +1418,11 @@ int ImgCheckAnalysisy::CheckRun() sprintf(strTmp, "%.1f ", fDiff); strDiff += strTmp; } - // edge_select 为空或无效时 nDiffNum=0,结果不参与判定(仅打日志) + // edge_select 为空或无效时 nDiffNum=0,距离不参与判定(仅打日志) float fAvgDiff = (nDiffNum > 0) ? (fSumDiff / nDiffNum) : 0.0f; - bool bOffset = (nDiffNum > 0) && (fAvgDiff > supportDet.x_offset); + bool bOffsetDist = (nDiffNum > 0) && (fAvgDiff > supportDet.x_offset); + // 距离偏移 或 旋转偏移(r_offset) 任一超限 → 支架偏移 + bool bOffset = bOffsetDist || bRotOffset; // 记录绘制信息(DrawResult_Step_1 用):支架中心十字 + 各方向距离 m_SupportDrawInfo.bValid = true; @@ -1300,11 +1432,13 @@ 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) edge %s quad %ld 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] support area %.2f mm2 (min %.2f) 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 | rot %.2f deg limit %.2f ng %d", rpRect.x, rpRect.y, rpRect.width, rpRect.height, rsRect.x, rsRect.y, rsRect.width, rsRect.height, + dSupportAreaMm2, supportDet.support_min_area, 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); + strSelect.c_str(), strDiff.c_str(), fAvgDiff, supportDet.x_offset, + fSupportAngle, supportDet.r_offset, bOffset); if (bOffset) { diff --git a/ConfigModule/include/CheckConfigDefine.h b/ConfigModule/include/CheckConfigDefine.h index 657d422..b66fae6 100644 --- a/ConfigModule/include/CheckConfigDefine.h +++ b/ConfigModule/include/CheckConfigDefine.h @@ -929,7 +929,8 @@ struct Function_Support_Det std::vector edge_select; // 参与计算的方向:up/down/left/right float x_offset; float y_offset; - float r_offset; + float r_offset; // 支架旋转偏移阈值(度):支架旋转矩形长轴(对应左/右边缘)与产品左/右边缘夹角超过该值判偏移;<=0 不判定 + float support_min_area; // 支架缺失判定:支架面积阈值(单位 mm²)。支架由 Align_Inner 模型(smallmask)检出,支架面积 < 该值判缺失;<=0 不判定 // 把手参数(Handle_Detect) bool bDetHandle; // 把手缺失检测开关(Handle_Detect.isOpen) // 把手区域(最多 HANDLE_REGION_MAX_NUM 个,只保存非空的;区域宽高用于把手卡控) @@ -966,6 +967,7 @@ struct Function_Support_Det x_offset = 0; y_offset = 0; r_offset = 0; + support_min_area = 0; bDetHandle = false; handleRegions.clear(); handleBoxes.clear(); @@ -993,6 +995,7 @@ struct Function_Support_Det this->x_offset = tem.x_offset; this->y_offset = tem.y_offset; this->r_offset = tem.r_offset; + this->support_min_area = tem.support_min_area; this->bDetHandle = tem.bDetHandle; this->handleRegions.assign(tem.handleRegions.begin(), tem.handleRegions.end()); this->handleBoxes.assign(tem.handleBoxes.begin(), tem.handleBoxes.end()); @@ -1011,16 +1014,16 @@ struct Function_Support_Det } void print(std::string str) { - printf("%s>>bDetSupport %d bDrawSupport %d bDetHandle %d bDetTag %d x_standard %f y_standard %f x_offset %f y_offset %f r_offset %f edge num %ld handleRegion num %ld handle_diff %f handleRange[size %.2f~%.2f search %.1f depth %.1f area %.1f margin %.1f(0=自动,<0=默认)] BQ[area %f energy %f len %f hj %f]\n", - str.c_str(), bDetSupport, bDrawSupport, bDetHandle, bDetTag, x_standard, y_standard, x_offset, y_offset, r_offset, edge_select.size(), handleRegions.size(), handle_diff, + printf("%s>>bDetSupport %d bDrawSupport %d bDetHandle %d bDetTag %d x_standard %f y_standard %f x_offset %f y_offset %f r_offset %f support_min_area %f edge num %ld handleRegion num %ld handle_diff %f handleRange[size %.2f~%.2f search %.1f depth %.1f area %.1f margin %.1f(0=自动,<0=默认)] BQ[area %f energy %f len %f hj %f]\n", + str.c_str(), bDetSupport, bDrawSupport, bDetHandle, bDetTag, x_standard, y_standard, x_offset, y_offset, r_offset, support_min_area, edge_select.size(), handleRegions.size(), handle_diff, handle_size_min_ratio, handle_size_max_ratio, handle_search_ratio, handle_min_depth_coef, handle_min_area_coef, handle_edge_margin_coef, BQ_area, BQ_energy, BQ_len, BQ_hj); } std::string GetInfo(std::string str) { char buffer[1024]; - sprintf(buffer, "%s>>bDetSupport %d bDrawSupport %d bDetHandle %d bDetTag %d x_standard %f y_standard %f x_offset %f y_offset %f r_offset %f edge num %ld handleRegion num %ld handle_diff %f handleRange[size %.2f~%.2f search %.1f depth %.1f area %.1f margin %.1f(0=自动,<0=默认)] BQ[area %f energy %f len %f hj %f]\n", - str.c_str(), bDetSupport, bDrawSupport, bDetHandle, bDetTag, x_standard, y_standard, x_offset, y_offset, r_offset, edge_select.size(), handleRegions.size(), handle_diff, + sprintf(buffer, "%s>>bDetSupport %d bDrawSupport %d bDetHandle %d bDetTag %d x_standard %f y_standard %f x_offset %f y_offset %f r_offset %f support_min_area %f edge num %ld handleRegion num %ld handle_diff %f handleRange[size %.2f~%.2f search %.1f depth %.1f area %.1f margin %.1f(0=自动,<0=默认)] BQ[area %f energy %f len %f hj %f]\n", + str.c_str(), bDetSupport, bDrawSupport, bDetHandle, bDetTag, x_standard, y_standard, x_offset, y_offset, r_offset, support_min_area, edge_select.size(), handleRegions.size(), handle_diff, handle_size_min_ratio, handle_size_max_ratio, handle_search_ratio, handle_min_depth_coef, handle_min_area_coef, handle_edge_margin_coef, BQ_area, BQ_energy, BQ_len, BQ_hj); std::string str123 = buffer; diff --git a/ConfigModule/src/JsonConfig.cpp b/ConfigModule/src/JsonConfig.cpp index 47895e1..e3a7a9e 100644 --- a/ConfigModule/src/JsonConfig.cpp +++ b/ConfigModule/src/JsonConfig.cpp @@ -952,6 +952,12 @@ int BaseFuntonConfigJson::GetFunction(Json::Value value) _config.supportDet.r_offset = value_param["r_offset"].asFloat(); } + // 支架缺失判定:支架(Align_Inner 模型)面积 < support_min_area(单位 mm²)→ 支架缺失;<=0 不判定(兼容旧参数) + if (value_param["support_min_area"].isNumeric()) + { + _config.supportDet.support_min_area = value_param["support_min_area"].asFloat(); + } + auto value_qx = value_param["edge_select"]; if (value_qx.isArray()) {