查看点云的软件增加线点的显示

This commit is contained in:
yiyi 2026-01-17 12:29:57 +08:00
parent a892b1a7ee
commit f8e9f4f13f
6 changed files with 424 additions and 38 deletions

View File

@ -2,7 +2,7 @@
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
#define MyAppName "点云查看器"
#define MyAppVersion "1.0.1"
#define MyAppVersion "1.0.2"
#define MyAppPublisher ""
#define MyAppURL ""
#define MyAppExeName "CloudView.exe"

View File

@ -15,11 +15,16 @@
#include <QStatusBar>
#include <QLineEdit>
#include <QRadioButton>
#include <QCheckBox>
#include <QDialog>
#include <memory>
#include "PointCloudGLWidget.h"
#include "PointCloudConverter.h"
class QTextEdit;
class QTableWidget;
/**
* @brief
*
@ -83,6 +88,16 @@ private slots:
*/
void onLineSelectModeChanged(bool checked);
/**
* @brief 线
*/
void onShowLinePoints();
/**
* @brief 线
*/
void onLinePointTableClicked(int row, int column);
private:
/**
* @brief
@ -124,6 +139,16 @@ private:
*/
void updateSelectedPointsDisplay();
/**
* @brief 线0,0,0
*/
QVector<QVector3D> getOriginalLinePoints(const SelectedLineInfo& lineInfo);
/**
* @brief 线
*/
void updateLinePointsDialog();
// 点云显示控件
PointCloudGLWidget* m_glWidget;
@ -136,6 +161,7 @@ private:
QPushButton* m_btnResetView;
// 选点测距控件
QCheckBox* m_cbMeasureDistance;
QPushButton* m_btnClearPoints;
QLabel* m_lblPoint1;
QLabel* m_lblPoint2;
@ -143,6 +169,7 @@ private:
// 选线拟合控件
QPushButton* m_btnClearLine;
QPushButton* m_btnShowLinePoints;
QLineEdit* m_lineNumberInput;
QPushButton* m_btnSelectByNumber;
QRadioButton* m_rbVertical;
@ -162,6 +189,11 @@ private:
// 原始完整点云数据包含0,0,0点用于旋转
PointCloudXYZ m_originalCloud;
// 线上点对话框
QDialog* m_linePointsDialog;
QTableWidget* m_linePointsTable;
QVector<QVector3D> m_currentLinePoints; // 当前线的原始点坐标
};
#endif // CLOUD_VIEW_MAIN_WINDOW_H

View File

@ -103,6 +103,33 @@ public:
*/
void setLineSelectMode(LineSelectMode mode) { m_lineSelectMode = mode; }
/**
* @brief
*/
void setMeasureDistanceEnabled(bool enabled) { m_measureDistanceEnabled = enabled; }
/**
* @brief
*/
bool isMeasureDistanceEnabled() const { return m_measureDistanceEnabled; }
/**
* @brief 线
* @return (x, y, z)
*/
QVector<QVector3D> getSelectedLinePoints() const;
/**
* @brief
* @param point
*/
void setListHighlightPoint(const QVector3D& point);
/**
* @brief
*/
void clearListHighlightPoint();
/**
* @brief
*/
@ -178,10 +205,15 @@ private:
float m_pointSize;
LineSelectMode m_lineSelectMode;
bool m_measureDistanceEnabled;
QVector<SelectedPointInfo> m_selectedPoints;
SelectedLineInfo m_selectedLine;
static const int MAX_SELECTED_POINTS = 2;
// 列表高亮点(与选点功能区分)
bool m_hasListHighlightPoint;
QVector3D m_listHighlightPoint;
int m_colorIndex; // 颜色轮换索引
static const int COLOR_COUNT = 7; // 可用颜色数量
};

View File

