update 优化物理坐标计算

dev_lsy
liusiyang 2 months ago
parent ca8f64b104
commit 6099c864ee

@ -71,8 +71,19 @@ public:
float fscale_detToresult_y = 0;
// 产品左上角(rot_cur_roi.boundingRect)在检测图 detImg 中的偏移,缺陷坐标转换到产品坐标系时需减去该间隙
// 注意:这是轴对齐的近似换算,仅在无旋转矩形(vProductQuadInDet 为空)时作为退化路径使用
cv::Point ptRoiOffsetInDet = cv::Point(0, 0);
// 产品旋转外接矩形四角在 detImg 坐标系下的坐标,顺序固定为 0左上 1右上 2左下 3右下
// 非空时用「缺陷点到两条边线段的距离」把点换算成产品坐标,可抗产品倾斜
std::vector<cv::Point2f> vProductQuadInDet;
// 缺陷点(detImg 坐标) -> 产品坐标(像素)
// x = 到左边线段(左下->左上)的垂距,y = 到顶边线段(左上->右上)的垂距,落在矩形内为正
// 这样同一物理位置在产品任意倾斜角下都能得到相同的产品坐标(轴对齐相减做不到)
// vProductQuadInDet 无效时退化为 ptRoiOffsetInDet 轴对齐平移(旧行为)
cv::Point2f DetPointToProductCoord(const cv::Point2f &ptDet) const;
int NG_num = 0;
int YS_num = 0;
int Ok_num = 0;

@ -18,7 +18,9 @@ struct QX_ERROR_INFO_
int Idx;
int result;
std::string result_name;
cv::Rect roi;
cv::Rect roi; // 缺陷框(产品坐标系,抗倾斜;roi.x/y 为框左上角)
cv::Rect roiDet; // 缺陷框(检测图 detImg 坐标系,小图裁剪/定位等内部用途)
cv::Point2f ptProduct = cv::Point2f(0.f, 0.f); // 缺陷中心的产品坐标(像素,抗倾斜,float 精度)
int area;
int energy;
float JudgArea;
@ -59,7 +61,9 @@ struct QX_ERROR_INFO_
result = 0;
result_name = "OK";
density = 0;
cv::Rect roi = cv::Rect(0, 0, 0, 0);
roi = cv::Rect(0, 0, 0, 0);
roiDet = cv::Rect(0, 0, 0, 0);
ptProduct = cv::Point2f(0.f, 0.f);
detLogList.erase(detLogList.begin(), detLogList.end());
detLogList.clear();
detRegionidxList.clear();

@ -196,6 +196,9 @@ private:
// 边缘缺陷检测
int Edge_Qx_Det(const cv::Mat &img);
// 缺陷框(detImg 坐标) -> 产品坐标(抗倾斜),并记录 detImg 坐标框
int ConvertDetRoiToProduct(QX_ERROR_INFO_ &qxerror);
// 把blob 汇总成 检测结果。
int BLobToDetResult();

