|
|
|
|
@ -12,6 +12,15 @@
|
|
|
|
|
#include <omp.h>
|
|
|
|
|
#include "AICommonDefine.h"
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
|
|
|
|
|
// 把手判定参数:
|
|
|
|
|
// 每个非空的"把手区域"附近都应该有一个把手:凸起宽高与该区域外接矩形宽高的比值落在下面范围内才算匹配
|
|
|
|
|
#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)
|
|
|
|
|
{
|
|
|
|
|
@ -425,7 +434,7 @@ int ImgCheckAnalysisy::CheckRun()
|
|
|
|
|
{
|
|
|
|
|
m_pdetlog->bPrintStr = true;
|
|
|
|
|
}
|
|
|
|
|
m_pdetlog->bPrintStr = true;
|
|
|
|
|
// m_pdetlog->bPrintStr = true;
|
|
|
|
|
|
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_0, "1、basic Info", "---------------------------1、basic Info---------------------------------");
|
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_0, "Version", "%s", GetVersion().c_str());
|
|
|
|
|
@ -454,6 +463,22 @@ int ImgCheckAnalysisy::CheckRun()
|
|
|
|
|
}
|
|
|
|
|
long time_edge_s = CheckUtil::getcurTime();
|
|
|
|
|
|
|
|
|
|
m_pFuntion = GetChannelFuntion(m_strCurDetChannel);
|
|
|
|
|
int nfunction = 0;
|
|
|
|
|
if (m_pFuntion != NULL)
|
|
|
|
|
{
|
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_0, "Detect function", "%s",
|
|
|
|
|
m_pFuntion->GetInfo("").c_str());
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
nfunction = 1;
|
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_0, "Error", "m_pFuntion is NULL");
|
|
|
|
|
m_nErrorCode = 22;
|
|
|
|
|
m_nCheckResultErrorCode = m_nErrorCode;
|
|
|
|
|
return m_nErrorCode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*AI 边缘定位(内外边缘)*/
|
|
|
|
|
// 模型定位内外边缘
|
|
|
|
|
int reedge = AI_Edge(m_CheckResult_shareP->in_shareImage->img, m_outer_rroi, m_inner_rroi, m_tag_roiList);
|
|
|
|
|
@ -470,6 +495,8 @@ int ImgCheckAnalysisy::CheckRun()
|
|
|
|
|
/*投影对齐模板*/
|
|
|
|
|
// Adapt_Config(m_tplOuterRect, m_outer_rroi, m_CheckResult_shareP->in_shareImage->img);
|
|
|
|
|
m_outer_roi = m_outer_rroi.boundingRect();
|
|
|
|
|
// 旋转矩形 boundingRect 在图像边缘可能越界(如 x=-1),裁剪到图像范围内,避免 image(roi) 越界崩溃
|
|
|
|
|
m_outer_roi &= cv::Rect(0, 0, m_CheckResult_shareP->in_shareImage->img.cols, m_CheckResult_shareP->in_shareImage->img.rows);
|
|
|
|
|
m_Crop_Roi_paramImg = m_outer_roi;
|
|
|
|
|
m_pImageAllResult->pDetResult->CutRoi = m_outer_roi;
|
|
|
|
|
m_pImageAllResult->pDetResult->Param_CropRoi = m_Crop_Roi_paramImg;
|
|
|
|
|
@ -501,7 +528,7 @@ int ImgCheckAnalysisy::CheckRun()
|
|
|
|
|
|
|
|
|
|
/* 检测支架偏移 */
|
|
|
|
|
{
|
|
|
|
|
Base_Function_Support_Det &supportDet = m_pbaseCheckFunction->supportDet;
|
|
|
|
|
Function_Support_Det &supportDet = m_pFuntion->function.f_supportDet;
|
|
|
|
|
if (supportDet.bOpen && supportDet.supportRect.size.width > 0 && supportDet.supportRect.size.height > 0)
|
|
|
|
|
{
|
|
|
|
|
cv::RotatedRect &inner = m_inner_rroi;
|
|
|
|
|
@ -580,6 +607,69 @@ int ImgCheckAnalysisy::CheckRun()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 把手缺失检测:bigmask 的凸起本身不算 NG,只有"参数里的某个把手区域附近找不到把手"才算 NG(每个缺失区域报一条) */
|
|
|
|
|
{
|
|
|
|
|
if (m_bHandleDetSucc && m_bHandleExpect && !m_bHandleFound)
|
|
|
|
|
{
|
|
|
|
|
for (size_t iRegion = 0; iRegion < m_HandleRegionResultList.size(); iRegion++)
|
|
|
|
|
{
|
|
|
|
|
Handle_Region_Result_ ®ionResult = m_HandleRegionResultList[iRegion];
|
|
|
|
|
if (regionResult.bFound)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 上报该把手区域的位置:原图坐标 -> 检测图(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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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];
|
|
|
|
|
|
|
|
|
|
// 计算把手区域中心所在的检测区域
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Tag检测 */
|
|
|
|
|
{
|
|
|
|
|
for (const auto &tagRoi : m_tag_roiList)
|
|
|
|
|
@ -2047,91 +2137,301 @@ int ImgCheckAnalysisy::AI_Edge(const cv::Mat &img, cv::RotatedRect &outerRoi, cv
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*使用m_pEdge_Align_Result->bigmask检测把手是否缺失*/
|
|
|
|
|
// 截取把手大致区域
|
|
|
|
|
RotatedRect tpl_handle_rroi = m_pbaseCheckFunction->supportDet.handleRect;
|
|
|
|
|
Rect handle_rect = Rect(tpl_handle_rroi.boundingRect().x, 0, tpl_handle_rroi.boundingRect().width, m_pEdge_Align_Result->bigmask.rows);
|
|
|
|
|
Mat handle_roi = m_pEdge_Align_Result->bigmask(handle_rect & Rect(0, 0, m_pEdge_Align_Result->bigmask.cols, m_pEdge_Align_Result->bigmask.rows)).clone();
|
|
|
|
|
|
|
|
|
|
// 对handle_roi做一下开运算
|
|
|
|
|
Mat element = getStructuringElement(MORPH_RECT, Size(tpl_handle_rroi.boundingRect().width / 3, tpl_handle_rroi.boundingRect().height / 3));
|
|
|
|
|
morphologyEx(handle_roi, handle_roi, MORPH_OPEN, element);
|
|
|
|
|
{
|
|
|
|
|
long handle_s = CheckUtil::getcurTime();
|
|
|
|
|
CheckHandleByBigMask(img, m_pEdge_Align_Result->bigmask);
|
|
|
|
|
long handle_e = CheckUtil::getcurTime();
|
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Handle", "handle detect use time %ld", handle_e - handle_s);
|
|
|
|
|
}
|
|
|
|
|
return re;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 用 bigmask 检测把手:
|
|
|
|
|
// 1、bigmask 主体近似矩形,把手是主体四周凸起的"类矩形块"
|
|
|
|
|
// 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_HandleCandList.clear();
|
|
|
|
|
m_HandleRegionResultList.clear();
|
|
|
|
|
|
|
|
|
|
if (bigmask.empty())
|
|
|
|
|
{
|
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Handle", "bigmask is empty, skip handle detect");
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// imwrite("handle_roi.png", handle_roi);
|
|
|
|
|
// 1、抠出 bigmask 四周凸起的矩形块
|
|
|
|
|
std::vector<Mask_Protrusion_> protrusionList;
|
|
|
|
|
int re = CheckUtil::GetMaskProtrusions(bigmask, protrusionList);
|
|
|
|
|
if (re != 0)
|
|
|
|
|
{
|
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Handle", "GetMaskProtrusions error %d", re);
|
|
|
|
|
return re;
|
|
|
|
|
}
|
|
|
|
|
m_bHandleDetSucc = true;
|
|
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
for (size_t i = 0; i < supportDet.handleBoxes.size() && i < HANDLE_REGION_MAX_NUM; i++)
|
|
|
|
|
{
|
|
|
|
|
regionBoxList.push_back(supportDet.handleBoxes[i]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
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());
|
|
|
|
|
|
|
|
|
|
// 灰阶判定是预留项:只有开启时才做灰度换算,避免无谓开销
|
|
|
|
|
cv::Mat grayImg;
|
|
|
|
|
if (handleParam.bJudgeGray && !img.empty())
|
|
|
|
|
{
|
|
|
|
|
if (img.channels() != 1)
|
|
|
|
|
{
|
|
|
|
|
cv::cvtColor(img, grayImg, cv::COLOR_BGR2GRAY);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
grayImg = img;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// handle_roi找到最大连通域,和tpl_handle_rroi做面积形状比较,差异过大判为把手缺失,添加到缺陷并NG
|
|
|
|
|
// 3、凸起候选实测特征(面积/填充率/灰度差,供日志与后续判定用)
|
|
|
|
|
for (size_t i = 0; i < protrusionList.size(); i++)
|
|
|
|
|
{
|
|
|
|
|
Base_Function_Support_Det &handleDet = m_pbaseCheckFunction->supportDet;
|
|
|
|
|
if (handleDet.bOpen && tpl_handle_rroi.size.width > 0 && tpl_handle_rroi.size.height > 0 && !handle_roi.empty())
|
|
|
|
|
Handle_Candidate_ cand;
|
|
|
|
|
cand.protr = protrusionList[i];
|
|
|
|
|
// 左/右凸起:沿边为 y 方向,深度为 x 方向;上/下凸起相反
|
|
|
|
|
bool bHorz = (cand.protr.nSide == 0 || cand.protr.nSide == 1);
|
|
|
|
|
cand.nLen = bHorz ? cand.protr.roi.height : cand.protr.roi.width;
|
|
|
|
|
cand.nDepth = bHorz ? cand.protr.roi.width : cand.protr.roi.height;
|
|
|
|
|
if (!grayImg.empty())
|
|
|
|
|
{
|
|
|
|
|
cand.fGrayDiff = CalProtrusionGrayDiff(grayImg, 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",
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 4、逐个把手区域:在区域附近找宽高匹配的凸起(同一个凸起只匹配给一个区域)
|
|
|
|
|
std::vector<bool> usedList(m_HandleCandList.size(), false);
|
|
|
|
|
int nFound = 0;
|
|
|
|
|
for (size_t ir = 0; ir < regionBoxList.size(); ir++)
|
|
|
|
|
{
|
|
|
|
|
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)
|
|
|
|
|
{
|
|
|
|
|
std::vector<std::vector<cv::Point>> handle_contours;
|
|
|
|
|
cv::findContours(handle_roi, handle_contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
|
|
|
|
|
usedList[nMatchIdx] = true;
|
|
|
|
|
nFound++;
|
|
|
|
|
}
|
|
|
|
|
m_HandleRegionResultList.push_back(result);
|
|
|
|
|
|
|
|
|
|
bool bHandleLoss = false;
|
|
|
|
|
if (handle_contours.empty())
|
|
|
|
|
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());
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
if (bigmask.channels() == 1)
|
|
|
|
|
{
|
|
|
|
|
cv::cvtColor(bigmask, show, cv::COLOR_GRAY2BGR);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
show = bigmask.clone();
|
|
|
|
|
}
|
|
|
|
|
for (size_t i = 0; i < m_HandleCandList.size(); i++)
|
|
|
|
|
{
|
|
|
|
|
cv::rectangle(show, m_HandleCandList[i].protr.roi, cv::Scalar(0, 0, 255), 8);
|
|
|
|
|
}
|
|
|
|
|
for (size_t i = 0; i < m_HandleRegionResultList.size(); i++)
|
|
|
|
|
{
|
|
|
|
|
cv::rectangle(show, m_HandleRegionResultList[i].boxRegion, cv::Scalar(255, 0, 0), 8);
|
|
|
|
|
if (m_HandleRegionResultList[i].bFound)
|
|
|
|
|
{
|
|
|
|
|
bHandleLoss = true; // 没有连通域,把手完全缺失
|
|
|
|
|
cv::rectangle(show, m_HandleRegionResultList[i].roiHandle, cv::Scalar(0, 255, 0), 12);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// 取最大连通域
|
|
|
|
|
std::sort(handle_contours.begin(), handle_contours.end(), compareContourAreas);
|
|
|
|
|
cv::RotatedRect cur_handle_rrect = cv::minAreaRect(handle_contours[0]);
|
|
|
|
|
double curArea = cv::contourArea(handle_contours[0]);
|
|
|
|
|
|
|
|
|
|
// 面积比较
|
|
|
|
|
double tplArea = tpl_handle_rroi.size.width * tpl_handle_rroi.size.height;
|
|
|
|
|
double areaRatio = (tplArea > 0.0) ? (curArea / tplArea) : 0.0;
|
|
|
|
|
|
|
|
|
|
// 形状比较(长宽比)
|
|
|
|
|
float tplW = std::max(tpl_handle_rroi.size.width, tpl_handle_rroi.size.height);
|
|
|
|
|
float tplH = std::min(tpl_handle_rroi.size.width, tpl_handle_rroi.size.height);
|
|
|
|
|
float curW = std::max(cur_handle_rrect.size.width, cur_handle_rrect.size.height);
|
|
|
|
|
float curH = std::min(cur_handle_rrect.size.width, cur_handle_rrect.size.height);
|
|
|
|
|
float tplRatio = (tplH > 0.0f) ? (tplW / tplH) : 0.0f;
|
|
|
|
|
float curRatio = (curH > 0.0f) ? (curW / curH) : 0.0f;
|
|
|
|
|
float ratioDiff = (tplRatio > 0.0f) ? fabs(curRatio - tplRatio) / tplRatio : 0.0f;
|
|
|
|
|
|
|
|
|
|
// 面积差异过大 或 形状差异过大 判为把手缺失
|
|
|
|
|
if (areaRatio < 0.6f || ratioDiff > 0.6f)
|
|
|
|
|
{
|
|
|
|
|
bHandleLoss = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
cv::Mat showSmall;
|
|
|
|
|
cv::resize(show, showSmall, cv::Size(show.cols / 4, show.rows / 4));
|
|
|
|
|
std::vector<int> paramJpg = {cv::IMWRITE_JPEG_QUALITY, 90};
|
|
|
|
|
cv::imwrite(DetImgInfo_shareP->strChannel + "_handle_det.jpg", showSmall, paramJpg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_0, "把手检测", "areaRatio %f ratioDiff %f", areaRatio, ratioDiff);
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (bHandleLoss)
|
|
|
|
|
{
|
|
|
|
|
QX_ERROR_INFO_ temerror;
|
|
|
|
|
temerror.Idx = m_pDetResult->pQx_ErrorList->size();
|
|
|
|
|
// 在某个把手区域附近找宽高匹配的凸起
|
|
|
|
|
// 匹配条件: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)
|
|
|
|
|
{
|
|
|
|
|
if (boxRegion.width <= 0 || boxRegion.height <= 0)
|
|
|
|
|
{
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 把手模板 roi 在原图坐标系,转换为检测图(detImg)坐标系
|
|
|
|
|
cv::Rect roi = tpl_handle_rroi.boundingRect();
|
|
|
|
|
roi.x -= outerRoi.boundingRect().x;
|
|
|
|
|
roi.y -= outerRoi.boundingRect().y;
|
|
|
|
|
roi &= cv::Rect(0, 0, outerRoi.boundingRect().width, outerRoi.boundingRect().height);
|
|
|
|
|
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 = tpl_handle_rroi.size.width;
|
|
|
|
|
float h = tpl_handle_rroi.size.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_support_loss;
|
|
|
|
|
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];
|
|
|
|
|
temerror.detRegionidxList.push_back(0);
|
|
|
|
|
// 搜索范围:把手区域外扩(宽/高各外扩区域尺寸的一半),容忍产品摆放偏差
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
m_pDetResult->pQx_ErrorList->push_back(temerror);
|
|
|
|
|
int nBestIdx = -1;
|
|
|
|
|
double fBestDis = 1e18;
|
|
|
|
|
int nNearIdx = -1; // 区域内离区域中心最近的凸起(仅用于日志排查)
|
|
|
|
|
double fNearDis = 1e18;
|
|
|
|
|
for (size_t i = 0; i < candList.size(); i++)
|
|
|
|
|
{
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_0, "把手检测", "handle loss NG roi [%d %d %d %d]", roi.x, roi.y, roi.width, roi.height);
|
|
|
|
|
}
|
|
|
|
|
// 位置:凸起中心要落在把手区域附近
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return re;
|
|
|
|
|
if (nBestIdx < 0 && nNearIdx >= 0)
|
|
|
|
|
{
|
|
|
|
|
// 没匹配上时打印区域内最近的凸起,便于排查阈值
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (nBestIdx >= 0)
|
|
|
|
|
{
|
|
|
|
|
roiHandle = candList[nBestIdx].protr.roi;
|
|
|
|
|
}
|
|
|
|
|
return nBestIdx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 计算凸起区域与周边背景的平均灰度差(灰阶判定的预留量测值,grayImg 需为单通道)
|
|
|
|
|
float ImgCheckAnalysisy::CalProtrusionGrayDiff(const cv::Mat &grayImg, const cv::Rect &roi)
|
|
|
|
|
{
|
|
|
|
|
if (grayImg.empty() || grayImg.channels() != 1 || roi.width <= 0 || roi.height <= 0)
|
|
|
|
|
{
|
|
|
|
|
return 0.0f;
|
|
|
|
|
}
|
|
|
|
|
// 凸起区域通常整个都在掩膜内,向外扩一圈才能取到背景
|
|
|
|
|
int nExpand = std::max(20, std::min(roi.width, roi.height) / 2);
|
|
|
|
|
cv::Rect roiBg(roi.x - nExpand, roi.y - nExpand, roi.width + 2 * nExpand, roi.height + 2 * nExpand);
|
|
|
|
|
roiBg &= cv::Rect(0, 0, grayImg.cols, grayImg.rows);
|
|
|
|
|
if (roiBg.width <= 0 || roiBg.height <= 0)
|
|
|
|
|
{
|
|
|
|
|
return 0.0f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cv::Mat grayRoi = grayImg(roiBg);
|
|
|
|
|
cv::Mat maskIn = cv::Mat::zeros(roiBg.size(), CV_8UC1);
|
|
|
|
|
cv::Rect inter = roi & roiBg;
|
|
|
|
|
cv::rectangle(maskIn, inter - roiBg.tl(), cv::Scalar(255), cv::FILLED);
|
|
|
|
|
cv::Mat maskOut;
|
|
|
|
|
cv::bitwise_not(maskIn, maskOut);
|
|
|
|
|
if (cv::countNonZero(maskIn) <= 0 || cv::countNonZero(maskOut) <= 0)
|
|
|
|
|
{
|
|
|
|
|
return 0.0f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
double fMeanIn = cv::mean(grayRoi, maskIn)[0];
|
|
|
|
|
double fMeanOut = cv::mean(grayRoi, maskOut)[0];
|
|
|
|
|
double fDiff = fMeanIn - fMeanOut;
|
|
|
|
|
return (float)(fDiff >= 0 ? fDiff : -fDiff);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int ImgCheckAnalysisy::CalProductSize()
|
|
|
|
|
|