feat 支架缺失,偏移旋转角

dev_lsy
liusiyang 3 weeks ago
parent ec5933dba1
commit a24995500b

@ -272,6 +272,17 @@ int AI_Edge_Algin::Detect(const cv::Mat &img, DetConfig *pDetConfig, std::shared
return re0;
}
if (re1 != 0)
{
// Align_Inner(支架)未检出有效轮廓(4=无轮廓 / 6=轮廓点太少):
// 支架检测开启时不视为定位失败,继续带 smallmask(可能为空/全零) 返回,
// 由后续"支架缺失"判定(support_min_area)上报 ZJ_QueShi NG
bool bSupportDetOn = (pDetConfig->pBaseCheckFunction != NULL && pDetConfig->pBaseCheckFunction->supportDet.bDetSupport);
if (bSupportDetOn && (re1 == 4 || re1 == 6))
{
m_pdetlog->AddCheckstr(PrintLevel_2, DET_LOG_LEVEL_3, "AI_Edge_Algin ", "AICheck_Edge_Small no contour(%d), treat as support missing ", re1);
re1 = 0;
}
else
{
m_pdetlog->AddCheckstr(PrintLevel_2, DET_LOG_LEVEL_3, "AI_Edge_Algin ", "AICheck_Edge_Small----error %d ", re1);
if (m_pDetConfig->IsSaveProcessImg())
@ -280,6 +291,7 @@ int AI_Edge_Algin::Detect(const cv::Mat &img, DetConfig *pDetConfig, std::shared
}
return re1;
}
}
if (re2 != 0)
{
m_pdetlog->AddCheckstr(PrintLevel_2, DET_LOG_LEVEL_3, "AI_Edge_Algin ", "AICheck_Tag----error %d ", re2);

@ -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)
{

@ -929,7 +929,8 @@ struct Function_Support_Det
std::vector<std::string> edge_select; // 参与计算的方向:up/down/left/right
float x_offset;
float y_offset;
float r_offset;
float r_offset; // 支架旋转偏移阈值(度):支架旋转矩形长轴(对应左/右边缘)与产品左/右边缘夹角超过该值判偏移;<=0 不判定
float support_min_area; // 支架缺失判定:支架面积阈值(单位 mm²)。支架由 Align_Inner 模型(smallmask)检出,支架面积 < 该值判缺失;<=0 不判定
// 把手参数(Handle_Detect)
bool bDetHandle; // 把手缺失检测开关(Handle_Detect.isOpen)
// 把手区域(最多 HANDLE_REGION_MAX_NUM 个,只保存非空的;区域宽高用于把手卡控)
@ -966,6 +967,7 @@ struct Function_Support_Det
x_offset = 0;
y_offset = 0;
r_offset = 0;
support_min_area = 0;
bDetHandle = false;
handleRegions.clear();
handleBoxes.clear();
@ -993,6 +995,7 @@ struct Function_Support_Det
this->x_offset = tem.x_offset;
this->y_offset = tem.y_offset;
this->r_offset = tem.r_offset;
this->support_min_area = tem.support_min_area;
this->bDetHandle = tem.bDetHandle;
this->handleRegions.assign(tem.handleRegions.begin(), tem.handleRegions.end());
this->handleBoxes.assign(tem.handleBoxes.begin(), tem.handleBoxes.end());
@ -1011,16 +1014,16 @@ struct Function_Support_Det
}
void print(std::string str)
{
printf("%s>>bDetSupport %d bDrawSupport %d bDetHandle %d bDetTag %d x_standard %f y_standard %f x_offset %f y_offset %f r_offset %f edge num %ld handleRegion num %ld handle_diff %f handleRange[size %.2f~%.2f search %.1f depth %.1f area %.1f margin %.1f(0=自动,<0=默认)] BQ[area %f energy %f len %f hj %f]\n",
str.c_str(), bDetSupport, bDrawSupport, bDetHandle, bDetTag, x_standard, y_standard, x_offset, y_offset, r_offset, edge_select.size(), handleRegions.size(), handle_diff,
printf("%s>>bDetSupport %d bDrawSupport %d bDetHandle %d bDetTag %d x_standard %f y_standard %f x_offset %f y_offset %f r_offset %f support_min_area %f edge num %ld handleRegion num %ld handle_diff %f handleRange[size %.2f~%.2f search %.1f depth %.1f area %.1f margin %.1f(0=自动,<0=默认)] BQ[area %f energy %f len %f hj %f]\n",
str.c_str(), bDetSupport, bDrawSupport, bDetHandle, bDetTag, x_standard, y_standard, x_offset, y_offset, r_offset, support_min_area, edge_select.size(), handleRegions.size(), handle_diff,
handle_size_min_ratio, handle_size_max_ratio, handle_search_ratio, handle_min_depth_coef, handle_min_area_coef, handle_edge_margin_coef,
BQ_area, BQ_energy, BQ_len, BQ_hj);
}
std::string GetInfo(std::string str)
{
char buffer[1024];
sprintf(buffer, "%s>>bDetSupport %d bDrawSupport %d bDetHandle %d bDetTag %d x_standard %f y_standard %f x_offset %f y_offset %f r_offset %f edge num %ld handleRegion num %ld handle_diff %f handleRange[size %.2f~%.2f search %.1f depth %.1f area %.1f margin %.1f(0=自动,<0=默认)] BQ[area %f energy %f len %f hj %f]\n",
str.c_str(), bDetSupport, bDrawSupport, bDetHandle, bDetTag, x_standard, y_standard, x_offset, y_offset, r_offset, edge_select.size(), handleRegions.size(), handle_diff,
sprintf(buffer, "%s>>bDetSupport %d bDrawSupport %d bDetHandle %d bDetTag %d x_standard %f y_standard %f x_offset %f y_offset %f r_offset %f support_min_area %f edge num %ld handleRegion num %ld handle_diff %f handleRange[size %.2f~%.2f search %.1f depth %.1f area %.1f margin %.1f(0=自动,<0=默认)] BQ[area %f energy %f len %f hj %f]\n",
str.c_str(), bDetSupport, bDrawSupport, bDetHandle, bDetTag, x_standard, y_standard, x_offset, y_offset, r_offset, support_min_area, edge_select.size(), handleRegions.size(), handle_diff,
handle_size_min_ratio, handle_size_max_ratio, handle_search_ratio, handle_min_depth_coef, handle_min_area_coef, handle_edge_margin_coef,
BQ_area, BQ_energy, BQ_len, BQ_hj);
std::string str123 = buffer;

@ -952,6 +952,12 @@ int BaseFuntonConfigJson::GetFunction(Json::Value value)
_config.supportDet.r_offset = value_param["r_offset"].asFloat();
}
// 支架缺失判定:支架(Align_Inner 模型)面积 < support_min_area(单位 mm²)→ 支架缺失;<=0 不判定(兼容旧参数)
if (value_param["support_min_area"].isNumeric())
{
_config.supportDet.support_min_area = value_param["support_min_area"].asFloat();
}
auto value_qx = value_param["edge_select"];
if (value_qx.isArray())
{

Loading…
Cancel
Save