@ -8,6 +8,9 @@
*/
#include "ImageAllResult.h"
#include <algorithm>
#include <cmath>
ImageAllResult::ImageAllResult()
{
detlog = std::make_shared<DetLog>();
@ -44,6 +47,55 @@ bool ImageAllResult::IsNotDet()
return false;
}
// 点到「线段所在直线」的有符号垂距,法线 n 指向的一侧为正
static bool SignedPerpDistToEdge(const cv::Point2f &p, const cv::Point2f &a, const cv::Point2f &b, float &fDist)
{
const cv::Point2f d = b - a;
const float len = std::sqrt(d.x * d.x + d.y * d.y);
if (len < 1e-6f)
{
return false;
}
// 顺时针顶点序(左上->右上->右下->左下)下,内侧法线 = (-dy, dx)/|d|
const cv::Point2f n(-d.y / len, d.x / len);
fDist = (p.x - a.x) * n.x + (p.y - a.y) * n.y;
return true;
}
cv::Point2f ImageAllResult::DetPointToProductCoord(const cv::Point2f &ptDet) const
{
if (vProductQuadInDet.size() != 4)
{
// 没有旋转矩形:退化为轴对齐平移(旧行为)
return cv::Point2f(ptDet.x - ptRoiOffsetInDet.x, ptDet.y - ptRoiOffsetInDet.y);
}
// 固定四角顺序:0左上 1右上 2左下 3右下(先按 y 再按 x 排序)
std::vector<cv::Point2f> v = vProductQuadInDet;
std::sort(v.begin(), v.end(), [](const cv::Point2f &a, const cv::Point2f &b) { return a.y < b.y; });
if (v[0].x > v[1].x)
{
std::swap(v[0], v[1]);
}
if (v[2].x > v[3].x)
{
std::swap(v[2], v[3]);
}
const cv::Point2f &pLT = v[0];
const cv::Point2f &pRT = v[1];
const cv::Point2f &pLB = v[2];
float fX = 0.f;
float fY = 0.f;
// x: 到左边线段(左下->左上)的垂距;y: 到顶边线段(左上->右上)的垂距
if (!SignedPerpDistToEdge(ptDet, pLB, pLT, fX) ||
!SignedPerpDistToEdge(ptDet, pLT, pRT, fY))
{
return cv::Point2f(ptDet.x - ptRoiOffsetInDet.x, ptDet.y - ptRoiOffsetInDet.y);
}
return cv::Point2f(fX, fY);
}
int ImageAllResult::AddDetImage(std::shared_ptr<shareImage> p)
{
{

@ -119,11 +119,16 @@ int ImageResultJudge::GetAIDetImg(std::shared_ptr<ImageAllResult> pImageResult,
QXImageResult ImageResultJudge::BuildDefectImage(std::shared_ptr<ImageAllResult> pImageResult, QX_ERROR_INFO_ *QX_info,
int qxidx, cv::Point pCenter, float fs_resize_x, float fs_resize_y)
{
cv::Rect roi = QX_info->roi; // 缺陷坐标(产品坐标系,已减去裁剪框与产品左上角的间隙)
// 还原检测图(detImg)坐标,用于缺陷小图裁剪等内部用途
cv::Rect roi_det = roi;
roi_det.x += pImageResult->ptRoiOffsetInDet.x;
roi_det.y += pImageResult->ptRoiOffsetInDet.y;
cv::Rect roi = QX_info->roi; // 缺陷坐标(产品坐标系,抗倾斜)
// 检测图(detImg)坐标框,用于缺陷小图裁剪等内部用途。
// 优先用 ImgCheckAnalysisy 记录的真实 detImg 框:产品倾斜时无法再由产品坐标加回偏移还原
cv::Rect roi_det = QX_info->roiDet;
if (roi_det.empty())
{
roi_det = roi;
roi_det.x += pImageResult->ptRoiOffsetInDet.x;
roi_det.y += pImageResult->ptRoiOffsetInDet.y;
}
float JudgArea = QX_info->JudgArea;
float flen = QX_info->flen;
float fbreadth = QX_info->fbreadth;
@ -167,9 +172,18 @@ QXImageResult ImageResultJudge::BuildDefectImage(std::shared_ptr<ImageAllResult>
tem.resizeImgroi.y = roi_det.y * fs_resize_y;
tem.resizeImgroi.height = roi_det.height * fs_resize_y;
// 缺陷中心:产品坐标系(像素/物理),相对产品左上角
tem.x_pixel = roi.x + roi.width * 0.5;
tem.y_pixel = roi.y + roi.height * 0.5;
// 缺陷中心:产品坐标系(像素/物理),相对产品左边/顶边
if (!QX_info->roiDet.empty())
{
// 抗倾斜路径:直接用点到左边/顶边线段的垂距(float 精度,不受框取整影响)
tem.x_pixel = QX_info->ptProduct.x;
tem.y_pixel = QX_info->ptProduct.y;
}
else
{
tem.x_pixel = roi.x + roi.width * 0.5;
tem.y_pixel = roi.y + roi.height * 0.5;
}
tem.x_mm = tem.x_pixel * m_fImgage_Scale_X;
tem.y_mm = tem.y_pixel * m_fImgage_Scale_Y;
@ -241,13 +255,25 @@ int ImageResultJudge::ResultJudge(std::shared_ptr<ImageAllResult> pImageResult)
std::string qx_wb = WHITE_BLCAK_Names[QX_whiteBLACK];
pQxLog->AddCheckstr(PrintLevel_1,
"", "qxidx: %d roi[%d %d %d %d] qx %d %s %s JudgArea %f hj %f len %f breadth %f energy %d region num %ld",
qxidx, roi.x, roi.y, roi.width, roi.height, config_qx_type, qx_name.c_str(), qx_wb.c_str(),
"", "qxidx: %d roi[%d %d %d %d] roidet[%d %d %d %d] qx %d %s %s JudgArea %f hj %f len %f breadth %f energy %d region num %ld",
qxidx, roi.x, roi.y, roi.width, roi.height,
QX_info->roiDet.x, QX_info->roiDet.y, QX_info->roiDet.width, QX_info->roiDet.height,
config_qx_type, qx_name.c_str(), qx_wb.c_str(),
JudgArea, grayDis, flen, fbreadth, energy, QX_info->detRegionidxList.size());
// 中心点用 detImg 坐标:检测区域(roiList_Src)与结果图绘制都在该坐标系下
// (产品坐标会整体平移一个常量,不影响两两距离,但绘制会偏)
cv::Point pCenter;
pCenter.x = roi.x + roi.width * 0.5;
pCenter.y = roi.y + roi.height * 0.5;
if (!QX_info->roiDet.empty())
{
pCenter.x = QX_info->roiDet.x + QX_info->roiDet.width * 0.5;
pCenter.y = QX_info->roiDet.y + QX_info->roiDet.height * 0.5;
}
else
{
pCenter.x = roi.x + roi.width * 0.5;
pCenter.y = roi.y + roi.height * 0.5;
}
bool bNG_Status = false;
bool bYS_Status = false;

@ -881,17 +881,36 @@ int ImgCheckAnalysisy::CheckRun()
m_pdetlog->AddCheckstr(PrintLevel_0, "2、pre detect", "-------------------------AI_Edge------------succ---time %ld----\n", time_edge_e - time_edge_s);
m_Crop_Roi_paramImg = m_CutRoi;
// 计算产品左上角在检测图(detImg)中的偏移(间隙),用于缺陷坐标转换到产品坐标系
// 产品坐标换算所需的参考几何:
// 1) ptRoiOffsetInDet:产品左上角顶点在 detImg 中的偏移(轴对齐,仅作退化路径)
// 2) vProductQuadInDet:产品旋转外接矩形四角在 detImg 中的坐标(0左上 1右上 2左下 3右下),
// 用于「缺陷点到两边线段的距离」的抗倾斜产品坐标换算
if (m_bRotCurRoiValid)
{
// 取旋转矩形实际的左上角顶点(非轴对齐外接框 boundingRect,后者在带旋转角时会偏大)
std::vector<cv::Point2f> rroi_vertices = sort_vertices(m_rot_cur_roi);
m_pImageAllResult->ptRoiOffsetInDet.x = cvRound(rroi_vertices[0].x) - m_CutRoi.x;
m_pImageAllResult->ptRoiOffsetInDet.y = cvRound(rroi_vertices[0].y) - m_CutRoi.y;
m_pImageAllResult->vProductQuadInDet.clear();
m_pImageAllResult->vProductQuadInDet.reserve(4);
for (int i = 0; i < 4; i++)
{
// 原图坐标 -> detImg 坐标
m_pImageAllResult->vProductQuadInDet.push_back(
cv::Point2f(rroi_vertices[i].x - m_CutRoi.x, rroi_vertices[i].y - m_CutRoi.y));
}
m_pdetlog->AddCheckstr(PrintLevel_1, DET_LOG_LEVEL_3, "Convert",
"product quad in det [(%d,%d) (%d,%d) (%d,%d) (%d,%d)]",
cvRound(rroi_vertices[0].x - m_CutRoi.x), cvRound(rroi_vertices[0].y - m_CutRoi.y),
cvRound(rroi_vertices[1].x - m_CutRoi.x), cvRound(rroi_vertices[1].y - m_CutRoi.y),
cvRound(rroi_vertices[2].x - m_CutRoi.x), cvRound(rroi_vertices[2].y - m_CutRoi.y),
cvRound(rroi_vertices[3].x - m_CutRoi.x), cvRound(rroi_vertices[3].y - m_CutRoi.y));
}
else
{
m_pImageAllResult->ptRoiOffsetInDet = cv::Point(0, 0);
m_pImageAllResult->vProductQuadInDet.clear();
}
m_pImageAllResult->pDetResult->CutRoi = m_CutRoi;
m_pImageAllResult->pDetResult->Param_CropRoi = m_Crop_Roi_paramImg;
@ -2512,6 +2531,35 @@ int ImgCheckAnalysisy::UpdateImgageScale()
return 0;
}
// 缺陷框(detImg 坐标) -> 产品坐标
// 旧实现:roi = roiDet - 产品左上角顶点,只做了轴对齐平移,产品倾斜时同一物理位置的
// 坐标会随倾斜角漂移(沿产品长/宽方向产生 offset*sin(θ) 量级的误差)。
// 新实现:缺陷中心点到旋转矩形左边线段的垂距作为 x,到顶边线段的垂距作为 y,
// 任意倾斜角下同一物理位置得到相同坐标;再用中心反推产品坐标系下的框左上角,
// 保持 roi 原有的「框左上角 + 框尺寸」语义。roiDet 保留 detImg 坐标供内部裁剪使用。
int ImgCheckAnalysisy::ConvertDetRoiToProduct(QX_ERROR_INFO_ &qxerror)
{
// 1) 记录 detImg 坐标下的缺陷框(小图裁剪等内部用途,不受倾斜影响)
qxerror.roiDet = qxerror.roi;
if (m_pImageAllResult == nullptr)
{
return 1;
}
// 2) 缺陷中心 -> 产品坐标(点到左边/顶边线段的垂距)
const cv::Point2f pcDet(qxerror.roiDet.x + qxerror.roiDet.width * 0.5f,
qxerror.roiDet.y + qxerror.roiDet.height * 0.5f);
const cv::Point2f pcProduct = m_pImageAllResult->DetPointToProductCoord(pcDet);
qxerror.ptProduct = pcProduct;
// 3) 用中心反推框左上角
qxerror.roi.x = cvRound(pcProduct.x - qxerror.roiDet.width * 0.5f);
qxerror.roi.y = cvRound(pcProduct.y - qxerror.roiDet.height * 0.5f);
return 0;
}
int ImgCheckAnalysisy::Edge_Qx_Det(const cv::Mat &img)
{
@ -2603,12 +2651,8 @@ int ImgCheckAnalysisy::Edge_Qx_Det(const cv::Mat &img)
// }
// }
// 缺陷坐标转换到产品坐标系:减去裁剪框与产品左上角的间隙
if (m_bRotCurRoiValid)
{
temerror.roi.x -= m_pImageAllResult->ptRoiOffsetInDet.x;
temerror.roi.y -= m_pImageAllResult->ptRoiOffsetInDet.y;
}
// 缺陷坐标转换到产品坐标系(点到左边/顶边线段的垂距,抗倾斜)
ConvertDetRoiToProduct(temerror);
m_pDetResult->pQx_ErrorList->push_back(temerror);
}
@ -2681,12 +2725,8 @@ int ImgCheckAnalysisy::BLobToDetResult()
{
QX_ERROR_INFO_ temerror;
temerror.roi = roi;
// 缺陷坐标转换到产品坐标系:减去裁剪框与产品左上角的间隙
if (m_bRotCurRoiValid)
{
temerror.roi.x -= m_pImageAllResult->ptRoiOffsetInDet.x;
temerror.roi.y -= m_pImageAllResult->ptRoiOffsetInDet.y;
}
// 缺陷坐标转换到产品坐标系(点到左边/顶边线段的垂距,抗倾斜)
ConvertDetRoiToProduct(temerror);
temerror.Idx = m_pDetResult->pQx_ErrorList->size();
temerror.area = blobs.blobTab[i].area;
temerror.JudgArea = JudgArea;

Loading…
Cancel
Save