From 5e978eaea4f7891cc2f4a9ac871598cbb3178f50 Mon Sep 17 00:00:00 2001 From: yiyi Date: Sun, 29 Mar 2026 22:21:10 +0800 Subject: [PATCH] =?UTF-8?q?=E8=BE=93=E5=85=A5=E5=9D=90=E6=A0=87=E5=8F=AF?= =?UTF-8?q?=E7=9B=B4=E6=8E=A5=E6=98=BE=E7=A4=BA;=E7=82=B9=E4=BA=91?= =?UTF-8?q?=E7=94=9F=E6=88=90=E5=9B=BE=E5=83=8F=E5=A2=9E=E5=8A=A0=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CloudUtils/Inc/PointCloudImageUtils.h | 4 +- CloudUtils/Src/PointCloudImageUtils.cpp | 131 +++++++++++++++++++++++- CloudView/Inc/CloudViewMainWindow.h | 3 + CloudView/Src/CloudViewMainWindow.cpp | 66 ++++++------ 4 files changed, 166 insertions(+), 38 deletions(-) diff --git a/CloudUtils/Inc/PointCloudImageUtils.h b/CloudUtils/Inc/PointCloudImageUtils.h index 43b8402..026d250 100644 --- a/CloudUtils/Inc/PointCloudImageUtils.h +++ b/CloudUtils/Inc/PointCloudImageUtils.h @@ -99,11 +99,13 @@ public: double rotateY_deg = 0.0); // 孔洞检测图像生成 - 热力图底图 + 孔洞标记 + // useZGradient: 为true时根据点云Z值范围生成灰度渐变底图,否则使用统一灰色底图 static QImage GenerateHoleDetectionImage( const std::vector>& scanLines, const std::vector& holes, double rotateX_deg = 0.0, - double rotateY_deg = 0.0); + double rotateY_deg = 0.0, + bool useZGradient = false); private: // 定义线特征颜色和大小获取函数 diff --git a/CloudUtils/Src/PointCloudImageUtils.cpp b/CloudUtils/Src/PointCloudImageUtils.cpp index 7b0e8f8..b32d2ca 100644 --- a/CloudUtils/Src/PointCloudImageUtils.cpp +++ b/CloudUtils/Src/PointCloudImageUtils.cpp @@ -1448,10 +1448,135 @@ QImage PointCloudImageUtils::GenerateHoleDetectionImage( const std::vector>& scanLines, const std::vector& holes, double rotateX_deg, - double rotateY_deg) + double rotateY_deg, + bool useZGradient) { - // 先生成热力图底图(透传旋转参数) - QImage image = GenerateHeatmapImage(scanLines, rotateX_deg, rotateY_deg); + QImage image; + + if (useZGradient) { + // 根据Z值范围生成灰度渐变底图 + if (scanLines.empty()) { + return QImage(); + } + + // 预计算旋转参数 + double cosX = cos(rotateX_deg * PI / 180.0); + double sinX = sin(rotateX_deg * PI / 180.0); + double cosY = cos(rotateY_deg * PI / 180.0); + double sinY = sin(rotateY_deg * PI / 180.0); + bool needRotate = (fabs(rotateX_deg) > 1e-6 || fabs(rotateY_deg) > 1e-6); + + auto rotatePoint = [&](double x, double y, double z, double& rx, double& ry, double& rz) { + double y1 = y * cosX - z * sinX; + double z1 = y * sinX + z * cosX; + rx = x * cosY + z1 * sinY; + ry = y1; + rz = -x * sinY + z1 * cosY; + }; + + // 固定图像尺寸(与 GenerateHeatmapImage 一致) + int imgRows = 992; + int imgCols = 1056; + int x_skip = 50; + int y_skip = 50; + + // 计算 XY 范围和 Z 范围 + double xMin, xMax, yMin, yMax; + double zMin = std::numeric_limits::max(); + double zMax = -std::numeric_limits::max(); + + if (!needRotate) { + CalculateScanLinesRange(scanLines, xMin, xMax, yMin, yMax); + for (const auto& scanLine : scanLines) { + for (const auto& point : scanLine) { + if (point.pt3D.z < 1e-4) continue; + if (point.pt3D.z < zMin) zMin = point.pt3D.z; + if (point.pt3D.z > zMax) zMax = point.pt3D.z; + } + } + } else { + xMin = yMin = std::numeric_limits::max(); + xMax = yMax = -std::numeric_limits::max(); + for (const auto& scanLine : scanLines) { + for (const auto& point : scanLine) { + if (point.pt3D.z < 1e-4) continue; + double rx, ry, rz; + rotatePoint(point.pt3D.x, point.pt3D.y, point.pt3D.z, rx, ry, rz); + if (rx < xMin) xMin = rx; + if (rx > xMax) xMax = rx; + if (ry < yMin) yMin = ry; + if (ry > yMax) yMax = ry; + if (point.pt3D.z < zMin) zMin = point.pt3D.z; + if (point.pt3D.z > zMax) zMax = point.pt3D.z; + } + } + } + + if (xMax <= xMin || yMax <= yMin) { + return QImage(); + } + + double y_rows = (double)(imgRows - y_skip * 2); + double x_cols = (double)(imgCols - x_skip * 2); + double x_scale = (xMax - xMin) / x_cols; + double y_scale = (yMax - yMin) / y_rows; + if (x_scale < y_scale) + x_scale = y_scale; + else + y_scale = x_scale; + + // 自适应点大小 + int lineCount = static_cast(scanLines.size()); + int pointSize = 2; + if (lineCount > 1) { + double lineSpacing = (yMax - yMin) / (lineCount - 1); + pointSize = (int)std::ceil(lineSpacing / x_scale); + if (pointSize < 2) pointSize = 2; + if (pointSize > 6) pointSize = 6; + } + + // 创建图像 + image = QImage(imgCols, imgRows, QImage::Format_RGB888); + image.fill(Qt::black); + + QPainter painter(&image); + + double zRange = zMax - zMin; + for (const auto& scanLine : scanLines) { + for (const auto& point : scanLine) { + if (point.pt3D.z < 1e-4) continue; + + double projX, projY; + if (needRotate) { + double rx, ry, rz; + rotatePoint(point.pt3D.x, point.pt3D.y, point.pt3D.z, rx, ry, rz); + projX = rx; + projY = ry; + } else { + projX = point.pt3D.x; + projY = point.pt3D.y; + } + + int px = (int)((projX - xMin) / x_scale + x_skip); + int py = (int)((projY - yMin) / y_scale + y_skip); + + if (px >= 0 && px < imgCols && py >= 0 && py < imgRows) { + // Z值映射到灰度:zMin → 0(黑), zMax → 255(白) + int gray = 0; + if (zRange > 1e-6) { + gray = (int)(255.0 * (point.pt3D.z - zMin) / zRange); + if (gray < 0) gray = 0; + if (gray > 255) gray = 255; + } + painter.fillRect(px, py, pointSize, pointSize, QColor(gray, gray, gray)); + } + } + } + } else { + // 使用统一灰色底图 + image = GenerateHeatmapImage(scanLines, rotateX_deg, rotateY_deg); + } + if (image.isNull() || holes.empty()) { return image; } diff --git a/CloudView/Inc/CloudViewMainWindow.h b/CloudView/Inc/CloudViewMainWindow.h index 04ba8ea..e7e34f8 100644 --- a/CloudView/Inc/CloudViewMainWindow.h +++ b/CloudView/Inc/CloudViewMainWindow.h @@ -303,6 +303,9 @@ private: QLineEdit* m_editRz2; QPushButton* m_btnShowPose2; + // 姿态坐标轴缩放 + QLineEdit* m_editPoseScale; + // 欧拉角旋转顺序选择 QComboBox* m_comboEulerOrder; diff --git a/CloudView/Src/CloudViewMainWindow.cpp b/CloudView/Src/CloudViewMainWindow.cpp index b7e261a..bfd3786 100644 --- a/CloudView/Src/CloudViewMainWindow.cpp +++ b/CloudView/Src/CloudViewMainWindow.cpp @@ -596,6 +596,16 @@ QGroupBox* CloudViewMainWindow::createMeasureGroup() connect(m_editRy2, &QLineEdit::returnPressed, this, &CloudViewMainWindow::onShowPose2); connect(m_editRz2, &QLineEdit::returnPressed, this, &CloudViewMainWindow::onShowPose2); + // 姿态坐标轴缩放输入 + QHBoxLayout* scaleLayout = new QHBoxLayout(); + scaleLayout->setSpacing(5); + scaleLayout->addWidget(new QLabel("坐标轴长度:", group)); + m_editPoseScale = new QLineEdit("10.0", group); + m_editPoseScale->setMaximumWidth(60); + scaleLayout->addWidget(m_editPoseScale); + scaleLayout->addStretch(); + layout->addLayout(scaleLayout); + // 分隔线 QFrame* line3 = new QFrame(group); line3->setFrameShape(QFrame::HLine); @@ -1588,8 +1598,9 @@ void CloudViewMainWindow::onShowPose1() return; } - // 固定大小为10 - float scale = 10.0f; + // 读取坐标轴缩放 + float scale = m_editPoseScale->text().toFloat(&ok); + if (!ok || scale <= 0) scale = 25.0f; // 清除之前的姿态点 m_glWidget->clearPosePoints(); @@ -1661,8 +1672,9 @@ void CloudViewMainWindow::onShowPose2() return; } - // 固定大小为10 - float scale = 10.0f; + // 读取坐标轴缩放 + float scale = m_editPoseScale->text().toFloat(&ok); + if (!ok || scale <= 0) scale = 25.0f; // 清除之前的姿态点 m_glWidget->clearPosePoints(); @@ -2023,12 +2035,6 @@ void CloudViewMainWindow::onViewAnglesChanged(float rotX, float rotY, float rotZ void CloudViewMainWindow::onPoint1CoordChanged() { - auto selectedPoints = m_glWidget->getSelectedPoints(); - if (selectedPoints.isEmpty() || !selectedPoints[0].valid) { - QMessageBox::warning(this, "提示", "请先选择点1"); - return; - } - // 读取编辑框中的坐标 bool ok = true; float x = m_editPoint1X->text().toFloat(&ok); @@ -2049,17 +2055,16 @@ void CloudViewMainWindow::onPoint1CoordChanged() return; } - // 更新选中点的坐标 - m_glWidget->updateSelectedPointCoord(0, x, y, z); + // 直接设置选中点坐标(无论是否已有鼠标选点) + m_glWidget->setSelectedPointCoord(0, x, y, z); + updateSelectedPointsDisplay(); // 如果启用了测距且有两个点,重新计算距离 - if (m_glWidget->isMeasureDistanceEnabled() && selectedPoints.size() >= 2 && selectedPoints[1].valid) { - auto updatedPoints = m_glWidget->getSelectedPoints(); - if (updatedPoints.size() >= 2) { - float distance = m_glWidget->calculateDistance(updatedPoints[0], updatedPoints[1]); - m_lblDistance->setText(QString("%1 mm").arg(distance, 0, 'f', 3)); - statusBar()->showMessage(QString("点1坐标已更新,距离: %1 mm").arg(distance, 0, 'f', 3)); - } + auto updatedPoints = m_glWidget->getSelectedPoints(); + if (m_glWidget->isMeasureDistanceEnabled() && updatedPoints.size() >= 2 && updatedPoints[1].valid) { + float distance = m_glWidget->calculateDistance(updatedPoints[0], updatedPoints[1]); + m_lblDistance->setText(QString("%1 mm").arg(distance, 0, 'f', 3)); + statusBar()->showMessage(QString("点1坐标已更新,距离: %1 mm").arg(distance, 0, 'f', 3)); } else { statusBar()->showMessage(QString("点1坐标已更新为 (%1, %2, %3)").arg(x, 0, 'f', 3).arg(y, 0, 'f', 3).arg(z, 0, 'f', 3)); } @@ -2069,12 +2074,6 @@ void CloudViewMainWindow::onPoint1CoordChanged() void CloudViewMainWindow::onPoint2CoordChanged() { - auto selectedPoints = m_glWidget->getSelectedPoints(); - if (selectedPoints.size() < 2 || !selectedPoints[1].valid) { - QMessageBox::warning(this, "提示", "请先选择点2"); - return; - } - // 读取编辑框中的坐标 bool ok = true; float x = m_editPoint2X->text().toFloat(&ok); @@ -2095,17 +2094,16 @@ void CloudViewMainWindow::onPoint2CoordChanged() return; } - // 更新选中点的坐标 - m_glWidget->updateSelectedPointCoord(1, x, y, z); + // 直接设置选中点坐标(无论是否已有鼠标选点) + m_glWidget->setSelectedPointCoord(1, x, y, z); + updateSelectedPointsDisplay(); // 如果启用了测距,重新计算距离 - if (m_glWidget->isMeasureDistanceEnabled()) { - auto updatedPoints = m_glWidget->getSelectedPoints(); - if (updatedPoints.size() >= 2) { - float distance = m_glWidget->calculateDistance(updatedPoints[0], updatedPoints[1]); - m_lblDistance->setText(QString("%1 mm").arg(distance, 0, 'f', 3)); - statusBar()->showMessage(QString("点2坐标已更新,距离: %1 mm").arg(distance, 0, 'f', 3)); - } + auto updatedPoints = m_glWidget->getSelectedPoints(); + if (m_glWidget->isMeasureDistanceEnabled() && updatedPoints.size() >= 2 && updatedPoints[0].valid) { + float distance = m_glWidget->calculateDistance(updatedPoints[0], updatedPoints[1]); + m_lblDistance->setText(QString("%1 mm").arg(distance, 0, 'f', 3)); + statusBar()->showMessage(QString("点2坐标已更新,距离: %1 mm").arg(distance, 0, 'f', 3)); } else { statusBar()->showMessage(QString("点2坐标已更新为 (%1, %2, %3)").arg(x, 0, 'f', 3).arg(y, 0, 'f', 3).arg(z, 0, 'f', 3)); }