From 598cd176f5390520aa400dbe8cb7cc7b7a213b91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=B0=E4=BB=94?= Date: Sun, 12 Apr 2026 00:28:12 +0800 Subject: [PATCH] =?UTF-8?q?feat(PointCloudImageUtils):=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E9=80=9A=E7=94=A8=E7=82=B9=E4=BA=91=E7=94=BB=E5=B8=83?= =?UTF-8?q?=E7=B1=BB=E7=94=A8=E4=BA=8E=E7=BB=98=E5=88=B6=E7=82=B9=E4=BA=91?= =?UTF-8?q?=E5=BA=95=E5=9B=BE=E5=92=8C=E6=A0=87=E8=AE=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CloudUtils/Inc/PointCloudImageUtils.h | 57 +++++++++- CloudUtils/Src/PointCloudImageUtils.cpp | 141 ++++++++++++++++++++++++ 2 files changed, 194 insertions(+), 4 deletions(-) diff --git a/CloudUtils/Inc/PointCloudImageUtils.h b/CloudUtils/Inc/PointCloudImageUtils.h index 2764635..b076998 100644 --- a/CloudUtils/Inc/PointCloudImageUtils.h +++ b/CloudUtils/Inc/PointCloudImageUtils.h @@ -111,6 +111,10 @@ public: double rotateY_deg = 0.0, bool useZGradient = false); + // 计算scan lines格式点云的范围 + static void CalculateScanLinesRange(const std::vector>& scanLines, + double& xMin, double& xMax, + double& yMin, double& yMax); private: // 定义线特征颜色和大小获取函数 static void GetLineFeatureStyle(int vType, int hType, int objId, @@ -124,10 +128,6 @@ private: double& xMin, double& xMax, double& yMin, double& yMax); - // 计算scan lines格式点云的范围 - static void CalculateScanLinesRange(const std::vector>& scanLines, - double& xMin, double& xMax, - double& yMin, double& yMax); // 绘制LapWeld检测结果 static void DrawLapWeldResults(QPainter& painter, @@ -149,4 +149,53 @@ private: int imgCols, int imgRows); }; +/** + * @brief 通用点云画布类 + * 封装点云底图生成 + 3D→2D投影 + 通用绘图原语 + * 调用方可自由组合绘制点、文字、线段、圆等标记 + */ +class PointCloudCanvas +{ +public: + /** + * @brief 从 scan lines 创建画布,生成灰色点云底图 + * @param scanLines 扫描线数据 + * @param imageWidth 图像宽度(像素) + * @param imageHeight 图像高度(像素) + * @param margin 边距(像素) + */ + static PointCloudCanvas Create(const std::vector>& scanLines, + int imageWidth = 1056, int imageHeight = 992, int margin = 50); + + bool isValid() const; + QImage& image(); + const QImage& image() const; + + /// 3D坐标→图像像素坐标 + QPoint project(double x, double y) const; + + /// 绘制实心圆点 + void drawPoint(double x, double y, const QColor& color, int size = 4); + + /// 绘制圆(radius3D为3D空间半径,自动投影为像素半径) + void drawCircle(double x, double y, double radius3D, const QColor& color, int penWidth = 2, bool fill = false); + + /// 绘制文字(offsetX/offsetY 为像素偏移) + void drawText(double x, double y, const QString& text, const QColor& color, int fontSize = 14, int offsetX = 8, int offsetY = 6); + + /// 绘制线段 + void drawLine(double x1, double y1, double x2, double y2, const QColor& color, int penWidth = 2); + +private: + QImage m_image; + double m_xMin = 0.0; + double m_yMin = 0.0; + double m_scale = 1.0; + int m_offsetX = 0; + int m_offsetY = 0; + int m_imgWidth = 0; + int m_imgHeight = 0; + bool m_valid = false; +}; + #endif // POINTCLOUDIMAGEUTILS_H diff --git a/CloudUtils/Src/PointCloudImageUtils.cpp b/CloudUtils/Src/PointCloudImageUtils.cpp index 0b4f0d7..3e761f0 100644 --- a/CloudUtils/Src/PointCloudImageUtils.cpp +++ b/CloudUtils/Src/PointCloudImageUtils.cpp @@ -1695,3 +1695,144 @@ QImage PointCloudImageUtils::GenerateHoleDetectionImage( return image; } + +// ==================== PointCloudCanvas 实现 ==================== + +PointCloudCanvas PointCloudCanvas::Create( + const std::vector>& scanLines, + int imageWidth, int imageHeight, int margin) +{ + PointCloudCanvas canvas; + canvas.m_imgWidth = imageWidth; + canvas.m_imgHeight = imageHeight; + + if (scanLines.empty()) { + return canvas; + } + + // 计算点云范围 + double xMin, xMax, yMin, yMax; + PointCloudImageUtils::CalculateScanLinesRange(scanLines, xMin, xMax, yMin, yMax); + + if (xMax <= xMin || yMax <= yMin) { + return canvas; + } + + // 计算投影参数 + double drawableW = (double)(imageWidth - margin * 2); + double drawableH = (double)(imageHeight - margin * 2); + double xScale = (xMax - xMin) / drawableW; + double yScale = (yMax - yMin) / drawableH; + double scale = std::max(xScale, yScale); + + canvas.m_xMin = xMin; + canvas.m_yMin = yMin; + canvas.m_scale = scale; + canvas.m_offsetX = margin; + canvas.m_offsetY = margin; + canvas.m_valid = true; + + // 创建黑底图像 + canvas.m_image = QImage(imageWidth, imageHeight, QImage::Format_RGB888); + canvas.m_image.fill(Qt::black); + + // 绘制灰色点云底图 + QPainter painter(&canvas.m_image); + QColor grayColor(150, 150, 150); + for (const auto& scanLine : scanLines) { + for (const auto& point : scanLine) { + if (point.pt3D.z < 1e-4) continue; + + int px = (int)((point.pt3D.x - xMin) / scale + margin); + int py = (int)((point.pt3D.y - yMin) / scale + margin); + + if (px >= 0 && px < imageWidth && py >= 0 && py < imageHeight) { + painter.setPen(QPen(grayColor, 1)); + painter.drawPoint(px, py); + } + } + } + + return canvas; +} + +bool PointCloudCanvas::isValid() const +{ + return m_valid; +} + +QImage& PointCloudCanvas::image() +{ + return m_image; +} + +const QImage& PointCloudCanvas::image() const +{ + return m_image; +} + +QPoint PointCloudCanvas::project(double x, double y) const +{ + int px = (int)((x - m_xMin) / m_scale + m_offsetX); + int py = (int)((y - m_yMin) / m_scale + m_offsetY); + return QPoint(px, py); +} + +void PointCloudCanvas::drawPoint(double x, double y, const QColor& color, int size) +{ + if (!m_valid) return; + QPoint pt = project(x, y); + if (pt.x() < 0 || pt.x() >= m_imgWidth || pt.y() < 0 || pt.y() >= m_imgHeight) return; + + QPainter painter(&m_image); + painter.setRenderHint(QPainter::Antialiasing); + painter.setPen(QPen(color, 1)); + painter.setBrush(QBrush(color)); + painter.drawEllipse(pt.x() - size / 2, pt.y() - size / 2, size, size); +} + +void PointCloudCanvas::drawCircle(double x, double y, double radius3D, + const QColor& color, int penWidth, bool fill) +{ + if (!m_valid) return; + QPoint pt = project(x, y); + if (pt.x() < 0 || pt.x() >= m_imgWidth || pt.y() < 0 || pt.y() >= m_imgHeight) return; + + int pixelRadius = (int)(radius3D / m_scale); + if (pixelRadius < 3) pixelRadius = 3; + + QPainter painter(&m_image); + painter.setRenderHint(QPainter::Antialiasing); + painter.setPen(QPen(color, penWidth)); + painter.setBrush(fill ? QBrush(color) : Qt::NoBrush); + painter.drawEllipse(pt.x() - pixelRadius, pt.y() - pixelRadius, + pixelRadius * 2, pixelRadius * 2); +} + +void PointCloudCanvas::drawText(double x, double y, const QString& text, + const QColor& color, int fontSize, + int offsetX, int offsetY) +{ + if (!m_valid) return; + QPoint pt = project(x, y); + if (pt.x() < 0 || pt.x() >= m_imgWidth || pt.y() < 0 || pt.y() >= m_imgHeight) return; + + QPainter painter(&m_image); + painter.setPen(QPen(color, 1)); + QFont font("Arial", fontSize, QFont::Bold); + painter.setFont(font); + painter.drawText(pt.x() + offsetX, pt.y() + offsetY, text); +} + +void PointCloudCanvas::drawLine(double x1, double y1, double x2, double y2, + const QColor& color, int penWidth) +{ + if (!m_valid) return; + QPoint pt1 = project(x1, y1); + QPoint pt2 = project(x2, y2); + + QPainter painter(&m_image); + painter.setRenderHint(QPainter::Antialiasing); + painter.setPen(QPen(color, penWidth)); + painter.drawLine(pt1, pt2); +}