|
|
|
|
@ -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_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<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_ ®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<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 ¶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<Handle_Candidate_> &candList, const std::vector<bool> &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 需为单通道)
|
|
|
|
|
|