@ -1,5 +1,10 @@
#include "CloudViewMainWindow.h"
#include <QFileInfo>
#include <QDialog>
#include <QTextEdit>
#include <QTableWidget>
#include <QHeaderView>
#include <QVector3D>
#include "VrLog.h"
CloudViewMainWindow::CloudViewMainWindow(QWidget* parent)
@ -9,6 +14,8 @@ CloudViewMainWindow::CloudViewMainWindow(QWidget* parent)
, m_cloudCount(0)
, m_currentLineNum(0)
, m_currentLinePtNum(0)
, m_linePointsDialog(nullptr)
, m_linePointsTable(nullptr)
{
setupUI();
LOG_INFO("CloudViewMainWindow initialized\n");
@ -131,6 +138,19 @@ QGroupBox* CloudViewMainWindow::createMeasureGroup()
lblTip->setStyleSheet("color: gray; font-size: 10px;");
layout->addWidget(lblTip);
// 测距复选框
m_cbMeasureDistance = new QCheckBox("启用测距", group);
m_cbMeasureDistance->setChecked(false);
connect(m_cbMeasureDistance, &QCheckBox::toggled, this, [this](bool checked) {
m_glWidget->setMeasureDistanceEnabled(checked);
// 切换模式时清除已选点
m_glWidget->clearSelectedPoints();
m_lblPoint1->setText("--");
m_lblPoint2->setText("--");
m_lblDistance->setText("--");
});
layout->addWidget(m_cbMeasureDistance);
// 清除选点按钮
m_btnClearPoints = new QPushButton("清除选点", group);
connect(m_btnClearPoints, &QPushButton::clicked, this, &CloudViewMainWindow::onClearSelectedPoints);
@ -203,6 +223,11 @@ QGroupBox* CloudViewMainWindow::createLineGroup()
connect(m_btnClearLine, &QPushButton::clicked, this, &CloudViewMainWindow::onClearLinePoints);
layout->addWidget(m_btnClearLine);
// 显示线上点按钮
m_btnShowLinePoints = new QPushButton("显示线上点", group);
connect(m_btnShowLinePoints, &QPushButton::clicked, this, &CloudViewMainWindow::onShowLinePoints);
layout->addWidget(m_btnShowLinePoints);
// 线索引信息
QHBoxLayout* indexLayout = new QHBoxLayout();
QLabel* lblIndexTitle = new QLabel("索引:", group);
@ -335,10 +360,19 @@ void CloudViewMainWindow::onPointSelected(const SelectedPointInfo& point)
}
updateSelectedPointsDisplay();
statusBar()->showMessage(QString("已选中点: (%1, %2, %3)")
// 状态栏显示:坐标、线号、索引号
QString statusMsg = QString("选中点: (%1, %2, %3)")
.arg(point.x, 0, 'f', 3)
.arg(point.y, 0, 'f', 3)
.arg(point.z, 0, 'f', 3));
.arg(point.z, 0, 'f', 3);
if (point.lineIndex >= 0) {
statusMsg += QString(" | 线号: %1").arg(point.lineIndex);
if (point.pointIndexInLine >= 0) {
statusMsg += QString(" | 索引号: %1").arg(point.pointIndexInLine);
}
}
statusBar()->showMessage(statusMsg);
}
void CloudViewMainWindow::onTwoPointsSelected(const SelectedPointInfo& p1, const SelectedPointInfo& p2, float distance)
@ -353,15 +387,19 @@ void CloudViewMainWindow::updateSelectedPointsDisplay()
auto selectedPoints = m_glWidget->getSelectedPoints();
if (selectedPoints.size() >= 1 && selectedPoints[0].valid) {
QString text = QString("(%1, %2, %3)")
.arg(selectedPoints[0].x, 0, 'f', 3)
.arg(selectedPoints[0].y, 0, 'f', 3)
.arg(selectedPoints[0].z, 0, 'f', 3);
QString text;
if (selectedPoints[0].lineIndex >= 0) {
text += QString("\n线索引:%1").arg(selectedPoints[0].lineIndex);
if (selectedPoints[0].pointIndexInLine >= 0) {
text += QString(" 点索引:%1").arg(selectedPoints[0].pointIndexInLine);
}
text = QString("线索引:%1 点索引:%2\nx: %3 y: %4 z: %5")
.arg(selectedPoints[0].lineIndex)
.arg(selectedPoints[0].pointIndexInLine)
.arg(selectedPoints[0].x, 0, 'f', 3)
.arg(selectedPoints[0].y, 0, 'f', 3)
.arg(selectedPoints[0].z, 0, 'f', 3);
} else {
text = QString("x: %1 y: %2 z: %3")
.arg(selectedPoints[0].x, 0, 'f', 3)
.arg(selectedPoints[0].y, 0, 'f', 3)
.arg(selectedPoints[0].z, 0, 'f', 3);
}
m_lblPoint1->setText(text);
} else {
@ -369,15 +407,19 @@ void CloudViewMainWindow::updateSelectedPointsDisplay()
}
if (selectedPoints.size() >= 2 && selectedPoints[1].valid) {
QString text = QString("(%1, %2, %3)")
.arg(selectedPoints[1].x, 0, 'f', 3)
.arg(selectedPoints[1].y, 0, 'f', 3)
.arg(selectedPoints[1].z, 0, 'f', 3);
QString text;
if (selectedPoints[1].lineIndex >= 0) {
text += QString("\n线索引:%1").arg(selectedPoints[1].lineIndex);
if (selectedPoints[1].pointIndexInLine >= 0) {
text += QString(" 点索引:%1").arg(selectedPoints[1].pointIndexInLine);
}
text = QString("线索引:%1 点索引:%2\nx: %3 y: %4 z: %5")
.arg(selectedPoints[1].lineIndex)
.arg(selectedPoints[1].pointIndexInLine)
.arg(selectedPoints[1].x, 0, 'f', 3)
.arg(selectedPoints[1].y, 0, 'f', 3)
.arg(selectedPoints[1].z, 0, 'f', 3);
} else {
text = QString("x: %1 y: %2 z: %3")
.arg(selectedPoints[1].x, 0, 'f', 3)
.arg(selectedPoints[1].y, 0, 'f', 3)
.arg(selectedPoints[1].z, 0, 'f', 3);
}
m_lblPoint2->setText(text);
} else {
@ -400,6 +442,7 @@ void CloudViewMainWindow::onLineSelectModeChanged(bool checked)
void CloudViewMainWindow::onClearLinePoints()
{
m_glWidget->clearSelectedLine();
m_glWidget->clearListHighlightPoint(); // 清除列表选中的高亮点
m_lblLineIndex->setText("--");
m_lblLinePointCount->setText("--");
statusBar()->showMessage("已清除选线");
@ -407,25 +450,32 @@ void CloudViewMainWindow::onClearLinePoints()
void CloudViewMainWindow::onLineSelected(const SelectedLineInfo& line)
{
// 重新选线时清除列表高亮点
m_glWidget->clearListHighlightPoint();
if (!line.valid) {
m_lblLineIndex->setText("--");
m_lblLinePointCount->setText("--");
return;
}
// 状态栏显示:线号/索引号、线点数
if (line.mode == LineSelectMode::Vertical) {
m_lblLineIndex->setText(QString::number(line.lineIndex));
statusBar()->showMessage(QString("已选中索引 %1 的线,共 %2 个点")
statusBar()->showMessage(QString("选中线 | 线号: %1 | 线点数: %2")
.arg(line.lineIndex)
.arg(line.pointCount));
} else {
// 横向选线:显示索引从0开始
// 横向选线:显示索引
m_lblLineIndex->setText(QString::number(line.pointIndex));
statusBar()->showMessage(QString("已选中索引 %1 的横向线,共 %2 个点")
statusBar()->showMessage(QString("选中横向线 | 索引号: %1 | 线点数: %2")
.arg(line.pointIndex)
.arg(line.pointCount));
}
m_lblLinePointCount->setText(QString::number(line.pointCount));
// 如果线上点对话框已打开,刷新内容
updateLinePointsDialog();
}
void CloudViewMainWindow::onSelectLineByNumber()
@ -464,3 +514,194 @@ void CloudViewMainWindow::onSelectLineByNumber()
}
}
QVector<QVector3D> CloudViewMainWindow::getOriginalLinePoints(const SelectedLineInfo& lineInfo)
{
QVector<QVector3D> points;
if (!lineInfo.valid || m_originalCloud.empty()) {
return points;
}
if (lineInfo.mode == LineSelectMode::Vertical) {
// 纵向选线:获取同一条扫描线上的所有点
for (size_t i = 0; i < m_originalCloud.points.size(); ++i) {
if (i < m_originalCloud.lineIndices.size() &&
m_originalCloud.lineIndices[i] == lineInfo.lineIndex) {
const auto& pt = m_originalCloud.points[i];
points.append(QVector3D(pt.x, pt.y, pt.z));
}
}
} else {
// 横向选线:获取所有线的相同索引点
if (m_currentLinePtNum > 0 && lineInfo.pointIndex >= 0) {
for (size_t i = 0; i < m_originalCloud.points.size(); ++i) {
int originalIdx = static_cast<int>(i);
if (originalIdx % m_currentLinePtNum == lineInfo.pointIndex) {
const auto& pt = m_originalCloud.points[i];
points.append(QVector3D(pt.x, pt.y, pt.z));
}
}
}
}
return points;
}
void CloudViewMainWindow::updateLinePointsDialog()
{
if (!m_linePointsDialog || !m_linePointsTable) {
return;
}
SelectedLineInfo lineInfo = m_glWidget->getSelectedLine();
if (!lineInfo.valid) {
m_linePointsTable->setRowCount(0);
m_linePointsDialog->setWindowTitle("线上点坐标");
m_currentLinePoints.clear();
return;
}
// 从原始数据获取线上点包含0,0,0
m_currentLinePoints = getOriginalLinePoints(lineInfo);
// 更新标题
QString title;
if (lineInfo.mode == LineSelectMode::Vertical) {
title = QString("线上点坐标 - 线号: %1 (共 %2 个点)")
.arg(lineInfo.lineIndex)
.arg(m_currentLinePoints.size());
} else {
title = QString("线上点坐标 - 索引号: %1 (共 %2 个点)")
.arg(lineInfo.pointIndex)
.arg(m_currentLinePoints.size());
}
m_linePointsDialog->setWindowTitle(title);
// 更新表格
m_linePointsTable->setRowCount(m_currentLinePoints.size());
// 斑马线颜色
QColor evenColor(245, 245, 245); // 浅灰色
QColor oddColor(255, 255, 255); // 白色
for (int i = 0; i < m_currentLinePoints.size(); ++i) {
const QVector3D& pt = m_currentLinePoints[i];
QColor rowColor = (i % 2 == 0) ? evenColor : oddColor;
// 序号
QTableWidgetItem* indexItem = new QTableWidgetItem(QString::number(i));
indexItem->setTextAlignment(Qt::AlignCenter);
indexItem->setBackground(rowColor);
indexItem->setFlags(indexItem->flags() & ~Qt::ItemIsEditable);
m_linePointsTable->setItem(i, 0, indexItem);
// X
QTableWidgetItem* xItem = new QTableWidgetItem(QString::number(pt.x(), 'f', 3));
xItem->setTextAlignment(Qt::AlignCenter);
xItem->setBackground(rowColor);
xItem->setFlags(xItem->flags() & ~Qt::ItemIsEditable);
m_linePointsTable->setItem(i, 1, xItem);
// Y
QTableWidgetItem* yItem = new QTableWidgetItem(QString::number(pt.y(), 'f', 3));
yItem->setTextAlignment(Qt::AlignCenter);
yItem->setBackground(rowColor);
yItem->setFlags(yItem->flags() & ~Qt::ItemIsEditable);
m_linePointsTable->setItem(i, 2, yItem);
// Z
QTableWidgetItem* zItem = new QTableWidgetItem(QString::number(pt.z(), 'f', 3));
zItem->setTextAlignment(Qt::AlignCenter);
zItem->setBackground(rowColor);
zItem->setFlags(zItem->flags() & ~Qt::ItemIsEditable);
m_linePointsTable->setItem(i, 3, zItem);
}
}
void CloudViewMainWindow::onShowLinePoints()
{
SelectedLineInfo lineInfo = m_glWidget->getSelectedLine();
if (!lineInfo.valid) {
QMessageBox::warning(this, "提示", "请先选择一条线");
return;
}
// 如果对话框已存在,刷新内容并显示
if (m_linePointsDialog) {
updateLinePointsDialog();
m_linePointsDialog->raise();
m_linePointsDialog->activateWindow();
return;
}
// 创建对话框
m_linePointsDialog = new QDialog(this);
m_linePointsDialog->resize(450, 500);
m_linePointsDialog->setAttribute(Qt::WA_DeleteOnClose);
// 对话框关闭时清理指针
connect(m_linePointsDialog, &QDialog::destroyed, this, [this]() {
m_linePointsDialog = nullptr;
m_linePointsTable = nullptr;
m_currentLinePoints.clear();
m_glWidget->clearListHighlightPoint();
});
QVBoxLayout* layout = new QVBoxLayout(m_linePointsDialog);
// 提示标签
QLabel* lblTip = new QLabel("点击行在3D视图中高亮显示", m_linePointsDialog);
lblTip->setStyleSheet("color: gray; font-size: 10px;");
layout->addWidget(lblTip);
// 创建表格控件
m_linePointsTable = new QTableWidget(m_linePointsDialog);
m_linePointsTable->setColumnCount(4);
m_linePointsTable->setHorizontalHeaderLabels({"序号", "X", "Y", "Z"});
m_linePointsTable->setFont(QFont("Consolas", 9));
m_linePointsTable->setSelectionBehavior(QAbstractItemView::SelectRows);
m_linePointsTable->setSelectionMode(QAbstractItemView::SingleSelection);
m_linePointsTable->verticalHeader()->setVisible(false);
// 设置列宽
m_linePointsTable->setColumnWidth(0, 60); // 序号
m_linePointsTable->setColumnWidth(1, 110); // X
m_linePointsTable->setColumnWidth(2, 110); // Y
m_linePointsTable->setColumnWidth(3, 110); // Z
// 表头样式
m_linePointsTable->horizontalHeader()->setStretchLastSection(true);
m_linePointsTable->horizontalHeader()->setDefaultAlignment(Qt::AlignCenter);
connect(m_linePointsTable, &QTableWidget::cellClicked,
this, &CloudViewMainWindow::onLinePointTableClicked);
layout->addWidget(m_linePointsTable);
// 关闭按钮
QPushButton* btnClose = new QPushButton("关闭", m_linePointsDialog);
connect(btnClose, &QPushButton::clicked, m_linePointsDialog, &QDialog::close);
layout->addWidget(btnClose);
// 填充数据
updateLinePointsDialog();
m_linePointsDialog->show();
}
void CloudViewMainWindow::onLinePointTableClicked(int row, int column)
{
Q_UNUSED(column);
if (row >= 0 && row < m_currentLinePoints.size()) {
const QVector3D& pt = m_currentLinePoints[row];
m_glWidget->setListHighlightPoint(pt);
// 在状态栏显示选中点信息
statusBar()->showMessage(QString("列表选中点 %1: (%2, %3, %4)")
.arg(row)
.arg(pt.x(), 0, 'f', 3)
.arg(pt.y(), 0, 'f', 3)
.arg(pt.z(), 0, 'f', 3));
}
}

View File

@ -29,6 +29,8 @@ PointCloudGLWidget::PointCloudGLWidget(QWidget* parent)
, m_currentColor(PointCloudColor::White)
, m_pointSize(1.0f)
, m_lineSelectMode(LineSelectMode::Vertical)
, m_measureDistanceEnabled(false)
, m_hasListHighlightPoint(false)
, m_colorIndex(0)
{
setFocusPolicy(Qt::StrongFocus);
@ -378,6 +380,70 @@ float PointCloudGLWidget::calculateDistance(const SelectedPointInfo& p1, const S
return std::sqrt(dx * dx + dy * dy + dz * dz);
}
QVector<QVector3D> PointCloudGLWidget::getSelectedLinePoints() const
{
QVector<QVector3D> points;
if (!m_selectedLine.valid || m_selectedLine.cloudIndex < 0) {
return points;
}
if (m_selectedLine.cloudIndex >= static_cast<int>(m_pointClouds.size())) {
return points;
}
const auto& cloudData = m_pointClouds[m_selectedLine.cloudIndex];
if (!cloudData.hasLineInfo) {
return points;
}
if (m_selectedLine.mode == LineSelectMode::Vertical) {
// 纵向选线:获取同一条扫描线上的所有点
for (size_t i = 0; i < cloudData.lineIndices.size(); ++i) {
if (cloudData.lineIndices[i] == m_selectedLine.lineIndex) {
size_t vertIdx = i * 3;
if (vertIdx + 2 < cloudData.vertices.size()) {
points.append(QVector3D(
cloudData.vertices[vertIdx],
cloudData.vertices[vertIdx + 1],
cloudData.vertices[vertIdx + 2]));
}
}
}
} else {
// 横向选线:获取所有线的相同索引点
if (cloudData.pointsPerLine > 0 && m_selectedLine.pointIndex >= 0) {
for (size_t i = 0; i < cloudData.originalIndices.size(); ++i) {
int originalIdx = cloudData.originalIndices[i];
if (originalIdx % cloudData.pointsPerLine == m_selectedLine.pointIndex) {
size_t vertIdx = i * 3;
if (vertIdx + 2 < cloudData.vertices.size()) {
points.append(QVector3D(
cloudData.vertices[vertIdx],
cloudData.vertices[vertIdx + 1],
cloudData.vertices[vertIdx + 2]));
}
}
}
}
}
return points;
}
void PointCloudGLWidget::setListHighlightPoint(const QVector3D& point)
{
m_hasListHighlightPoint = true;
m_listHighlightPoint = point;
update();
}
void PointCloudGLWidget::clearListHighlightPoint()
{
m_hasListHighlightPoint = false;
update();
}
void PointCloudGLWidget::computeBoundingBox()
{
if (m_pointClouds.empty()) {
@ -533,20 +599,25 @@ SelectedPointInfo PointCloudGLWidget::pickPoint(int screenX, int screenY)
void PointCloudGLWidget::drawSelectedPoints()
{
if (m_selectedPoints.isEmpty()) {
return;
}
glPointSize(10.0f);
glDisable(GL_DEPTH_TEST);
glBegin(GL_POINTS);
// 绘制选中的点(橙色)
for (const auto& pt : m_selectedPoints) {
if (pt.valid) {
glColor3f(1.0f, 0.5f, 0.0f);
glColor3f(1.0f, 0.5f, 0.0f); // 橙色
glVertex3f(pt.x, pt.y, pt.z);
}
}
// 绘制列表高亮点(蓝色,与选点区分)
if (m_hasListHighlightPoint) {
glColor3f(0.0f, 0.5f, 1.0f); // 蓝色
glVertex3f(m_listHighlightPoint.x(), m_listHighlightPoint.y(), m_listHighlightPoint.z());
}
glEnd();
glEnable(GL_DEPTH_TEST);
@ -712,16 +783,24 @@ void PointCloudGLWidget::mousePressEvent(QMouseEvent* event)
doneCurrent();
if (point.valid) {
if (m_selectedPoints.size() >= MAX_SELECTED_POINTS) {
if (m_measureDistanceEnabled) {
// 启用测距:最多保留两个点
if (m_selectedPoints.size() >= MAX_SELECTED_POINTS) {
m_selectedPoints.clear();
}
m_selectedPoints.append(point);
emit pointSelected(point);
if (m_selectedPoints.size() == 2) {
float distance = calculateDistance(m_selectedPoints[0], m_selectedPoints[1]);
emit twoPointsSelected(m_selectedPoints[0], m_selectedPoints[1], distance);
}
} else {
// 未启用测距:只保留一个点
m_selectedPoints.clear();
}
m_selectedPoints.append(point);
emit pointSelected(point);
if (m_selectedPoints.size() == 2) {
float distance = calculateDistance(m_selectedPoints[0], m_selectedPoints[1]);
emit twoPointsSelected(m_selectedPoints[0], m_selectedPoints[1], distance);
m_selectedPoints.append(point);
emit pointSelected(point);
}
update();

View File

@ -3,6 +3,8 @@
#include <QIcon>
#include "CloudViewMainWindow.h"
#define APP_VERSION "1.0.2"
int main(int argc, char* argv[])
{
QApplication app(argc, argv);
@ -18,14 +20,14 @@ int main(int argc, char* argv[])
// 设置应用信息
app.setApplicationName("CloudView");
app.setOrganizationName("CloudView");
app.setApplicationVersion("1.0.1");
app.setApplicationVersion(APP_VERSION);
// 设置应用程序图标
app.setWindowIcon(QIcon(":/logo.png"));
// 创建并显示主窗口
CloudViewMainWindow mainWindow;
mainWindow.setWindowTitle("点云查看器 v1.0.1");
mainWindow.setWindowTitle(QString("点云查看器 v%1").arg(APP_VERSION));
mainWindow.resize(1280, 720);
mainWindow.show();