From 13973e9794f77ae765b17d866702b56184ca489c Mon Sep 17 00:00:00 2001 From: liusiyang Date: Sun, 20 Sep 2026 11:41:37 +0800 Subject: [PATCH] =?UTF-8?q?update=20=E5=88=9D=E6=AD=A5=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E6=8A=8A=E6=89=8B=E6=A3=80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AlgorithmModule/include/ImgCheckAnalysisy.hpp | 37 +- AlgorithmModule/src/ImgCheckAnalysisy.cpp | 356 ++++++++++-------- ConfigModule/include/CheckConfigDefine.h | 58 ++- ConfigModule/src/ConfigManager.cpp | 2 +- ConfigModule/src/JsonConfig.cpp | 69 ++-- example/Read_Image.cpp | 6 +- 6 files changed, 290 insertions(+), 238 deletions(-) diff --git a/AlgorithmModule/include/ImgCheckAnalysisy.hpp b/AlgorithmModule/include/ImgCheckAnalysisy.hpp index 0d528a8..ac7d56f 100644 --- a/AlgorithmModule/include/ImgCheckAnalysisy.hpp +++ b/AlgorithmModule/include/ImgCheckAnalysisy.hpp @@ -57,13 +57,27 @@ struct Handle_Candidate_ int nLen; // 沿边长度(像素) int nDepth; // 垂直深度(像素) float fGrayDiff; // 凸起区域与周边背景的平均灰度差(预留灰阶判定) - bool bHandle; // 是否为把手 Handle_Candidate_() { nLen = 0; nDepth = 0; fGrayDiff = 0.0f; - bHandle = false; + } +}; + +// 单个把手区域的判定结果 +struct Handle_Region_Result_ +{ + int nIdx; // 区域序号(1开始) + cv::Rect boxRegion; // 区域外接矩形(检测图坐标),宽高用于卡控 + bool bFound; // 该区域附近是否找到匹配的把手 + cv::Rect roiHandle; // 匹配到的把手 roi(bFound=true 时有效) + Handle_Region_Result_() + { + nIdx = 0; + boxRegion = cv::Rect(0, 0, 0, 0); + bFound = false; + roiHandle = cv::Rect(0, 0, 0, 0); } }; @@ -137,9 +151,9 @@ private: int AI_Edge(const cv::Mat &img, cv::RotatedRect &outerRoi, cv::RotatedRect &innerRoi, std::vector &tagroiList); // 用 bigmask 检测把手(bigmask 主体近似矩形,把手是四周凸起的矩形块) int CheckHandleByBigMask(const cv::Mat &img, const cv::Mat &bigmask); - // 判断凸起是否为把手:参数里每一项开启的判定条件(尺寸/填充率/面积/灰阶…)都满足才算把手 - bool IsHandleProtrusion(const Handle_Candidate_ &cand, const Handle_Check_Param ¶m, - const cv::Rect &handleBox, const cv::Size &maskSize); + // 在某个把手区域附近找宽高匹配的凸起:返回凸起在 candList 中的下标(-1=没找到) + int MatchHandleRegion(const std::vector &candList, const std::vector &usedList, + const cv::Rect &boxRegion, const Handle_Check_Param ¶m, cv::Rect &roiHandle); // 计算凸起区域与周边背景的平均灰度差(灰阶判定的预留量测值,img 需为单通道) float CalProtrusionGrayDiff(const cv::Mat &grayImg, const cv::Rect &roi); // 计算产品尺寸 @@ -315,13 +329,12 @@ private: cv::RotatedRect m_inner_rroi; std::vector m_tag_roiList; - // 把手检测结果(bigmask 四周凸起) - std::vector m_HandleCandList; // bigmask 四周抠出的凸起候选 + 实测特征 - std::vector m_HandleRoiList; // 判定为把手的凸起 roi(检测图坐标) - cv::Rect m_HandleBoxParamImg; // 参数里绘制的把手区域(检测图坐标) - bool m_bHandleDetSucc; // 本次是否完成把手检测 - bool m_bHandleFound; // 是否检测到把手 - bool m_bHandleExpect; // 参数中是否绘制了把手区域(有期望) + // 把手检测结果(bigmask 四周凸起 + 各把手区域判定结果) + std::vector m_HandleCandList; // 从 bigmask 四周抠出的凸起候选 + 实测特征 + std::vector m_HandleRegionResultList; // 各把手区域(最多4个)的判定结果 + bool m_bHandleDetSucc; // 本次是否完成把手检测 + bool m_bHandleFound; // 所有非空把手区域是否都找到了把手 + bool m_bHandleExpect; // 是否开启了把手缺失检测(且有非空把手区域) }; #endif \ No newline at end of file diff --git a/AlgorithmModule/src/ImgCheckAnalysisy.cpp b/AlgorithmModule/src/ImgCheckAnalysisy.cpp index 5b7fc10..4c95925 100644 --- a/AlgorithmModule/src/ImgCheckAnalysisy.cpp +++ b/AlgorithmModule/src/ImgCheckAnalysisy.cpp @@ -14,16 +14,12 @@ #include // 把手判定参数: -// 1、有参数(参数里绘制了把手区域)时,凸起"沿边长度"与手绘区域对应边长度的比值范围 -#define HANDLE_LEN_MIN_RATIO 0.75f -#define HANDLE_LEN_MAX_RATIO 1.5f -// 2、凸起"垂直深度"与手绘区域对应边宽度的比值范围(凸起被图像边界截断时不校深度) -#define HANDLE_DEPTH_MIN_RATIO 0.8f -#define HANDLE_DEPTH_MAX_RATIO 1.5f -// 3、无参数时,按掩膜短边推算沿边长度/垂直深度范围(最小/最大占比) -#define HANDLE_DEFAULT_MIN_RATIO 0.03f -#define HANDLE_DEFAULT_MAX_RATIO 0.40f -// 4、凸起填充率下限、面积/灰阶等判定参数的默认值都在 Handle_Check_Param(CheckConfigDefine.h)里 +// 每个非空的"把手区域"附近都应该有一个把手:凸起宽高与该区域外接矩形宽高的比值落在下面范围内才算匹配 +#define HANDLE_SIZE_MIN_RATIO 0.8f +#define HANDLE_SIZE_MAX_RATIO 1.2f +// "附近"的搜索范围:把手区域外接矩形在宽/高方向各外扩该比例 +#define HANDLE_NEAR_EXPAND_RATIO 0.5f +// 凸起填充率下限等参数的默认值在 Handle_Check_Param(CheckConfigDefine.h)里 // 用于排序轮廓的比较函数 static bool compareContourAreas(const vector &contour1, const vector &contour2) @@ -611,57 +607,66 @@ int ImgCheckAnalysisy::CheckRun() } } - /* 把手缺失检测:bigmask 的凸起本身不算 NG,只有"参数里画了把手区域但没检出把手"才算 NG */ + /* 把手缺失检测:bigmask 的凸起本身不算 NG,只有"参数里的某个把手区域附近找不到把手"才算 NG(每个缺失区域报一条) */ { if (m_bHandleDetSucc && m_bHandleExpect && !m_bHandleFound) { - QX_ERROR_INFO_ temerror; - temerror.Idx = m_pDetResult->pQx_ErrorList->size(); - - // 上报参数里绘制的把手区域位置:原图坐标 -> 检测图(detImg)坐标 - cv::Rect roi = m_HandleBoxParamImg; - roi.x -= m_outer_roi.x; - roi.y -= m_outer_roi.y; - roi &= cv::Rect(0, 0, m_pImageAllResult->detImg.cols, m_pImageAllResult->detImg.rows); - if (roi.width <= 0 || roi.height <= 0) + for (size_t iRegion = 0; iRegion < m_HandleRegionResultList.size(); iRegion++) { - roi = cv::Rect(0, 0, m_pImageAllResult->detImg.cols, m_pImageAllResult->detImg.rows); - } - temerror.roi = roi; - temerror.area = roi.width * roi.height; - temerror.JudgArea = roi.width * m_fImgage_Scale_X * roi.height * m_fImgage_Scale_Y; - temerror.JudgArea_second = temerror.JudgArea; - float w = roi.width; - float h = roi.height; - temerror.flen = (w > h ? w : h) * m_fImgage_Scale_X; - temerror.fbreadth = (w > h ? h : w) * m_fImgage_Scale_Y; - temerror.nconfig_qx_type = CONFIG_QX_NAME_handle_loss; - temerror.qx_name = CONFIG_QX_NAME_Names[CONFIG_QX_NAME_handle_loss]; - temerror.result = QX_RESULT_TYPE_NG; - temerror.result_name = QX_RESULT_TYPE_Names[QX_RESULT_TYPE_NG]; + Handle_Region_Result_ ®ionResult = m_HandleRegionResultList[iRegion]; + if (regionResult.bFound) + { + continue; + } - // 计算把手中心所在的检测区域 - 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) + // 上报该把手区域的位置:原图坐标 -> 检测图(detImg)坐标 + cv::Rect roi = regionResult.boxRegion; + roi.x -= m_outer_roi.x; + roi.y -= m_outer_roi.y; + roi &= cv::Rect(0, 0, m_pImageAllResult->detImg.cols, m_pImageAllResult->detImg.rows); + if (roi.width <= 0 || roi.height <= 0) { continue; } - nmaxregionIdx = iregion; - } - temerror.detRegionidxList.push_back(nmaxregionIdx); - m_pDetResult->pQx_ErrorList->push_back(temerror); + QX_ERROR_INFO_ temerror; + temerror.Idx = m_pDetResult->pQx_ErrorList->size(); + temerror.roi = roi; + temerror.area = roi.width * roi.height; + temerror.JudgArea = roi.width * m_fImgage_Scale_X * roi.height * m_fImgage_Scale_Y; + temerror.JudgArea_second = temerror.JudgArea; + float w = roi.width; + float h = roi.height; + temerror.flen = (w > h ? w : h) * m_fImgage_Scale_X; + temerror.fbreadth = (w > h ? h : w) * m_fImgage_Scale_Y; + temerror.nconfig_qx_type = CONFIG_QX_NAME_handle_loss; + temerror.qx_name = CONFIG_QX_NAME_Names[CONFIG_QX_NAME_handle_loss]; + temerror.result = QX_RESULT_TYPE_NG; + temerror.result_name = QX_RESULT_TYPE_Names[QX_RESULT_TYPE_NG]; - m_pdetlog->AddCheckstr(PrintLevel_0, "把手检测", "%s %d roi [%d %d %d %d] handle param box [%d %d %d %d]", - temerror.qx_name.c_str(), temerror.Idx, roi.x, roi.y, roi.width, roi.height, - m_HandleBoxParamImg.x, m_HandleBoxParamImg.y, m_HandleBoxParamImg.width, m_HandleBoxParamImg.height); + // 计算把手区域中心所在的检测区域 + 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 region %d roi [%d %d %d %d]", + temerror.qx_name.c_str(), temerror.Idx, regionResult.nIdx, + roi.x, roi.y, roi.width, roi.height); + } } } @@ -2143,16 +2148,17 @@ int ImgCheckAnalysisy::AI_Edge(const cv::Mat &img, cv::RotatedRect &outerRoi, cv // 用 bigmask 检测把手: // 1、bigmask 主体近似矩形,把手是主体四周凸起的"类矩形块" -// 2、把主体四周的凸起抠掉(凸起在该边的边界值上表现为一次突变进入 + 一次反向突变离开),剩下的就是矩形主体 -// 3、按参数里绘制的把手区域尺寸(沿边长度 + 垂直深度,带容差)逐个判定凸起是否为把手 +// 2、把主体四周的凸起抠出来(凸起在该边的边界值上表现为一次突变进入 + 一次反向突变离开) +// 3、参数里每个非空的"把手区域"附近都应该有一个把手:在区域附近找凸起, +// 凸起宽高与区域外接矩形宽高相近(HANDLE_SIZE_MIN_RATIO ~ HANDLE_SIZE_MAX_RATIO)才算匹配 +// 4、凸起本身不算 NG,只有"某个把手区域附近找不到匹配的凸起"才算把手缺失(NG 由 CheckRun 上报) int ImgCheckAnalysisy::CheckHandleByBigMask(const cv::Mat &img, const cv::Mat &bigmask) { m_bHandleDetSucc = false; m_bHandleFound = false; m_bHandleExpect = false; - m_HandleBoxParamImg = cv::Rect(0, 0, 0, 0); m_HandleCandList.clear(); - m_HandleRoiList.clear(); + m_HandleRegionResultList.clear(); if (bigmask.empty()) { @@ -2170,22 +2176,21 @@ int ImgCheckAnalysisy::CheckHandleByBigMask(const cv::Mat &img, const cv::Mat &b } m_bHandleDetSucc = true; - // 2、把手参照尺寸 + 判定参数:都来自参数里绘制的把手区域(保留图像 x/y 方向) + // 2、把手判定参数 + 把手区域(最多4个,非空区域表示该处应该有一个把手) Handle_Check_Param handleParam; + std::vector regionBoxList; if (m_pFuntion != NULL) { const Function_Support_Det &supportDet = m_pFuntion->function.f_supportDet; handleParam = supportDet.handleParam; - if (supportDet.handleRegion.size() > 0) + for (size_t i = 0; i < supportDet.handleBoxes.size() && i < HANDLE_REGION_MAX_NUM; i++) { - m_HandleBoxParamImg = cv::boundingRect(supportDet.handleRegion); + regionBoxList.push_back(supportDet.handleBoxes[i]); } } - m_bHandleExpect = (handleParam.bOpen && m_HandleBoxParamImg.width > 1 && m_HandleBoxParamImg.height > 1); - m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Handle", - "protrusion num %ld handle param box [%d %d %d %d] expect %s", - protrusionList.size(), m_HandleBoxParamImg.x, m_HandleBoxParamImg.y, - m_HandleBoxParamImg.width, m_HandleBoxParamImg.height, BOOL_TO_STR(m_bHandleExpect)); + m_bHandleExpect = (handleParam.bOpen && !regionBoxList.empty()); + m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Handle", "protrusion num %ld handle region num %ld expect %s", + protrusionList.size(), regionBoxList.size(), BOOL_TO_STR(m_bHandleExpect)); m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Handle", "%s", handleParam.GetInfo("judge").c_str()); // 灰阶判定是预留项:只有开启时才做灰度换算,避免无谓开销 @@ -2202,8 +2207,7 @@ int ImgCheckAnalysisy::CheckHandleByBigMask(const cv::Mat &img, const cv::Mat &b } } - // 3、逐个凸起:先算实测特征(沿边长度/深度/面积/灰度差),再做判定 - cv::Size maskSize(bigmask.cols, bigmask.rows); + // 3、凸起候选实测特征(面积/填充率/灰度差,供日志与后续判定用) for (size_t i = 0; i < protrusionList.size(); i++) { Handle_Candidate_ cand; @@ -2216,33 +2220,48 @@ int ImgCheckAnalysisy::CheckHandleByBigMask(const cv::Mat &img, const cv::Mat &b { cand.fGrayDiff = CalProtrusionGrayDiff(grayImg, cand.protr.roi); } - cand.bHandle = IsHandleProtrusion(cand, handleParam, m_HandleBoxParamImg, maskSize); - if (cand.bHandle) - { - m_HandleRoiList.push_back(cand.protr.roi); - } m_HandleCandList.push_back(cand); // grayDiff 打印 -1 表示本次未开启灰阶判定(未计算) m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Handle", - "protrusion %ld side %d roi [%d %d %d %d] len %d depth %d area %0.0f fill %0.2f grayDiff %0.1f -> %s", + "protrusion %ld side %d roi [%d %d %d %d] len %d depth %d area %0.0f fill %0.2f grayDiff %0.1f", i, cand.protr.nSide, cand.protr.roi.x, cand.protr.roi.y, cand.protr.roi.width, cand.protr.roi.height, cand.nLen, cand.nDepth, cand.protr.fArea, cand.protr.fFillRatio, - grayImg.empty() ? -1.0f : cand.fGrayDiff, cand.bHandle ? "handle" : "not handle"); + grayImg.empty() ? -1.0f : cand.fGrayDiff); } - m_bHandleFound = (!m_HandleRoiList.empty()); - m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, "Handle", "handle num %ld found %s use handle param %s", - m_HandleRoiList.size(), BOOL_TO_STR(m_bHandleFound), BOOL_TO_STR(m_bHandleExpect)); - - // 参数里绘制了把手区域,但 bigmask 四周没有满足判定条件的凸起 -> 把手缺失(NG 由 CheckRun 上报) - if (m_bHandleExpect && !m_bHandleFound) + // 4、逐个把手区域:在区域附近找宽高匹配的凸起(同一个凸起只匹配给一个区域) + std::vector usedList(m_HandleCandList.size(), false); + int nFound = 0; + for (size_t ir = 0; ir < regionBoxList.size(); ir++) { - m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, "Handle", "handle is missing !!"); - m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, "Handle", "handle param box [%d %d %d %d]", - m_HandleBoxParamImg.x, m_HandleBoxParamImg.y, m_HandleBoxParamImg.width, m_HandleBoxParamImg.height); + Handle_Region_Result_ result; + result.nIdx = (int)ir + 1; + result.boxRegion = regionBoxList[ir]; + int nMatchIdx = MatchHandleRegion(m_HandleCandList, usedList, result.boxRegion, handleParam, result.roiHandle); + result.bFound = (nMatchIdx >= 0); + if (result.bFound) + { + usedList[nMatchIdx] = true; + nFound++; + } + m_HandleRegionResultList.push_back(result); + + std::string strResult = "handle missing !!"; + if (result.bFound) + { + strResult = "handle roi [" + std::to_string(result.roiHandle.x) + " " + std::to_string(result.roiHandle.y) + " " + + std::to_string(result.roiHandle.width) + " " + std::to_string(result.roiHandle.height) + "]"; + } + m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Handle", "handle region %d box [%d %d %d %d] -> %s", + result.nIdx, result.boxRegion.x, result.boxRegion.y, result.boxRegion.width, result.boxRegion.height, + strResult.c_str()); } + m_bHandleFound = (!regionBoxList.empty() && nFound == (int)regionBoxList.size()); - // 4、存过程图:凸起红框、把手绿框(仅调试时) + m_pdetlog->AddCheckstr(PrintLevel_0, DET_LOG_LEVEL_3, "Handle", "handle region num %ld found num %d -> %s", + regionBoxList.size(), nFound, m_bHandleFound ? "handle ok" : "handle missing !!"); + + // 5、存过程图:凸起红框、把手区域蓝框、匹配上的把手绿框(仅调试时) if (DetImgInfo_shareP->bsaveProcessImg) { cv::Mat show; @@ -2258,9 +2277,13 @@ int ImgCheckAnalysisy::CheckHandleByBigMask(const cv::Mat &img, const cv::Mat &b { cv::rectangle(show, m_HandleCandList[i].protr.roi, cv::Scalar(0, 0, 255), 8); } - for (size_t i = 0; i < m_HandleRoiList.size(); i++) + for (size_t i = 0; i < m_HandleRegionResultList.size(); i++) { - cv::rectangle(show, m_HandleRoiList[i], cv::Scalar(0, 255, 0), 12); + cv::rectangle(show, m_HandleRegionResultList[i].boxRegion, cv::Scalar(255, 0, 0), 8); + if (m_HandleRegionResultList[i].bFound) + { + cv::rectangle(show, m_HandleRegionResultList[i].roiHandle, cv::Scalar(0, 255, 0), 12); + } } cv::Mat showSmall; cv::resize(show, showSmall, cv::Size(show.cols / 4, show.rows / 4)); @@ -2271,90 +2294,111 @@ int ImgCheckAnalysisy::CheckHandleByBigMask(const cv::Mat &img, const cv::Mat &b return 0; } -// 判断凸起是否为把手:参数里每一项"开启"的判定条件都满足才算把手 -// 尺寸参照参数里绘制的把手区域(handleBox,保留图像 x/y 方向): -// 沿边长度 对应 handleBox 在该边方向上的长度,垂直深度 对应 handleBox 的宽度; -// 凸起外边缘被图像边界截断时,深度不可信,只校验沿边长度 -// 参数里没写显式像素范围时,按把手区域尺寸的比例推算(HANDLE_LEN_*_RATIO / HANDLE_DEPTH_*_RATIO) -bool ImgCheckAnalysisy::IsHandleProtrusion(const Handle_Candidate_ &cand, const Handle_Check_Param ¶m, - const cv::Rect &handleBox, const cv::Size &maskSize) +// 在某个把手区域附近找宽高匹配的凸起 +// 匹配条件:1、凸起中心落在把手区域附近(区域宽/高各外扩 HANDLE_NEAR_EXPAND_RATIO) +// 2、凸起宽高与该区域外接矩形宽高的比值都在 [HANDLE_SIZE_MIN_RATIO, HANDLE_SIZE_MAX_RATIO](允许横竖互换) +// 3、填充率、预留的面积/灰阶等开关条件都满足 +// 返回凸起在 candList 中的下标,-1 表示没找到 +int ImgCheckAnalysisy::MatchHandleRegion(const std::vector &candList, const std::vector &usedList, + const cv::Rect &boxRegion, const Handle_Check_Param ¶m, cv::Rect &roiHandle) { - const Mask_Protrusion_ &protr = cand.protr; - if (protr.roi.width <= 0 || protr.roi.height <= 0) + if (boxRegion.width <= 0 || boxRegion.height <= 0) { - return false; + return -1; } - // 左/右凸起:沿边为 y 方向(长度=height),深度为 x 方向(width);上/下凸起相反 - const bool bHorz = (protr.nSide == 0 || protr.nSide == 1); - // 凸起外边缘是否贴到图像边界(被截断) - bool bClip = bHorz ? (protr.roi.x <= 0 || protr.roi.x + protr.roi.width >= maskSize.width) - : (protr.roi.y <= 0 || protr.roi.y + protr.roi.height >= maskSize.height); + // 搜索范围:把手区域外扩(宽/高各外扩区域尺寸的一半),容忍产品摆放偏差 + int nx = std::max(10, (int)(boxRegion.width * HANDLE_NEAR_EXPAND_RATIO)); + int ny = std::max(10, (int)(boxRegion.height * HANDLE_NEAR_EXPAND_RATIO)); + cv::Rect searchBox(boxRegion.x - nx, boxRegion.y - ny, boxRegion.width + 2 * nx, boxRegion.height + 2 * ny); + cv::Point pRegionCenter(boxRegion.x + boxRegion.width / 2, boxRegion.y + boxRegion.height / 2); - // 该凸起对应的期望尺寸范围 - int nMinLen = 0, nMaxLen = 0, nMinDepth = 0, nMaxDepth = 0; - if (handleBox.width > 1 && handleBox.height > 1) + int nBestIdx = -1; + double fBestDis = 1e18; + int nNearIdx = -1; // 区域内离区域中心最近的凸起(仅用于日志排查) + double fNearDis = 1e18; + for (size_t i = 0; i < candList.size(); i++) { - int nExpLen = bHorz ? handleBox.height : handleBox.width; - int nExpDepth = bHorz ? handleBox.width : handleBox.height; - nMinLen = (int)(nExpLen * HANDLE_LEN_MIN_RATIO); - nMaxLen = (int)(nExpLen * HANDLE_LEN_MAX_RATIO); - nMinDepth = (int)(nExpDepth * HANDLE_DEPTH_MIN_RATIO); - nMaxDepth = (int)(nExpDepth * HANDLE_DEPTH_MAX_RATIO); - } - else // 参数里没画把手区域:按掩膜短边推算一个大致范围 - { - int nMinSide = std::min(maskSize.width, maskSize.height); - nMinLen = nMinDepth = (int)(nMinSide * HANDLE_DEFAULT_MIN_RATIO); - nMaxLen = nMaxDepth = (int)(nMinSide * HANDLE_DEFAULT_MAX_RATIO); - } - // 参数里写了显式像素范围时以参数值为准 - if (param.bJudgeLen && param.fLenMin > 0) - { - nMinLen = (int)param.fLenMin; - } - if (param.bJudgeLen && param.fLenMax > 0) - { - nMaxLen = (int)param.fLenMax; - } - if (param.bJudgeDepth && param.fDepthMin > 0) - { - nMinDepth = (int)param.fDepthMin; - } - if (param.bJudgeDepth && param.fDepthMax > 0) - { - nMaxDepth = (int)param.fDepthMax; - } + if (i < usedList.size() && usedList[i]) + { + continue; + } + const Handle_Candidate_ &cand = candList[i]; + const cv::Rect &roi = cand.protr.roi; + if (roi.width <= 0 || roi.height <= 0) + { + continue; + } - // 以下条件逐条校验,任意一条不满足即不是把手 - // 1、类似矩形块:凸起区域内掩膜填充率要高 - if (param.bJudgeFill && protr.fFillRatio < param.fFillMin) - { - return false; - } - // 2、沿边长度 - if (param.bJudgeLen && (cand.nLen < nMinLen || cand.nLen > nMaxLen)) - { - return false; - } - // 3、垂直深度(凸起被图像边界截断时跳过) - if (param.bJudgeDepth && !bClip && (cand.nDepth < nMinDepth || cand.nDepth > nMaxDepth)) - { - return false; + // 位置:凸起中心要落在把手区域附近 + cv::Point pCenter(roi.x + roi.width / 2, roi.y + roi.height / 2); + if (!searchBox.contains(pCenter)) + { + continue; + } + double fDis = CheckUtil::calDis(pCenter, pRegionCenter); + if (fDis < fNearDis) + { + fNearDis = fDis; + nNearIdx = (int)i; + } + + // 类似矩形块:凸起区域内掩膜填充率要高 + if (param.bJudgeFill && cand.protr.fFillRatio < param.fFillMin) + { + continue; + } + // 预留:凸起面积 + if (param.bJudgeArea && param.fAreaMax > param.fAreaMin && + (cand.protr.fArea < param.fAreaMin || cand.protr.fArea > param.fAreaMax)) + { + continue; + } + // 预留:凸起与背景的灰度差 + if (param.bJudgeGray && param.fGrayMax > param.fGrayMin && + (cand.fGrayDiff < param.fGrayMin || cand.fGrayDiff > param.fGrayMax)) + { + continue; + } + + // 宽高:与把手区域外接矩形的宽高相近(允许横竖互换) + float fw = (float)roi.width; + float fh = (float)roi.height; + bool bSizeOK = (fw >= boxRegion.width * HANDLE_SIZE_MIN_RATIO && fw <= boxRegion.width * HANDLE_SIZE_MAX_RATIO && + fh >= boxRegion.height * HANDLE_SIZE_MIN_RATIO && fh <= boxRegion.height * HANDLE_SIZE_MAX_RATIO); + if (!bSizeOK) + { + bSizeOK = (fw >= boxRegion.height * HANDLE_SIZE_MIN_RATIO && fw <= boxRegion.height * HANDLE_SIZE_MAX_RATIO && + fh >= boxRegion.width * HANDLE_SIZE_MIN_RATIO && fh <= boxRegion.width * HANDLE_SIZE_MAX_RATIO); + } + if (!bSizeOK) + { + continue; + } + + if (fDis < fBestDis) + { + fBestDis = fDis; + nBestIdx = (int)i; + } } - // 4、预留:凸起面积 - if (param.bJudgeArea && param.fAreaMax > param.fAreaMin && - (protr.fArea < param.fAreaMin || protr.fArea > param.fAreaMax)) + + if (nBestIdx < 0 && nNearIdx >= 0) { - return false; + // 没匹配上时打印区域内最近的凸起,便于排查阈值 + const cv::Rect &roi = candList[nNearIdx].protr.roi; + m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Handle", + "region box [%d %d %d %d] no match, nearest protrusion roi [%d %d %d %d] w/h ratio %0.2f %0.2f", + boxRegion.x, boxRegion.y, boxRegion.width, boxRegion.height, + roi.x, roi.y, roi.width, roi.height, + (float)roi.width / (float)boxRegion.width, (float)roi.height / (float)boxRegion.height); } - // 5、预留:凸起与背景的灰度差 - if (param.bJudgeGray && param.fGrayMax > param.fGrayMin && - (cand.fGrayDiff < param.fGrayMin || cand.fGrayDiff > param.fGrayMax)) + + if (nBestIdx >= 0) { - return false; + roiHandle = candList[nBestIdx].protr.roi; } - return true; + return nBestIdx; } // 计算凸起区域与周边背景的平均灰度差(灰阶判定的预留量测值,grayImg 需为单通道) diff --git a/ConfigModule/include/CheckConfigDefine.h b/ConfigModule/include/CheckConfigDefine.h index 9a40dc8..21371e7 100644 --- a/ConfigModule/include/CheckConfigDefine.h +++ b/ConfigModule/include/CheckConfigDefine.h @@ -934,24 +934,23 @@ struct Function_Image_Align return str123; } }; +// 把手区域最大数量(参数里最多可画4个把手区域) +#define HANDLE_REGION_MAX_NUM 4 + // 把手缺失判定参数: -// 当前用"沿边长度 + 垂直深度"判定,后续可继续添加面积、灰阶等参数, -// 每一项都有独立的开关,所有开启的条件都满足才把凸起判为把手 +// 判定标准:参数里非空的"把手区域"附近必须存在一个凸起,且凸起宽高与区域宽高相近 +// (比值范围见 ImgCheckAnalysisy 里的 HANDLE_SIZE_MIN_RATIO ~ HANDLE_SIZE_MAX_RATIO) +// 面积/灰阶等参数保留开关,后续需要时再启用 struct Handle_Check_Param { bool bOpen; // 是否检测把手缺失(false 时不做把手判定,也不报 NG) - bool bJudgeFill; // 是否用"类矩形块"填充率判定 - float fFillMin; // 填充率下限 - bool bJudgeLen; // 是否用"沿边长度"判定 - float fLenMin; // 沿边长度下限(像素,<=0 时按把手区域尺寸比例推算) - float fLenMax; // 沿边长度上限(像素,<=0 时按把手区域尺寸比例推算) - bool bJudgeDepth; // 是否用"垂直深度"判定 - float fDepthMin; // 垂直深度下限(像素,<=0 时按把手区域尺寸比例推算) - float fDepthMax; // 垂直深度上限(像素,<=0 时按把手区域尺寸比例推算) - bool bJudgeArea; // 预留:凸起面积判定 + // 默认关闭,由 "Support_QX_Detect" 检测项开启时置为 true(可用 handle_disabled 单独关闭) + bool bJudgeFill; // 是否用"类矩形块"填充率判定 + float fFillMin; // 填充率下限 + bool bJudgeArea; // 预留:凸起面积判定 float fAreaMin; float fAreaMax; - bool bJudgeGray; // 预留:凸起与背景灰度差判定 + bool bJudgeGray; // 预留:凸起与背景灰度差判定 float fGrayMin; float fGrayMax; @@ -961,15 +960,9 @@ struct Handle_Check_Param } void Init() { - bOpen = true; + bOpen = false; bJudgeFill = true; fFillMin = 0.75f; - bJudgeLen = true; - fLenMin = 0; - fLenMax = 0; - bJudgeDepth = true; - fDepthMin = 0; - fDepthMax = 0; bJudgeArea = false; fAreaMin = 0; fAreaMax = 0; @@ -985,9 +978,9 @@ struct Handle_Check_Param std::string GetInfo(std::string str) { char buffer[256]; - sprintf(buffer, "%s>>bOpen %d fill[%d %0.2f] len[%d %0.0f %0.0f] depth[%d %0.0f %0.0f] area[%d %0.0f %0.0f] gray[%d %0.0f %0.0f]\n", - str.c_str(), bOpen, bJudgeFill, fFillMin, bJudgeLen, fLenMin, fLenMax, - bJudgeDepth, fDepthMin, fDepthMax, bJudgeArea, fAreaMin, fAreaMax, bJudgeGray, fGrayMin, fGrayMax); + sprintf(buffer, "%s>>bOpen %d fill[%d %0.2f] area[%d %0.0f %0.0f] gray[%d %0.0f %0.0f]\n", + str.c_str(), bOpen, bJudgeFill, fFillMin, + bJudgeArea, fAreaMin, fAreaMax, bJudgeGray, fGrayMin, fGrayMax); std::string str123 = buffer; return str123; } @@ -1002,8 +995,9 @@ struct Function_Support_Det float x_offset; float y_offset; float r_offset; - std::vector handleRegion; - cv::RotatedRect handleRect; + // 把手区域(最多 HANDLE_REGION_MAX_NUM 个,只保存非空的;区域宽高用于把手卡控) + std::vector> handleRegions; + std::vector handleBoxes; Handle_Check_Param handleParam; // 把手缺失判定参数 Function_Support_Det () @@ -1019,8 +1013,8 @@ struct Function_Support_Det x_offset = 0; y_offset = 0; r_offset = 0; - handleRegion.clear(); - handleRect = cv::RotatedRect(); + handleRegions.clear(); + handleBoxes.clear(); handleParam.Init(); } void copy(Function_Support_Det tem) @@ -1031,21 +1025,21 @@ struct Function_Support_Det this->x_offset = tem.x_offset; this->y_offset = tem.y_offset; this->r_offset = tem.r_offset; - this->handleRegion.assign(tem.handleRegion.begin(), tem.handleRegion.end()); - this->handleRect = tem.handleRect; + this->handleRegions.assign(tem.handleRegions.begin(), tem.handleRegions.end()); + this->handleBoxes.assign(tem.handleBoxes.begin(), tem.handleBoxes.end()); this->handleParam.copy(tem.handleParam); } void print(std::string str) { - printf("%s>>bOpen %d x_offset %f y_offset %f r_offset %f\n", str.c_str(), - bOpen, x_offset, y_offset, r_offset); + printf("%s>>bOpen %d x_offset %f y_offset %f r_offset %f handleRegion num %ld\n", str.c_str(), + bOpen, x_offset, y_offset, r_offset, handleRegions.size()); printf("%s", handleParam.GetInfo("handleParam").c_str()); } std::string GetInfo(std::string str) { char buffer[256]; - sprintf(buffer, "%s>>bOpen %d x_offset %f y_offset %f r_offset %f\n", str.c_str(), - bOpen, x_offset, y_offset, r_offset); + sprintf(buffer, "%s>>bOpen %d x_offset %f y_offset %f r_offset %f handleRegion num %ld\n", str.c_str(), + bOpen, x_offset, y_offset, r_offset, handleRegions.size()); std::string str123 = buffer; str123 += handleParam.GetInfo("handleParam"); return str123; diff --git a/ConfigModule/src/ConfigManager.cpp b/ConfigModule/src/ConfigManager.cpp index 6d9fa46..083efbe 100644 --- a/ConfigModule/src/ConfigManager.cpp +++ b/ConfigModule/src/ConfigManager.cpp @@ -171,7 +171,7 @@ int ConfigManager::UpdateConfig() ReadFlawCodeConfig(defect_list_file); bool bFileName = false; // std::regex pattern(R"(param_[0-9]\.json)"); - std::regex pattern(R"(param_(\d+|left|right)\.json)"); + std::regex pattern(R"(param_.*\.json)"); if (!fs::exists(m_strConfigRootPath)) { std::cerr << "目录不存在: " << m_strConfigRootPath << std::endl; diff --git a/ConfigModule/src/JsonConfig.cpp b/ConfigModule/src/JsonConfig.cpp index c1180ba..2b91b64 100644 --- a/ConfigModule/src/JsonConfig.cpp +++ b/ConfigModule/src/JsonConfig.cpp @@ -453,28 +453,53 @@ int ChannelFuntonConfigJson::GetFunction(Json::Value value, CheckFunction &funct // 把手参数 { - auto value_region = value_f["form"]["handle_param"]["handle_region"]; - if (value_region.isArray()) + auto handle_p = value_f["form"]["handle_param"]; + bool bNewFormat = handle_p["handle_region1"].isArray(); + for (int idx = 0; idx < HANDLE_REGION_MAX_NUM; idx++) { - for (int idx = 0; idx < value_region.size(); idx++) + std::string strKey = bNewFormat ? ("handle_region" + std::to_string(idx + 1)) + : ((idx == 0) ? std::string("handle_region") : std::string("")); + if (strKey.empty()) + { + break; + } + // 该把手区域被禁用时跳过 + if (handle_p[strKey + "_disabled"].isBool() && handle_p[strKey + "_disabled"].asBool()) + { + continue; + } + auto value_region = handle_p[strKey]; + if (!value_region.isArray() || value_region.size() < 3) { + continue; // 空区域:表示该处不需要检测把手 + } + std::vector region; + for (int ip = 0; ip < value_region.size(); ip++) + { + if (!value_region[ip].isArray() || value_region[ip].size() < 2) + { + continue; + } cv::Point p; - p.x = value_region[idx][0].asInt(); - p.y = value_region[idx][1].asInt(); - - function.f_supportDet.handleRegion.emplace_back(p); + p.x = value_region[ip][0].asInt(); + p.y = value_region[ip][1].asInt(); + region.emplace_back(p); } - if (function.f_supportDet.handleRegion.size() > 0) + if (region.size() < 3) { - function.f_supportDet.handleRect = minAreaRect(function.f_supportDet.handleRegion); + continue; } + function.f_supportDet.handleRegions.push_back(region); + function.f_supportDet.handleBoxes.push_back(cv::boundingRect(region)); } } - // 把手缺失判定参数(参数缺失时保留默认值:长度/深度按把手区域尺寸比例推算) + // 把手缺失判定参数 { auto handle_p = value_f["form"]["handle_param"]; Handle_Check_Param &handleParam = function.f_supportDet.handleParam; + // "Support_QX_Detect" 检测项开启时,默认做把手缺失检测 + handleParam.bOpen = true; if (handle_p["handle_disabled"].isBool()) { handleParam.bOpen = !handle_p["handle_disabled"].asBool(); @@ -487,30 +512,6 @@ int ChannelFuntonConfigJson::GetFunction(Json::Value value, CheckFunction &funct { handleParam.fFillMin = handle_p["handle_fill_min"].asFloat(); } - if (handle_p["handle_len_disabled"].isBool()) - { - handleParam.bJudgeLen = !handle_p["handle_len_disabled"].asBool(); - } - if (handle_p["handle_len_min"].isNumeric()) - { - handleParam.fLenMin = handle_p["handle_len_min"].asFloat(); - } - if (handle_p["handle_len_max"].isNumeric()) - { - handleParam.fLenMax = handle_p["handle_len_max"].asFloat(); - } - if (handle_p["handle_depth_disabled"].isBool()) - { - handleParam.bJudgeDepth = !handle_p["handle_depth_disabled"].asBool(); - } - if (handle_p["handle_depth_min"].isNumeric()) - { - handleParam.fDepthMin = handle_p["handle_depth_min"].asFloat(); - } - if (handle_p["handle_depth_max"].isNumeric()) - { - handleParam.fDepthMax = handle_p["handle_depth_max"].asFloat(); - } if (handle_p["handle_area_disabled"].isBool()) { handleParam.bJudgeArea = !handle_p["handle_area_disabled"].asBool(); diff --git a/example/Read_Image.cpp b/example/Read_Image.cpp index 42e7d17..b4d5a4f 100644 --- a/example/Read_Image.cpp +++ b/example/Read_Image.cpp @@ -185,12 +185,12 @@ std::string Extract_ALL::Extract_Product_Name(std::string strPath, int userflag) // std::cout << strPath << std::endl; fs::path p = fs::path(strPath); - fs::path lastDir = p.parent_path().filename(); + fs::path lastDir = p.parent_path().parent_path().filename(); std::string name = lastDir.string(); - if (name.length()<7) + if (name.empty()) { - lastDir = p.parent_path().parent_path().filename(); + lastDir = p.parent_path().filename(); name = lastDir.string(); }