feat 精细化找支架边缘

dev_lsy
liusiyang 2 weeks ago
parent ac5855208c
commit 2d1ce72a6f

@ -59,6 +59,27 @@
// edge_refine_step:沿边采样步长(像素)。不需要每个点都找,调大可省时间;调小边缘拟合更稳 // edge_refine_step:沿边采样步长(像素)。不需要每个点都找,调大可省时间;调小边缘拟合更稳
#define EDGE_REFINE_STEP_DEFAULT 40 #define EDGE_REFINE_STEP_DEFAULT 40
// ===== 支架方向精细化默认参数 =====
// (JSON: Support_Detect.form.support_param 下的
// support_refine_search / support_refine_step / support_refine_segments,均可选)
// 背景:支架由 Align_Inner 模型(smallmask,1696×800)检出,上采样回原图后边缘量化误差约 4px,
// 且支架区域接近方形时 minAreaRect 的"长轴"方向不稳定,用它求旋转角误差大。
// 改为用模型支架边缘做初值,在原图(detImg 灰度)上拟合支架长边(朝产品内侧的边)求方向角。
// support_refine_search:取模型支架外接矩形最外侧多少列作为边缘搜索带(未配置默认 30 列)。
// 右边缘 → 取外接矩形最右 nStrip 列,从右往左逐像素找灰度梯度最大的点;
// 左边缘 → 取最左 nStrip 列,从左往右找(相反逻辑)
// 调大:覆盖更大的模型误差/边缘更靠内,但可能扫到支架内部结构;
// 调小:更严格,模型边缘偏差大时找不到边缘(回退 minAreaRect 方案)
#define SUPPORT_REFINE_STRIP_DEFAULT 30
// support_refine_step:沿支架长边采样的步长(像素)。调大可省时间;调小拟合更稳
#define SUPPORT_REFINE_STEP_DEFAULT 20
// support_refine_segments:把采样点沿边分成 n 段,每段稳健拟合一条直线,取各段方向角的中位数。
// 支架边缘有凸起时:凸起处采样点成为所在段的离群点(MAD 剔除);凸起外边缘与支架边缘平行,
// 即使拟合到凸起外边缘也不影响方向角
#define SUPPORT_REFINE_SEGMENTS_DEFAULT 6
// 搜索列带内相邻像素(3 行灰度均值)的灰度差小于该值视为无边缘(极性无关,支架比外侧亮/暗均可)
#define SUPPORT_REFINE_MIN_CONTRAST 15.0
// 分类映射配置文件(每行:label索引:缺陷名称;冒号后名称为该缺陷类型名称,行的顺序即缺陷类型索引) // 分类映射配置文件(每行:label索引:缺陷名称;冒号后名称为该缺陷类型名称,行的顺序即缺陷类型索引)
static const std::string CLS_LABEL_CONFIG_FILE = "/home/aidlux/BOE/UseModel_ZB/BM_cls.txt"; static const std::string CLS_LABEL_CONFIG_FILE = "/home/aidlux/BOE/UseModel_ZB/BM_cls.txt";
@ -1623,68 +1644,523 @@ int ImgCheckAnalysisy::CheckRun()
} }
} }
// 3.5、旋转偏移:支架最小外接旋转矩形(长轴对应左/右边缘、短轴对应上/下边缘) // 3.5、旋转偏移:支架最小外接旋转矩形(长轴对应左/右边缘、短轴对应上/下边缘)
// 长轴与产品左/右边缘的夹角 > r_offset(度) → 偏移;r_offset<=0 不判定(兼容旧参数) // 长轴与产品左/右边缘的夹角 > r_offset(度) → 偏移;r_offset<=0 不判定(兼容旧参数)
float fSupportAngle = 0; // 支架长轴相对产品左/右边缘的夹角(度,带符号;正=支架长轴相对产品边缘向右倾) float fSupportAngle = 0; // 支架长轴相对产品左/右边缘的夹角(度,带符号;正=支架长轴相对产品边缘向右倾)
bool bRotOffset = false; bool bRotOffset = false;
bool bSupportAngleValid = false; // 是否用上了原图精细化角度(false=走 minAreaRect 兜底)
if (supportDet.r_offset > 0 && rsContour.size() >= 3) if (supportDet.r_offset > 0 && rsContour.size() >= 3)
{ {
// 轮廓先映射回原图坐标再拟合旋转矩形(smallmask 的 x/y 缩放可能不同,不还原会带角度误差) // 轮廓先映射回原图坐标再拟合旋转矩形(smallmask 的 x/y 缩放可能不同,不还原会带角度误差)
// 注意:smallmask 是 Align_Inner 在"大轮廓裁剪图"上的输出,映射原图时要加裁剪原点 bigRect
std::vector<cv::Point2f> rsContourSrc; std::vector<cv::Point2f> rsContourSrc;
rsContourSrc.reserve(rsContour.size()); rsContourSrc.reserve(rsContour.size());
for (size_t ip = 0; ip < rsContour.size(); ip++) for (size_t ip = 0; ip < rsContour.size(); ip++)
{ {
rsContourSrc.emplace_back(rsContour[ip].x * fx, rsContour[ip].y * fy); rsContourSrc.emplace_back(bigRect.x + rsContour[ip].x * fx,
bigRect.y + rsContour[ip].y * fy);
} }
cv::RotatedRect rsRotRect = cv::minAreaRect(rsContourSrc); cv::RotatedRect rsRotRect = cv::minAreaRect(rsContourSrc);
cv::Point2f rpts[4];
rsRotRect.points(rpts); // ===== 精细化计算支架方向(原图灰度上拟合支架长边)=====
cv::Point2f d01 = rpts[1] - rpts[0]; // 模型 smallmask 分辨率低(1696×800 上采样回原图,量化误差约 4px),且支架区域接近方形时
cv::Point2f d12 = rpts[2] - rpts[1]; // minAreaRect 的"长轴"方向不稳定,直接用它求旋转角误差大;这里用模型支架边缘做初值,
float len01 = (float)cv::norm(d01); // 在 detImg 灰度原图上重新拟合支架长边方向:
float len12 = (float)cv::norm(d12); // 1、把 smallmask 最大轮廓映射回原图,得到支架外接矩形(rsRect);
// 长轴方向 = 长边方向(长轴对应左/右边缘),并统一朝下(与左边缘 上->下 方向一致) // 取外接矩形最外侧 nStrip 列(默认 30 列)作为该侧边缘的搜索带
cv::Point2f vLong(0, 1); // 2、结合 edge_select:选 left → 拟合支架右边缘;选 right → 拟合支架左边缘
if (len01 >= len12 && len01 > 1e-3f) // (up/down 对应支架上下短边,水平的线不好拟合,不处理,仍用 minAreaRect 结果)
{ // 3、沿外接矩形高度按 step 采样;每行在搜索带内逐像素找灰度梯度最大的位置作为边缘点:
vLong = d01 * (1.0f / len01); // 右边缘取最右 nStrip 列、从右往左找;左边缘取最左 nStrip 列、从左往右找;
} // 梯度不足(该侧没有真实边缘)的行不参与拟合
else if (len12 > 1e-3f) // 4、采样点沿 y 分成 n 段,每段用 FitLineRobustTV 稳健拟合直线,取各段方向角的中位数:
{ // 支架边缘的凸起是矩形,凸起外边缘与支架长边平行,拟合到凸起处也不影响方向角;
vLong = d12 * (1.0f / len12); // 凸起侧边/内部纹理等离群点由 MAD 剔除
} // 失败(有效采样/分段不足、角度离散过大)时保留下面的 minAreaRect 长轴方案
if (vLong.y < 0) if (!m_pImageAllResult->detImg.empty() && m_pImageAllResult->detImg.type() == CV_8UC1)
{
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; const cv::Mat &grayRef = m_pImageAllResult->detImg;
// 边缘搜索带宽度(列):取模型支架外接矩形最外侧多少列;未配置默认 30
int nStrip = (supportDet.support_refine_search > 0)
? cvRound(supportDet.support_refine_search)
: SUPPORT_REFINE_STRIP_DEFAULT;
int nStep = (supportDet.support_refine_step > 0)
? cvRound(supportDet.support_refine_step)
: SUPPORT_REFINE_STEP_DEFAULT;
int nSegments = (supportDet.support_refine_segments > 0)
? cvRound(supportDet.support_refine_segments)
: SUPPORT_REFINE_SEGMENTS_DEFAULT;
nStrip = std::max(4, nStrip);
nStep = std::max(4, nStep);
nSegments = std::max(1, nSegments);
// 3 行灰度均值(抑制单行噪声;调用处保证 y 在 [1, rows-2])
auto gray3 = [&grayRef](int y, int x) -> float {
return (grayRef.at<uchar>(y - 1, x) + grayRef.at<uchar>(y, x) + grayRef.at<uchar>(y + 1, x)) * (1.0f / 3.0f);
};
// 拟合支架一条长边:nSide 0=支架左边缘(外侧 -x)、1=支架右边缘(外侧 +x);
// nRefIdx 0=产品左边缘、1=产品右边缘
// 返回 true 时 fAngleOut=支架长边相对产品该边缘的带符号夹角(度),fEdgeVertOut=支架长边相对竖直方向角度(度)
// pSamplesOut/pSegLinesOut 非空时输出采样边缘点和各段拟合线(检测图坐标),供 -s 绘制调试图
auto FitSupportEdgeAngle = [&](int nSide, int nRefIdx, float &fAngleOut, int &nSampleOut, int &nSegOut, float &fEdgeVertOut,
std::vector<cv::Point2f> *pSamplesOut, std::vector<std::vector<cv::Point2f>> *pSegLinesOut) -> bool {
fAngleOut = 0.0f;
fEdgeVertOut = 0.0f;
nSampleOut = 0;
nSegOut = 0;
if (pSamplesOut != NULL)
{
pSamplesOut->clear();
}
if (pSegLinesOut != NULL)
{
pSegLinesOut->clear();
}
// 产品基准边缘方向(统一朝下,与原有旋转角符号约定一致)
cv::Point2f vRef = ptSegBArr[nRefIdx] - ptSegAArr[nRefIdx];
float fRefLen = (float)cv::norm(vRef);
if (fRefLen < 1e-3f)
{
return false;
}
vRef *= (1.0f / fRefLen);
if (vRef.y < 0)
{
vRef = -vRef;
}
// ===== 取模型支架外接矩形(rsRect)最外侧 nStrip 列,逐行找边缘点 =====
// 右边缘:取外接矩形最右 nStrip 列,从右往左扫;左边缘:取最左 nStrip 列,从左往右扫。
// 每行按灰度梯度找边缘点;梯度不足(该侧没有真实边缘)的行不参与拟合
int nXStart = 0; // 外侧起始列(原图坐标)
int nXEnd = 0; // 内侧结束列(原图坐标)
if (nSide == 0)
{
nXStart = rsRect.x;
nXEnd = rsRect.x + nStrip - 1;
}
else
{
nXStart = rsRect.x + rsRect.width - 1;
nXEnd = rsRect.x + rsRect.width - nStrip;
}
const int nStepX = (nXEnd >= nXStart) ? 1 : -1;
// 沿外接矩形高度采样(两端各缩 5%,至少 6px,避开圆角)
double fY0 = rsRect.y;
double fY1 = rsRect.y + rsRect.height - 1;
const double fTrim = std::max(6.0, (fY1 - fY0) * 0.05);
fY0 += fTrim;
fY1 -= fTrim;
if (fY1 - fY0 < 20.0)
{
return false;
}
std::vector<cv::Point2f> vecEdgePts; // (t,v) = (yDet, xDet)
vecEdgePts.reserve((int)((fY1 - fY0) / nStep) + 1);
for (double fT = fY0; fT <= fY1; fT += nStep)
{
// 原图 -> 检测图(detImg) 为纯平移;列带两端换算到检测图坐标
cv::Point2f ptDetStart = MatAffinePoint(m_matSrc2Det, cv::Point2f((float)nXStart, (float)fT));
cv::Point2f ptDetEnd = MatAffinePoint(m_matSrc2Det, cv::Point2f((float)nXEnd, (float)fT));
const int nYDet = cvRound(ptDetStart.y);
if (nYDet < 2 || nYDet >= grayRef.rows - 2)
{
continue;
}
const int nX0 = cvRound(ptDetStart.x); // 外侧端
const int nX1 = cvRound(ptDetEnd.x); // 内侧端
if ((nStepX > 0 && nX1 <= nX0) || (nStepX < 0 && nX1 >= nX0))
{
continue;
}
// 外侧 -> 内侧逐像素扫,取灰度梯度最大的位置作为该行支架边缘点
int nEdge = -1;
float fBestGrad = 0.0f;
for (int x = nX0; (nStepX > 0) ? (x + nStepX <= nX1) : (x + nStepX >= nX1); x += nStepX)
{
if (x < 1 || x + nStepX < 1 || x >= grayRef.cols - 1 || x + nStepX >= grayRef.cols - 1)
{
continue;
}
const float fGrad = fabs(gray3(nYDet, x + nStepX) - gray3(nYDet, x));
if (fGrad > fBestGrad)
{
fBestGrad = fGrad;
nEdge = x;
}
}
if (nEdge < 0 || fBestGrad < (float)SUPPORT_REFINE_MIN_CONTRAST)
{
continue; // 该行列带内没有明显灰度阶跃,本行不参与拟合
}
vecEdgePts.push_back(cv::Point2f((float)nYDet, (float)(nEdge + nStepX * 0.5f)));
}
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "支架检测",
"support edge diag(side %d): strip %d cols [%d,%d], pts %d",
nSide, nStrip, nXStart, nXEnd, (int)vecEdgePts.size());
nSampleOut = (int)vecEdgePts.size();
if (nSampleOut < 8)
{
return false;
}
// 沿边分成 n 段,每段稳健拟合一条直线(凸起处点被 MAD 剔除;拟合到凸起外边缘也不影响方向)
std::vector<float> vecAngles;
std::vector<float> vecAnglesVert;
vecAngles.reserve(nSegments);
vecAnglesVert.reserve(nSegments);
for (int s = 0; s < nSegments; s++)
{
const size_t i0 = (size_t)((long long)s * nSampleOut / nSegments);
const size_t i1 = (size_t)((long long)(s + 1) * nSampleOut / nSegments);
if (i1 - i0 < 4)
{
continue;
}
std::vector<cv::Point2f> vecSeg(vecEdgePts.begin() + i0, vecEdgePts.begin() + i1);
double fKSeg = 0, fBSeg = 0;
if (FitLineRobustTV(vecSeg, fKSeg, fBSeg) < 4)
{
continue;
}
// 拟合线方向(统一朝下)与产品基准边缘的带符号夹角
cv::Point2f vSeg((float)fKSeg, 1.0f);
float fSegLen = (float)cv::norm(vSeg);
if (fSegLen < 1e-6f)
{
continue;
}
vSeg *= (1.0f / fSegLen);
const double dCross = (double)vSeg.x * vRef.y - (double)vSeg.y * vRef.x;
const double dDot = (double)vSeg.x * vRef.x + (double)vSeg.y * vRef.y;
const float fAngle = (float)(std::atan2(dCross, dDot) * 180.0 / CV_PI);
if (fabs(fAngle) > 45.0f)
{
continue; // 与产品边缘夹角不合理,丢弃该段
}
vecAngles.push_back(fAngle);
vecAnglesVert.push_back((float)(std::atan2(fKSeg, 1.0) * 180.0 / CV_PI));
// 记录该段拟合直线的两端点(检测图坐标),-s 时用于绘制支架边缘
if (pSegLinesOut != NULL)
{
const float fSegT0 = vecSeg.front().x; // vecSeg 点为 (t,v) = (yDet, xDet)
const float fSegT1 = vecSeg.back().x;
std::vector<cv::Point2f> vecLine(2);
vecLine[0] = cv::Point2f((float)(fKSeg * fSegT0 + fBSeg), fSegT0);
vecLine[1] = cv::Point2f((float)(fKSeg * fSegT1 + fBSeg), fSegT1);
pSegLinesOut->push_back(vecLine);
}
}
nSegOut = (int)vecAngles.size();
if (nSegOut < 2)
{
return false;
}
// 取各段角度中位数,并用 MAD 检查各段一致性(凸起/纹理导致的异常段会被中位数稀释)
std::vector<float> vecTmp = vecAngles;
std::nth_element(vecTmp.begin(), vecTmp.begin() + vecTmp.size() / 2, vecTmp.end());
const float fMedian = vecTmp[vecTmp.size() / 2];
for (size_t i = 0; i < vecAngles.size(); i++)
{
vecTmp[i] = fabs(vecAngles[i] - fMedian);
}
std::nth_element(vecTmp.begin(), vecTmp.begin() + vecTmp.size() / 2, vecTmp.end());
if (vecTmp[vecTmp.size() / 2] > 5.0f)
{
return false; // 各段方向离散过大(可能拟合到了不同结构),不采用
}
fAngleOut = fMedian;
std::nth_element(vecAnglesVert.begin(), vecAnglesVert.begin() + vecAnglesVert.size() / 2, vecAnglesVert.end());
fEdgeVertOut = vecAnglesVert[vecAnglesVert.size() / 2];
if (pSamplesOut != NULL)
{
pSamplesOut->reserve(vecEdgePts.size());
for (size_t i = 0; i < vecEdgePts.size(); i++)
{
pSamplesOut->push_back(cv::Point2f(vecEdgePts[i].y, vecEdgePts[i].x)); // (t,v) -> (x,y)
}
}
return true;
};
// 按 edge_select 选择拟合方向:left -> 支架右边缘,right -> 支架左边缘(up/down 不处理)
int nCandSide[2] = {0, 0};
int nCandRef[2] = {0, 0};
int nCandNum = 0;
for (size_t iselect = 0; iselect < supportDet.edge_select.size(); iselect++)
{
const std::string &strEdge = supportDet.edge_select[iselect];
int nSide = -1;
int nRef = -1;
if (strEdge == "left")
{
nSide = 1; // 拟合支架右边缘
nRef = 0; // 参考产品左边缘
}
else if (strEdge == "right")
{
nSide = 0; // 拟合支架左边缘
nRef = 1; // 参考产品右边缘
}
else
{
continue;
}
bool bDup = false;
for (int ic = 0; ic < nCandNum; ic++)
{
if (nCandSide[ic] == nSide && nCandRef[ic] == nRef)
{
bDup = true;
}
}
if (!bDup && nCandNum < 2)
{
nCandSide[nCandNum] = nSide;
nCandRef[nCandNum] = nRef;
nCandNum++;
}
}
int nBestCand = -1;
int nBestSample = 0;
int nBestSeg = 0;
float fBestAngle = 0.0f;
float fBestEdgeVert = 0.0f;
int nBestSideUsed = -1;
int nBestRefUsed = -1;
std::vector<cv::Point2f> vecBestSamples; // 采样边缘点(检测图坐标)
std::vector<std::vector<cv::Point2f>> vecBestSegLines; // 各段拟合线(检测图坐标)
for (int ic = 0; ic < nCandNum; ic++)
{
float fAngle = 0.0f;
float fEdgeVert = 0.0f;
int nSample = 0;
int nSeg = 0;
std::vector<cv::Point2f> vecCandSamples;
std::vector<std::vector<cv::Point2f>> vecCandSegLines;
// 只拟合 edge_select 指定的支架长边;该侧失败时保留 minAreaRect 结果,不换边
// (两条长边方向角虽一致,但换边会静默得到另一条边的结果,与配置含义不符)
bool bOk = FitSupportEdgeAngle(nCandSide[ic], nCandRef[ic], fAngle, nSample, nSeg, fEdgeVert,
&vecCandSamples, &vecCandSegLines);
const int nUseSide = nCandSide[ic];
if (!bOk)
if (!bOk)
{
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "支架检测",
"support angle refine(ref %s): %s support edge fail, keep minAreaRect",
(nCandRef[ic] == 0) ? "left" : "right", (nCandSide[ic] == 0) ? "left" : "right");
continue;
}
const cv::Point2f vRefLog = ptSegBArr[nCandRef[ic]] - ptSegAArr[nCandRef[ic]];
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "支架检测",
"support angle refine(ref %s): use %s support edge: sample %d seg %d angle %.2f deg (edge vs vert %.2f, ref vs vert %.2f)",
(nCandRef[ic] == 0) ? "left" : "right", (nCandSide[ic] == 0) ? "left" : "right",
nSample, nSeg, fAngle, fEdgeVert,
(float)(std::atan2((double)vRefLog.x, (double)vRefLog.y) * 180.0 / CV_PI));
if (nSample > nBestSample)
{
nBestSample = nSample;
nBestSeg = nSeg;
fBestAngle = fAngle;
fBestEdgeVert = fEdgeVert;
nBestSideUsed = nUseSide;
nBestRefUsed = nCandRef[ic];
vecBestSamples = vecCandSamples;
vecBestSegLines = vecCandSegLines;
nBestCand = ic;
}
}
if (nBestCand >= 0)
{
fSupportAngle = fBestAngle;
bRotOffset = (fabs(fSupportAngle) > supportDet.r_offset);
bSupportAngleValid = true;
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "支架检测",
"support angle refine ok: angle %.2f deg (sample %d seg %d), replace minAreaRect",
fSupportAngle, nBestSample, nBestSeg);
// -s(存过程图):绘制精细化拟合的支架边缘,便于从图像确认拟合效果
// 蓝=smallmask 支架轮廓 黄=参与拟合的采样边缘点 绿=各段稳健拟合直线
// 红=最终方向线(产品基准边方向 + 精细夹角,过采样点重心) 品红=产品基准边
if (DetImgInfo_shareP->bsaveProcessImg && !vecBestSamples.empty())
{
cv::Mat show;
if (grayRef.channels() == 1)
{
cv::cvtColor(grayRef, show, cv::COLOR_GRAY2BGR);
}
else
{
show = grayRef.clone();
}
// 支架区域轮廓(蓝):smallmask 最大轮廓映射到检测图坐标
std::vector<cv::Point> vecRsPoly;
vecRsPoly.reserve(rsContour.size());
for (size_t ip = 0; ip < rsContour.size(); ip++)
{
cv::Point2f ptSrc(bigRect.x + rsContour[ip].x * fx, bigRect.y + rsContour[ip].y * fy);
cv::Point2f ptDet = MatAffinePoint(m_matSrc2Det, ptSrc);
vecRsPoly.push_back(cv::Point(cvRound(ptDet.x), cvRound(ptDet.y)));
}
if (vecRsPoly.size() >= 3)
{
cv::polylines(show, vecRsPoly, true, cv::Scalar(255, 0, 0), 4);
}
// 采样边缘点(黄)
for (size_t i = 0; i < vecBestSamples.size(); i++)
{
cv::circle(show, cv::Point(cvRound(vecBestSamples[i].x), cvRound(vecBestSamples[i].y)), 4,
cv::Scalar(0, 255, 255), -1);
}
// 各段稳健拟合的直线(绿)
for (size_t i = 0; i < vecBestSegLines.size(); i++)
{
if (vecBestSegLines[i].size() == 2)
{
cv::line(show, cv::Point(cvRound(vecBestSegLines[i][0].x), cvRound(vecBestSegLines[i][0].y)),
cv::Point(cvRound(vecBestSegLines[i][1].x), cvRound(vecBestSegLines[i][1].y)),
cv::Scalar(0, 255, 0), 4);
}
}
// 最终方向线(红):产品基准边方向旋转支架精细夹角,过采样点重心
cv::Point2f vRefBest = ptSegBArr[nBestRefUsed] - ptSegAArr[nBestRefUsed];
float fRefBestLen = (float)cv::norm(vRefBest);
if (fRefBestLen > 1e-3f)
{
vRefBest *= (1.0f / fRefBestLen);
if (vRefBest.y < 0)
{
vRefBest = -vRefBest;
}
const double dRad = (double)fSupportAngle * CV_PI / 180.0;
cv::Point2f vDir((float)(vRefBest.x * std::cos(dRad) + vRefBest.y * std::sin(dRad)),
(float)(-vRefBest.x * std::sin(dRad) + vRefBest.y * std::cos(dRad)));
float fDirLen = (float)cv::norm(vDir);
if (fDirLen > 1e-6f)
{
vDir *= (1.0f / fDirLen);
}
cv::Point2f ptCenter(0.0f, 0.0f);
float fSampleYMin = vecBestSamples[0].y;
float fSampleYMax = vecBestSamples[0].y;
for (size_t i = 0; i < vecBestSamples.size(); i++)
{
ptCenter += vecBestSamples[i];
fSampleYMin = std::min(fSampleYMin, vecBestSamples[i].y);
fSampleYMax = std::max(fSampleYMax, vecBestSamples[i].y);
}
ptCenter *= (1.0f / (float)vecBestSamples.size());
const float fHalf = std::max(20.0f, (fSampleYMax - fSampleYMin) * 0.6f);
cv::line(show, cv::Point(cvRound(ptCenter.x - vDir.x * fHalf), cvRound(ptCenter.y - vDir.y * fHalf)),
cv::Point(cvRound(ptCenter.x + vDir.x * fHalf), cvRound(ptCenter.y + vDir.y * fHalf)),
cv::Scalar(0, 0, 255), 4);
// 产品基准边缘(品红)
cv::Point2f ptRefA = MatAffinePoint(m_matSrc2Det, ptSegAArr[nBestRefUsed]);
cv::Point2f ptRefB = MatAffinePoint(m_matSrc2Det, ptSegBArr[nBestRefUsed]);
cv::line(show, cv::Point(cvRound(ptRefA.x), cvRound(ptRefA.y)),
cv::Point(cvRound(ptRefB.x), cvRound(ptRefB.y)),
cv::Scalar(255, 0, 255), 3);
}
// 文字信息:所用支架边/参考产品边/采样与分段数/角度
char strDbg[256];
snprintf(strDbg, sizeof(strDbg), "support edge refine: side %s ref %s sample %d seg %d",
(nBestSideUsed == 0) ? "left" : "right", (nBestRefUsed == 0) ? "left" : "right",
nBestSample, nBestSeg);
cv::putText(show, strDbg, cv::Point(20, 70), cv::FONT_HERSHEY_SIMPLEX, 2.0, cv::Scalar(0, 255, 255), 4);
cv::Point2f vRefDbg = ptSegBArr[nBestRefUsed] - ptSegAArr[nBestRefUsed];
snprintf(strDbg, sizeof(strDbg), "angle %.2f deg (edge vs vert %.2f, ref vs vert %.2f)",
fSupportAngle, fBestEdgeVert,
(float)(std::atan2((double)vRefDbg.x, (double)vRefDbg.y) * 180.0 / CV_PI));
cv::putText(show, strDbg, cv::Point(20, 140), cv::FONT_HERSHEY_SIMPLEX, 2.0, cv::Scalar(0, 255, 255), 4);
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 + "_support_angle.jpg", showSmall, paramJpg);
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "支架检测",
"support angle debug image saved: %s_support_angle.jpg", DetImgInfo_shareP->strChannel.c_str());
}
}
else if (!supportDet.edge_select.empty())
{
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "支架检测",
"support angle refine not applied(no left/right edge or fit failed), use minAreaRect");
}
} }
else if (fSupportAngle < -90.0f)
// 3.5、旋转偏移(兜底):支架最小外接旋转矩形(长轴对应左/右边缘、短轴对应上/下边缘)
// 长轴与产品左/右边缘的夹角 > r_offset(度) → 偏移;r_offset<=0 不判定(兼容旧参数)
if (!bSupportAngleValid)
{ {
fSupportAngle += 180.0f; 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);
} }
bRotOffset = (fabs(fSupportAngle) > supportDet.r_offset);
} }
// 像素 -> mm // 像素 -> mm
@ -1766,13 +2242,13 @@ int ImgCheckAnalysisy::CheckRun()
m_SupportDrawInfo.ptCenter = cv::Point2f(fCx, fCy); m_SupportDrawInfo.ptCenter = cv::Point2f(fCx, fCy);
m_pdetlog->AddCheckstr(PrintLevel_0, "支架检测", m_pdetlog->AddCheckstr(PrintLevel_0, "支架检测",
"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", "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(%s) ng %d",
rpRect.x, rpRect.y, rpRect.width, rpRect.height, rpRect.x, rpRect.y, rpRect.width, rpRect.height,
rsRect.x, rsRect.y, rsRect.width, rsRect.height, rsRect.x, rsRect.y, rsRect.width, rsRect.height,
dSupportAreaMm2, supportDet.support_min_area, dSupportAreaMm2, supportDet.support_min_area,
fx, fy, fCx, fCy, bFitQuad ? "fitquad" : "rpbox", (long)m_HandleBodyQuad.size(), fDL, fDR, fDT, fDB, 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, strSelect.c_str(), strDiff.c_str(), fAvgDiff, supportDet.x_offset,
fSupportAngle, supportDet.r_offset, bOffset); fSupportAngle, supportDet.r_offset, bSupportAngleValid ? "refine" : "rect", bOffset);
if (bOffset) if (bOffset)
{ {

@ -937,6 +937,11 @@ struct Function_Support_Det
// 沿边缘按步长采样,从边缘向产品内侧逐像素找第一个 255 像素作为新边缘点,再重新拟合边缘线 // 沿边缘按步长采样,从边缘向产品内侧逐像素找第一个 255 像素作为新边缘点,再重新拟合边缘线
float edge_refine_search; // edge_refine_search:最多向内搜索的像素数;<=0=未配置(默认 80) float edge_refine_search; // edge_refine_search:最多向内搜索的像素数;<=0=未配置(默认 80)
float edge_refine_step; // edge_refine_step:沿边采样步长(像素,不需要每个点都找);<=0=未配置(默认 40) float edge_refine_step; // edge_refine_step:沿边采样步长(像素,不需要每个点都找);<=0=未配置(默认 40)
// 支架方向精细化(Support_Detect.form.support_param,均可选):模型 smallmask 精度低
// (1696×800 上采样回原图),minAreaRect 求支架方向角误差大;改为在原图(detImg)上拟合支架长边求方向角
float support_refine_search; // support_refine_search:取模型支架外接矩形最外侧多少列作为边缘搜索带;<=0=未配置(默认 30)
float support_refine_step; // support_refine_step:沿支架长边采样步长(像素);<=0=未配置(默认 20)
float support_refine_segments; // support_refine_segments:采样点分段数(每段稳健拟合一条线取角度中位数);<=0=未配置(默认 6)
// 把手参数(Handle_Detect) // 把手参数(Handle_Detect)
bool bDetHandle; // 把手缺失检测开关(Handle_Detect.isOpen) bool bDetHandle; // 把手缺失检测开关(Handle_Detect.isOpen)
// 把手区域(最多 HANDLE_REGION_MAX_NUM 个,只保存非空的;区域宽高用于把手卡控) // 把手区域(最多 HANDLE_REGION_MAX_NUM 个,只保存非空的;区域宽高用于把手卡控)
@ -977,6 +982,9 @@ struct Function_Support_Det
support_min_area = 0; support_min_area = 0;
edge_refine_search = 0; edge_refine_search = 0;
edge_refine_step = 0; edge_refine_step = 0;
support_refine_search = 0;
support_refine_step = 0;
support_refine_segments = 0;
bDetHandle = false; bDetHandle = false;
handleRegions.clear(); handleRegions.clear();
handleBoxes.clear(); handleBoxes.clear();
@ -1008,6 +1016,9 @@ struct Function_Support_Det
this->support_min_area = tem.support_min_area; this->support_min_area = tem.support_min_area;
this->edge_refine_search = tem.edge_refine_search; this->edge_refine_search = tem.edge_refine_search;
this->edge_refine_step = tem.edge_refine_step; this->edge_refine_step = tem.edge_refine_step;
this->support_refine_search = tem.support_refine_search;
this->support_refine_step = tem.support_refine_step;
this->support_refine_segments = tem.support_refine_segments;
this->bDetHandle = tem.bDetHandle; this->bDetHandle = tem.bDetHandle;
this->handleRegions.assign(tem.handleRegions.begin(), tem.handleRegions.end()); this->handleRegions.assign(tem.handleRegions.begin(), tem.handleRegions.end());
this->handleBoxes.assign(tem.handleBoxes.begin(), tem.handleBoxes.end()); this->handleBoxes.assign(tem.handleBoxes.begin(), tem.handleBoxes.end());
@ -1026,16 +1037,16 @@ struct Function_Support_Det
} }
void print(std::string str) void print(std::string str)
{ {
printf("%s>>bDetSupport %d bDrawSupport %d bUseTraditionEdge %d bDetHandle %d bDetTag %d x_standard %f y_standard %f x_offset %f y_offset %f r_offset %f support_min_area %f edgeRefine[search %.0f step %.0f] 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", printf("%s>>bDetSupport %d bDrawSupport %d bUseTraditionEdge %d bDetHandle %d bDetTag %d x_standard %f y_standard %f x_offset %f y_offset %f r_offset %f support_min_area %f edgeRefine[search %.0f step %.0f] supportRefine[search %.0f step %.0f seg %.0f] 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, bUseTraditionEdge, bDetHandle, bDetTag, x_standard, y_standard, x_offset, y_offset, r_offset, support_min_area, edge_refine_search, edge_refine_step, edge_select.size(), handleRegions.size(), handle_diff, str.c_str(), bDetSupport, bDrawSupport, bUseTraditionEdge, bDetHandle, bDetTag, x_standard, y_standard, x_offset, y_offset, r_offset, support_min_area, edge_refine_search, edge_refine_step, support_refine_search, support_refine_step, support_refine_segments, 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, 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); BQ_area, BQ_energy, BQ_len, BQ_hj);
} }
std::string GetInfo(std::string str) std::string GetInfo(std::string str)
{ {
char buffer[1024]; char buffer[1024];
sprintf(buffer, "%s>>bDetSupport %d bDrawSupport %d bUseTraditionEdge %d bDetHandle %d bDetTag %d x_standard %f y_standard %f x_offset %f y_offset %f r_offset %f support_min_area %f edgeRefine[search %.0f step %.0f] 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", sprintf(buffer, "%s>>bDetSupport %d bDrawSupport %d bUseTraditionEdge %d bDetHandle %d bDetTag %d x_standard %f y_standard %f x_offset %f y_offset %f r_offset %f support_min_area %f edgeRefine[search %.0f step %.0f] supportRefine[search %.0f step %.0f seg %.0f] 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, bUseTraditionEdge, bDetHandle, bDetTag, x_standard, y_standard, x_offset, y_offset, r_offset, support_min_area, edge_refine_search, edge_refine_step, edge_select.size(), handleRegions.size(), handle_diff, str.c_str(), bDetSupport, bDrawSupport, bUseTraditionEdge, bDetHandle, bDetTag, x_standard, y_standard, x_offset, y_offset, r_offset, support_min_area, edge_refine_search, edge_refine_step, support_refine_search, support_refine_step, support_refine_segments, 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, 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); BQ_area, BQ_energy, BQ_len, BQ_hj);
std::string str123 = buffer; std::string str123 = buffer;

@ -974,6 +974,24 @@ int BaseFuntonConfigJson::GetFunction(Json::Value value)
_config.supportDet.edge_refine_step = value_param["edge_refine_step"].asFloat(); _config.supportDet.edge_refine_step = value_param["edge_refine_step"].asFloat();
} }
// 支架方向精细化(可选):模型 smallmask 精度低(1696×800 上采样回原图),
// minAreaRect 求支架方向角误差大,改为在 detImg 灰度原图上拟合支架长边求方向角。
// support_refine_search :取模型支架外接矩形最外侧多少列作为边缘搜索带(未配置默认 30)
// support_refine_step :沿支架长边采样步长(像素)(未配置默认 20)
// support_refine_segments:采样点分段数(每段稳健拟合一条线取角度中位数,未配置默认 6)
if (value_param["support_refine_search"])
{
_config.supportDet.support_refine_search = value_param["support_refine_search"].asFloat();
}
if (value_param["support_refine_step"])
{
_config.supportDet.support_refine_step = value_param["support_refine_step"].asFloat();
}
if (value_param["support_refine_segments"])
{
_config.supportDet.support_refine_segments = value_param["support_refine_segments"].asFloat();
}
auto value_qx = value_param["edge_select"]; auto value_qx = value_param["edge_select"];
if (value_qx.isArray()) if (value_qx.isArray())
{ {

Loading…
Cancel
Save