update 初步优化把手检测

dev_lsy
liusiyang 20 hours ago
parent 4b7e1677ff
commit 13973e9794

@ -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; // 匹配到的把手 roibFound=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<cv::RotatedRect> &tagroiList);
// 用 bigmask 检测把手bigmask 主体近似矩形,把手是四周凸起的矩形块)
int CheckHandleByBigMask(const cv::Mat &img, const cv::Mat &bigmask);
// 判断凸起是否为把手:参数里每一项开启的判定条件(尺寸/填充率/面积/灰阶…)都满足才算把手
bool IsHandleProtrusion(const Handle_Candidate_ &cand, const Handle_Check_Param &param,
const cv::Rect &handleBox, const cv::Size &maskSize);
// 在某个把手区域附近找宽高匹配的凸起:返回凸起在 candList 中的下标(-1=没找到)
int MatchHandleRegion(const std::vector<Handle_Candidate_> &candList, const std::vector<bool> &usedList,
const cv::Rect &boxRegion, const Handle_Check_Param &param, 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<cv::RotatedRect> m_tag_roiList;
// 把手检测结果bigmask 四周凸起)
std::vector<Handle_Candidate_> m_HandleCandList; // bigmask 四周抠出的凸起候选 + 实测特征
std::vector<cv::Rect> m_HandleRoiList; // 判定为把手的凸起 roi检测图坐标
cv::Rect m_HandleBoxParamImg; // 参数里绘制的把手区域(检测图坐标)
bool m_bHandleDetSucc; // 本次是否完成把手检测
bool m_bHandleFound; // 是否检测到把手
bool m_bHandleExpect; // 参数中是否绘制了把手区域(有期望)
// 把手检测结果bigmask 四周凸起 + 各把手区域判定结果)
std::vector<Handle_Candidate_> m_HandleCandList; // 从 bigmask 四周抠出的凸起候选 + 实测特征
std::vector<Handle_Region_Result_> m_HandleRegionResultList; // 各把手区域最多4个的判定结果
bool m_bHandleDetSucc; // 本次是否完成把手检测
bool m_bHandleFound; // 所有非空把手区域是否都找到了把手
bool m_bHandleExpect; // 是否开启了把手缺失检测(且有非空把手区域)
};
#endif

@ -14,16 +14,12 @@
#include <algorithm>
// 把手判定参数:
// 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_ParamCheckConfigDefine.h
// 每个非空的"把手区域"附近都应该有一个把手:凸起宽高与该区域外接矩形宽高的比值落在下面范围内才算匹配
#define HANDLE_SIZE_MIN_RATIO 0.8f
#define HANDLE_SIZE_MAX_RATIO 1.2f
// "附近"的搜索范围:把手区域外接矩形在宽/高方向各外扩该比例
#define HANDLE_NEAR_EXPAND_RATIO 0.5f
// 凸起填充率下限等参数的默认值在 Handle_Check_ParamCheckConfigDefine.h
// 用于排序轮廓的比较函数
static bool compareContourAreas(const vector<Point> &contour1, const vector<Point> &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_ &regionResult = 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<cv::Point> &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<cv::Point> &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<cv::Rect> 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<bool> 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 &param,
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<Handle_Candidate_> &candList, const std::vector<bool> &usedList,
const cv::Rect &boxRegion, const Handle_Check_Param &param, 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 需为单通道)

@ -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<cv::Point> handleRegion;
cv::RotatedRect handleRect;
// 把手区域(最多 HANDLE_REGION_MAX_NUM 个,只保存非空的;区域宽高用于把手卡控)
std::vector<std::vector<cv::Point>> handleRegions;
std::vector<cv::Rect> 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;

@ -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;

@ -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<cv::Point> 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();

@ -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();
}

Loading…
Cancel
Save