From 9fbc9e7edfd93679a10f7acc41a9f3296ecd2704 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=B0=E4=BB=94?= Date: Sun, 14 Jun 2026 17:04:39 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=95=B0=E6=8D=AE=E7=9A=84?= =?UTF-8?q?=E5=AD=98=E5=82=A8=E6=8F=90=E5=8D=87=E6=95=88=E7=8E=87;cloudvie?= =?UTF-8?q?w=20=E5=A2=9E=E5=8A=A0ply=E7=9A=84=E5=AD=98=E5=82=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CloudUtils/Src/LaserDataLoader.cpp | 45 +- CloudView/Inc/CloudViewMainWindow.h | 39 +- CloudView/Inc/PointCloudConverter.h | 16 + CloudView/Src/CloudViewMainWindow.cpp | 1162 ++++++------ CloudView/Src/PointCloudConverter.cpp | 116 ++ CloudView/Src/PointCloudGLWidget.cpp | 15 +- CloudView/main.cpp | 2 +- VrCommon/Inc/VZNL_Types.h | 2328 +++++++++++++++++++++++++ VrCommon/Inc/VrError.h | 40 +- VrUtils/VrUtils.pro | 1 + 10 files changed, 3036 insertions(+), 728 deletions(-) create mode 100644 VrCommon/Inc/VZNL_Types.h diff --git a/CloudUtils/Src/LaserDataLoader.cpp b/CloudUtils/Src/LaserDataLoader.cpp index 2f3447a..7fadd92 100644 --- a/CloudUtils/Src/LaserDataLoader.cpp +++ b/CloudUtils/Src/LaserDataLoader.cpp @@ -1,13 +1,15 @@ #include "LaserDataLoader.h" -#include -#include +#include #include #include +#include +#include +#include +#include #include #include -#include + #include "VrLog.h" -#include // 辅助函数:去除字符串末尾的 \r 字符(处理 Windows 格式的 CR LF 换行符) static inline void TrimCarriageReturn(std::string& str) @@ -33,7 +35,7 @@ int LaserDataLoader::LoadLaserScanData(const std::string& fileName, int& maxTimeStamp, int& clockPerSecond) { - LOG_INFO("Loading laser scan data from file: %s\n", fileName.c_str()); + LOG_INFO("Loading from file: %s\n", fileName.c_str()); // 清空输出参数 laserLines.clear(); @@ -167,7 +169,7 @@ int LaserDataLoader::SaveLaserScanData(const std::string& fileName, int maxTimeStamp, int clockPerSecond) { - LOG_INFO("Saving unified laser scan data to file: %s\n", fileName.c_str()); + LOG_INFO("Saving to file: %s\n", fileName.c_str()); if (laserLines.empty() || lineNum <= 0) { m_lastError = "Invalid input parameters for saving unified data"; @@ -211,6 +213,25 @@ int LaserDataLoader::SaveLaserScanData(const std::string& fileName, sw << "{ " << points2D[i].ptLeft2D.x << "," << points2D[i].ptLeft2D.y << " }-"; sw << "{ " << points2D[i].ptRight2D.x << "," << points2D[i].ptRight2D.y << " }" << std::endl; } + } else if (dataType == keResultDataType_PointXYZI && lineData.p3DPoint) { + // LiDAR 点云:snprintf 批量写入,每 256 点 flush + // 坐标 mm 范围可达 ±200000 → "%.6f" 最长 14 字符 → 整行 ≤73 字符 + const SVzNLPointXYZI* points = static_cast(lineData.p3DPoint); + const int n = lineData.nPointCount; + constexpr int BATCH = 256; + constexpr int PER_POINT = 80; + char buf[BATCH * PER_POINT]; + for (int base = 0; base < n; base += BATCH) + { + int end = (base + BATCH < n) ? (base + BATCH) : n; + char* p = buf; + for (int i = base; i < end; ++i) + { + p += snprintf(p, PER_POINT, "{ %.6f, %.6f, %.6f } - { 0, 0 } - { 0, 0 }\n", + points[i].fData[0], points[i].fData[1], points[i].fData[2]); + } + sw.write(buf, p - buf); + } } else if (dataType == keResultDataType_PointXYZRGBA && lineData.p3DPoint) { // 写入RGBD格式数据 const SVzNLPointXYZRGBA* points = static_cast(lineData.p3DPoint); @@ -222,12 +243,12 @@ int LaserDataLoader::SaveLaserScanData(const std::string& fileName, int r = (points[i].nRGB >> 16) & 0xFF; int g = (points[i].nRGB >> 8) & 0xFF; int b = points[i].nRGB & 0xFF; - - sw << "{" << std::fixed << std::setprecision(6) << x << "," - << std::fixed << std::setprecision(6) << y << "," - << std::fixed << std::setprecision(6) << z << "," - << std::fixed << std::setprecision(6) << b * 1.0f / 255 << "," - << std::fixed << std::setprecision(6) << g * 1.0f / 255 << "," + + sw << "{" << std::fixed << std::setprecision(6) << x << "," + << std::fixed << std::setprecision(6) << y << "," + << std::fixed << std::setprecision(6) << z << "," + << std::fixed << std::setprecision(6) << b * 1.0f / 255 << "," + << std::fixed << std::setprecision(6) << g * 1.0f / 255 << "," << std::fixed << std::setprecision(6) << r * 1.0f / 255 << "}-"; sw << "{ " << points2D[i].sLeft.x << "," << points2D[i].sLeft.y << " }-"; sw << "{ " << points2D[i].sRight.x << "," << points2D[i].sRight.y << " }" << std::endl; diff --git a/CloudView/Inc/CloudViewMainWindow.h b/CloudView/Inc/CloudViewMainWindow.h index 80cb71d..d5d3701 100644 --- a/CloudView/Inc/CloudViewMainWindow.h +++ b/CloudView/Inc/CloudViewMainWindow.h @@ -154,25 +154,26 @@ private slots: void onEulerOrderChanged(int index); /** - * @brief 从文件加载变换矩阵 + * @brief 眼在手外矩阵变换(直接应用矩阵到点云) */ - void onLoadMatrix(); + void onEyeToHandTransform(); /** - * @brief 应用矩阵变换到所有点云 + * @brief 眼在手上矩阵变换(手眼矩阵 + 机械臂位姿) */ - void onApplyMatrix(); - void onApplyEyeInHandTransform(); + void onEyeInHandTransform(); - /** - * @brief 重置矩阵为单位矩阵 - */ - void onResetMatrix(); void onGeneratePointCloud(); void onRotateCloudByZ(); - void onSavePointCloud(); + void onSavePointCloud(); // 保存 txt 点云 + void onSavePlyPcdCloud(); // 保存 ply/pcd 点云 void onConvertEulerMatrix(); + /** + * @brief 姿态补偿计算 + */ + void onPoseCompensation(); + private: /** * @brief 初始化界面 @@ -214,11 +215,6 @@ private: */ QWidget* createLinePage(); - /** - * @brief 创建矩阵变换页面 - */ - QWidget* createTransformPage(); - /** * @brief 创建选线拟合组 */ @@ -354,19 +350,6 @@ private: QTableWidget* m_linePointsTable; QVector m_currentLinePoints; // 当前线的原始点坐标 - // 矩阵变换控件 - QTextEdit* m_matrixEdit; - QPushButton* m_btnLoadMatrix; - QPushButton* m_btnApplyMatrix; - QLineEdit* m_editRobotX; - QLineEdit* m_editRobotY; - QLineEdit* m_editRobotZ; - QLineEdit* m_editRobotRx; - QLineEdit* m_editRobotRy; - QLineEdit* m_editRobotRz; - QPushButton* m_btnApplyEyeInHand; - QPushButton* m_btnResetMatrix; - // 悬浮缩放按钮 QToolButton* m_btnZoomIn; QToolButton* m_btnZoomOut; diff --git a/CloudView/Inc/PointCloudConverter.h b/CloudView/Inc/PointCloudConverter.h index 6925a45..dfc446c 100644 --- a/CloudView/Inc/PointCloudConverter.h +++ b/CloudView/Inc/PointCloudConverter.h @@ -91,6 +91,22 @@ public: */ int saveToTxt(const std::string& fileName, const PointCloudXYZ& cloud, int lineNum, int linePtNum); + /** + * @brief 保存点云到 PLY 文件(ASCII 格式) + * @param fileName 输出文件路径 + * @param cloud 点云数据 + * @return 0=成功, -1=失败 + */ + int saveToPly(const std::string& fileName, const PointCloudXYZ& cloud); + + /** + * @brief 保存点云到 PCD 文件(ASCII 格式,兼容 PCL) + * @param fileName 输出文件路径 + * @param cloud 点云数据 + * @return 0=成功, -1=失败 + */ + int saveToPcd(const std::string& fileName, const PointCloudXYZ& cloud); + /** * @brief 旋转点云(行列转置 + 交换XY) * @param cloud 输入点云 diff --git a/CloudView/Src/CloudViewMainWindow.cpp b/CloudView/Src/CloudViewMainWindow.cpp index 613b2a7..18d1cfc 100644 --- a/CloudView/Src/CloudViewMainWindow.cpp +++ b/CloudView/Src/CloudViewMainWindow.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include "VrLog.h" @@ -65,14 +66,9 @@ bool parseMatrixText(const QString& text, QMatrix4x4& matrix, QString* errorMess if (!ok || row.size() != 4) { if (errorMessage) { - *errorMessage = QString("Matrix row %1 must contain 4 numeric values.").arg(rows.size() + 1); - } - return false; -#if 0 *errorMessage = QString("矩阵格式无效,第 %1 行需要 4 个数值").arg(rows.size() + 1); } - */ return false; -#endif + return false; } rows.append(row); @@ -80,14 +76,9 @@ bool parseMatrixText(const QString& text, QMatrix4x4& matrix, QString* errorMess if (rows.size() != 4) { if (errorMessage) { - *errorMessage = QString("Matrix must contain 4 rows. Current row count: %1.").arg(rows.size()); - } - return false; -#if 0 *errorMessage = QString("矩阵需要 4 行数值,当前为 %1 行").arg(rows.size()); } - */ return false; -#endif + return false; } matrix.setToIdentity(); @@ -418,17 +409,37 @@ bool CloudViewMainWindow::eventFilter(QObject* obj, QEvent* event) void CloudViewMainWindow::setupUI() { + // ── 文件菜单 ── + QMenu* fileMenu = menuBar()->addMenu("文件"); + QAction* actOpenFile = fileMenu->addAction("打开...\tCtrl+O"); + actOpenFile->setShortcut(QKeySequence("Ctrl+O")); + connect(actOpenFile, &QAction::triggered, this, &CloudViewMainWindow::onOpenFile); + fileMenu->addSeparator(); + QAction* actSaveCloud = fileMenu->addAction("保存txt点云...\tCtrl+S"); + actSaveCloud->setShortcut(QKeySequence("Ctrl+S")); + connect(actSaveCloud, &QAction::triggered, this, &CloudViewMainWindow::onSavePointCloud); + QAction* actSavePlyPcd = fileMenu->addAction("保存ply/pcd点云...\tCtrl+Shift+S"); + actSavePlyPcd->setShortcut(QKeySequence("Ctrl+Shift+S")); + connect(actSavePlyPcd, &QAction::triggered, this, &CloudViewMainWindow::onSavePlyPcdCloud); + + // ── 点云菜单 ── QMenu* cloudMenu = menuBar()->addMenu("点云"); QAction* actGenerateCloud = cloudMenu->addAction("生成长方体点云..."); connect(actGenerateCloud, &QAction::triggered, this, &CloudViewMainWindow::onGeneratePointCloud); QAction* actRotateByZ = cloudMenu->addAction("点云旋转..."); connect(actRotateByZ, &QAction::triggered, this, &CloudViewMainWindow::onRotateCloudByZ); - QAction* actSaveCloud = cloudMenu->addAction("保存点云..."); - connect(actSaveCloud, &QAction::triggered, this, &CloudViewMainWindow::onSavePointCloud); QMenu* toolMenu = menuBar()->addMenu("工具"); QAction* actEulerPose = toolMenu->addAction("欧拉角与方向向量矩阵互转..."); connect(actEulerPose, &QAction::triggered, this, &CloudViewMainWindow::onConvertEulerMatrix); + QAction* actPoseComp = toolMenu->addAction("姿态补偿计算..."); + connect(actPoseComp, &QAction::triggered, this, &CloudViewMainWindow::onPoseCompensation); + + QMenu* transformMenu = menuBar()->addMenu("矩阵变换"); + QAction* actEyeToHand = transformMenu->addAction("眼在手外..."); + connect(actEyeToHand, &QAction::triggered, this, &CloudViewMainWindow::onEyeToHandTransform); + QAction* actEyeInHand = transformMenu->addAction("眼在手上..."); + connect(actEyeInHand, &QAction::triggered, this, &CloudViewMainWindow::onEyeInHandTransform); // 创建中央控件 QWidget* centralWidget = new QWidget(this); setCentralWidget(centralWidget); @@ -521,14 +532,10 @@ QWidget* CloudViewMainWindow::createViewerArea() m_glWidget->installEventFilter(this); // 连接信号 - connect(m_glWidget, &PointCloudGLWidget::pointSelected, - this, &CloudViewMainWindow::onPointSelected); - connect(m_glWidget, &PointCloudGLWidget::twoPointsSelected, - this, &CloudViewMainWindow::onTwoPointsSelected); - connect(m_glWidget, &PointCloudGLWidget::lineSelected, - this, &CloudViewMainWindow::onLineSelected); - connect(m_glWidget, &PointCloudGLWidget::viewAnglesChanged, - this, &CloudViewMainWindow::onViewAnglesChanged); + connect(m_glWidget, &PointCloudGLWidget::pointSelected, this, &CloudViewMainWindow::onPointSelected); + connect(m_glWidget, &PointCloudGLWidget::twoPointsSelected, this, &CloudViewMainWindow::onTwoPointsSelected); + connect(m_glWidget, &PointCloudGLWidget::lineSelected, this, &CloudViewMainWindow::onLineSelected); + connect(m_glWidget, &PointCloudGLWidget::viewAnglesChanged, this, &CloudViewMainWindow::onViewAnglesChanged); return widget; } @@ -548,7 +555,6 @@ QWidget* CloudViewMainWindow::createControlPanel() QTabWidget* tabWidget = new QTabWidget(widget); tabWidget->addTab(createMeasurePage(), "选点测距"); tabWidget->addTab(createLinePage(), "选线"); - tabWidget->addTab(createTransformPage(), "矩阵变换"); layout->addWidget(tabWidget); // 点云列表组 @@ -1012,7 +1018,7 @@ QGroupBox* CloudViewMainWindow::createMeasureGroup() QLabel* lblDistTitle = new QLabel("距离:", group); lblDistTitle->setStyleSheet("font-size: 10px;"); m_lblDistance = new QLabel("--", group); - m_lblDistance->setStyleSheet("font-weight: bold; color: green; font-size: 10px;"); + m_lblDistance->setStyleSheet("font-weight: bold; color: green; font-size: 20px;"); distLayout->addWidget(lblDistTitle); distLayout->addWidget(m_lblDistance, 1); layout->addLayout(distLayout); @@ -1366,8 +1372,10 @@ void CloudViewMainWindow::onOpenPoseFile() float pitch = rot[1].toFloat(&ok); if (!ok) continue; float yaw = rot[2].toFloat(&ok); if (!ok) continue; - // 固定大小为10 - float scale = 10.0f; + // 使用界面上配置的坐标轴长度 + bool scaleOk = true; + float scale = m_editPoseScale->text().toFloat(&scaleOk); + if (!scaleOk || scale <= 0) scale = 10.0f; poses.append(PosePoint(x, y, z, roll, pitch, yaw, scale)); validCount++; @@ -1455,6 +1463,11 @@ bool CloudViewMainWindow::loadBoundingBoxFile(const QString& fileName) }; const int colorCount = sizeof(colors) / sizeof(colors[0]); + // 使用界面上配置的坐标轴长度 + bool poseScaleOk = true; + float poseScale = m_editPoseScale->text().toFloat(&poseScaleOk); + if (!poseScaleOk || poseScale <= 0) poseScale = 10.0f; + for (int i = 0; i < records.size(); ++i) { QJsonObject record = records[i].toObject(); QJsonObject bbox = record["bounding_box"].toObject(); @@ -1504,7 +1517,7 @@ bool CloudViewMainWindow::loadBoundingBoxFile(const QString& fileName) float pitch = static_cast(ori["pitch"].toDouble()); float yaw = static_cast(ori["yaw"].toDouble()); - graspPoses.append(PosePoint(px, py, pz, roll, pitch, yaw, 15.0f)); + graspPoses.append(PosePoint(px, py, pz, roll, pitch, yaw, poseScale)); } // 解析左抓取点 @@ -1520,7 +1533,7 @@ bool CloudViewMainWindow::loadBoundingBoxFile(const QString& fileName) float pitch = static_cast(ori["pitch"].toDouble()); float yaw = static_cast(ori["yaw"].toDouble()); - graspPoses.append(PosePoint(px, py, pz, roll, pitch, yaw, 15.0f)); + graspPoses.append(PosePoint(px, py, pz, roll, pitch, yaw, poseScale)); } } @@ -2227,682 +2240,270 @@ void CloudViewMainWindow::onShowInputLine() void CloudViewMainWindow::onClearInputLine() { m_glWidget->clearLineSegments(); - statusBar()->showMessage("已清除输入的线段"); + statusBar()->showMessage(QString("已清除输入的线段")); } -#if 0 -QWidget* CloudViewMainWindow::createTransformPage() -{ - QWidget* page = new QWidget(this); - QVBoxLayout* layout = new QVBoxLayout(page); - layout->setContentsMargins(0, 5, 0, 0); - - QGroupBox* group = new QGroupBox("矩阵变换", page); - group->setMaximumWidth(400); - QVBoxLayout* groupLayout = new QVBoxLayout(group); - - // 操作说明 - QLabel* lblTip = new QLabel("输入或从文件加载 4x4 变换矩阵,应用到所有点云", group); - lblTip->setWordWrap(true); - lblTip->setStyleSheet("color: gray; font-size: 10px;"); - groupLayout->addWidget(lblTip); - - // 矩阵编辑区域 - QLabel* lblMatrix = new QLabel("变换矩阵 (4x4):", group); - lblMatrix->setStyleSheet("font-weight: bold;"); - groupLayout->addWidget(lblMatrix); - - m_matrixEdit = new QTextEdit(group); - m_matrixEdit->setFont(QFont("Consolas", 10)); - m_matrixEdit->setMinimumHeight(100); - m_matrixEdit->setMaximumHeight(120); - // 初始化为单位矩阵 - m_matrixEdit->setPlainText( - "1.0 0.0 0.0 0.0\n" - "0.0 1.0 0.0 0.0\n" - "0.0 0.0 1.0 0.0\n" - "0.0 0.0 0.0 1.0" - ); - groupLayout->addWidget(m_matrixEdit); - - // 从文件加载按钮 - m_btnLoadMatrix = new QPushButton("从文件加载矩阵", group); - m_btnLoadMatrix->setMinimumHeight(30); - connect(m_btnLoadMatrix, &QPushButton::clicked, this, &CloudViewMainWindow::onLoadMatrix); - groupLayout->addWidget(m_btnLoadMatrix); - - // 按钮行 - QHBoxLayout* btnLayout = new QHBoxLayout(); - m_btnApplyMatrix = new QPushButton("应用变换", group); - m_btnApplyMatrix->setMinimumHeight(30); - m_btnApplyMatrix->setStyleSheet("QPushButton { background-color: #4CAF50; color: white; font-weight: bold; }" - "QPushButton:hover { background-color: #45a049; }"); - connect(m_btnApplyMatrix, &QPushButton::clicked, this, &CloudViewMainWindow::onApplyMatrix); - btnLayout->addWidget(m_btnApplyMatrix); - - m_btnResetMatrix = new QPushButton("重置矩阵", group); - connect(m_btnResetMatrix, &QPushButton::clicked, this, &CloudViewMainWindow::onResetMatrix); - btnLayout->addWidget(m_btnResetMatrix); - groupLayout->addLayout(btnLayout); - - // 文件格式说明 - QLabel* lblFormat = new QLabel( - "矩阵文件格式:4行,每行4个数值\n" - "分隔符:空格/Tab/逗号\n" - "#开头的行为注释", group); - lblFormat->setWordWrap(true); - lblFormat->setStyleSheet("color: gray; font-size: 9px;"); - groupLayout->addWidget(lblFormat); - - layout->addWidget(group); - - QGroupBox* eyeGroup = new QGroupBox("眼在手上转换", page); - eyeGroup->setMaximumWidth(400); - QVBoxLayout* eyeLayout = new QVBoxLayout(eyeGroup); - - QLabel* lblEyeTip = new QLabel(QString(), eyeGroup); /* - "使用上方 4x4 矩阵作为“相机坐标系 -> 末端坐标系”的手眼标定矩阵。
" - "输入当前机械臂位姿(mm / deg)后,可将所有点云转换到机械臂基坐标系。
" - "角度组合方式:ZYX(Rz-Ry-Rx)。", - eyeGroup); - */ lblEyeTip->setWordWrap(true); - /* lblEyeTip->setText( - "使用上方 4x4 矩阵作为“相机坐标系 -> 末端坐标系”的手眼标定矩阵。
" - "输入当前机械臂位姿(mm / deg)后,可将所有点云转换到机械臂基坐标系。
" - "角度组合方式:ZYX(Rz-Ry-Rx)。"); - lblEyeTip->setText( - "使用上方 4x4 矩阵作为“相机坐标系 -> 末端坐标系”的手眼标定矩阵。
" - "输入当前机械臂位姿(mm / deg)后,可将所有点云转换到机械臂基坐标系。
" - "角度组合方式:ZYX(Rz-Ry-Rx)。"); - */ lblEyeTip->setTextFormat(Qt::RichText); - lblEyeTip->setText( - "使用上方 4x4 矩阵作为“相机坐标系 -> 末端坐标系”的手眼标定矩阵。
" - "输入当前机械臂位姿(mm / deg)后,可将所有点云转换到机械臂基坐标系。
" - "角度组合方式:ZYX(Rz-Ry-Rx)。"); - lblEyeTip->setStyleSheet("color: gray; font-size: 10px;"); - eyeLayout->addWidget(lblEyeTip); - - QGridLayout* poseLayout = new QGridLayout(); - poseLayout->setHorizontalSpacing(8); - poseLayout->setVerticalSpacing(6); - - auto addPoseEdit = [&](const QString& label, QLineEdit*& edit, int row, int col) { - QLabel* lbl = new QLabel(label, eyeGroup); - edit = new QLineEdit("0.0", eyeGroup); - edit->setAlignment(Qt::AlignRight); - edit->setFont(QFont("Consolas", 10)); - poseLayout->addWidget(lbl, row, col * 2); - poseLayout->addWidget(edit, row, col * 2 + 1); - }; - - addPoseEdit("X:", m_editRobotX, 0, 0); - addPoseEdit("Y:", m_editRobotY, 0, 1); - addPoseEdit("Z:", m_editRobotZ, 0, 2); - addPoseEdit("RX:", m_editRobotRx, 1, 0); - addPoseEdit("RY:", m_editRobotRy, 1, 1); - addPoseEdit("RZ:", m_editRobotRz, 1, 2); - eyeLayout->addLayout(poseLayout); - - m_btnApplyEyeInHand = new QPushButton("应用眼在手上转换", eyeGroup); - m_btnApplyEyeInHand->setMinimumHeight(30); - m_btnApplyEyeInHand->setStyleSheet("QPushButton { background-color: #1976D2; color: white; font-weight: bold; }" - "QPushButton:hover { background-color: #1565C0; }"); - connect(m_btnApplyEyeInHand, &QPushButton::clicked, this, &CloudViewMainWindow::onApplyEyeInHandTransform); - eyeLayout->addWidget(m_btnApplyEyeInHand); - - for (QLineEdit* edit : {m_editRobotX, m_editRobotY, m_editRobotZ, m_editRobotRx, m_editRobotRy, m_editRobotRz}) { - connect(edit, &QLineEdit::returnPressed, this, &CloudViewMainWindow::onApplyEyeInHandTransform); - } - - layout->addWidget(eyeGroup); - layout->addStretch(); - return page; -} - -void CloudViewMainWindow::onLoadMatrix() -{ - QString fileName = QFileDialog::getOpenFileName( - this, - "打开矩阵文件", - QString(), - "文本文件 (*.txt);;所有文件 (*.*)" - ); - - if (fileName.isEmpty()) { - return; - } - - QFile file(fileName); - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - QMessageBox::critical(this, "错误", QString("无法打开文件: %1").arg(fileName)); - return; - } - - QTextStream matrixStream(&file); - const QString loadedMatrixText = matrixStream.readAll(); - file.close(); - - QMatrix4x4 matrix; - QString errorMessage; - if (!parseMatrixText(loadedMatrixText, matrix, &errorMessage)) { - QMessageBox::warning(this, "格式错误", errorMessage); - return; - } - - m_matrixEdit->setPlainText(formatMatrixText(matrix)); - statusBar()->showMessage(QString("已从 %1 加载矩阵").arg(QFileInfo(fileName).fileName())); - LOG_INFO("[CloudView] Loaded matrix from %s\n", fileName.toStdString().c_str()); - return; - - QTextStream in(&file); - QVector> rows; - - while (!in.atEnd()) { - QString line = in.readLine().trimmed(); - // 跳过空行和注释 - if (line.isEmpty() || line.startsWith('#')) { - continue; - } - - // 将逗号替换为空格,统一分隔符 - line.replace(',', ' '); - line.replace('\t', ' '); - QStringList parts = line.split(' ', QString::SkipEmptyParts); - - QVector row; - bool ok = true; - for (const QString& part : parts) { - float val = part.toFloat(&ok); - if (!ok) break; - row.append(val); - } - - if (!ok || row.size() != 4) { - QMessageBox::warning(this, "格式错误", - QString("第 %1 行格式无效,需要4个数值").arg(rows.size() + 1)); - file.close(); - return; - } - rows.append(row); - - if (rows.size() == 4) { - break; - } - } - file.close(); - - if (rows.size() != 4) { - QMessageBox::warning(this, "格式错误", - QString("矩阵需要4行数据,当前只有 %1 行").arg(rows.size())); - return; - } - - // 将矩阵显示到编辑区域 - QString matrixText; - for (int r = 0; r < 4; ++r) { - QStringList vals; - for (int c = 0; c < 4; ++c) { - vals.append(QString::number(static_cast(rows[r][c]), 'f', 6)); - } - matrixText += vals.join(" "); - if (r < 3) matrixText += "\n"; - } - m_matrixEdit->setPlainText(matrixText); - - statusBar()->showMessage(QString("已从 %1 加载矩阵").arg(QFileInfo(fileName).fileName())); - LOG_INFO("[CloudView] Loaded matrix from %s\n", fileName.toStdString().c_str()); -} - -void CloudViewMainWindow::onApplyMatrix() +void CloudViewMainWindow::onEyeToHandTransform() { if (m_glWidget->getCloudCount() == 0) { QMessageBox::warning(this, "提示", "请先加载点云"); return; } - // 解析编辑区域中的矩阵 - QMatrix4x4 parsedMatrix; - QString errorMessage; - if (!parseMatrixText(m_matrixEdit->toPlainText().trimmed(), parsedMatrix, &errorMessage)) { - QMessageBox::warning(this, "格式错误", errorMessage); - return; - } + QDialog dialog(this); + dialog.setWindowTitle("眼在手外 - 矩阵变换"); + dialog.setModal(true); - if (parsedMatrix.isIdentity()) { - QMessageBox::information(this, "提示", "当前矩阵为单位矩阵,无需变换"); - return; - } + QVBoxLayout* layout = new QVBoxLayout(&dialog); - applyTransformToAllClouds(parsedMatrix); - statusBar()->showMessage("已应用矩阵变换到所有点云"); - LOG_INFO("[CloudView] Applied matrix transform to all point clouds\n"); - return; + QLabel* tip = new QLabel("输入或从文件加载 4x4 变换矩阵(相机坐标系 -> 基坐标系),直接应用到所有点云。", &dialog); + tip->setWordWrap(true); + tip->setStyleSheet("color: gray; font-size: 10px;"); + layout->addWidget(tip); - QString text = m_matrixEdit->toPlainText().trimmed(); - QStringList lines = text.split('\n', QString::SkipEmptyParts); - - QVector> rows; - for (const QString& line : lines) { - QString cleaned = line.trimmed(); - if (cleaned.isEmpty() || cleaned.startsWith('#')) { - continue; - } - cleaned.replace(',', ' '); - cleaned.replace('\t', ' '); - QStringList parts = cleaned.split(' ', QString::SkipEmptyParts); - - QVector row; - bool ok = true; - for (const QString& part : parts) { - float val = part.toFloat(&ok); - if (!ok) break; - row.append(val); - } - - if (!ok || row.size() != 4) { - QMessageBox::warning(this, "格式错误", "矩阵格式无效,需要4行4列数值"); - return; - } - rows.append(row); - } - - if (rows.size() != 4) { - QMessageBox::warning(this, "格式错误", - QString("矩阵需要4行数据,当前 %1 行").arg(rows.size())); - return; - } - - // 构造 QMatrix4x4(按行优先存储) - float values[16]; - for (int r = 0; r < 4; ++r) { - for (int c = 0; c < 4; ++c) { - values[r * 4 + c] = rows[r][c]; - } - } - QMatrix4x4 matrix(values); - - // 检查是否为单位矩阵 - if (matrix.isIdentity()) { - QMessageBox::information(this, "提示", "当前矩阵为单位矩阵,无需变换"); - return; - } - - // 应用变换 - m_glWidget->transformAllClouds(matrix); - - statusBar()->showMessage("已应用矩阵变换到所有点云"); - LOG_INFO("[CloudView] Applied matrix transform to all point clouds\n"); -} - -void CloudViewMainWindow::onApplyEyeInHandTransform() -{ - if (m_glWidget->getCloudCount() == 0) { - QMessageBox::warning(this, "提示", "请先加载点云"); - return; - } - - QMatrix4x4 handEyeMatrix; - QString errorMessage; - if (!parseMatrixText(m_matrixEdit->toPlainText().trimmed(), handEyeMatrix, &errorMessage)) { QMessageBox::warning(this, "格式错误", QString("Hand-eye matrix is invalid: %1").arg(errorMessage)); return; /* - QMessageBox::warning(this, "格式错误", QString("手眼矩阵无效:%1").arg(errorMessage)); - return; - QMessageBox::warning(this, "格式错误", QString("手眼矩阵无效:%1").arg(errorMessage)); - return; - QMessageBox::warning(this, "格式错误", QString("手眼矩阵无效:%1").arg(errorMessage)); - return; - */ } - - bool ok = false; - const float x = m_editRobotX->text().trimmed().toFloat(&ok); - if (!ok) { - QMessageBox::warning(this, "格式错误", "机械臂 X 位姿无效"); - return; - } - - const float y = m_editRobotY->text().trimmed().toFloat(&ok); - if (!ok) { - QMessageBox::warning(this, "格式错误", "机械臂 Y 位姿无效"); - return; - } - - const float z = m_editRobotZ->text().trimmed().toFloat(&ok); - if (!ok) { - QMessageBox::warning(this, "格式错误", "机械臂 Z 位姿无效"); - return; - } - - const float rx = m_editRobotRx->text().trimmed().toFloat(&ok); - if (!ok) { - QMessageBox::warning(this, "格式错误", "机械臂 RX 位姿无效"); - return; - } - - const float ry = m_editRobotRy->text().trimmed().toFloat(&ok); - if (!ok) { - QMessageBox::warning(this, "格式错误", "机械臂 RY 位姿无效"); - return; - } - - const float rz = m_editRobotRz->text().trimmed().toFloat(&ok); - if (!ok) { - QMessageBox::warning(this, "格式错误", "机械臂 RZ 位姿无效"); - return; - } - - const QMatrix4x4 robotPoseMatrix = buildRobotPoseMatrix(x, y, z, rx, ry, rz); - const QMatrix4x4 totalMatrix = robotPoseMatrix * handEyeMatrix; - - if (totalMatrix.isIdentity()) { - QMessageBox::information(this, "提示", "当前位姿和手眼矩阵组合为单位矩阵,无需变换"); - return; - } - - applyTransformToAllClouds(totalMatrix); - - statusBar()->showMessage( - QString("已应用眼在手上转换,当前机械臂位姿: [%1, %2, %3, %4, %5, %6]") - .arg(x, 0, 'f', 3) - .arg(y, 0, 'f', 3) - .arg(z, 0, 'f', 3) - .arg(rx, 0, 'f', 3) - .arg(ry, 0, 'f', 3) - .arg(rz, 0, 'f', 3)); - LOG_INFO("[CloudView] Applied eye-in-hand transform, robot pose=(%.3f, %.3f, %.3f, %.3f, %.3f, %.3f)\n", - x, y, z, rx, ry, rz); -} - -void CloudViewMainWindow::onResetMatrix() -{ - m_matrixEdit->setPlainText( - "1.0 0.0 0.0 0.0\n" - "0.0 1.0 0.0 0.0\n" - "0.0 0.0 1.0 0.0\n" - "0.0 0.0 0.0 1.0" - ); - statusBar()->showMessage("矩阵已重置为单位矩阵"); -} - -#endif - -QWidget* CloudViewMainWindow::createTransformPage() -{ - QWidget* page = new QWidget(this); - QVBoxLayout* layout = new QVBoxLayout(page); - layout->setContentsMargins(0, 5, 0, 0); - - QGroupBox* group = new QGroupBox("矩阵变换", page); - group->setMaximumWidth(400); - QVBoxLayout* groupLayout = new QVBoxLayout(group); - - QLabel* lblTip = new QLabel(group); /* - "输入或从文件加载 4x4 变换矩阵,可直接应用到所有点云,也可作为下方眼在手上转换的手眼矩阵。", - group); */ - lblTip->setText("输入或从文件加载 4x4 变换矩阵,可直接应用到所有点云,也可作为下方眼在手上转换的手眼矩阵。"); - lblTip->setText("输入或从文件加载 4x4 变换矩阵,可直接应用到所有点云,也可作为下方眼在手上转换的手眼矩阵。"); - lblTip->setWordWrap(true); - lblTip->setStyleSheet("color: gray; font-size: 10px;"); - groupLayout->addWidget(lblTip); - - QLabel* lblMatrix = new QLabel("变换矩阵 (4x4):", group); + QLabel* lblMatrix = new QLabel("变换矩阵 (4x4):", &dialog); lblMatrix->setStyleSheet("font-weight: bold;"); - groupLayout->addWidget(lblMatrix); + layout->addWidget(lblMatrix); - m_matrixEdit = new QTextEdit(group); - m_matrixEdit->setFont(QFont("Consolas", 10)); - m_matrixEdit->setMinimumHeight(100); - m_matrixEdit->setMaximumHeight(120); - m_matrixEdit->setPlainText( + QTextEdit* matrixEdit = new QTextEdit(&dialog); + matrixEdit->setFont(QFont("Consolas", 10)); + matrixEdit->setMinimumHeight(100); + matrixEdit->setMaximumHeight(120); + matrixEdit->setPlainText( "1.0 0.0 0.0 0.0\n" "0.0 1.0 0.0 0.0\n" "0.0 0.0 1.0 0.0\n" "0.0 0.0 0.0 1.0"); - groupLayout->addWidget(m_matrixEdit); + layout->addWidget(matrixEdit); - m_btnLoadMatrix = new QPushButton("从文件加载矩阵", group); - m_btnLoadMatrix->setMinimumHeight(30); - connect(m_btnLoadMatrix, &QPushButton::clicked, this, &CloudViewMainWindow::onLoadMatrix); - groupLayout->addWidget(m_btnLoadMatrix); + QPushButton* btnLoad = new QPushButton("从文件加载矩阵", &dialog); + connect(btnLoad, &QPushButton::clicked, [this, matrixEdit]() { + const QString fileName = QFileDialog::getOpenFileName( + this, "打开矩阵文件", QString(), + "标定矩阵文件 (*.ini *.txt);;INI文件 (*.ini);;文本文件 (*.txt);;所有文件 (*.*)"); + if (fileName.isEmpty()) return; - QHBoxLayout* btnLayout = new QHBoxLayout(); - m_btnApplyMatrix = new QPushButton("应用变换", group); - m_btnApplyMatrix->setMinimumHeight(30); - m_btnApplyMatrix->setStyleSheet("QPushButton { background-color: #4CAF50; color: white; font-weight: bold; }" - "QPushButton:hover { background-color: #45a049; }"); - connect(m_btnApplyMatrix, &QPushButton::clicked, this, &CloudViewMainWindow::onApplyMatrix); - btnLayout->addWidget(m_btnApplyMatrix); + if (fileName.endsWith(".ini", Qt::CaseInsensitive)) { + QVector> matrixList; + QString errorMessage; + if (!parseCalibIniFile(fileName, matrixList, &errorMessage)) { + QMessageBox::warning(this, "格式错误", errorMessage); + return; + } + if (matrixList.size() == 1) { + matrixEdit->setPlainText(formatMatrixText(matrixList[0].second)); + return; + } + QDialog dlg(this); + dlg.setWindowTitle("选择标定矩阵"); + QVBoxLayout* l = new QVBoxLayout(&dlg); + l->addWidget(new QLabel(QString("文件中包含 %1 个标定矩阵,请选择:").arg(matrixList.size()), &dlg)); + QListWidget* lw = new QListWidget(&dlg); + for (auto& m : matrixList) lw->addItem(m.first); + lw->setCurrentRow(0); + l->addWidget(lw); + QDialogButtonBox* bb = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &dlg); + l->addWidget(bb); + connect(bb, &QDialogButtonBox::accepted, &dlg, &QDialog::accept); + connect(bb, &QDialogButtonBox::rejected, &dlg, &QDialog::reject); + if (dlg.exec() == QDialog::Accepted && lw->currentRow() >= 0 && lw->currentRow() < matrixList.size()) + matrixEdit->setPlainText(formatMatrixText(matrixList[lw->currentRow()].second)); + return; + } - m_btnResetMatrix = new QPushButton("重置矩阵", group); - connect(m_btnResetMatrix, &QPushButton::clicked, this, &CloudViewMainWindow::onResetMatrix); - btnLayout->addWidget(m_btnResetMatrix); - groupLayout->addLayout(btnLayout); - - QLabel* lblFormat = new QLabel(group); /* - "矩阵文件格式:4 行,每行 4 个数值\n" - "分隔符:空格 / Tab / 逗号\n" - "# 开头的行会被视为注释", - group); */ - lblFormat->setText("矩阵文件格式:4 行,每行 4 个数值\n分隔符:空格 / Tab / 逗号\n# 开头的行会被视为注释"); - lblFormat->setText("矩阵文件格式:4 行,每行 4 个数值\n分隔符:空格 / Tab / 逗号\n# 开头的行会被视为注释"); - lblFormat->setWordWrap(true); - lblFormat->setStyleSheet("color: gray; font-size: 9px;"); - groupLayout->addWidget(lblFormat); - - layout->addWidget(group); - - QGroupBox* eyeGroup = new QGroupBox("眼在手上转换", page); - eyeGroup->setMaximumWidth(400); - QVBoxLayout* eyeLayout = new QVBoxLayout(eyeGroup); - - QLabel* lblEyeTip = new QLabel(eyeGroup); /* - "使用上方 4x4 矩阵作为“相机坐标系 -> 末端坐标系”的手眼矩阵。
" - "输入当前机械臂位姿(单位:mm / deg)后,可将所有点云转换到机械臂基坐标系。
" - "角度组合方式:ZYX(Rz-Ry-Rx)。", - eyeGroup); */ - lblEyeTip->setText("使用上方 4x4 矩阵作为“相机坐标系 -> 末端坐标系”的手眼矩阵。
输入当前机械臂位姿(单位:mm / deg)后,可将所有点云转换到机械臂基坐标系。
角度组合方式:ZYX(Rz-Ry-Rx)。"); - lblEyeTip->setText("使用上方 4x4 矩阵作为“相机坐标系 -> 末端坐标系”的手眼矩阵。
输入当前机械臂位姿(单位:mm / deg)后,可将所有点云转换到机械臂基坐标系。
角度组合方式:ZYX(Rz-Ry-Rx)。"); - lblEyeTip->setWordWrap(true); - lblEyeTip->setTextFormat(Qt::RichText); - lblEyeTip->setStyleSheet("color: gray; font-size: 10px;"); - eyeLayout->addWidget(lblEyeTip); - - QGridLayout* poseLayout = new QGridLayout(); - poseLayout->setHorizontalSpacing(8); - poseLayout->setVerticalSpacing(6); - - auto addPoseEdit = [&](const QString& label, QLineEdit*& edit, int row, int col) { - QLabel* lbl = new QLabel(label, eyeGroup); - edit = new QLineEdit("0.0", eyeGroup); - edit->setAlignment(Qt::AlignRight); - edit->setFont(QFont("Consolas", 10)); - poseLayout->addWidget(lbl, row, col * 2); - poseLayout->addWidget(edit, row, col * 2 + 1); - }; - - addPoseEdit("X:", m_editRobotX, 0, 0); - addPoseEdit("Y:", m_editRobotY, 0, 1); - addPoseEdit("Z:", m_editRobotZ, 0, 2); - addPoseEdit("RX:", m_editRobotRx, 1, 0); - addPoseEdit("RY:", m_editRobotRy, 1, 1); - addPoseEdit("RZ:", m_editRobotRz, 1, 2); - eyeLayout->addLayout(poseLayout); - - m_btnApplyEyeInHand = new QPushButton("应用眼在手上转换", eyeGroup); - m_btnApplyEyeInHand->setMinimumHeight(30); - m_btnApplyEyeInHand->setStyleSheet("QPushButton { background-color: #1976D2; color: white; font-weight: bold; }" - "QPushButton:hover { background-color: #1565C0; }"); - connect(m_btnApplyEyeInHand, &QPushButton::clicked, this, &CloudViewMainWindow::onApplyEyeInHandTransform); - eyeLayout->addWidget(m_btnApplyEyeInHand); - - for (QLineEdit* edit : {m_editRobotX, m_editRobotY, m_editRobotZ, m_editRobotRx, m_editRobotRy, m_editRobotRz}) { - connect(edit, &QLineEdit::returnPressed, this, &CloudViewMainWindow::onApplyEyeInHandTransform); - } - - layout->addWidget(eyeGroup); - layout->addStretch(); - return page; -} - -void CloudViewMainWindow::onLoadMatrix() -{ - const QString fileName = QFileDialog::getOpenFileName( - this, - "打开矩阵文件", - QString(), - "标定矩阵文件 (*.ini *.txt);;INI文件 (*.ini);;文本文件 (*.txt);;所有文件 (*.*)"); - - if (fileName.isEmpty()) { - return; - } - - // INI 格式:EyeHandCalibMatrixInfo.ini 标定矩阵 - if (fileName.endsWith(".ini", Qt::CaseInsensitive)) { - QVector> matrixList; + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + QMessageBox::critical(this, "错误", QString("无法打开文件: %1").arg(fileName)); + return; + } + QTextStream in(&file); + QString text = in.readAll(); + file.close(); + QMatrix4x4 matrix; QString errorMessage; - if (!parseCalibIniFile(fileName, matrixList, &errorMessage)) { + if (!parseMatrixText(text, matrix, &errorMessage)) { QMessageBox::warning(this, "格式错误", errorMessage); return; } + matrixEdit->setPlainText(formatMatrixText(matrix)); + }); + layout->addWidget(btnLoad); - // 只有一个矩阵时直接加载 - if (matrixList.size() == 1) { - m_matrixEdit->setPlainText(formatMatrixText(matrixList[0].second)); - statusBar()->showMessage(QString("已从 %1 加载 %2").arg(QFileInfo(fileName).fileName(), matrixList[0].first)); - LOG_INFO("[CloudView] Loaded %s from %s\n", matrixList[0].first.toStdString().c_str(), fileName.toStdString().c_str()); - return; - } + QLabel* lblFormat = new QLabel("矩阵文件格式:4 行,每行 4 个数值,分隔符:空格 / Tab / 逗号", &dialog); + lblFormat->setWordWrap(true); + lblFormat->setStyleSheet("color: gray; font-size: 9px;"); + layout->addWidget(lblFormat); - // 多个矩阵时弹出选择对话框 - QDialog dlg(this); - dlg.setWindowTitle("选择标定矩阵"); - QVBoxLayout* dlgLayout = new QVBoxLayout(&dlg); - dlgLayout->addWidget(new QLabel(QString("文件中包含 %1 个标定矩阵,请选择:").arg(matrixList.size()), &dlg)); + QDialogButtonBox* btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &dialog); + btnBox->button(QDialogButtonBox::Ok)->setText("应用变换"); + btnBox->button(QDialogButtonBox::Ok)->setStyleSheet("QPushButton { background-color: #4CAF50; color: white; font-weight: bold; }" + "QPushButton:hover { background-color: #45a049; }"); + layout->addWidget(btnBox); + connect(btnBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); + connect(btnBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); - QListWidget* listWidget = new QListWidget(&dlg); - for (int i = 0; i < matrixList.size(); ++i) { - listWidget->addItem(matrixList[i].first); - } - listWidget->setCurrentRow(0); - dlgLayout->addWidget(listWidget); - - QDialogButtonBox* btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &dlg); - dlgLayout->addWidget(btnBox); - connect(btnBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept); - connect(btnBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject); - - if (dlg.exec() != QDialog::Accepted) return; - - const int selected = listWidget->currentRow(); - if (selected < 0 || selected >= matrixList.size()) return; - - m_matrixEdit->setPlainText(formatMatrixText(matrixList[selected].second)); - statusBar()->showMessage(QString("已从 %1 加载 %2").arg(QFileInfo(fileName).fileName(), matrixList[selected].first)); - LOG_INFO("[CloudView] Loaded %s from %s\n", matrixList[selected].first.toStdString().c_str(), fileName.toStdString().c_str()); - return; - } - - // 纯文本格式 - QFile file(fileName); - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - QMessageBox::critical(this, "错误", QString("无法打开文件: %1").arg(fileName)); - return; - } - - QTextStream in(&file); - const QString text = in.readAll(); - file.close(); + if (dialog.exec() != QDialog::Accepted) return; QMatrix4x4 matrix; QString errorMessage; - if (!parseMatrixText(text, matrix, &errorMessage)) { + if (!parseMatrixText(matrixEdit->toPlainText().trimmed(), matrix, &errorMessage)) { QMessageBox::warning(this, "格式错误", errorMessage); return; } - - m_matrixEdit->setPlainText(formatMatrixText(matrix)); - statusBar()->showMessage(QString("已从 %1 加载矩阵").arg(QFileInfo(fileName).fileName())); - LOG_INFO("[CloudView] Loaded matrix from %s\n", fileName.toStdString().c_str()); -} - -void CloudViewMainWindow::onApplyMatrix() -{ - if (m_glWidget->getCloudCount() == 0) { - QMessageBox::warning(this, "提示", "请先加载点云"); - return; - } - - QMatrix4x4 matrix; - QString errorMessage; - if (!parseMatrixText(m_matrixEdit->toPlainText().trimmed(), matrix, &errorMessage)) { - QMessageBox::warning(this, "格式错误", errorMessage); - return; - } - if (matrix.isIdentity()) { QMessageBox::information(this, "提示", "当前矩阵为单位矩阵,无需变换"); return; } applyTransformToAllClouds(matrix); - statusBar()->showMessage("已将矩阵变换应用到所有点云"); - LOG_INFO("[CloudView] Applied matrix transform to all point clouds\n"); + statusBar()->showMessage("已将眼在手外矩阵变换应用到所有点云"); + LOG_INFO("[CloudView] Applied eye-to-hand matrix transform to all point clouds\n"); } -void CloudViewMainWindow::onApplyEyeInHandTransform() +void CloudViewMainWindow::onEyeInHandTransform() { if (m_glWidget->getCloudCount() == 0) { QMessageBox::warning(this, "提示", "请先加载点云"); return; } + QDialog dialog(this); + dialog.setWindowTitle("眼在手上 - 矩阵变换"); + dialog.setModal(true); + + QVBoxLayout* layout = new QVBoxLayout(&dialog); + + QLabel* tip = new QLabel( + "使用 4x4 矩阵作为“相机坐标系 -> 末端坐标系”的手眼矩阵。
" + "输入当前机械臂位姿(单位:mm / deg)后,将所有点云转换到机械臂基坐标系。
" + "角度组合方式:ZYX(Rz-Ry-Rx)。", &dialog); + tip->setWordWrap(true); + tip->setTextFormat(Qt::RichText); + tip->setStyleSheet("color: gray; font-size: 10px;"); + layout->addWidget(tip); + + QLabel* lblMatrix = new QLabel("手眼矩阵 (4x4):", &dialog); + lblMatrix->setStyleSheet("font-weight: bold;"); + layout->addWidget(lblMatrix); + + QTextEdit* matrixEdit = new QTextEdit(&dialog); + matrixEdit->setFont(QFont("Consolas", 10)); + matrixEdit->setMinimumHeight(100); + matrixEdit->setMaximumHeight(120); + matrixEdit->setPlainText( + "1.0 0.0 0.0 0.0\n" + "0.0 1.0 0.0 0.0\n" + "0.0 0.0 1.0 0.0\n" + "0.0 0.0 0.0 1.0"); + layout->addWidget(matrixEdit); + + QPushButton* btnLoad = new QPushButton("从文件加载矩阵", &dialog); + connect(btnLoad, &QPushButton::clicked, [this, matrixEdit]() { + const QString fileName = QFileDialog::getOpenFileName( + this, "打开矩阵文件", QString(), + "标定矩阵文件 (*.ini *.txt);;INI文件 (*.ini);;文本文件 (*.txt);;所有文件 (*.*)"); + if (fileName.isEmpty()) return; + + if (fileName.endsWith(".ini", Qt::CaseInsensitive)) { + QVector> matrixList; + QString errorMessage; + if (!parseCalibIniFile(fileName, matrixList, &errorMessage)) { + QMessageBox::warning(this, "格式错误", errorMessage); + return; + } + if (matrixList.size() == 1) { + matrixEdit->setPlainText(formatMatrixText(matrixList[0].second)); + return; + } + QDialog dlg(this); + dlg.setWindowTitle("选择标定矩阵"); + QVBoxLayout* l = new QVBoxLayout(&dlg); + l->addWidget(new QLabel(QString("文件中包含 %1 个标定矩阵,请选择:").arg(matrixList.size()), &dlg)); + QListWidget* lw = new QListWidget(&dlg); + for (auto& m : matrixList) lw->addItem(m.first); + lw->setCurrentRow(0); + l->addWidget(lw); + QDialogButtonBox* bb = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &dlg); + l->addWidget(bb); + connect(bb, &QDialogButtonBox::accepted, &dlg, &QDialog::accept); + connect(bb, &QDialogButtonBox::rejected, &dlg, &QDialog::reject); + if (dlg.exec() == QDialog::Accepted && lw->currentRow() >= 0 && lw->currentRow() < matrixList.size()) + matrixEdit->setPlainText(formatMatrixText(matrixList[lw->currentRow()].second)); + return; + } + + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + QMessageBox::critical(this, "错误", QString("无法打开文件: %1").arg(fileName)); + return; + } + QTextStream in(&file); + QString text = in.readAll(); + file.close(); + QMatrix4x4 matrix; + QString errorMessage; + if (!parseMatrixText(text, matrix, &errorMessage)) { + QMessageBox::warning(this, "格式错误", errorMessage); + return; + } + matrixEdit->setPlainText(formatMatrixText(matrix)); + }); + layout->addWidget(btnLoad); + + // 机械臂位姿输入 + QGroupBox* poseGroup = new QGroupBox("机械臂位姿", &dialog); + QGridLayout* poseLayout = new QGridLayout(poseGroup); + poseLayout->setHorizontalSpacing(8); + poseLayout->setVerticalSpacing(6); + + auto addPoseEdit = [&](const QString& label, int row, int col) -> QLineEdit* { + QLabel* lbl = new QLabel(label, poseGroup); + QLineEdit* edit = new QLineEdit("0.0", poseGroup); + edit->setAlignment(Qt::AlignRight); + edit->setFont(QFont("Consolas", 10)); + poseLayout->addWidget(lbl, row, col * 2); + poseLayout->addWidget(edit, row, col * 2 + 1); + return edit; + }; + + QLineEdit* editX = addPoseEdit("X:", 0, 0); + QLineEdit* editY = addPoseEdit("Y:", 0, 1); + QLineEdit* editZ = addPoseEdit("Z:", 0, 2); + QLineEdit* editRx = addPoseEdit("RX:", 1, 0); + QLineEdit* editRy = addPoseEdit("RY:", 1, 1); + QLineEdit* editRz = addPoseEdit("RZ:", 1, 2); + layout->addWidget(poseGroup); + + QDialogButtonBox* btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &dialog); + btnBox->button(QDialogButtonBox::Ok)->setText("应用转换"); + btnBox->button(QDialogButtonBox::Ok)->setStyleSheet("QPushButton { background-color: #1976D2; color: white; font-weight: bold; }" + "QPushButton:hover { background-color: #1565C0; }"); + layout->addWidget(btnBox); + connect(btnBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); + connect(btnBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); + + if (dialog.exec() != QDialog::Accepted) return; + QMatrix4x4 handEyeMatrix; QString errorMessage; - if (!parseMatrixText(m_matrixEdit->toPlainText().trimmed(), handEyeMatrix, &errorMessage)) { - QMessageBox::warning(this, "格式错误", QString("手眼矩阵无效:%1").arg(errorMessage)); - return; - QMessageBox::warning(this, "格式错误", QString("手眼矩阵无效:%1").arg(errorMessage)); - return; + if (!parseMatrixText(matrixEdit->toPlainText().trimmed(), handEyeMatrix, &errorMessage)) { QMessageBox::warning(this, "格式错误", QString("手眼矩阵无效:%1").arg(errorMessage)); return; } bool ok = false; - const float x = m_editRobotX->text().trimmed().toFloat(&ok); - if (!ok) { - QMessageBox::warning(this, "格式错误", "机械臂 X 位姿无效"); - return; + float vals[6]; + const char* labels[] = {"X", "Y", "Z", "RX", "RY", "RZ"}; + QLineEdit* edits[] = {editX, editY, editZ, editRx, editRy, editRz}; + for (int i = 0; i < 6; ++i) { + vals[i] = edits[i]->text().trimmed().toFloat(&ok); + if (!ok) { + QMessageBox::warning(this, "格式错误", QString("机械臂 %1 位姿无效").arg(labels[i])); + return; + } } - const float y = m_editRobotY->text().trimmed().toFloat(&ok); - if (!ok) { - QMessageBox::warning(this, "格式错误", "机械臂 Y 位姿无效"); - return; - } - - const float z = m_editRobotZ->text().trimmed().toFloat(&ok); - if (!ok) { - QMessageBox::warning(this, "格式错误", "机械臂 Z 位姿无效"); - return; - } - - const float rx = m_editRobotRx->text().trimmed().toFloat(&ok); - if (!ok) { - QMessageBox::warning(this, "格式错误", "机械臂 RX 位姿无效"); - return; - } - - const float ry = m_editRobotRy->text().trimmed().toFloat(&ok); - if (!ok) { - QMessageBox::warning(this, "格式错误", "机械臂 RY 位姿无效"); - return; - } - - const float rz = m_editRobotRz->text().trimmed().toFloat(&ok); - if (!ok) { - QMessageBox::warning(this, "格式错误", "机械臂 RZ 位姿无效"); - return; - } - - const QMatrix4x4 totalMatrix = buildRobotPoseMatrix(x, y, z, rx, ry, rz) * handEyeMatrix; + const QMatrix4x4 totalMatrix = buildRobotPoseMatrix(vals[0], vals[1], vals[2], vals[3], vals[4], vals[5]) * handEyeMatrix; if (totalMatrix.isIdentity()) { QMessageBox::information(this, "提示", "当前位姿和手眼矩阵组合为单位矩阵,无需变换"); return; @@ -2910,33 +2511,11 @@ void CloudViewMainWindow::onApplyEyeInHandTransform() applyTransformToAllClouds(totalMatrix); statusBar()->showMessage( - QString("已应用眼在手上转换,当前机械臂位姿: [%1, %2, %3, %4, %5, %6]") - .arg(x, 0, 'f', 3) - .arg(y, 0, 'f', 3) - .arg(z, 0, 'f', 3) - .arg(rx, 0, 'f', 3) - .arg(ry, 0, 'f', 3) - .arg(rz, 0, 'f', 3)); + QString("已应用眼在手上转换,机械臂位姿: [%1, %2, %3, %4, %5, %6]") + .arg(vals[0], 0, 'f', 3).arg(vals[1], 0, 'f', 3).arg(vals[2], 0, 'f', 3) + .arg(vals[3], 0, 'f', 3).arg(vals[4], 0, 'f', 3).arg(vals[5], 0, 'f', 3)); LOG_INFO("[CloudView] Applied eye-in-hand transform, robot pose=(%.3f, %.3f, %.3f, %.3f, %.3f, %.3f)\n", - x, y, z, rx, ry, rz); -} - -void CloudViewMainWindow::onResetMatrix() -{ - m_matrixEdit->setPlainText( - "1.0 0.0 0.0 0.0\n" - "0.0 1.0 0.0 0.0\n" - "0.0 0.0 1.0 0.0\n" - "0.0 0.0 0.0 1.0"); - - if (m_editRobotX) m_editRobotX->setText("0.0"); - if (m_editRobotY) m_editRobotY->setText("0.0"); - if (m_editRobotZ) m_editRobotZ->setText("0.0"); - if (m_editRobotRx) m_editRobotRx->setText("0.0"); - if (m_editRobotRy) m_editRobotRy->setText("0.0"); - if (m_editRobotRz) m_editRobotRz->setText("0.0"); - - statusBar()->showMessage("矩阵已重置为单位矩阵"); + vals[0], vals[1], vals[2], vals[3], vals[4], vals[5]); } void CloudViewMainWindow::onSavePointCloud() @@ -2947,7 +2526,7 @@ void CloudViewMainWindow::onSavePointCloud() } const QString fileName = QFileDialog::getSaveFileName( - this, "保存点云", QString(), "文本文件 (*.txt);;所有文件 (*.*)"); + this, "保存 TXT 点云", QString(), "文本文件 (*.txt);;所有文件 (*.*)"); if (fileName.isEmpty()) return; std::vector> scanLines; @@ -3006,6 +2585,76 @@ void CloudViewMainWindow::onSavePointCloud() scanLines.size(), totalPoints, fileName.toStdString().c_str()); } +void CloudViewMainWindow::onSavePlyPcdCloud() +{ + if (m_glWidget->getCloudCount() == 0) { + QMessageBox::warning(this, "提示", "请先加载点云"); + return; + } + + const QString fileName = QFileDialog::getSaveFileName( + this, "保存 PLY/PCD 点云", QString(), + "PLY 文件 (*.ply);;PCD 文件 (*.pcd);;所有文件 (*.*)"); + if (fileName.isEmpty()) return; + + // 获取所有显示的点云,展平为 PointCloudXYZ(跳过零点) + std::vector> scanLines; + const size_t totalPoints = m_glWidget->getAllCloudsByLines(scanLines); + if (scanLines.empty() || totalPoints == 0) { + QMessageBox::warning(this, "提示", "当前无可保存的点云数据"); + return; + } + + PointCloudXYZ cloud; + const float EPS = 1e-6f; + for (const auto& line : scanLines) { + for (const auto& pt : line) { + // 跳过零点(网格填充的空位) + if (std::fabs(pt.pt3D.x) < EPS && + std::fabs(pt.pt3D.y) < EPS && + std::fabs(pt.pt3D.z) < EPS) + continue; + cloud.points.emplace_back(pt.pt3D.x, pt.pt3D.y, pt.pt3D.z); + } + } + + if (cloud.empty()) { + QMessageBox::warning(this, "提示", "当前无可保存的有效点云数据"); + return; + } + + // 根据扩展名选择保存格式 + const std::string path = fileName.toStdString(); + std::string ext; + const auto dotPos = path.rfind('.'); + if (dotPos != std::string::npos) + ext = path.substr(dotPos + 1); + for (auto& c : ext) c = static_cast(std::tolower(c)); + + int ret = -1; + if (ext == "ply") { + ret = m_converter->saveToPly(path, cloud); + } else if (ext == "pcd") { + ret = m_converter->saveToPcd(path, cloud); + } else { + // 用户手动输入了其他扩展名,默认用 PLY + ret = m_converter->saveToPly(path, cloud); + } + + if (ret != 0) { + QMessageBox::critical(this, "保存失败", + QString("保存点云失败: %1") + .arg(QString::fromStdString(m_converter->getLastError()))); + return; + } + + statusBar()->showMessage( + QString("已保存点云: %1 个点 -> %2") + .arg(cloud.points.size()).arg(QFileInfo(fileName).fileName())); + LOG_INFO("[CloudView] Saved %zu points to %s\n", + cloud.points.size(), fileName.toStdString().c_str()); +} + void CloudViewMainWindow::onRotateCloudByZ() { if (m_glWidget->getCloudCount() == 0) { @@ -3295,6 +2944,167 @@ void CloudViewMainWindow::onConvertEulerMatrix() dialog->show(); } +void CloudViewMainWindow::onPoseCompensation() +{ + QDialog* dialog = new QDialog(this); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->setWindowTitle("姿态补偿计算"); + dialog->setWindowFlags(dialog->windowFlags() | Qt::Tool); + + QVBoxLayout* layout = new QVBoxLayout(dialog); + + QLabel* tip = new QLabel( + "输入当前欧拉角和期望欧拉角(单位: 度),计算出补偿角。\n" + "分量相减:补偿 = 期望 − 当前(仅小角度差/单轴旋转时近似有效,结果与顺序无关)\n" + "矩阵补偿:补偿 = 提取(R_cur^T × R_des)\n" + " · 输入顺序:当前/期望欧拉角的旋转顺序(取决于机器人/算法侧约定)\n" + " · 补偿输出顺序:反解 R_comp 用的旋转顺序,要与下游施加补偿的约定一致\n" + " (DetectPresenter::ApplyAxesRotation 硬编码 R = Rx·Ry·Rz,即 XYZ 内旋)", + dialog); + tip->setWordWrap(true); + tip->setStyleSheet("color: gray;"); + layout->addWidget(tip); + + auto fillEulerOrderItems = [](QComboBox* combo) { + combo->addItem("ZYX (Yaw-Pitch-Roll)", static_cast(EulerRotationOrder::ZYX)); + combo->addItem("XYZ (Roll-Pitch-Yaw)", static_cast(EulerRotationOrder::XYZ)); + combo->addItem("ZXY (Yaw-Roll-Pitch)", static_cast(EulerRotationOrder::ZXY)); + combo->addItem("YXZ (Pitch-Roll-Yaw)", static_cast(EulerRotationOrder::YXZ)); + combo->addItem("XZY (Roll-Yaw-Pitch)", static_cast(EulerRotationOrder::XZY)); + combo->addItem("YZX (Pitch-Yaw-Roll)", static_cast(EulerRotationOrder::YZX)); + }; + + QComboBox* comboInputOrder = new QComboBox(dialog); + fillEulerOrderItems(comboInputOrder); + if (m_comboEulerOrder && m_comboEulerOrder->currentIndex() >= 0) { + comboInputOrder->setCurrentIndex(m_comboEulerOrder->currentIndex()); + } + + QComboBox* comboOutputOrder = new QComboBox(dialog); + fillEulerOrderItems(comboOutputOrder); + // 默认 XYZ,匹配 DetectPresenter::ApplyAxesRotation 中 R = Rx·Ry·Rz 的硬编码 + const int outputXyzIndex = comboOutputOrder->findData(static_cast(EulerRotationOrder::XYZ)); + if (outputXyzIndex >= 0) { + comboOutputOrder->setCurrentIndex(outputXyzIndex); + } + + QHBoxLayout* orderLayout = new QHBoxLayout(); + orderLayout->addWidget(new QLabel("输入顺序:", dialog)); + orderLayout->addWidget(comboInputOrder); + orderLayout->addSpacing(20); + orderLayout->addWidget(new QLabel("补偿输出顺序:", dialog)); + orderLayout->addWidget(comboOutputOrder); + orderLayout->addStretch(); + layout->addLayout(orderLayout); + + QComboBox* comboMethod = new QComboBox(dialog); + comboMethod->addItem("分量相减(期望 − 当前)"); + comboMethod->addItem("矩阵补偿(R_cur^T × R_des)"); + + QHBoxLayout* methodLayout = new QHBoxLayout(); + methodLayout->addWidget(new QLabel("计算方法:", dialog)); + methodLayout->addWidget(comboMethod); + methodLayout->addStretch(); + layout->addLayout(methodLayout); + + auto makeAngleSpin = [](QWidget* parent) { + QDoubleSpinBox* spin = new QDoubleSpinBox(parent); + spin->setDecimals(6); + spin->setRange(-3600.0, 3600.0); + spin->setSingleStep(0.1); + spin->setValue(0.0); + spin->setAlignment(Qt::AlignRight); + spin->setButtonSymbols(QAbstractSpinBox::NoButtons); + return spin; + }; + + QGroupBox* currentGroup = new QGroupBox("当前欧拉角 (deg)", dialog); + QGridLayout* currentLayout = new QGridLayout(currentGroup); + QDoubleSpinBox* curRx = makeAngleSpin(currentGroup); + QDoubleSpinBox* curRy = makeAngleSpin(currentGroup); + QDoubleSpinBox* curRz = makeAngleSpin(currentGroup); + currentLayout->addWidget(new QLabel("RX:", currentGroup), 0, 0); + currentLayout->addWidget(curRx, 0, 1); + currentLayout->addWidget(new QLabel("RY:", currentGroup), 1, 0); + currentLayout->addWidget(curRy, 1, 1); + currentLayout->addWidget(new QLabel("RZ:", currentGroup), 2, 0); + currentLayout->addWidget(curRz, 2, 1); + + QGroupBox* desiredGroup = new QGroupBox("期望欧拉角 (deg)", dialog); + QGridLayout* desiredLayout = new QGridLayout(desiredGroup); + QDoubleSpinBox* desRx = makeAngleSpin(desiredGroup); + QDoubleSpinBox* desRy = makeAngleSpin(desiredGroup); + QDoubleSpinBox* desRz = makeAngleSpin(desiredGroup); + desiredLayout->addWidget(new QLabel("RX:", desiredGroup), 0, 0); + desiredLayout->addWidget(desRx, 0, 1); + desiredLayout->addWidget(new QLabel("RY:", desiredGroup), 1, 0); + desiredLayout->addWidget(desRy, 1, 1); + desiredLayout->addWidget(new QLabel("RZ:", desiredGroup), 2, 0); + desiredLayout->addWidget(desRz, 2, 1); + + QGroupBox* resultGroup = new QGroupBox("补偿欧拉角 (deg)", dialog); + QGridLayout* resultLayout = new QGridLayout(resultGroup); + QLineEdit* resultRx = new QLineEdit(resultGroup); + QLineEdit* resultRy = new QLineEdit(resultGroup); + QLineEdit* resultRz = new QLineEdit(resultGroup); + resultRx->setReadOnly(true); + resultRy->setReadOnly(true); + resultRz->setReadOnly(true); + resultRx->setAlignment(Qt::AlignRight); + resultRy->setAlignment(Qt::AlignRight); + resultRz->setAlignment(Qt::AlignRight); + resultRx->setStyleSheet("background-color: #f0f0f0; font-weight: bold;"); + resultRy->setStyleSheet("background-color: #f0f0f0; font-weight: bold;"); + resultRz->setStyleSheet("background-color: #f0f0f0; font-weight: bold;"); + resultLayout->addWidget(new QLabel("RX:", resultGroup), 0, 0); + resultLayout->addWidget(resultRx, 0, 1); + resultLayout->addWidget(new QLabel("RY:", resultGroup), 1, 0); + resultLayout->addWidget(resultRy, 1, 1); + resultLayout->addWidget(new QLabel("RZ:", resultGroup), 2, 0); + resultLayout->addWidget(resultRz, 2, 1); + + layout->addWidget(currentGroup); + layout->addWidget(desiredGroup); + layout->addWidget(resultGroup); + + QHBoxLayout* actionLayout = new QHBoxLayout(); + QPushButton* calcButton = new QPushButton("计算补偿", dialog); + QPushButton* closeButton = new QPushButton("关闭", dialog); + actionLayout->addStretch(); + actionLayout->addWidget(calcButton); + actionLayout->addWidget(closeButton); + layout->addLayout(actionLayout); + + connect(calcButton, &QPushButton::clicked, dialog, [=]() { + if (comboMethod->currentIndex() == 0) { + // 分量相减 + resultRx->setText(QString::number(desRx->value() - curRx->value(), 'f', 6)); + resultRy->setText(QString::number(desRy->value() - curRy->value(), 'f', 6)); + resultRz->setText(QString::number(desRz->value() - curRz->value(), 'f', 6)); + } else { + // 矩阵补偿 + const EulerRotationOrder inputOrder = static_cast(comboInputOrder->currentData().toInt()); + const EulerRotationOrder outputOrder = static_cast(comboOutputOrder->currentData().toInt()); + + // 输入欧拉角按 inputOrder 还原成旋转矩阵 + const QMatrix4x4 curMat = buildEulerRotationMatrix(inputOrder, curRx->value(), curRy->value(), curRz->value()); + const QMatrix4x4 desMat = buildEulerRotationMatrix(inputOrder, desRx->value(), desRy->value(), desRz->value()); + + // R_des = R_cur * R_comp(工具坐标系补偿,R_comp = R_cur^T * R_des) + const QMatrix4x4 compMat = curMat.transposed() * desMat; + + // 补偿矩阵按 outputOrder 反解(要与下游施加补偿的旋转顺序一致) + const QVector3D euler = rotationMatrixToEuler(outputOrder, compMat); + resultRx->setText(QString::number(euler.x(), 'f', 6)); + resultRy->setText(QString::number(euler.y(), 'f', 6)); + resultRz->setText(QString::number(euler.z(), 'f', 6)); + } + }); + + connect(closeButton, &QPushButton::clicked, dialog, &QDialog::close); + dialog->show(); +} + void CloudViewMainWindow::onEulerOrderChanged(int index) { if (!m_glWidget) { @@ -3349,7 +3159,7 @@ void CloudViewMainWindow::onPoint1CoordChanged() // 如果启用了测距且有两个点,重新计算距离 auto updatedPoints = m_glWidget->getSelectedPoints(); if (m_glWidget->isMeasureDistanceEnabled() && updatedPoints.size() >= 2 && updatedPoints[1].valid) { - float distance = m_glWidget->calculateDistance(updatedPoints[0], updatedPoints[1]); + float distance = m_glWidget->calculateDistance(updatedPoints[0], updatedPoints[1]) * 2.0f; m_lblDistance->setText(QString("%1 mm").arg(distance, 0, 'f', 3)); statusBar()->showMessage(QString("点1坐标已更新,距离: %1 mm").arg(distance, 0, 'f', 3)); } else { diff --git a/CloudView/Src/PointCloudConverter.cpp b/CloudView/Src/PointCloudConverter.cpp index ad3ab06..c556f2e 100644 --- a/CloudView/Src/PointCloudConverter.cpp +++ b/CloudView/Src/PointCloudConverter.cpp @@ -4,6 +4,7 @@ #include "VrLog.h" #include +#include #include #include #include @@ -517,6 +518,121 @@ int PointCloudConverter::saveToTxt(const std::string& fileName, const PointCloud return 0; } +// ============================================================ +// PLY 保存(ASCII 格式) +// ============================================================ +int PointCloudConverter::saveToPly(const std::string& fileName, const PointCloudXYZ& cloud) +{ + if (cloud.empty()) { + m_lastError = "点云数据为空"; + return -1; + } + + // 统计有效点数(跳过 NaN/Inf) + size_t validCount = 0; + for (const auto& pt : cloud.points) { + if (std::isfinite(pt.x) && std::isfinite(pt.y) && std::isfinite(pt.z)) + ++validCount; + } + + if (validCount == 0) { + m_lastError = "无有效点云数据"; + return -1; + } + + std::ofstream file(fileName, std::ios::out | std::ios::trunc); + if (!file.is_open()) { + m_lastError = "无法创建文件: " + fileName; + return -1; + } + + // 写入 PLY 头 + file << "ply\n"; + file << "format ascii 1.0\n"; + file << "comment Created by CloudView\n"; + file << "element vertex " << validCount << "\n"; + file << "property float x\n"; + file << "property float y\n"; + file << "property float z\n"; + file << "end_header\n"; + + // 写入点数据(6位小数的定点格式,控制文件体积) + for (const auto& pt : cloud.points) { + if (!std::isfinite(pt.x) || !std::isfinite(pt.y) || !std::isfinite(pt.z)) + continue; + file << std::fixed << std::setprecision(6) + << pt.x << " " << pt.y << " " << pt.z << "\n"; + } + + file.close(); + if (!file.good()) { + m_lastError = "写入文件失败: " + fileName; + return -1; + } + + LOG_INFO("[CloudView] Saved %zu points to PLY: %s\n", validCount, fileName.c_str()); + return 0; +} + +// ============================================================ +// PCD 保存(ASCII 格式,兼容 PCL) +// ============================================================ +int PointCloudConverter::saveToPcd(const std::string& fileName, const PointCloudXYZ& cloud) +{ + if (cloud.empty()) { + m_lastError = "点云数据为空"; + return -1; + } + + // 统计有效点数 + size_t validCount = 0; + for (const auto& pt : cloud.points) { + if (std::isfinite(pt.x) && std::isfinite(pt.y) && std::isfinite(pt.z)) + ++validCount; + } + + if (validCount == 0) { + m_lastError = "无有效点云数据"; + return -1; + } + + std::ofstream file(fileName, std::ios::out | std::ios::trunc); + if (!file.is_open()) { + m_lastError = "无法创建文件: " + fileName; + return -1; + } + + // 写入 PCD 头(与 PCL 兼容) + file << "# .PCD v0.7 - Point Cloud Data file format\n"; + file << "VERSION 0.7\n"; + file << "FIELDS x y z\n"; + file << "SIZE 4 4 4\n"; + file << "TYPE F F F\n"; + file << "COUNT 1 1 1\n"; + file << "WIDTH " << validCount << "\n"; + file << "HEIGHT 1\n"; + file << "VIEWPOINT 0 0 0 1 0 0 0\n"; + file << "POINTS " << validCount << "\n"; + file << "DATA ascii\n"; + + // 写入点数据 + for (const auto& pt : cloud.points) { + if (!std::isfinite(pt.x) || !std::isfinite(pt.y) || !std::isfinite(pt.z)) + continue; + file << std::fixed << std::setprecision(6) + << pt.x << " " << pt.y << " " << pt.z << "\n"; + } + + file.close(); + if (!file.good()) { + m_lastError = "写入文件失败: " + fileName; + return -1; + } + + LOG_INFO("[CloudView] Saved %zu points to PCD: %s\n", validCount, fileName.c_str()); + return 0; +} + int PointCloudConverter::getPlyTypeByteSize(const std::string& type) { if (type == "char" || type == "int8") return 1; diff --git a/CloudView/Src/PointCloudGLWidget.cpp b/CloudView/Src/PointCloudGLWidget.cpp index a02d7e9..13b604f 100644 --- a/CloudView/Src/PointCloudGLWidget.cpp +++ b/CloudView/Src/PointCloudGLWidget.cpp @@ -119,7 +119,9 @@ void PointCloudGLWidget::resizeGL(int w, int h) m_projection.setToIdentity(); float aspect = static_cast(w) / static_cast(h > 0 ? h : 1); - m_projection.perspective(45.0f, aspect, 0.1f, 10000.0f); + // 放大裁剪范围以容纳激光雷达点云(最大可能跨越数十万 mm) + // near 取 1.0 保留 depth buffer 精度,far 放到 1e7 避免远点被裁掉 + m_projection.perspective(45.0f, aspect, 1.0f, 1.0e7f); } void PointCloudGLWidget::paintGL() @@ -474,8 +476,9 @@ void PointCloudGLWidget::resetView() QVector3D size = m_maxBound - m_minBound; float maxSize = qMax(qMax(size.x(), size.y()), size.z()); - // 确保最小视距 - m_distance = qMax(maxSize * 2.0f, 10.0f); + // 视距至少为 maxSize 的 2.5 倍,并确保不小于 10 + // 上限放宽至 1e7,适配激光雷达大范围点云 + m_distance = qBound(10.0f, maxSize * 2.5f, 1.0e7f); // 默认视角:面向 XY 平面,X 向右,Y 向下 // 绕 X 轴旋转 180°:Y 轴从向上翻转为向下 m_rotationX = 180.0f; @@ -494,14 +497,14 @@ void PointCloudGLWidget::resetView() void PointCloudGLWidget::zoomIn() { m_distance *= 0.9f; - m_distance = qBound(0.1f, m_distance, 10000.0f); + m_distance = qBound(0.1f, m_distance, 1.0e7f); update(); } void PointCloudGLWidget::zoomOut() { m_distance *= 1.1f; - m_distance = qBound(0.1f, m_distance, 10000.0f); + m_distance = qBound(0.1f, m_distance, 1.0e7f); update(); } @@ -1419,7 +1422,7 @@ void PointCloudGLWidget::wheelEvent(QWheelEvent* event) { float delta = event->angleDelta().y() / 120.0f; m_distance *= (1.0f - delta * 0.1f); - m_distance = qBound(0.1f, m_distance, 10000.0f); + m_distance = qBound(0.1f, m_distance, 1.0e7f); update(); } diff --git a/CloudView/main.cpp b/CloudView/main.cpp index 88a7742..7266b1b 100644 --- a/CloudView/main.cpp +++ b/CloudView/main.cpp @@ -3,7 +3,7 @@ #include #include "CloudViewMainWindow.h" -#define APP_VERSION "1.1.4" +#define APP_VERSION "1.1.5" int main(int argc, char* argv[]) { diff --git a/VrCommon/Inc/VZNL_Types.h b/VrCommon/Inc/VZNL_Types.h new file mode 100644 index 0000000..231ce9c --- /dev/null +++ b/VrCommon/Inc/VZNL_Types.h @@ -0,0 +1,2328 @@ +/* + * Header: vizum_type.h + * Description:当前文件为双目相机(Vizum)研发的EyeCB板提供所有类型的定义。 + * Author: Mjw + * Date: 2018/08/28 + * Update: 2023/04/27 + */ + +#ifndef __VIZUM_TYPES_HEADER__ +#define __VIZUM_TYPES_HEADER__ + +/// @name 网络相关定义 +/// @{ +/// @brief +/// 网络IP长度 +#define VZNL_SDK_NETWORK_IPv4_LENGTH 16 + +/// @brief +/// 网络MAC长度 +#define VZNL_SDK_NETWORK_MAC_LENGTH 18 +/// @} + +/// @breif +/// 版本字符串长度 +#define VZNL_VERSION_LENGTH 64 + +/// @brief +/// 设备名称长度。 +#define VZNL_DEVICE_NAME_LENGTH 64 + +/// @brief +/// 唯一标识符大小 +#define VZNL_GUID_LENGTH 64 + +/// @brief +/// 设备名称长度 +#define VZ_DEVICE_NAME 64 + +/// @brief +/// 设备描述长度 +#define VZ_DEVICE_DESC 256 + +/// @brief +/// 设备ID长度 +#define VZNL_DEVICEID_LENGTH 64 + +/// @brief +/// 设备地址 +#define VZNL_DEVICE_ADDR_LENGTH 64 + +/// @brief +/// File Path Length +#define VZ_MAXPATH 256 + +/// @brief UserProfile MAX Count +#define VZ_USERPROFILE_MAX_COUNT 10 + +#define VZ_USERCONFIG_MAX_COUNT 200 + +/// @brief UserProfile Name Length +#define VZ_USERPROFILE_NAME_LENGTH 64 + +#define VZ_USERCONFIG_NAME_LENGTH 64 + +#define VZ_USERCONFIG_DESC_LENGTH 256 + + +/// @brief +/// 设备句柄 +typedef void* VZNLHANDLE; + +/// @brief +/// 设备句柄 +typedef void* VZNLFILE; + +/// @brief +/// 检测工具句柄 SDK 2.2.0开始支持 +typedef void* VZNLDETECTHANDLE; + +/// @brief +/// 数据处理句柄 +typedef void* VZNLPOINTCLOUDHANDLE; + +/// @brief +/// 数据处理句柄 +typedef void* VZNLIMAGEGENERATOR; + +/// @brief +/// RGB点云处理工具句柄 +typedef void* VZNLRGBCLOUDPOINTTOOL; + +enum +{ + VzFalse = 0, + VzTrue = 1, +}; +typedef unsigned char VzBool; + +/// @brief +/// 颜色定义 +typedef unsigned int VzColorRGB; //RGB + +/// @brief +/// Data Type +#ifdef _WIN64 + typedef unsigned long long VzParam; +#else + typedef unsigned long VzParam; +#endif + +typedef struct +{ + unsigned short shYear; + unsigned short shMonth; + unsigned short shDay; + unsigned short shHour; + unsigned short shMinute; + unsigned short shSecond; + unsigned short shWeek; +} SVzDateTime; + +// 设备搜索标志 +enum +{ + keSearchDeviceFlagNone = 0x0000, //< 不搜索 + keSearchDeviceFlag_EyeCB = 0x0001, //< 搜索控制器 + keSearchDeviceFlag_USBLargeEye = 0x0002, //< 搜索USB极光眼 + keSearchDeviceFlag_EthLargeEye = 0x0004, //< 搜索网络星光眼:激光 + keSearchDeviceFlag_EthSmallEye = 0x0008, //< 搜索网络星光眼:智能 + keSearchDeviceFlag_EthLaserRobotEye = 0x0010, //< 搜索网络智光眼:激光 + keSearchDeviceFlagAll = keSearchDeviceFlag_EthLaserRobotEye, //< 搜索全部支持的设备 +}; +typedef unsigned int EVzSearchDeviceFlag; + +/// @brief +/// 眼睛分辨率枚举值 +typedef enum +{ + keVzNLEyeResolution_Unknown = 0, + keVzNLEyeResolution_130WH, ///< 130W像素的眼睛 1280 * 960 + keVzNLEyeResolution_130WV, ///< 130W像素的眼睛 960 * 1280 + keVzNLEyeResolution_300WH, ///< 300W像素的眼睛 2048 * 1520 + keVzNLEyeResolution_300WV, ///< 300W像素的眼睛 1536 * 2048 + keVzNLEyeResolution_1300W, ///< 1300W像素的眼睛 + keVzNLEyeResolutionLast +} EVzNLEyeResolution; + +/// @brief +/// 设备类型 +typedef enum +{ + keDeviceType_Unknown = 0, + keDeviceType_EyeCB, // EyeCB控制器 + keDeviceType_LaserEye, // 星光眼:激光 + keDeviceType_SmartEye, // 星光眼:智能 + keDeviceType_LaserRobotEye, // 智光眼:激光 + keDeviceType_SmartRobotEye, // 智光眼:智能 +} EVzDeviceType; + +/// @brief +/// 眼睛类型标志 +enum +{ + keEyeTypeFlag_Unknown = 0x00, + keEyeTypeFlag_USB = 0x01, //< USB + keEyeTypeFlag_Eth = 0x02, //< 网络 + + keEyeTypeFlag_Color = 0x10, //< 彩色 + keEyeTypeFlag_Gray = 0x20, //< 黑白 + + keEyeTypeFlag_Laser = 0x100, //< 极光眼 + keEyeTypeFlag_Smart = 0x200, //< 智能眼 + + keEyeTypeFlag_Product1 = 0x1000, //< 小眼睛 + keEyeTypeFlag_Product2 = 0x2000, //< 大眼睛 + keEyeTypeFlag_Product3 = 0x3000, //< 大眼睛 +}; +typedef unsigned int EVzEyeTypeFlag; + +enum +{ + keEyeSensorType_Left = 0x01, + keEyeSensorType_Right = 0x02, +}; +typedef unsigned int EVzEyeSensorType; + +/// @bried +/// 眼睛出图方式 +typedef enum +{ + keEyeImgMode_Low = 1, + keEyeImgMode_Middle, + keEyeImgMode_High, + keEyeImgMode_Higher, + keEyeImgMode_Hights, + keEyeImgMode_Algo +}EVzEyeImgBitMode; + +/// @brief +/// 运动方向 +typedef enum +{ + keObjRunDirect_Plus = 0, //< 正方向 + keObjRunDirect_Minus = 1, //< 反方向 +} EVzObjRunDirect; + +/// @brief +/// 电动机运行方向 +typedef enum +{ + keEncodeMotorRunDirectNone = 0, // 未知 + keEncodeMotorRunDirect_ClockWise, // 顺时针 + keEncodeMotorRunDirect_CounterClockWise, // 逆时针 +} EVzEncodeMotorRunDirect; + +/// @brief +/// 外部触发模式 +enum +{ + keEyeTriggerMode_Invalid = -1, // 无效值 + keEyeTriggerMode_Slave = 0, // 被动触发 + keEyeTriggerMode_Master = 1, // 主动模式 + keEyeTriggerMode_FallingEdge = 2, // 下降沿触发 + keEyeTriggerMode_RisingEdge = 3, // 上升沿触发 + keEyeTriggerMode_LowLevelEnable = 4, // 低电平使能数据输出 + keEyeTriggerMode_ManualTrigger = 6, // 手动模式 + keEyeTriggerMode_Package = 9, // 物流包裹触发模式 + keEyeTriggerMode_HighFrequenceFallingEdge = 10, // 高帧率下降沿触发 +}; +typedef unsigned int EVzEyeTriggerMode; + +/// @brief +/// 设置点云计算模式 +typedef enum +{ + kePointCloudProcMode_Invalid = 0, //< 无效 + kePointCloudProcMode_Speed, //< 速度偏移模式 + kePointCloudProcMode_Encoder, //< 编码器模式 + kePointCloudProcMode_FixedStep, //< 固定间隔模式 +} EVzPointCloudProcMode; + +/// @brief +/// 设备状态 +typedef enum +{ + keDeviceWorkStatusUnknown = 0, //< 未知 + keDeviceWorkStatus_Eye_Comming = 1, //< 眼睛连接 + keDeviceWorkStatus_Eye_Reconnect, //< 眼睛重连 + keDeviceWorkStatus_Working, //< 工作中 + keDeviceWorkStatus_Offline, //< 离线 + keDeviceWorkStatus_Eye_TimeOut, //< 眼睛超时 + keDeviceWorkStatus_Eye_UnStability, //< 眼睛不稳定 + keDeviceWorkStatus_EyeCB_Mem, //< 内存 + keDeviceWorkStatus_EyeCB_TimeStampDiffErr, //< 时间戳错误 + keDeviceWorkStatus_Device_Swing_Finish, //< 摆动模块已完成 + keDeviceWorkStatus_Device_Auto_Stop, //< 设备自动停止 + keDeviceWorkStatus_EyeCB_TaskFull, //< 任务饱和 + keDeviceWorkStatus_EyeCB_GetImageErr, //< 获取图像错误 + + keDeviceWorkStatus_EyeCB_ObjectCome, //< 物体来了 + keDeviceWorkStatus_EyeCB_ObjectLeave, //< 物体走了 + + keDeviceWorkStatus_Trigger_Coming, //< Trigger信号来了 + keDeviceWorkStatus_Trigger_Leave, //< Trigger信号结束了 + + keDeviceWorkStatus_Eye_StreamResume, //< 数据恢复 + keDeviceWorkStatus_Eye_SwingNoResponse, //< 静态模块无响应 + + keDeviceWorkStatus_PetaMsg, //< Peta 消息 + keDeviceWorkStatus_PSMsg, //< PS 消息 + keDeviceWorkStatus_Eye_LaserlineErr, //< + keDeviceWorkStatus_Device_Swing_Running, //< 开流后摆动模块开始运动时给出信号 + + keDeviceWorkStatus_Eye_SwingEncodeNoChange, //< 摆动编码器值不改变 + keDeviceWorkStatus_Eye_SwingUpdateStatus, //< 发现了摆动模块 + + keDeviceWorkStatus_Eye_New_Client_Connected, //< 有新客户端连接 + + keDeviceWorkStatus_Eye_LinearActuator_Warning, //< 推杆电机关闭点可能存在异常 +} EVzDeviceWorkStatus; + +/// @brief +/// 设备状态 +typedef enum +{ + keMulDevWorkStatusUnknown = 0, //< 未知 + keMulDevWorkStatus_Auto_Stop, //< 自动停止 +} EVzMulDevWorkStatus; + +/// @brief +/// Laser FrameRate +typedef enum +{ + keLaserFrameRate_30 = 0, + keLaserFrameRate_100, + keLaserFrameRate_220, + keLaserFrameRate_400, + keLaserFrameRate_630, + keLaserFrameRate_50, + keLaserFrameRate_120, + keLaserFrameRate_320, + keLaserFrameRate_550, + keLaserFrameRate_830, + keLaserFrameRate_80, + keLaserFrameRate_210, + keLaserFrameRate_530, + keLaserFrameRate_840, + keLaserFrameRate_1200, + keLaserFrameRateLast +} EVzLaserFrameRate; + +/// @brief +/// Laser FrameRate +typedef enum +{ + keLaserCaptureMode_LastFrame, + keLaserCaptureMode_AllFrame, + keLaserCaptureMode_CalcAllFrameWhenStop, +} EVzLaserCaptureMode; + +/// @brief +/// 产品类型 +enum +{ + keProjectType_None = 0, + keProjectType_Coal, + keProjectType_Logistics, + keProjectType_InfraredMark, + keProjectType_DivRebar, + keProjectTypeLast, +}; +typedef unsigned int EVzProjectType; + +/// @brief +/// 定义检测物体类型 +enum +{ + keVzNLDetectObjType_Unknown = 0, + keVzNLDetectObjType_Line = 1, // 直线 + keVzNLDetectObjType_CoplaneCircle = 2, // 圆/椭圆(共面) + keVzNLDetectObjType_Triangle = 3, + keVzNLDetectObjType_Rect = 4, // 矩形 + keVzNLDetectObjType_Cube = 5, // 立方体 + keVzNLDetectObjType_StickWidth = 6, // 杆状物 + keVzNLDetectObjType_StickLength = 7, + keVzNLDetectObjType_StickVolume = 8, + keVzNLDetectObjType_StickApex = 9, + keVzNLDetectObjType_Laser = 10, // 激光 + keVzNLDetectObjType_Slot = 11, // 凹槽 + keVzNLDetectObjType_SameSizeCircle = 12, // 同大小 + keVzNLDetectObjType_3DModel = 14, // 匹配三维模型 +}; +typedef unsigned int EVzNLDetectObjType; + +enum +{ + keSDKType_Unknown = 0, + keSDKType_DetectShape, + keSDKType_DetectLaser, + keSDKType_DetectCircle, + keSDKType_DetectColorMark, + keSDKType_DetectGrains, +}; +typedef int EVzSDKType; + +typedef enum +{ + keVzColorType_None = 0, + keVzColorType_Red, + keVzColorType_Green, + keVzColorType_Blue, + keVzColorType_Last, +} EVzColorType; + +/// @brief +/// Log Level +enum +{ + keNLLogLevel_None = 0, + keNLLogLevel_Error, + keNLLogLevel_Warning, + keNLLogLevel_Debug, + keNLLogLevel_Information, +}; +typedef int EVzNLLogLevel; + +/// @brief +/// Log Type +enum +{ + keNLLogType_None = 0, + keNLLogType_Console = 0x01, + keNLLogType_File = 0x02, + keNLLogType_LocalPgm= 0x04, + keNLLogType_Remote = 0x08, +}; +typedef int EVzNLLogType; + +/* +* @brief +* 眼睛配置信息 +*/ +typedef enum +{ + keNLEthernetEyeIPType_Unknown = 0, //< 未知 + keNLEthernetEyeIPType_StaticIP, //< 静态IP + keNLEthernetEyeIPType_DHCP, //< DHCP +} EVzNLEthernetEyeIPType; + +/// @brief +/// 爆光模式 +typedef enum +{ + keVzNLExposeMode_Fix = 0, ///< 定值曝光 + keVzNLExposeMode_Auto = 1, ///< 自动曝光 +} EVzNLExposeMode; + +/// @brief +/// Expose Type +typedef enum +{ + keExposeThresType_Average = 0, + keExposeThresType_Max = 1, +} EVzExposeThresType; + +/// @brief +/// 图像压缩格式 +typedef enum +{ + keVzNLImageCompress_Unknown = 0, + keVzNLImageCompress_Jpeg = 1, // Jpeg格式 + keVzNLImageCompress_Png = 2, // png格式 + keVzNLImageCompress_Bmp = 3 // bmp格式 +} EVzNLImageCompress; + +/// @brief +/// 图像类型 +typedef enum +{ + keVzNLImageType_None = 0, + keVzNLImageType_RGB888, // 3 channel 24bit + keVzNLImageType_BGR888, // 3 channel 24bit + keVzNLImageType_BAYERGR, // 1 channel + keVzNLImageType_BAYERRG, // 1 channel + keVzNLImageType_BAYERBG, // 1 channel + keVzNLImageType_BAYERGB, // 1 channel + keVzNLImageType_GRAY, // 1 channel + keVzNLImageType_YUV420, + keVzNLImageType_BGRA8888, // 4 channel 32bit + keVzNLImageType_YUYV_YUV422,// + keVzNLImageType_UNKNOWN = 0xffff, +} EVzNLImageType; + +/// @brief +/// 圆形定义 +typedef enum +{ + keNL3DCircleTypeUnknown = 0, + keNL3DCircleType_Circle = 3, // 单圆 + keNL3DCircleType_ConcentricCircle = 4, // 同心圆 + keNL3DCircleType_SteelTube = 5, // 钢管 +} EVzNL3DCircleType; + +/// @brief +/// 眼睛数据工作模式 +typedef enum +{ + keNLEyeDataWorkMode_Data = 0, // 数据模式 + keNLEyeDataWorkMode_ImageData = 1, // 图像数据模式 + keNLEyeDataWorkMode_Image = 2, +} EVzNLEyeDataWorkMode; + +/// @breif +/// 数据翻转 +enum +{ + keFlipType_None = 0x00, ///< 无翻转 + keFlipType_Vertical = 0x01, ///< 垂直翻转 +}; +typedef unsigned int EVzFlipType; + +/// @brief +/// 激光点过滤类型 +enum +{ + keLaserPointFilterType_None = 0, ///< 不过滤 + keLaserPointFilterType_BetweenYMinMax = 0x01, ///< 保留Y轴某范围内的点 + keLaserPointFilterType_ExceptYRange = 0x02, ///< 排除Y轴某范围 + keLaserPointFilterType_BetweenZMinMax = 0x10, ///< 保留Z轴某范围的点 + keLaserPointFilterType_ExceptZRange = 0x20, ///< 排除Z轴某范围的点 + keLaserPointFilterType_BetweenXMinMax = 0x100, ///< 保留X轴某范围的点 + keLaserPointFilterType_ExceptXRange = 0x200 ///< 排除X轴某范围的点 +}; + +typedef unsigned int EVzLaserPointFilterType; + +// @brief +// 摆动机构扫描模式 +typedef enum +{ + keSwingMotorScanModeNone = 0, + keSwingMotorScanMode_Once, //< 一次 + keSwingMotorScanMode_Loop, //< 轮询 +} EVzSwingMotorScanMode; + +// @brief +// 灰度值来源 +typedef enum +{ + keGrayValueSource_Left = 0, //< 左目 + keGrayValueSource_Right, //< 右目 + keGrayValueSource_Both, //< 左右 +} EVzGrayValueSource; + +// @brief +// 摆动机构速度 +const float keSwingMotorSpeedMin = 1.f; +const float keSwingMotorSpeed_12 = 12.f; +const float keSwingMotorSpeed_24 = 24.f; +const float keSwingMotorSpeed_36 = 36.f; +const float keSwingMotorSpeed_48 = 48.f; +const float keSwingMotorSpeed_60 = 60.f; +const float keSwingMotorSpeed_72 = 72.f; +const float keSwingMotorSpeed_84 = 84.f; +const float keSwingMotorSpeed_96 = 96.f; +const float keSwingMotorSpeedMax = keSwingMotorSpeed_96; + +// @brief +// 参考ROI大小 +typedef enum +{ + keDeviceROIInvalid = 0, + keDeviceROI_128_1024, //< ROI 128 * 1024 + keDeviceROI_256_1024, //< ROI 256 * 1024 + keDeviceROI_Full, //< ROI Full Resolution + keDeviceROILast //< +} EVzDeviceROI; + +/// @brief +/// 摆动机构旋转方向 +typedef enum +{ + keSwingRotateDirect_Clockwise, //< 顺时针旋转 + keSwingRotateDirect_Counterclockwise //< 逆时针旋转 +} EVzSwingRotateDirect; + +/// @brief +/// 图像分辨率 +typedef struct +{ + unsigned int nFrameWidth; + unsigned int nFrameHeight; +} SVzVideoResolution; + +/// @brief +/// 点类型 +typedef enum +{ + keResultDataType_Invalid = -1, // 3D数据类型 2D数据类型 + keResultDataType_Position, //< Position数据类型 使用SVzNL3DPosition SVzNL2DPosition + keResultDataType_PointF, //< PointF数据类型 使用SVzNL3DPointF SVzNL2DLRPoint + keResultDataType_PointXYZ, //< PointXYZ数据类型 使用SVzNLPointXYZ SVzNL2DLRPoint + keResultDataType_PointXYZRGBA, //< PointXYZRGB数据 使用SVzNLPointXYZRGBA SVzNL2DLRPoint + keResultDataType_PointGray, //< PointXYZGray数据 使用SVzNLPointXYZGray SVzNL2DLRPoint + keResultDataType_PointXYZI, //< PointXYZI数据 使用SVzNLPointXYZI SVzNL2DLRPoint + keResultDataType_PointRGBA_D, //< PointRGBA_D 使用SVzNLPointRGBA_D SVzNL2DLRPoint + keResultDataType_PositionF, //< Position数据类型 使用SVzNL3DPosition SVzNL2DPositionF +} EVzResultDataType; + +/// @brief 设备参数类型 +typedef enum +{ + keDeviceParamType_Invalid = 0, //< 无效 + keDeviceParamType_FrameRate, //< 帧率参数 + keDeviceParamType_Expose, //< 曝光参数 + keDeviceParamType_Gain, //< 增益参数 + keDeviceParamType_LaserLight, //< 激光亮度参数 + keDeviceParamType_FrameRateWithoutColorData,//< 不携带颜色数据的帧率 + keDeviceParamType_FrameRateWithColorData, //< 携带颜色数据的帧率 + keDeviceParamType_StrobeTimeRatio, //< C版激光器亮度值 + keDeviceParamType_RGBExpose, //< RGB曝光参数 + keDeviceParamType_RGBGain, //< RGB增益参数 +} EVzDeviceParamType; + +/// @brief 设备参数类型 +typedef enum +{ + keDevChipInfoInvalid = 0, + keDevChipInfo_Temperature, + keDevChipInfo_VccPin, + keDevChipInfo_VccPaux, + keDevChipInfo_VccPdro, +} EVzDevChipInfo; + +/// @brief +/// 网络信息 +typedef struct +{ + unsigned int nAdapterIndex; //< 网络适配器索引 + char szNetCardName[VZ_MAXPATH]; //< 网卡名称 + unsigned char byLocalIP[4]; //< 本机网卡IP + unsigned char byMacAddress[6]; //< 本机网卡MAC + unsigned char bySubMask[4]; //< 子网掩码 + unsigned char byGetWay[4]; //< 网关 + unsigned int dwSpeed; //< 网络速度 Mbps +} SVzNetCardInfo; + +/// @brief +/// 眼睛控制板的设备信息 +typedef struct +{ + unsigned int nSize; // 结构大小 + EVzDeviceType eDeviceType; // 设备类型 + char szDeviceName[VZNL_DEVICE_NAME_LENGTH]; // 名称 + char byServerIP[VZNL_SDK_NETWORK_IPv4_LENGTH]; // 服务端IP + char byMAC[VZNL_SDK_NETWORK_MAC_LENGTH]; // MAC地址 + char szGUID[VZNL_GUID_LENGTH]; // 唯一标识 + char szDeviceID[VZNL_GUID_LENGTH]; // 设备ID + char szDesc[VZ_DEVICE_DESC]; // 设备描述 + VzBool bValidDevice; // Device是否有效 +} SVzNLEyeCBInfo; + +/// @brief +/// 控制板信息 +typedef struct +{ + SVzNLEyeCBInfo sEyeCBInfo; //< Base Info + unsigned int nHardwareVersion; //< Device HW Version + unsigned int chEyeVersion; //< Device Eye Version + char chEyeName[VZ_DEVICE_NAME]; //< Device Eye Name + char chEyeIPv4[VZNL_SDK_NETWORK_IPv4_LENGTH];//< Device IP Address + char chEyeMAC[VZNL_SDK_NETWORK_MAC_LENGTH]; //< Device Eye MAC + char szDeviceAddr[VZNL_DEVICE_ADDR_LENGTH]; //< Device Address + SVzVideoResolution sVideoRes; //< Device Video Resolution + char chHwSerialID[VZNL_DEVICEID_LENGTH]; //< Device ID Length + +} SVzNLEyeCBDeviceInfoEx; + +/// @brief 设备能力描述 +typedef union +{ + unsigned int nCapabilityMask; + struct + { + unsigned int bIsSupportRGBSensor : 1; //< 是否支持Center Sensor + unsigned int bSupportSwingMotor : 1; //< 是否支持摆动机构 + unsigned int bIsSupportAsyncRGBD : 1; //< 是否支持异步RGBD + }; + +} SVzEthLaserEyeCapability; + +/// @brief +/// 眼睛信息 +typedef struct +{ + SVzNLEyeCBInfo sEyeCBInfo; + EVzEyeTypeFlag eEyeTypeFlag; //< 眼睛类型标志 + SVzVideoResolution sVideoRes; //< 眼睛分辨率 + int nFixDisparity; //< 偏心 + SVzEthLaserEyeCapability sCapability; + unsigned int nMaxFrameIdx; //< 帧号最大值 + unsigned int nMaxTimeStamp; //< 时间戳最大值 + int nHardwareVersion; //< 硬件版本 + char szDeviceAddr[VZNL_DEVICE_ADDR_LENGTH]; //< Device Address + char szVersion[VZNL_VERSION_LENGTH]; //< Version + + // Ethernet Info + EVzNLEthernetEyeIPType eEthernetIPType; //< 以太网IP类型 + char chEyeIP[VZNL_SDK_NETWORK_IPv4_LENGTH]; //< 眼睛IP + char chEyeMac[VZNL_SDK_NETWORK_MAC_LENGTH]; //< 当前眼睛MAC + char chLocalIP[VZNL_SDK_NETWORK_IPv4_LENGTH];//< 网卡IP + char chLocalMac[VZNL_SDK_NETWORK_MAC_LENGTH];//< 网卡MAC + char vendor[8]; //< + char productName[8]; //< 产品名 + char serialID[8]; //< 序列号 + + // Ext Data + unsigned int nSDKClientCnt; //< SDK Link Count + unsigned int nModbusClientCnt; //< Modbus Link Count + unsigned int nEthernetSpeed; //< xxx Mbps + char szHardwareVersion[32]; //< HardWareVersion + unsigned int nUdpFlag; //< 用于判断是否可以使用UDP广播修改IP,0表示不可以 1表示可以 + unsigned int nPupilDistance; //< 瞳距 + + // NetCard Info + SVzNetCardInfo sNetCardInfo; //< 网卡信息 +} SVzNLEyeDeviceInfoEx; + +/// @brief 系统配置参数 +typedef struct +{ + unsigned int nDeviceTimeOut; // 设备超时设置 +} SVzNLConfigParam; + +/// @brief 打开设备类型 +typedef struct _tagSVzNLOpenDeviceParam +{ + EVzNLEyeResolution eEyeResolution; // 眼睛的分辨率 + EVzSDKType eSDKType; + VzBool bStopStream; + _tagSVzNLOpenDeviceParam() + { + eEyeResolution = keVzNLEyeResolution_Unknown; + eSDKType = keSDKType_Unknown; + bStopStream = VzTrue; + } +} SVzNLOpenDeviceParam; + +typedef struct +{ + char szPath[256]; +} SVzNLTestOpenDeviceParam; + +/// @brief +/// 更新工程信息 +typedef struct +{ + char szProject[32]; + char szVersion[32]; +} SVzSystemProjectInfo; + +/// @breif +/// 版本信息。 +typedef struct +{ + long bValidSDKVersion: 1; ///< SDK版本是否可用 + long bValidAppVersion: 1; ///< EyeCB版本是否可用 + long bValidCameraVersion: 1; ///< 眼睛库版本是否可用 + long bValidAlgoVersion: 1; ///< 算法版本是否可用 + long bValidApkVersion: 1; ///< 安卓版本是否可用 + long bValidDevIDVersion: 1; ///< DEVID版本是否可用 + long bValidWifiVersion: 1; ///< WIFI版本是否可用 + long bValidHDMIVersion: 1; ///< HDMI版本是否可用 + long bValidVizumEyeVersion : 1; ///< 硬件版本是否可用 + long bValidFwVersion : 1; ///< 硬件版本是否可用 +} SVzNLVersionCapGroup; + +typedef union +{ + SVzNLVersionCapGroup sVersionCap; + long nMaskCap; +} SVzNLDevVersionCap; + +/// @brief +/// 版本信息 +typedef struct +{ + SVzNLDevVersionCap sCap; + char szSDKVersion[VZNL_VERSION_LENGTH]; ///< SDK版本 + char szAppVersion[VZNL_VERSION_LENGTH]; ///< App版本 + char szCameraVersion[VZNL_VERSION_LENGTH]; ///< 眼睛版本 + char szAlgoVersion[VZNL_VERSION_LENGTH]; ///< 算法版本 + char szApkVersion[VZNL_VERSION_LENGTH]; ///< 安卓版本 + char szDevIDVersion[VZNL_VERSION_LENGTH]; ///< 设备ID版本 + char szWifiVersion[VZNL_VERSION_LENGTH]; ///< WIFI版本 + char szHDMIVersion[VZNL_VERSION_LENGTH]; ///< HDMI版本 + char szVizumEyeVersion[VZNL_VERSION_LENGTH];///< 硬件版本 + char szFwVersion[VZNL_VERSION_LENGTH]; ///< 硬件版本 +} SVzNLVersionInfo; + +/// @brief +/// 图像信息 +typedef struct +{ + unsigned char* pBuffer; // 图像buffer信息 + unsigned int nBufferSize; // 图像buffer的大小 + + unsigned int nWidth; // 图像宽度 + unsigned int nHeight; // 图像高度 + unsigned char nChannels; // 通道数 + EVzNLImageCompress eImageCompress; +} SVzNLCompressData; + +/// @brief +/// 点坐标 +typedef struct +{ + int x; + int y; +} SVzNL2DPoint; + +/// @brief +/// 点坐标 +typedef struct +{ + unsigned short x; + unsigned short y; +} SVzNL2DPointShortU; + +/// @brief +/// 点坐标 +typedef struct +{ + SVzNL2DPoint sLeft; + SVzNL2DPoint sRight; +} SVzNL2DLRPoint; + +/// @brief +/// 点坐标 +typedef struct +{ + float x; + float y; +} SVzNL2DPointF; + +typedef struct +{ + double x; + double y; +} SVzNL2DPointD; + +typedef struct +{ + double x; + double y; + double z; +} SVzNL3DPoint; + +typedef struct +{ + float x; + float y; + float z; +} SVzNL3DPointF; + +// 3D Point +typedef union +{ + float fData[4]; + struct + { + float x; + float y; + float z; + }; +} SVzNLPointXYZ; + +// 3D Point + RGB +typedef union +{ + float fData[4]; + struct + { + float x; + float y; + float z; + VzColorRGB nRGB; + }; +} SVzNLPointXYZRGBA; + +// 3D Point + Gray +typedef union +{ + float fData[4]; + struct + { + float x; + float y; + float z; + int nGray; + }; +} SVzNLPointXYZGray; + +// 3D Point + Gray +typedef struct +{ + union + { + float fData[4]; + struct + { + float x; + float y; + float z; + }; + }; + + union + { + float fData_c[4]; + struct + { + float intensity; + }; + }; +} SVzNLPointXYZI; + +// 3D Point + Gray +typedef struct +{ + union + { + double dData[4]; + struct + { + double x; + double y; + double z; + VzColorRGB nRGB; + }; + }; +} SVzNLPointRGBA_D; + +typedef struct +{ + int x; + int y; + int z; + int flag; +} SVzNLLogisticsPoint; + +typedef struct +{ + float x; + float y; + float z; + float reserve; +} SVzNLAlign3DPointF; + +typedef struct +{ + int x; + int y; + int z; + int reserve; +} SVzNLAlign3DPointI; + +struct SVz3DPointFWithPixel +{ + float x; + float y; + float z; + unsigned char byPixelL; + unsigned char byPixelR; + unsigned char byReserve[2]; +}; + +/// @brief 范围类型 double +typedef struct +{ + float min; //< 最小值 + float max; //< 最大值 +} SVzNLRangeF; + +/// @brief 范围类型 double +typedef struct +{ + double min; //< 最小值 + double max; //< 最大值 +} SVzNLRangeD; + +/// @brief 范围类型 int +typedef struct +{ + unsigned int nMin; //< 最小值 + unsigned int nMax; //< 最大值 +} SVzNLRangeU; + +/// @brief 范围类型 int +typedef struct +{ + int nMin; //< 最小值 + int nMax; //< 最大值 +} SVzNLRange; + +/// @brief 3D值范围 +typedef struct +{ + SVzNLRangeD xRange; //< X范围 + SVzNLRangeD yRange; //< Y范围 + SVzNLRangeD zRange; //< Z范围 +} SVzNL3DRangeD; + +/// @brief 线的2D信息 +typedef struct +{ + SVzNL2DPoint ptStart2D; // 2D起始坐标 + SVzNL2DPoint ptEnd2D; // 2D结束坐标 +} SVzNL2DLine; + +typedef struct +{ + SVzNL2DPointF ptStart2D; // 2D起始坐标 + SVzNL2DPointF ptEnd2D; // 2D结束坐标 +} SVzNL2DLineF; + +/// @brief 线的3D信息 +typedef struct +{ + SVzNL3DPoint ptStart3D; // 3D起始坐标 + SVzNL3DPoint ptEnd3D; // 3D结束坐标 +} SVzNL3DLine; + +/// @brief 2D矩形 +typedef struct +{ + SVzNL2DPoint ptLT; //< 左上 + SVzNL2DPoint ptRT; //< 右上 + SVzNL2DPoint ptRB; //< 右下 + SVzNL2DPoint ptLB; //< 左下 +} SVzNL2DRect; + +/// @brief 3D矩形 +typedef struct +{ + SVzNL3DPoint ptLT; //< 左上 + SVzNL3DPoint ptRT; //< 右上 + SVzNL3DPoint ptRB; //< 右下 + SVzNL3DPoint ptLB; //< 左下 +} SVzNL3DRect; + +/// @brief 三角形 +typedef struct +{ + SVzNL3DPoint ptVertexA; + SVzNL3DPoint ptVertexB; + SVzNL3DPoint ptVertexC; +} SVzNL3DTriangle; + +/// @brief 三角形 +typedef struct +{ + SVzNL2DPoint ptVertexA; + SVzNL2DPoint ptVertexB; + SVzNL2DPoint ptVertexC; +} SVzNL2DTriangle; + +/// @brief 点的坐标 +typedef struct +{ + int nPointIdx; // 点序 + SVzNL2DPoint ptLeft2D; // 2D左图坐标 + SVzNL2DPoint ptRight2D; // 2D右图坐标 + SVzNL3DPoint pt3D; // 3D坐标 +} SVzNLPosition; + +/// @brief 点坐标 (double) +typedef struct +{ + int nPointIdx; // 点序 + SVzNL2DPointD ptLeft2D; // 2D左图坐标 + SVzNL2DPointD ptRight2D; // 2D右图坐标 + SVzNL3DPoint pt3D; // 3D坐标 +} SVzNLPositionD; + +/// @brief 2D点坐标 +typedef struct +{ + int nPointIdx; // 点序 + SVzNL2DPoint ptLeft2D; // 2D左图坐标 + SVzNL2DPoint ptRight2D; // 2D右图坐标 +} SVzNL2DPosition; + +/// @brief 2D点坐标 +typedef struct +{ + int nPointIdx; // 点序 + SVzNL2DPointF ptLeft2D; // 2D左图坐标 + SVzNL2DPointF ptRight2D; // 2D右图坐标 +} SVzNL2DPositionF; + + +/// @brief 3D点坐标 +typedef struct +{ + int nPointIdx; // 点序 + SVzNL3DPoint pt3D; // 3D坐标 +} SVzNL3DPosition; + +/// @brief 3D激光线 +typedef struct +{ + SVzNL3DPosition* p3DPosition; //< 3D点(数组) + int nPositionCnt; //< 3D点个数 + unsigned long long nTimeStamp; //< 时间戳 +} SVzNL3DLaserLine; + +/// @brief 3D激光线 +typedef struct +{ + SVzNLPointXYZRGBA* p3DPoint; //< 3D点(数组) + int nPointCnt; //< 3D点个数 + unsigned long long nTimeStamp; //< 时间戳 +} SVzNLXYZRGBDLaserLine; + +/// @brief 3D点坐标(float) +typedef struct +{ + int nPointIdx; //< 点索引 + SVzNL3DPointF pt3D; //< 点值 +} SVzNL3DPositionF; + +/// @brief 矩形 +typedef struct +{ + int left; //< 左 + int right; //< 右 + int top; //< 上 + int bottom; //< 下 +} SVzNLRect; + +/// @brief 大小 +typedef struct +{ + int nWidth; //< 宽 + int nHeight; //< 高 +} SVzNLSize; + +/// @brief 大小 +typedef struct +{ + short shWidth; //< 宽 + short shHeight; //< 高 +} SVzNLSizeSh; + +/// @brief 大小 +typedef struct +{ + float fWidth; //< 宽 + float fHeight; //< 高 +} SVzNLSizeF; + +/// @brief 大小(double) +typedef struct +{ + double dWidth; //< 宽 + double dHeight; //< 高 +} SVzNLSizeD; + +/// @brief 三角形的类型 +typedef struct +{ + double dSideA; + double dSideB; + double dSideC; +} SVzNLTriangleInfo; + +/// @brief +/// ROI区域 +/// o————————————————o +/// | ↑ ↑ | +/// | |T |B | +/// | ↓ | | +/// | L o——|——o | +/// |←———→| | | | +/// | R | | | | +/// |←————|——↓—→| | +/// | o—————o | +/// o————————————————o +typedef struct +{ + int left; // 左 + int right; // 右 + int top; // 上 + int bottom; // 下 +} SVzNLROIRect; + +/// @brief 2D 位置 +typedef struct +{ + double x; //< X + double y; //< Y + double value; //< None +} SVzNL2DValueD; + +/// @brief 设备范围数据 +typedef struct +{ + SVzNLRangeU sFrameRate; //< 帧率范围 + SVzNLRangeU sExpose; //< 曝光范围 + SVzNLRangeU sGain; //< 增益范围 +} SVzDeviceLimitInfo; + +/// @brief 图像信息 +typedef struct +{ + unsigned char* pBuffer; // 图像buffer信息 + unsigned int nBufferSize; // 图像buffer的大小 + + SVzNL2DPoint ptOriPos; // 原始位置(顶点) + unsigned int nWidth; // 图像宽度 + unsigned int nHeight; // 图像高度 + EVzNLImageType eImageType; // 图像格式 + unsigned char byBitDepth; // 位图深度 + unsigned char nChannels; // 通道数 +} SVzNLImageData; + +//--------------------------------------------------------------- +// @brief 3D数据二维索引表 +//--------------------------------------------------------------- +typedef struct +{ + EVzResultDataType ePointType; // 3D点类型 + void* pPointPtr; // 3D点指针 + unsigned int nWidth; // 二维表宽 + unsigned int nHeight; // 二维表高 +} SVz3DPointMapTable; + +/// @breif 各检测类型的结构体 +typedef struct +{ + EVzNLDetectObjType eDetectType; + int nReserve[2];//< 保留指针,可能是内部的一个对象? +} SVzNLDetectObjectResult; + +/// @breif 线的2D结果 +typedef struct +{ + SVzNL2DLine sLeftLine; + SVzNL2DLine sRightLine; + double dLineLength; // 线长 +} SVzNL2DLineResult; + +/// @breif 线的3D结果 +typedef struct +{ + SVzNL3DLine sLine; + double dLineLength; // 线长 +} SVzNL3DLineResult; + +/// @breif 线的2D结果 +typedef struct +{ + SVzNL2DRect sLeftRect; + SVzNL2DRect sRightRect; + + double dTopDistance; + double dRightDistance; + double dBottomDistance; + double dLeftDistance; +} SVzNL2DRectResult; + +/// @breif 2D结果 +typedef struct +{ + SVzNL3DRect sRect; + double dTopDistance; + double dRightDistance; + double dBottomDistance; + double dLeftDistance; +} SVzNL3DRectResult; + +/// @breif 三角形结果 +typedef struct +{ + SVzNL2DTriangle sLeftTriangle; + SVzNL2DTriangle sRightTriangle; + + double dABDistance; + double dBCDistance; + double dACDistance; +} SVzNL2DTriangleResult; + +typedef struct +{ + SVzNL3DTriangle sTriangle; + + double dABDistance; + double dBCDistance; + double dACDistance; +} SVzNL3DTriangleResult; + +/// @brief 椭圆检测结果 +typedef struct +{ + SVzNLDetectObjectResult sDetectObj; + SVzNL2DPoint s2DPoint; +} SVzNLDetectEllipseResult; + +typedef struct +{ + int nSize; +} SVzNLDetectCubeParam; + +typedef struct +{ + SVzNLDetectObjectResult sDetectObj; +} SVzNLDetectCubeResult; + +/// @brief 3D物体的连接线 +typedef struct +{ + int nLineStart; + int nLineEnd; +} SVzNL3DObjectLine; + +/// @brief 像素深度值归一化模式 +typedef enum +{ + keDepthNormalizingMode_None = 0, //不使用归一化 + keDepthNormalizingMode_Indicated = 1, //指定固定归一化范围 + keDepthNormalizingMode_Auto = 2 //自动检测归一化范围 +} EVzDepthNormalizingMode; + +/// @brief 图像位深 +typedef enum +{ + keImageBitDepthTypeNone = 0, + keImageBitDepthType_8bit = 8, + keImageBitDepthType_16bit = 16, +} EVzImageBitDepthType; + +/// 深度图存储配置参数 +typedef struct +{ + EVzDepthNormalizingMode eDepthNormalizingMode; // 像素深度值归一化模式 + unsigned int nImageMargin; // 输出深度图边缘预留空白区域宽度(单位:像素值) + EVzImageBitDepthType eBitDepth; // 输出深度图的位深度: + float fDepthResolution; // 深度图中深度值精度,例如:0.1 表示1个像素值对应0.1mm 实际距离 + + float fWHPixelResolution; // 深度图中每个像素的精度,例如:0.1 表示1个像素点对应0.1mm实际距离,eDepthNormalizingMode = eDepthNormalizingMode_Indicated 时有效 + float fNormalizingStartVal; // 客户指定的归一化范围的起始值(mm):eDepthNormalizingMode = keDepthNormalizingMode_Indicated 时有效 + float fNormalizingEndVal; // 客户指定的归一化范围的结束值(mm):eDepthNormalizingMode = keDepthNormalizingMode_Indicated 时有效 + + float fNormalizingMargin; // eDepthNormalizingMode = keDepthNormalizingMode_Auto 时,在自动检测到的归一化区间上下预留的空白空间高度(单位mm)上下各预留指定高度空间 + +} SVzDepthMapImgCtlPara; + +/// @brief 3D物体面信息 +typedef struct +{ + int nPointCount; + unsigned int* pArryPointIdx; +} SVzNL3DObjectPanel; + +/// @brief 三维物体描述 +typedef struct +{ + int nType; + SVzNL3DPoint* pGrabPoint; ///< 物体抓取点 + int nGrabPointCount; + SVzNL3DPoint* p3DPoint; ///< 物体各顶点 + int n3DPointCount; ///< 物体上点的个数 + SVzNL3DObjectLine* p3DObjLine; ///< 物体上的线 + int n3DObjLineCount; ///< 物体上线的个数 + SVzNL3DObjectPanel* p3DObjPanel; + int n3DObjPanelCount; +} SVzNL3DObjectDesc; + +/// @brief 三维物体描述 +typedef struct +{ + SVzNL2DPoint* pLeftGrabPoint; ///< 物体抓取点 + SVzNL2DPoint* pRightGrabPoint; ///< 物体抓取点 + int nGrabPointCount; + SVzNL2DPoint* pLeft2DPoint; ///< 物体各顶点 + SVzNL2DPoint* pRight2DPoint; ///< 物体各顶点 + int n2DPointCount; ///< 物体上点的个数 + SVzNL3DObjectLine* p3DObjLine; ///< 物体上的线 + int n3DObjLineCount; ///< 物体上线的个数 + SVzNL3DObjectPanel* p3DObjPanel; + int n3DObjPanelCount; +} SVzNL3DObject2DResult; + +/// @brief ColorMark 形状 +typedef enum +{ + keVzNLColorMarkType_Unknown = 0, + keVzNLColorMarkType_Rect, + keVzNLColorMarkType_Triangle, + keVzNLColorMarkType_Circle, +} EVzNLColorMarkType; + +/// @brief Color Mark +typedef struct +{ + int nPointIdx; // 点序 + SVzNL3DPoint pt3D; // 3D坐标 +} SVzNLColorMark3DResult; + +/// @brief ROI +typedef struct +{ + int nPointIdx; // 点序 + SVzNL2DPoint ptLeft2D; // 2D左图坐标 + SVzNL2DPoint ptRight2D; // 2D右图坐标 + SVzNLRect sLeftBox; + SVzNLRect sRightBox; +} SVzNLColorMark2DResult; + +/// @brief ROI +typedef struct +{ + int nPointIdx; // 点序 + EVzNLColorMarkType eType; + SVzNL3DPoint pt3D; + SVzNL2DPoint ptLeft2D; // 2D左图坐标 + SVzNL2DPoint ptRight2D; // 2D右图坐标 + SVzNLRect sLeftBox; + SVzNLRect sRightBox; +} SVzNLColorMarkResult; + +typedef struct +{ + SVzNL3DPosition* p3DPoint; + int nCount; +}SVzDetectGrainsLaserResult; + +typedef struct +{ + unsigned int nIndex; + float fLength; + float fWidth; + float fHeight; + + float fVolume; //until mm + SVzNL3DPoint sObjCenter; + SVzNL3DPoint sBoxGoundTop[4]; + SVzNL3DPoint sBoxGoundBottom[4]; +} SVzLaserGrainsData; + +typedef struct +{ + SVzLaserGrainsData* pGrainData; + int nCount; +} SVzDetectGrainResult; + +typedef enum +{ + keGrainLaserStatus_Unknown = 0, + keGrainLaserStatus_BeginWork, + keGrainLaserStatus_Working, + keGrainLaserStatus_EndWork, +} EVzGrainLaserStatus; + +///< 每个石子的信息 +typedef struct +{ + int index_; + char lithology[128]; //岩性 + char origin_[128]; //产地 + double length_; //长 + double width_; //宽 + double height_; //高 + double vol_; //体积 + double area_; //面积 + double perimeter_; //周长 + float A_; //Axial factor 轴向系数 + float CR_; //Convexity 凸度 + float AP_; //棱角参数 + VzBool isNeedle_; //是否针片状 + SVzNLRect rcPos; +} SVzLaserAggregateData; + +///< 级配信息 +typedef struct +{ + int index_; // 级配级别 6 7 8 9 10 11 12 13 + int num_; // 数量 + float pi_; // pi + float passRate_; // 通过率 + float top_; // 上限 + float bottom_; // 下限 +} SVzLaserAggregateLevelData; + +///< 石子检测综合信息 +typedef struct +{ + char chLithology[128]; ///< 岩性 + char chOrigin[128]; ///< 产地 + float fNeedleRate; ///< 针片状含量 + float fMeanAP; ///< 综合棱角值 + float fMeanA; ///< 综合轴向系数 + float fMeanCR; ///< 综合凸度 +} SVzLaserAggregateMeanInfo; + +///< 石子检测状态 +typedef enum +{ + keAggerateLaserStatus_Unknown = 0, + keAggerateLaserStatus_BeginWork, ///< 开始工作 + keAggerateLaserStatus_Working, ///< 正在工作 + keAggerateLaserStatus_EndWork, ///< 结束工作 +} EVzAggerateLaserStatus; + +/// @brief +/// 钢筋信息 +typedef struct +{ + int nFlag; + int nRadius; + float fPhyRadius; + SVzNL2DPoint nPoint; + int nLineIndex; + unsigned int nTimeStamp; + SVzNL3DPoint n3DPoint; +}SVzNLCounterItem; + +/// @brief +/// 钢筋信息 +typedef struct +{ + int nNum; // 总数 + SVzNLCounterItem* pPointArr; // 二维扫描图坐标 + unsigned long long nStartTimeStamp;// 起始线的时间戳 + SVzNLPosition sTopPoint; // 最突出点数据 + SVzNLCounterItem sMarkPoint; // mark信息 + SVzNLImageData sImage; // 生成的二维图像 +} SVzNLCounter; + +/// @brief +typedef struct +{ + unsigned int nSize; + char szName[64]; + char szVersion[64]; + void* pVoid; + int nVersionCode; +} SVzAICreateParam; + +/** + * @brief + * 输出帧参数 + */ +typedef struct +{ + unsigned long long nFrameIdx; //< 帧号 + unsigned long long nTimeStamp; //< 时间戳 +} SVzOutputFrameProps; + +/** + * @brief + * Circle Mark Information + */ +typedef struct +{ + int nMarkID; //< Mark 标志 + SVzNL3DPosition ptCenterPos; //< 中心点 + SVzNL3DPosition ptLeftPos; //< 左 + SVzNL3DPosition ptTopPos; //< 上 + SVzNL3DPosition ptRightPos; //< 右 + SVzNL3DPosition ptBottomPos; //< 下 + unsigned int nGray; +} SVzCircleMarkPosInfo; + +/// @brief +/// 3D圆形 +typedef struct +{ + float fR; + SVzNL3DPointF ptCenter; + SVzNL3DPointF ptWordCenter; + SVzNL3DPointF ptNormalVector; + SVzNL3DPointF* pArray3DPoint; + SVzNL3DPointF* pArrayWorld3DPoint; + int nPointCount; +} SVzNL3DCircleResult; + +/// @brief +/// 3D圆形物体 +typedef struct +{ + EVzNL3DCircleType eType; // 圆类型 + + SVzNLROIRect box2dLeft; // 包围盒左图 + SVzNLROIRect box2dRight; // 包围盒右图 + + SVzNL3DCircleResult* pArrayCircle; + int nCircleCount; +} SVzNL3DCircleObjectResult; + +/// @brief +/// 3D圆形 +typedef struct +{ + double dR; + SVzNL2DPoint ptLeftCenter; + SVzNL2DPoint ptRightCenter; + SVzNL2DPoint* pArray2DLeftPoint; + SVzNL2DPoint* pArray2DRightPoint; + int nPointCount; +} SVzNL2DCircleResult; + +/// @brief +/// 3D圆形物体 +typedef struct +{ + EVzNL3DCircleType eType; // 圆类型 + + SVzNLROIRect box2dLeft; // 包围盒左图 + SVzNLROIRect box2dRight; // 包围盒右图 + + SVzNL2DCircleResult* pArrayCircle; + int nCircleCount; +} SVzNL2DCircleObjectResult; + +/* + * @brief 眼睛配置信息 + */ +typedef struct +{ + EVzNLEthernetEyeIPType eEthernetIPType; + unsigned char byEyeIP[4]; + unsigned char byEyeMac[6]; + unsigned char byLocalIP[4]; + unsigned char byLocalMac[6]; + char vendor[8]; + char productName[8]; + char serialID[8]; + VzBool bIsSupportUDPFilter; + + SVzNetCardInfo sNetCardInfo; +} SVzNLEthernetEyeConfigInfo; + +typedef struct +{ + double dWhiteCheckPercent; + unsigned char chRgbMax[3]; + double dBlockEffectiveTh; + short shMinBlockNumber; + double dGainRange[2]; + unsigned char chHistStartAddr; + int nBlockWidth; + int nBlockHeight; +} SVzNLAWBInfo; + +// 钢筋检测模式 +enum +{ + keEyeCBDetectCounterMode_All = 1, + keEyeCBDetectCounterMode_Center = 2, +}; +typedef unsigned int EVzEyeCBDetectMode; + +// @brief 摆动机构信息 +struct SVzSwingMotorDevInfo +{ + int nMotorMaxAngle; //< 电机最大旋转角度 + int nMaxAngle; //< 最大旋转角度 + int nMinSpeed; //< 最小角速度 + int nMaxSpeed; //< 最大角速度 + int nStartAngle; //< 起始位置 + int nEndAngle; //< 结束位置 + int nSpeed; //< 速度 + int nStopAngle; //< 停止角度 + float fEndAngleThres; //< 结束角度判断阈值 +}; + +/// @brief +typedef struct +{ + unsigned int nAirforceCount; //< 气枪数 + unsigned int nStartAddress; //< 起始位置 + double dVolumeThres; //< 体积阈值 + unsigned int nMaxAirTimes; //< 最大喷气次数 + unsigned int nTimePerAir; //< 单次喷气时间 ms +} SVzDetectGangueParam; + +/// @brief +typedef struct +{ + double dArea; + SVzNL3DPoint ptPos[8]; +} SVzGangueObjectResult; + +/// @brief +/// Dump文件回调 +/// @param [out] szCrashFile Dump全路径 +typedef void(*VzNL_CrashCallBack)(char szCrashFile[VZ_MAXPATH]); + +/// @name 自动检测回调函数 +/// @{ +/** + * @brief 获取自动检测结果 + * @param [out] p3DPoint 3D数据 + * @param [out] p2DPoint 2D数据 + * @param [out] nCount 数据个数 + * @param [out] nTimeStamp 时间戳 + * @param [out] dTotleOffset 总偏移 + * @param [out] dStep 单次偏移 + * @param [out] pLeftImage 左图 + * @param [out] pRightImage 右图 + * @param [out] pParam 回调参数 + */ +typedef int(*VzNL_GetAutoDetectResultCB)(SVzNL3DPosition* p3DPoint, SVzNL2DPosition* p2DPoint, int nCount, unsigned long long nTimeStamp, double dTotleOffset, double dStep, SVzNLImageData* pLeftImage, SVzNLImageData* pRightImage, void* pParam); + +/// @brief 激光线数据 +typedef struct +{ + void* p3DPoint; //< 3D点 根据ResultDataType使用不同的数据结构 + void* p2DPoint; //< 2D点 根据ResultDataType使用不同的数据结构 + int nPointCount; //< 2D 3D 数据个数 + double dTotleOffset; //< 总偏移 + double dStep; //< 距离上一条线的偏移 + unsigned long long llFrameIdx; //< 帧号 + unsigned long long llTimeStamp; //< 时间戳 + unsigned int nEncodeNo; //< 编码器值 + float fSwingAngle; //< 当前摆动角度 + VzBool bEndOnceScan; //< 是否已完成一次扫描 +} SVzLaserLineData; + +typedef void(*VzNL_AutoOutputLaserLineExCB)(EVzResultDataType eDataType, SVzLaserLineData* pLaserLinePoint, void* pParam); + +typedef struct +{ + unsigned int nCount; + EVzResultDataType eDataType; + SVzLaserLineData* pLaserLineData; +}SVzLaserLineCacheData; + +/// @brief 激光线数据 +typedef struct +{ + SVzLaserLineData sLineData; //< + + SVzNLImageData* pImageData[3]; //< 图像数据 + void* pUnknownData; //< 未知数据 +} SVzLaserLineDataEx; + +/// @} + +/// @brief 检测码类型 +enum +{ + keLogisticsDetectCodeTypeNone = 0, //< 什么都不检测 + keLogisticsDetectCodeType_QRCode = 0x01, //< 二维码 + keLogisticsDetectCodeType_BarCode = 0x02, //< 条形码 +}; +typedef unsigned int EVzLogisticsDetectCodeType; + +/// @brief 扣图类型 +enum +{ + keLogisticsClipObjModeNone = 0, //< 不做抠图操作 + keLogisticsClipObjMode_Object = 0x01, //< 抠物体图 + keLogisticsClipObjMode_QRCode = 0x02, //< 抠二维码图 + keLogisticsClipObjMode_BarCode = 0x04, //< 抠条形码图 +}; +typedef unsigned int EVzLogisticsClipObjMode; + +// 物流触发位置 +enum +{ + keLogisticsPackageSyncTypeInvalid = 0, //< 不触发 + keLogisticsPackageSyncType_In, //< 物体进入时触发 + keLogisticsPackageSyncType_Out, //< 物体出来时触发 +}; +typedef unsigned int EVzLogisticsPackageSyncType; + +// 物流相机运行模式 +typedef enum +{ + keLogisticsRunMode_None = 0, //< 无 + keLogisticsRunMode_SoftSignal = 1, //< 软件使能开始检测 + keLogisticsRunMode_ExtSignal, //< 外部使能开始检测 + keLogisticsRunMode_AutoSignal, //< 自动检测 +} EVzLogisticsRunMode; + +// 物流结果状态 +typedef enum +{ + keLogisticsResultStatusInvalid = 0, //< 无效 + keLogisticsResultStatus_Begin, //< 物体开始 + keLogisticsResultStatus_Work, //< 物体运动中 返回点云数据 + keLogisticsResultStatus_End //< 当前物体结束 返回物体体积 +} EVzLogisticsResultStatus; + +// @brief 结果信息掩码 +enum +{ + keLogisticsResultMask_PointCloud = 0x01, //< 点云数据 + keLogisticsResultMask_ObjectInfo = 0x02, //< 物体结果数据 + keLogisticsResultMask_ColorImage = 0x04, //< 彩色图像 +}; +typedef unsigned int EVzLogisticsResultMask; + +// @brief 硬件使能线信号类型 +enum +{ + keEnLineInPoint_None = 0x00, //< 无效 + keEnLineInPoint_EnSignal = 0x01, //< 使能信号 + keEnLineInPoint_BiPhaseEncoder = 0x02, //< 双向编码器 +}; +typedef unsigned int EVzEnLineInPoint; + +// @brief +typedef struct +{ + char szCode[VZ_MAXPATH]; //< 码值 + VzBool bValidImage; + SVzNLImageData sCodeImageData; //< 码图像 + void* pVoid; +} SVzLogisticsCodeInfo; + +// @brief 物体结果描述 +typedef struct +{ + float fVolume; //< 体积 + float fLength; //< 长 + float fWidth; //< 宽 + float fHeight; //< 高 + + // ①------------② + // /| /| + //③------------④ | + //| | | | + //| | | | + //| | | | + //|⑤------------⑥ + // / / + //⑦------------⑧ + SVzNL3DPoint sConnerPoint[8]; //< 8角点 + SVzNL3DPoint* pOutlinePoint; //< 轮廓点 + unsigned int nOutlinePointCount; //< 轮廓点个数 + + SVzLogisticsCodeInfo* psQRCodeInfo; //< 二维码信息 + unsigned int nQRCodeCount; + SVzLogisticsCodeInfo* psBarCodeInfo; //< 条形码信息 + unsigned int nBarCodeCount; + + VzBool bValidObjImage; //< 物体图像有效 + SVzNLImageData sObjImageData; //< 物体图像 + void* pVoidObjData; +} SVzLogisticsObjInfo; + +// 物流结果信息 +typedef struct +{ + EVzLogisticsResultMask eMask; //< 结果标志 + + // @name PointClound + // @{ + unsigned long long ullTimeStamp; //< 时间戳 + unsigned long long ullEncodeNo; //< 编码器值 + SVzNL3DPosition* p3DPoint; //< 3D点 + unsigned int nPointCount; //< 点数 + double dOffset; //< 偏移 + // @} + + // @name ObjectInfo + // @{ + unsigned int nObjectCount; //< 物体个数 + SVzLogisticsObjInfo* pArrayObjectInfo; //< 物体数组 + + VzBool bIsResetOffset; //< 当前帧开始X值被重置 + // @} + + // @name ColorImage + // @{ + SVzNLImageData* psCenterImage; //< 图像 + unsigned int nCenterImageTimeStamp; //< 图像时间 + // @} +} SVzLogisticsResultData; + +typedef enum +{ + keLaserFileType_Invalid = 0, + keLaserFileType_PureTxt = 1, //< 纯文本 + keLaserFileType_Txt, //< 本公司自用 + keLaserFileType_Pcd, //< PCD + keLaserFileType_Las, //< LAS + keLaserFileType_VzBinaryData, //< Binary File + keLaserFileType_Ply, +} EVzLaserFileType; + +/// @brief 文件模式 +enum +{ + keFileModeInvalid = 0, + keFileModeRead = 0x01, + keFileModeWrite = 0x02, +}; +typedef unsigned int EVzFileMode; + +/// @brief 点云后处理图像类型定义 +typedef enum +{ + kePointAfterProcTypeInvalid = -1, + kePointAfterProcType_DepthMap, // 深度图 + kePointAfterProcType_GrayPic, // 灰度图 + kePointAfterProcType_Intensity, // 光强图 +} EVzPointAfterProcType; + +/* + * 机械臂点数据结构定义 + */ +typedef struct +{ + float x; + float y; + float z; + float roll; + float pitch; + float yaw; +} SVzNLRobotPointF; + +typedef struct +{ + SVzNLRobotPointF sPoint; // 坐标系内的点坐标 + SVzNL3DPointF sRX; // 坐标系 X方向 + SVzNL3DPointF sRY; // 坐标系 Y方向 + SVzNL3DPointF sRZ; // 坐标系 Z方向 +} SVzNLRobotPointWithPoseF; + +typedef struct +{ + SVzNL3DPointF sPoint; // 坐标系内的点坐标 + SVzNL3DPointF sRX; // 坐标系 X方向 + SVzNL3DPointF sRY; // 坐标系 Y方向 + SVzNL3DPointF sRZ; // 坐标系 Z方向 +} SVzNL3DPosePointF; + +// +typedef struct +{ + SVzNL3DPointF sPoint; // 点位置 + SVzNL3DPointF sNormal; // 法向量 +} SVzNL3DTracePointF; + +/// @brief 激光线数据 +typedef struct +{ + SVzLaserLineData sLineData; + + unsigned int nLeftPointCount; + unsigned int nRightPointCount; + + SVzNLPointXYZ* pLeftTearData; + SVzNLPointXYZ* pRightTearData; + unsigned int nLeftTearPointCount; + unsigned int nRightTearPointCount; +} SVzLaserTearDataEx; + +/// @brief +typedef enum +{ + keLaserObjectDetectPresetProfile_Custom = -1, + keLaserObjectDetectPresetProfile_General, //< 通用 + keLaserObjectDetectPresetProfile_ThinWire, //< 细铁丝 + keLaserObjectDetectPresetProfile_BigSence, //< 大景深 + keLaserObjectDetectPresetProfile_ReBar, //< 钢筋计数 + keLaserObjectDetectPresetProfileLast +} EVzLaserObjectDetectPresetProfile; + + +/// @brief +/// 激光线文件配置参数 +typedef enum +{ + keLaserFileParamStart = 0, + keLaserFileParam_Speed = keLaserFileParamStart, //< 速度值 类型 double 当前用于 binarydata 和 txt 两种文件存储 + keLaserFileParam_DataType, //< 数据类型 类型 unsigned int 当值为 0时表示Ascii模式,当为1时表示Binary模式,目前用于pcd的配置 + keLaserFileParam_SavePointCnt, //< 是否存储point个数 类型 unsigned int ,当值为0时表示不存储,为1表示存储,目前用于pure txt的配置 + keLaserFileParam_SaveSwingAngle, + keLaserFileParamLast +} EVzLaserFileParam; + +/// @brief +/// 触发输出模式 +typedef enum +{ + keStrobeTriggerOutMode_None = 0, //< 不控制触发输出 : 默认 + keStrobeTriggerOutMode_CameraLink, //< 跟随主模式输出 + keStrobeTriggerOutMode_UserControl, //< 用户控制 + keStrobeTriggerOutMode_LoopBack, //< 回环控制 + keStrobeTriggerOutMode_ServoControl,//< 舵机控制 +} EVzStrobeTriggerOutMode; + +/// @brief +/// Sensor 是否为长焦镜头 +enum EVzSensorLenType +{ + keSensorLenType_Invalid = 0, + keSensorLenType_3mm, + keSensorLenType_6mm, +}; + +/// @brief +/// 镜头盖住的状态类型 +enum EVzServoStatusType +{ + keServoStatusType_Invalid = -1, + keServoStatusType_Cover, + keServoStatusType_UnCover, +}; + +/// @brief +/// 编码器参数类型 +enum EVzEncodeParamType +{ + keEncodeParamType_RollerResolution = 0, //< 使用 编码器分辨率 + 编码器半径参数 模式 + keEncodeParamType_DistancePerPulse, //< 使用 编码器每两个脉冲的间距 +}; + +/// @brief +/// 配置多相机触发模式 +enum EVzMultiDevWorkMode +{ + keMultiDevWorkMode_MasterSlave = 0, ///< 主从触发 + keMultiDevWorkMode_Ext_FallingEdge = 1, ///< 外部下降沿触发 + keMultiDevWorkMode_Ext_HighFrequenceFallingEdge = 2, ///< 外部高帧率下降沿触发 +}; + +/// @brief +/// 配置融合的相机类型 +enum EVzMultiDevType +{ + keMultiDev_Dynanmic = 0, ///< 动态相机融合 + keMultiDev_Static = 1, ///< 静态相机融合 +}; + +/// @brief +/// 配置静态相机融合扫描模式 +enum EVzMultiStaticDevScanMode +{ + keMultiStaticDevScanMode_OneByOne = 0, ///< 一个接一个的扫描 + keMultiStaticDevScanMode_SameTime = 1, ///< 同时扫描 +}; + +/// @brief +/// 编码器方向 +enum EVzEncoderRollingDirection +{ + keEncoderRollingDirection_Forward = 0, + keEncoderRollingDirection_Backward +}; + + +/// @brief +/// 旋转图像类型 +enum EVzImageRotateType +{ + keImageRotateType_Invalid = -1, + keImageRotateType_FlipY_Rotate = 0, + keImageRotateType_ClockWise = 1, + keImageRotateType_CounterClockWise = 2, + keImageRotateType_90ClockWise = 3, + keImageRotateType_FlipX = 4, +}; + +typedef struct +{ + unsigned int nEncodeNo[2]; + EVzEncoderRollingDirection eDirection; + unsigned int nFrequency[2]; +} SVzEncoderData; + +enum +{ + keEmulationDetectType_AutoDetect = 0, + keEmulationDetectType_Detect, +}; +using EVzEmulationDetectType = unsigned int; + +/// @brief +/// 旋转图像类型 +enum EVzDustCoverConfigType +{ + keDustCoverConfigType_Large = 0, + keDustCoverConfigType_Medium, + keDustCoverConfigType_Small, +}; + +/// @brief +//环境类型 +typedef enum +{ + keEnvType_Invalid = -1, + keEnvType_InDoor = 0, + keEnvType_OutDoor = 1, +} EVzEnvType; + +/// @brief 范围类型 int +typedef struct +{ + EVzEnvType eEnvType; + unsigned int nLevel; +} SVzEnvLevel; + +/// @brief +//抗反光增益 +typedef enum +{ + keAntiReflectGainType_Close = 1, + keAntiReflectGainType_Double = 2, + keAntiReflectGainType_Quadruple = 4, + keAntiReflectGainType_Sextuple = 6, + keAntiReflectGainType_Octuple = 8, +} EVzAntiReflectGainType; + +typedef enum +{ + keAntiReflectCmpsDirType_LeftToRight = 0, + keAntiReflectCmpsDirType_RightToLeft = 1, +} EVzAntiReflectCmpsDirType; + +typedef struct +{ + unsigned int m_nCheckTimeInterval; + unsigned int m_nCheckTimeOut; + unsigned int m_nCheckExposeRatio; +}SVzAutoCoverControlParam; + +typedef struct +{ + char chIP[VZNL_SDK_NETWORK_IPv4_LENGTH]; +}SVzNLDeviceIp; + +typedef enum +{ + keLRSensorType_Gray = 0, + keLRSensorType_Color = 1, +} EVzLRSensorType; + +typedef enum +{ + keRGBOutputChannelType_R = 0, + keRGBOutputChannelType_G = 1, + keRGBOutputChannelType_B = 2, + keRGBOutputChannelType_All = 3, +} EVzRGBOutputChannelType; + +/// @brief +// 多相机融合时各子设备融合配置参数 +typedef struct +{ + unsigned int nDevIdx; // 融合索引:0:主设备;>0:从设备; + double dFusionMatrix[3][4]; // 融合矩阵 + double dYRange[2]; // 当前设备融合时Y轴过滤范围 +}SVzNLDeviceFusionData; + +// RGBHDR 出图模式 +typedef enum +{ + keRGBHDROutImageMode_Ori = 0, // 输出原始图像:背景图 + 熔池图 + keRGBHDROutImageMode_HDR = 1, // 输出HDR 叠加后的图像:HDR图 + keRGBHDROutImageMode_MPool = 2, // 只输出熔池图像 + keRGBHDROutImageMode_BG = 3, // 只输出背景图 + keRGBHDROutImageMode_Num // 模式个数 +} EVzRGBHDROutImageMode; + +/** +* @brief 点云<->2D伪彩图转换参数结构体 +*/ +typedef struct +{ + unsigned int nEdgeMargin; + double d3DXMin; + double d3DXMax; + double d3DYMin; + double d3DYMax; + double dPixelResolution; +}SVzPColorImageConvertInfo; + +/** +* @brief 2D伪彩图坐标<->3D坐标选取方法枚举 +*/ +typedef enum +{ + kePColorImageTo3DPoint_NearestPoint = 0, // 输出距离回算点X/Y 最近的原始点云3D 点坐标。 + kePColorImageTo3DPoint_RealPoint // 输出实际回算点X/Y坐标 + 最邻近原始点的Z坐标。 +}SVzPColorImageTo3DPointMethod; + +/// @brief +/// 采集图像模式 +typedef enum +{ + keCaptureMode_LR_Image = 0, // 输出双目图像 + keCaptureMode_C_Image, // 输出RGB图像 + keCaptureMode_LRC_Image, // 输出双目+RGB图像 +}EVzCaptureMode; + +/// @brief 焊缝跟踪点云扫描配置参数(存储到相机内) +typedef struct +{ + // 可配参数部分 + bool bIsEnableCalibROI; // 是否启用校正图 + EVzNLExposeMode eExposeMode; // 曝光模式 + unsigned int nExposeTime; // 曝光值 + unsigned short nLeftCameraGain; // 左目增益 + unsigned short nRightCameraGain; // 右目增益 + unsigned short nDynamicScanAngle; // 动态扫描时摆动机构角度 + unsigned short nFltrRgnTh; // 小信号过滤值 + SVzNLROIRect sLeftROI; // 左目ROI + SVzNLROIRect sRightROI; // 右目ROI + unsigned int nFrameRate; // 采集帧率 + unsigned int nOutputFrameRate; // 输出帧率:对接收到的激光线进一步抽样输出(0:不进行抽帧处理) + // 固定参数部分(SDK 内部自动设置),无需修改 + bool bIsSwingEnable; // 是否使能摆动机构,固定为false + bool bIsRGBEnable; // 是否使能RGB,固定为false + bool bIsStrobeEnable; // 是否使能C版功能,固定为true + bool bIsAntiReflectGainEnable; // 是否使能抗强光增益,固定为true +} SVzTrackingScanPara; + + +typedef struct{ + int denoiseStrength; // 去噪强度 + int sharpenStrength; // 锐化强度 + int brightness; // 亮度 + int contrast; // 对比度 + bool useAdvancedDenoise; // 使用高级去噪 +}AlgorithmParams; + +/** +* @brief 多相机融合操作,重叠区数据处理方法 +*/ +typedef enum +{ + keMulDevOverlayProcessType_SeqFilter = 0, // 按Y轴顺序过滤重叠的点云 + keMulDevOverlayProcessType_PointsFilter, // 按重叠区域内点数据个数过滤数据少的相机的点云 + keMulDevOverlayProcessType_DataFusion, // 对重叠区域内的点数据进行数据融合,补齐缺失的数据 +}EVzMulDevOverlayProcessType; + + +// Feature标志位定义 +typedef enum +{ + KE_FEATURE_RGB_CAMERA = 0x01, // RGB相机 + KE_FEATURE_SWING_MECHANISM = 0x02, // 摆动机构 + KE_FEATURE_DUAL_OUTPUT = 0x04, // 双端出图 + KE_FEATURE_STEREO_COLOR = 0x08, // 双目彩色 + KE_FEATURE_WELDING_CAMERA = 0x10 // 焊接相机 +}EVzFeatureFlags; + +// Feature数据结构 +typedef struct +{ + unsigned short flags; // 特性标志位 + unsigned short pupilDistance; // 相机瞳距配置 +}SVzFeatureData; + +/* +* 命令操作类型定义 +*/ +enum EVzUSBSerialRelayCmdType +{ + keUSBSerialRelayCmdType_CloseNoAck = 0, // 关闭不反馈 + keUSBSerialRelayCmdType_OpenNoAck, // 打开不反馈 + keUSBSerialRelayCmdType_CloseWithAck, // 关闭并反馈 + keUSBSerialRelayCmdType_OpenWithACck, // 打开并反馈 + keUSBSerialRelayCmdType_ReverseWithAck, // 取反并反馈 + keUSBSerialRelayCmdType_CheckState, // 查询状态 + keUSBSerialRelayCmdType_FlashBreakWithAck, // 闪断并反馈 + keUSBSerialRelayCmdType_Num +}; + +/* +* 串口波特率枚举 +*/ +enum EVzSerialPortBaudRate +{ + keSerialPortBaudRate_1200 = 0, + keSerialPortBaudRate_2400, + keSerialPortBaudRate_4800, + keSerialPortBaudRate_9600, + keSerialPortBaudRate_19200, + keSerialPortBaudRate_38400, + keSerialPortBaudRate_57600, + keSerialPortBaudRate_115200, + keSerialPortBaudRate_Num +}; + +/* +* 串口数据长度枚举(4~8bits) +*/ +enum EVzSerialPortByteSize +{ + keSerialPortByteSize_4 = 0, + keSerialPortByteSize_5, + keSerialPortByteSize_6, + keSerialPortByteSize_7, + keSerialPortByteSize_8, + keSerialPortByteSize_Num +}; + +/* +* 串口数据校验位枚举(0-4=None,Odd,Even,Mark,Space) +*/ +enum EVzSerialPortParity +{ + keSerialPortParity_None = 0, + keSerialPortParity_Odd, + keSerialPortParity_Even, + keSerialPortParity_Mark, + keSerialPortParity_Space, + keSerialPortParity_Num +}; + +/* +* 串口数据停止位枚举(0,1,2 = 1, 1.5, 2) +*/ +enum EVzSerialPortStopBits +{ + keSerialPortStopBits_1 = 0, + keSerialPortStopBits_1_5, + keSerialPortStopBits_2, + keSerialPortStopBits_Num +}; + +/* +* 串口配置参数结构 +*/ +typedef struct +{ + EVzSerialPortBaudRate eBaudRate; // 串口波特率 + EVzSerialPortByteSize eByteSize; // 串口波特率 + EVzSerialPortParity eParity; // 串口校验位 + EVzSerialPortStopBits eStopBits; // 串口停止位 +}SVzSerialPortCfgPara; + +/* +* USB 继电器设备参数 +*/ +typedef struct +{ + unsigned int nChannelNum; // 模块控制的通道个数 + SVzSerialPortCfgPara sSerialPortCfgPara; // 串口控制参数 +}SVzUSBSerialRelayCfgPara; + +/* +* 搜索输出的信息结构,目前只包括设备端口名称 +*/ +typedef struct +{ + char sPortName[VZNL_DEVICE_NAME_LENGTH]; +}SVzSerialPortInfo; + +/* +* +*/ +typedef struct +{ + char szName[VZ_USERCONFIG_NAME_LENGTH]; + char szDesc[VZ_USERCONFIG_DESC_LENGTH]; +}SVzUserConfigInfo; + + +#endif //__VIZUM_TYPES_HEADER_ diff --git a/VrCommon/Inc/VrError.h b/VrCommon/Inc/VrError.h index f4d6a5a..5d96199 100644 --- a/VrCommon/Inc/VrError.h +++ b/VrCommon/Inc/VrError.h @@ -88,11 +88,41 @@ enum ErrorCode DEV_UNSUPPORT, //1510 DEV_ARG_INVAILD, DEV_BUSY, - DEV_CONFIG_ERR, - DEV_DATA_INVALID, - DEV_RESULT_EMPTY, //1515 - DEV_UNACTIVATE, //未激活 -}; + DEV_CONFIG_ERR, + DEV_DATA_INVALID, + DEV_RESULT_EMPTY, //1515 + DEV_UNACTIVATE, //未激活 + + // DroneScrew 业务错误 + DRONESCREW_ERR_BASE = 1600, + DRONESCREW_ERR_INVALID_JSON, + DRONESCREW_ERR_UNKNOWN_CMD, + DRONESCREW_ERR_SINGLE_HANDLER_NOT_SET, + DRONESCREW_ERR_MACHINE_CODE_MISMATCH, + DRONESCREW_ERR_CAMERA_NOT_CONNECTED, + DRONESCREW_ERR_BINOCULAR_FRAME_TIMEOUT, + DRONESCREW_ERR_ALGO_NOT_INIT, + DRONESCREW_ERR_CACHED_FRAME_NOT_READY, + DRONESCREW_ERR_LEFT_ACQ_NOT_STARTED, + DRONESCREW_ERR_RIGHT_ACQ_NOT_STARTED, + DRONESCREW_ERR_GPIO_TRIGGER, + DRONESCREW_ERR_SINGLE_BUSY, + DRONESCREW_ERR_MODE_CONFLICT, + DRONESCREW_ERR_DETECT_CONFIG_LEFT, + DRONESCREW_ERR_DETECT_CONFIG_RIGHT, + DRONESCREW_ERR_DETECT_START_LEFT, + DRONESCREW_ERR_DETECT_START_RIGHT, + DRONESCREW_ERR_LIVE_CONFIG_LEFT, + DRONESCREW_ERR_LIVE_START_LEFT, + DRONESCREW_ERR_RTSP_INIT, + DRONESCREW_ERR_RTSP_START, + DRONESCREW_ERR_ZMQ_HANDLER_NOT_SET, + DRONESCREW_ERR_UDP_DISCOVERY, + DRONESCREW_ERR_RESPONSE_NOT_OK, + DRONESCREW_ERR_GPIO_EXPORT, + DRONESCREW_ERR_GPIO_DIRECTION, + DRONESCREW_ERR_GPIO_VALUE, +}; #define ERR_CODE(nCode) -std::abs((int)nCode) #define ERR_CODE_RETURN(nCode) do{ if(SUCCESS != (nCode)) return ERR_CODE(nCode); }while(0) diff --git a/VrUtils/VrUtils.pro b/VrUtils/VrUtils.pro index 02cdd80..b77d3d5 100644 --- a/VrUtils/VrUtils.pro +++ b/VrUtils/VrUtils.pro @@ -100,6 +100,7 @@ SOURCES += \ Src/VrFileUtils.cpp \ Src/VrLog.cpp \ Src/VrNetUtils.cpp \ + Src/VrStringUtils.cpp \ Src/VrTimeUtils.cpp \ ini/ConvertUTF.c \ log4cpp/src/AbortAppender.cpp \