|
|
|
|
@ -1068,14 +1068,17 @@ int ImgCheckAnalysisy::CheckRun()
|
|
|
|
|
|
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_0, "3、pre detect", "-------------------------pre Det--------------- \n");
|
|
|
|
|
|
|
|
|
|
/* 检测支架偏移(bDetSupport 开关关闭时不做支架判定)
|
|
|
|
|
/* 检测支架缺失 + 支架偏移(bDetSupport 开关关闭时不做支架判定)
|
|
|
|
|
不使用参数绘制区域,结合两次定位的 mask 计算:
|
|
|
|
|
1、产品边缘:优先复用把手提取对 bigmask 做的"四边抗凸起拟合"主体四边形
|
|
|
|
|
1、smallmask(=Align_Inner 模型输出)最大连通域 = 支架区域 rs(按定位裁剪区 m_outer_rroi 外接矩形映射回原图坐标)
|
|
|
|
|
2、支架缺失:支架面积(轮廓面积 × 缩放 × 成像精度 X/Y,单位 mm²) < support_min_area → 支架缺失 NG(ZJ_QueShi),
|
|
|
|
|
缺失时不再做偏移判定(几何不可靠,避免重复/误报)
|
|
|
|
|
3、产品边缘:优先复用把手提取对 bigmask 做的"四边抗凸起拟合"主体四边形
|
|
|
|
|
(CheckHandleByBigMask -> GetMaskProtrusions;边缘上的凸起(把手等)不会把边缘带偏),
|
|
|
|
|
拟合不可用/无效时回退 bigmask 最大连通域外接矩形 rp(近似矩形,四周可能有凸起)
|
|
|
|
|
2、smallmask 最大连通域 = 支架区域 rs(按定位裁剪区 m_outer_rroi 外接矩形映射回原图坐标)
|
|
|
|
|
3、按 edge_select 指定方向(left/right/up/down)计算 rs 中心到产品对应边缘的距离
|
|
|
|
|
4、计算值与标准值(x_standard/y_standard)做差取平均,平均差超过 offset(x_offset) → 偏移 */
|
|
|
|
|
4、按 edge_select 指定方向(left/right/up/down)计算 rs 中心到产品对应边缘的距离
|
|
|
|
|
5、计算值与标准值(x_standard/y_standard)做差取平均,平均差超过 offset(x_offset) → 偏移
|
|
|
|
|
6、旋转偏移:支架最小外接旋转矩形的长轴(对应左/右边缘)与产品左/右边缘夹角超过 r_offset(度) → 偏移 */
|
|
|
|
|
{
|
|
|
|
|
Function_Support_Det &supportDet = m_pbaseCheckFunction->supportDet;
|
|
|
|
|
m_SupportDrawInfo.Reset(); // 清空本帧绘制信息(未开启/计算无效时不绘制)
|
|
|
|
|
@ -1127,7 +1130,81 @@ int ImgCheckAnalysisy::CheckRun()
|
|
|
|
|
std::vector<cv::Point> rpContour = CheckUtil::getLargestContourROI(bigBin, bFoundRp);
|
|
|
|
|
bool bFoundRs = false;
|
|
|
|
|
std::vector<cv::Point> rsContour = CheckUtil::getLargestContourROI(smallBin, bFoundRs);
|
|
|
|
|
if (!bFoundRp || rpContour.size() < 3 || !bFoundRs || rsContour.size() < 3)
|
|
|
|
|
|
|
|
|
|
// smallmask -> 原图坐标的比例:smallmask 对应定位时裁剪的大轮廓区域(m_outer_rroi 外接矩形),按比例映射
|
|
|
|
|
cv::Rect bigRect = m_outer_rroi.boundingRect();
|
|
|
|
|
float fx = smallmask.cols > 0 ? bigRect.width * 1.0f / smallmask.cols : 1.0f;
|
|
|
|
|
float fy = smallmask.rows > 0 ? bigRect.height * 1.0f / smallmask.rows : 1.0f;
|
|
|
|
|
|
|
|
|
|
// rs 映射回原图坐标(支架轮廓缺失时保持空矩形,面积按 0 处理)
|
|
|
|
|
cv::Rect rsRect;
|
|
|
|
|
if (bFoundRs && rsContour.size() >= 3)
|
|
|
|
|
{
|
|
|
|
|
cv::Rect rsSmallRect = cv::boundingRect(rsContour);
|
|
|
|
|
rsRect = cv::Rect(
|
|
|
|
|
cvRound(bigRect.x + rsSmallRect.x * fx),
|
|
|
|
|
cvRound(bigRect.y + rsSmallRect.y * fy),
|
|
|
|
|
cvRound(rsSmallRect.width * fx),
|
|
|
|
|
cvRound(rsSmallRect.height * fy));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3、支架缺失判定:支架由 Align_Inner 模型(smallmask)检出,支架面积 < support_min_area(单位 mm²) → 缺失
|
|
|
|
|
// 面积换算:smallmask 轮廓面积 × fx × fy = 原图像素面积,再乘成像精度 X/Y(mm/px)
|
|
|
|
|
float fScaleX = (m_fImgage_Scale_X > 0) ? m_fImgage_Scale_X : 1.0f;
|
|
|
|
|
float fScaleY = (m_fImgage_Scale_Y > 0) ? m_fImgage_Scale_Y : 1.0f;
|
|
|
|
|
double dSupportAreaSmall = (bFoundRs && rsContour.size() >= 3) ? cv::contourArea(rsContour) : 0.0;
|
|
|
|
|
double dSupportAreaMm2 = dSupportAreaSmall * fx * fy * fScaleX * fScaleY;
|
|
|
|
|
bool bSupportMiss = (supportDet.support_min_area > 0) && (dSupportAreaMm2 < supportDet.support_min_area);
|
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_0, "支架检测",
|
|
|
|
|
"support area %.2f mm2 (small px %.1f, scale %.4f %.4f) min_area %.2f miss %d",
|
|
|
|
|
dSupportAreaMm2, dSupportAreaSmall, fx * fScaleX, fy * fScaleY,
|
|
|
|
|
supportDet.support_min_area, bSupportMiss);
|
|
|
|
|
|
|
|
|
|
if (bSupportMiss)
|
|
|
|
|
{
|
|
|
|
|
// 支架缺失 NG:ROI 取支架区域(有轮廓时)或整个产品区域(无轮廓时),映射到检测图(detImg)坐标
|
|
|
|
|
cv::Rect missRect = (rsRect.width > 0 && rsRect.height > 0) ? rsRect : bigRect;
|
|
|
|
|
cv::Rect roi = MatAffineRect(m_matSrc2Det, missRect);
|
|
|
|
|
roi &= cv::Rect(0, 0, m_pImageAllResult->detImg.cols, m_pImageAllResult->detImg.rows);
|
|
|
|
|
|
|
|
|
|
QX_ERROR_INFO_ temerror;
|
|
|
|
|
temerror.Idx = m_pDetResult->pQx_ErrorList->size();
|
|
|
|
|
temerror.roi = roi;
|
|
|
|
|
temerror.area = roi.width * roi.height;
|
|
|
|
|
temerror.JudgArea = roi.width * fScaleX * roi.height * fScaleY;
|
|
|
|
|
temerror.JudgArea_second = temerror.JudgArea;
|
|
|
|
|
float w = missRect.width;
|
|
|
|
|
float h = missRect.height;
|
|
|
|
|
temerror.flen = (w > h ? w : h) * fScaleX;
|
|
|
|
|
temerror.fbreadth = (w > h ? h : w) * fScaleY;
|
|
|
|
|
temerror.nconfig_qx_type = CONFIG_QX_NAME_cell_ZJ_QueShi;
|
|
|
|
|
temerror.qx_name = CONFIG_QX_NAME_Names[temerror.nconfig_qx_type];
|
|
|
|
|
temerror.result = QX_RESULT_TYPE_NG;
|
|
|
|
|
temerror.result_name = QX_RESULT_TYPE_Names[QX_RESULT_TYPE_NG];
|
|
|
|
|
|
|
|
|
|
// 计算支架(缺失区域)中心所在的检测区域
|
|
|
|
|
cv::Point pCenter;
|
|
|
|
|
pCenter.x = roi.x + roi.width * 0.5;
|
|
|
|
|
pCenter.y = roi.y + roi.height * 0.5;
|
|
|
|
|
int nmaxregionIdx = 0;
|
|
|
|
|
for (int iregion = 0; iregion < m_DetRoiList.roiList_Src.size(); iregion++)
|
|
|
|
|
{
|
|
|
|
|
const std::vector<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 support missing: area %.2f mm2 < min_area %.2f roi [%d %d %d %d]",
|
|
|
|
|
temerror.qx_name.c_str(), temerror.Idx, dSupportAreaMm2, supportDet.support_min_area,
|
|
|
|
|
roi.x, roi.y, roi.width, roi.height);
|
|
|
|
|
}
|
|
|
|
|
else if (!bFoundRp || rpContour.size() < 3 || !bFoundRs || rsContour.size() < 3)
|
|
|
|
|
{
|
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_0, "支架检测", "contour invalid (rp %d %ld, rs %d %ld), skip support offset",
|
|
|
|
|
bFoundRp, rpContour.size(), bFoundRs, rsContour.size());
|
|
|
|
|
@ -1137,17 +1214,6 @@ int ImgCheckAnalysisy::CheckRun()
|
|
|
|
|
// rp 边缘(拟合不可用时的回退):产品最大连通域的外接矩形(含凸起)
|
|
|
|
|
cv::Rect rpRect = cv::boundingRect(rpContour);
|
|
|
|
|
|
|
|
|
|
// rs 映射回原图坐标:smallmask 对应定位时裁剪的大轮廓区域(m_outer_rroi 外接矩形),按比例映射
|
|
|
|
|
cv::Rect bigRect = m_outer_rroi.boundingRect();
|
|
|
|
|
float fx = bigRect.width * 1.0f / smallmask.cols;
|
|
|
|
|
float fy = bigRect.height * 1.0f / smallmask.rows;
|
|
|
|
|
cv::Rect rsSmallRect = cv::boundingRect(rsContour);
|
|
|
|
|
cv::Rect rsRect(
|
|
|
|
|
cvRound(bigRect.x + rsSmallRect.x * fx),
|
|
|
|
|
cvRound(bigRect.y + rsSmallRect.y * fy),
|
|
|
|
|
cvRound(rsSmallRect.width * fx),
|
|
|
|
|
cvRound(rsSmallRect.height * fy));
|
|
|
|
|
|
|
|
|
|
// 3、产品边缘:优先复用把手提取对 bigmask 做的"四边抗凸起拟合"主体四边形
|
|
|
|
|
// (CheckHandleByBigMask -> GetMaskProtrusions 输出):边缘上的凸起(把手等)不会把边缘带偏;
|
|
|
|
|
// 拟合不可用/无效时回退到产品最大连通域外接矩形 rpRect(含凸起)
|
|
|
|
|
@ -1223,6 +1289,70 @@ int ImgCheckAnalysisy::CheckRun()
|
|
|
|
|
ptSegBArr[3] = cv::Point2f((float)(rpRect.x + rpRect.width), (float)(rpRect.y + rpRect.height));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3.5、旋转偏移:支架最小外接旋转矩形(长轴对应左/右边缘、短轴对应上/下边缘)
|
|
|
|
|
// 长轴与产品左/右边缘的夹角 > r_offset(度) → 偏移;r_offset<=0 不判定(兼容旧参数)
|
|
|
|
|
float fSupportAngle = 0; // 支架长轴相对产品左/右边缘的夹角(度,带符号;正=支架长轴相对产品边缘向右倾)
|
|
|
|
|
bool bRotOffset = false;
|
|
|
|
|
if (supportDet.r_offset > 0 && rsContour.size() >= 3)
|
|
|
|
|
{
|
|
|
|
|
// 轮廓先映射回原图坐标再拟合旋转矩形(smallmask 的 x/y 缩放可能不同,不还原会带角度误差)
|
|
|
|
|
std::vector<cv::Point2f> rsContourSrc;
|
|
|
|
|
rsContourSrc.reserve(rsContour.size());
|
|
|
|
|
for (size_t ip = 0; ip < rsContour.size(); ip++)
|
|
|
|
|
{
|
|
|
|
|
rsContourSrc.emplace_back(rsContour[ip].x * fx, rsContour[ip].y * fy);
|
|
|
|
|
}
|
|
|
|
|
cv::RotatedRect rsRotRect = cv::minAreaRect(rsContourSrc);
|
|
|
|
|
cv::Point2f rpts[4];
|
|
|
|
|
rsRotRect.points(rpts);
|
|
|
|
|
cv::Point2f d01 = rpts[1] - rpts[0];
|
|
|
|
|
cv::Point2f d12 = rpts[2] - rpts[1];
|
|
|
|
|
float len01 = (float)cv::norm(d01);
|
|
|
|
|
float len12 = (float)cv::norm(d12);
|
|
|
|
|
// 长轴方向 = 长边方向(长轴对应左/右边缘),并统一朝下(与左边缘 上->下 方向一致)
|
|
|
|
|
cv::Point2f vLong(0, 1);
|
|
|
|
|
if (len01 >= len12 && len01 > 1e-3f)
|
|
|
|
|
{
|
|
|
|
|
vLong = d01 * (1.0f / len01);
|
|
|
|
|
}
|
|
|
|
|
else if (len12 > 1e-3f)
|
|
|
|
|
{
|
|
|
|
|
vLong = d12 * (1.0f / len12);
|
|
|
|
|
}
|
|
|
|
|
if (vLong.y < 0)
|
|
|
|
|
{
|
|
|
|
|
vLong = -vLong;
|
|
|
|
|
}
|
|
|
|
|
// 参考方向 = 产品左边缘(ptSegAArr[0]->ptSegBArr[0],拟合边/回退矩形边);退化时用垂直方向
|
|
|
|
|
cv::Point2f vRef = ptSegBArr[0] - ptSegAArr[0];
|
|
|
|
|
float lenRef = (float)cv::norm(vRef);
|
|
|
|
|
if (lenRef > 1e-3f)
|
|
|
|
|
{
|
|
|
|
|
vRef *= (1.0f / lenRef);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
vRef = cv::Point2f(0, 1);
|
|
|
|
|
}
|
|
|
|
|
if (vRef.y < 0)
|
|
|
|
|
{
|
|
|
|
|
vRef = -vRef;
|
|
|
|
|
}
|
|
|
|
|
// 有符号夹角(度);长轴是“线”,角度归一化到 [-90, 90]
|
|
|
|
|
double dCross = (double)vLong.x * vRef.y - (double)vLong.y * vRef.x;
|
|
|
|
|
double dDot = (double)vLong.x * vRef.x + (double)vLong.y * vRef.y;
|
|
|
|
|
fSupportAngle = (float)(std::atan2(dCross, dDot) * 180.0 / CV_PI);
|
|
|
|
|
if (fSupportAngle > 90.0f)
|
|
|
|
|
{
|
|
|
|
|
fSupportAngle -= 180.0f;
|
|
|
|
|
}
|
|
|
|
|
else if (fSupportAngle < -90.0f)
|
|
|
|
|
{
|
|
|
|
|
fSupportAngle += 180.0f;
|
|
|
|
|
}
|
|
|
|
|
bRotOffset = (fabs(fSupportAngle) > supportDet.r_offset);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 像素 -> mm
|
|
|
|
|
fDL *= m_fImgage_Scale_X;
|
|
|
|
|
fDR *= m_fImgage_Scale_X;
|
|
|
|
|
@ -1288,9 +1418,11 @@ int ImgCheckAnalysisy::CheckRun()
|
|
|
|
|
sprintf(strTmp, "%.1f ", fDiff);
|
|
|
|
|
strDiff += strTmp;
|
|
|
|
|
}
|
|
|
|
|
// edge_select 为空或无效时 nDiffNum=0,结果不参与判定(仅打日志)
|
|
|
|
|
// edge_select 为空或无效时 nDiffNum=0,距离不参与判定(仅打日志)
|
|
|
|
|
float fAvgDiff = (nDiffNum > 0) ? (fSumDiff / nDiffNum) : 0.0f;
|
|
|
|
|
bool bOffset = (nDiffNum > 0) && (fAvgDiff > supportDet.x_offset);
|
|
|
|
|
bool bOffsetDist = (nDiffNum > 0) && (fAvgDiff > supportDet.x_offset);
|
|
|
|
|
// 距离偏移 或 旋转偏移(r_offset) 任一超限 → 支架偏移
|
|
|
|
|
bool bOffset = bOffsetDist || bRotOffset;
|
|
|
|
|
|
|
|
|
|
// 记录绘制信息(DrawResult_Step_1 用):支架中心十字 + 各方向距离
|
|
|
|
|
m_SupportDrawInfo.bValid = true;
|
|
|
|
|
@ -1300,11 +1432,13 @@ int ImgCheckAnalysisy::CheckRun()
|
|
|
|
|
m_SupportDrawInfo.ptCenter = cv::Point2f(fCx, fCy);
|
|
|
|
|
|
|
|
|
|
m_pdetlog->AddCheckstr(PrintLevel_0, "支架检测",
|
|
|
|
|
"rp [%d %d %d %d] rs [%d %d %d %d] scale (%.4f %.4f) center (%.1f %.1f) edge %s quad %ld dL %.1f dR %.1f dT %.1f dB %.1f | select %s diff %s avgDiff %.1f limit %.1f ng %d",
|
|
|
|
|
"rp [%d %d %d %d] rs [%d %d %d %d] support area %.2f mm2 (min %.2f) scale (%.4f %.4f) center (%.1f %.1f) edge %s quad %ld dL %.1f dR %.1f dT %.1f dB %.1f | select %s diff %s avgDiff %.1f limit %.1f | rot %.2f deg limit %.2f ng %d",
|
|
|
|
|
rpRect.x, rpRect.y, rpRect.width, rpRect.height,
|
|
|
|
|
rsRect.x, rsRect.y, rsRect.width, rsRect.height,
|
|
|
|
|
dSupportAreaMm2, supportDet.support_min_area,
|
|
|
|
|
fx, fy, fCx, fCy, bFitQuad ? "fitquad" : "rpbox", (long)m_HandleBodyQuad.size(), fDL, fDR, fDT, fDB,
|
|
|
|
|
strSelect.c_str(), strDiff.c_str(), fAvgDiff, supportDet.x_offset, bOffset);
|
|
|
|
|
strSelect.c_str(), strDiff.c_str(), fAvgDiff, supportDet.x_offset,
|
|
|
|
|
fSupportAngle, supportDet.r_offset, bOffset);
|
|
|
|
|
|
|
|
|
|
if (bOffset)
|
|
|
|
|
{
|
|
|
|
|
|