手眼标定完善

This commit is contained in:
yiyi 2026-02-22 00:10:46 +08:00
parent 11c615d0c9
commit ce4bd9348a
8 changed files with 2073 additions and 1741 deletions

46
GrabBagPrj/CalibView.iss Normal file
View File

@ -0,0 +1,46 @@
; Script generated by the Inno Setup Script Wizard.
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
#define MyAppName "手眼标定工具"
#define MyAppVersion "1.0.0"
#define MyAppPublisher ""
#define MyAppURL ""
#define MyAppExeName "CalibView.exe"
#define MyProgramPath ".\buildwin\CalibView"
[Setup]
; NOTE: The value of AppId uniquely identifies this application.
; Do not use the same AppId value in installers for other applications.
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
AppId={{F8A7B6C5-D4E3-4F2A-9B1C-0D8E7F6A5B4C}}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
AppVerName={#MyAppName}_V{#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName={pf}\{#MyAppName}
DisableProgramGroupPage=yes
OutputBaseFilename={#MyAppName}V{#MyAppVersion}
OutputDir=..\Publish
Compression=lzma
SolidCompression=yes
[Languages]
Name: "default"; MessagesFile: "compiler:Languages\ChineseSimplified.isl"
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
[Files]
Source: "{#MyProgramPath}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs
[Icons]
Name: "{commonprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
[Run]
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent runascurrentuser
[Code]

View File

@ -0,0 +1,34 @@
@echo off
setlocal
set PRJ_PATH=c:\project\QT\GrabBag\GrabBagPrj
if not exist "%PRJ_PATH%\buildwin\CalibView" mkdir "%PRJ_PATH%\buildwin\CalibView"
cd /d "%PRJ_PATH%\buildwin\CalibView"
powershell -Command "Remove-Item * -Recurse -Force"
copy "..\..\build\Tools\CalibView\release\CalibView.exe" .\
:: VzNLSDK DLL
copy "..\..\SDK\Device\VzNLSDK\Windows\x64\Release\VzKernel.dll" .\
copy "..\..\SDK\Device\VzNLSDK\Windows\x64\Release\VzNLDetect.dll" .\
copy "..\..\SDK\Device\VzNLSDK\Windows\x64\Release\VzNLGraphics.dll" .\
copy "..\..\SDK\Device\VzNLSDK\Windows\x64\Release\VzLog.dll" .\
copy "..\..\SDK\Device\VzNLSDK\Windows\x64\Release\libViEyeApi.dll" .\
:: OpenCV DLL
copy "..\..\SDK\OpenCV320\Windows\vc14\Release\opencv_world320.dll" .\
"C:\tools\Qt\5.15.2\msvc2019_64\bin\windeployqt.exe" "CalibView.exe"
cd ..\..
set ISCC_PATH=C:\Program Files (x86)\Inno Setup 6\ISCC.exe
"%ISCC_PATH%" "CalibView.iss"
echo "finish"
endlocal

View File

@ -9,7 +9,7 @@ cd /d "%PRJ_PATH%\buildwin\CloudView"
powershell -Command "Remove-Item * -Recurse -Force"
copy "..\..\build\Tools\CloudView\release\CloudView.exe" .\
copy "..\..\build\Utils\CloudView\release\CloudView.exe" .\
"C:\tools\Qt\5.15.2\msvc2019_64\bin\windeployqt.exe" "CloudView.exe"

View File

@ -41,6 +41,11 @@ public:
*/
HECCalibrationType getCalibType() const;
/**
* @brief (0=EyeToHand, 1=EyeInHand, 2=TCP)
*/
int getCalibTypeIndex() const;
/**
* @brief
*/
@ -61,6 +66,16 @@ public:
*/
HECTCPCalibData getTCPCalibData() const;
/**
* @brief INI
*/
void saveCalibData(const QString& filePath) const;
/**
* @brief INI
*/
void loadCalibData(const QString& filePath);
signals:
/**
* @brief

View File

@ -75,6 +75,16 @@ private slots:
*/
void onLoadResult();
/**
* @brief
*/
void onSaveCalibData();
/**
* @brief
*/
void onLoadCalibData();
/**
* @brief RobotView
*/

View File

@ -3,6 +3,8 @@
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QSettings>
#include <QDateTime>
#include <QGridLayout>
#include <QHeaderView>
#include <QLabel>
@ -157,7 +159,6 @@ QWidget* CalibDataWidget::createEyeToHandGroup()
inputLayout->setColumnStretch(3, 1);
inputLayout->setColumnStretch(4, 0);
inputLayout->setColumnStretch(5, 1);
inputLayout->setColumnStretch(6, 0);
auto createSpinBox = [this]() {
QDoubleSpinBox* sb = new QDoubleSpinBox(this);
@ -177,7 +178,7 @@ QWidget* CalibDataWidget::createEyeToHandGroup()
m_inputEyeZ = createSpinBox();
inputLayout->addWidget(m_inputEyeZ, 0, 5);
// 第2行Robot X/Y/Z + 添加按钮
// 第2行Robot X/Y/Z
inputLayout->addWidget(new QLabel("Robot X:", this), 1, 0);
m_inputRobotX = createSpinBox();
inputLayout->addWidget(m_inputRobotX, 1, 1);
@ -188,6 +189,7 @@ QWidget* CalibDataWidget::createEyeToHandGroup()
m_inputRobotZ = createSpinBox();
inputLayout->addWidget(m_inputRobotZ, 1, 5);
// 第3行添加到表格按钮
m_btnEyeToHandAddInput = new QPushButton("添加到表格", this);
connect(m_btnEyeToHandAddInput, &QPushButton::clicked, this, [this]() {
int row = m_tableEyeToHand->rowCount();
@ -200,7 +202,7 @@ QWidget* CalibDataWidget::createEyeToHandGroup()
m_tableEyeToHand->setItem(row, 5, new QTableWidgetItem(QString::number(m_inputRobotZ->value(), 'f', 3)));
m_tableEyeToHand->scrollToBottom();
});
inputLayout->addWidget(m_btnEyeToHandAddInput, 1, 6);
inputLayout->addWidget(m_btnEyeToHandAddInput, 2, 0, 1, 6);
layout->addWidget(inputGroup);
@ -258,8 +260,12 @@ QWidget* CalibDataWidget::createEyeInHandGroup()
inputLayout->setHorizontalSpacing(2);
inputLayout->setVerticalSpacing(4);
inputLayout->setContentsMargins(4, 4, 4, 4);
for (int c = 0; c <= 11; ++c)
inputLayout->setColumnStretch(c, (c % 2 == 1) ? 1 : 0);
inputLayout->setColumnStretch(0, 0);
inputLayout->setColumnStretch(1, 1);
inputLayout->setColumnStretch(2, 0);
inputLayout->setColumnStretch(3, 1);
inputLayout->setColumnStretch(4, 0);
inputLayout->setColumnStretch(5, 1);
auto createSpinBox = [this]() {
QDoubleSpinBox* sb = new QDoubleSpinBox(this);
@ -268,7 +274,7 @@ QWidget* CalibDataWidget::createEyeInHandGroup()
return sb;
};
// 第1行末端位姿
// 第1行End X/Y/Z
inputLayout->addWidget(new QLabel("End X:", this), 0, 0);
m_inputEndX = createSpinBox();
inputLayout->addWidget(m_inputEndX, 0, 1);
@ -278,27 +284,30 @@ QWidget* CalibDataWidget::createEyeInHandGroup()
inputLayout->addWidget(new QLabel("Z:", this), 0, 4);
m_inputEndZ = createSpinBox();
inputLayout->addWidget(m_inputEndZ, 0, 5);
inputLayout->addWidget(new QLabel("Roll\302\260:", this), 0, 6);
// 第2行End Roll/Pitch/Yaw
inputLayout->addWidget(new QLabel("Roll\302\260:", this), 1, 0);
m_inputEndRoll = createSpinBox();
inputLayout->addWidget(m_inputEndRoll, 0, 7);
inputLayout->addWidget(new QLabel("Pitch\302\260:", this), 0, 8);
inputLayout->addWidget(m_inputEndRoll, 1, 1);
inputLayout->addWidget(new QLabel("Pitch\302\260:", this), 1, 2);
m_inputEndPitch = createSpinBox();
inputLayout->addWidget(m_inputEndPitch, 0, 9);
inputLayout->addWidget(new QLabel("Yaw\302\260:", this), 0, 10);
inputLayout->addWidget(m_inputEndPitch, 1, 3);
inputLayout->addWidget(new QLabel("Yaw\302\260:", this), 1, 4);
m_inputEndYaw = createSpinBox();
inputLayout->addWidget(m_inputEndYaw, 0, 11);
inputLayout->addWidget(m_inputEndYaw, 1, 5);
// 第2行相机观测点 + 添加按钮
inputLayout->addWidget(new QLabel("Cam X:", this), 1, 0);
// 第3行Cam X/Y/Z
inputLayout->addWidget(new QLabel("Cam X:", this), 2, 0);
m_inputCamX = createSpinBox();
inputLayout->addWidget(m_inputCamX, 1, 1);
inputLayout->addWidget(new QLabel("Y:", this), 1, 2);
inputLayout->addWidget(m_inputCamX, 2, 1);
inputLayout->addWidget(new QLabel("Y:", this), 2, 2);
m_inputCamY = createSpinBox();
inputLayout->addWidget(m_inputCamY, 1, 3);
inputLayout->addWidget(new QLabel("Z:", this), 1, 4);
inputLayout->addWidget(m_inputCamY, 2, 3);
inputLayout->addWidget(new QLabel("Z:", this), 2, 4);
m_inputCamZ = createSpinBox();
inputLayout->addWidget(m_inputCamZ, 1, 5);
inputLayout->addWidget(m_inputCamZ, 2, 5);
// 第4行添加到表格按钮
m_btnEyeInHandAddInput = new QPushButton("添加到表格", this);
connect(m_btnEyeInHandAddInput, &QPushButton::clicked, this, [this]() {
int row = m_tableEyeInHand->rowCount();
@ -314,7 +323,7 @@ QWidget* CalibDataWidget::createEyeInHandGroup()
m_tableEyeInHand->setItem(row, 8, new QTableWidgetItem(QString::number(m_inputCamZ->value(), 'f', 3)));
m_tableEyeInHand->scrollToBottom();
});
inputLayout->addWidget(m_btnEyeInHandAddInput, 1, 10, 1, 2);
inputLayout->addWidget(m_btnEyeInHandAddInput, 3, 0, 1, 6);
layout->addWidget(inputGroup);
@ -444,6 +453,11 @@ HECCalibrationType CalibDataWidget::getCalibType() const
HECCalibrationType::EyeToHand : HECCalibrationType::EyeInHand;
}
int CalibDataWidget::getCalibTypeIndex() const
{
return m_cbCalibType->currentIndex();
}
void CalibDataWidget::clearAll()
{
m_tableEyeToHand->setRowCount(0);
@ -514,7 +528,6 @@ QWidget* CalibDataWidget::createTCPCalibGroup()
inputLayout->setColumnStretch(3, 1);
inputLayout->setColumnStretch(4, 0);
inputLayout->setColumnStretch(5, 1);
inputLayout->setColumnStretch(6, 0);
auto createSpinBox = [this]() {
QDoubleSpinBox* sb = new QDoubleSpinBox(this);
@ -534,7 +547,7 @@ QWidget* CalibDataWidget::createTCPCalibGroup()
m_inputTcpZ = createSpinBox();
inputLayout->addWidget(m_inputTcpZ, 0, 5);
// 第2行Roll/Pitch/Yaw + 添加按钮
// 第2行Roll/Pitch/Yaw
inputLayout->addWidget(new QLabel("Roll\302\260:", this), 1, 0);
m_inputTcpRx = createSpinBox();
inputLayout->addWidget(m_inputTcpRx, 1, 1);
@ -545,6 +558,7 @@ QWidget* CalibDataWidget::createTCPCalibGroup()
m_inputTcpRz = createSpinBox();
inputLayout->addWidget(m_inputTcpRz, 1, 5);
// 第3行添加到表格按钮
m_btnTcpAddInput = new QPushButton("添加到表格", this);
connect(m_btnTcpAddInput, &QPushButton::clicked, this, [this]() {
int row = m_tableTCP->rowCount();
@ -557,7 +571,7 @@ QWidget* CalibDataWidget::createTCPCalibGroup()
m_tableTCP->setItem(row, 5, new QTableWidgetItem(QString::number(m_inputTcpRz->value(), 'f', 3)));
m_tableTCP->scrollToBottom();
});
inputLayout->addWidget(m_btnTcpAddInput, 1, 6);
inputLayout->addWidget(m_btnTcpAddInput, 2, 0, 1, 6);
layout->addWidget(inputGroup);
@ -699,3 +713,151 @@ void CalibDataWidget::setCameraInput(double x, double y, double z,
}
// TCP 模式不需要相机输入
}
void CalibDataWidget::saveCalibData(const QString& filePath) const
{
QSettings ini(filePath, QSettings::IniFormat);
ini.setIniCodec("UTF-8");
int mode = m_cbCalibType->currentIndex();
// [CommInfo] 通用信息
ini.beginGroup("CommInfo");
ini.setValue("eCalibType", mode); // 0=EyeToHand, 1=EyeInHand, 2=TCP
ini.setValue("sCalibTime", QDateTime::currentDateTime().toString("yyyy-MM-dd-HH-mm-ss"));
if (mode == 0) {
int count = m_tableEyeToHand->rowCount();
ini.setValue("nPointCount", count);
ini.endGroup();
for (int row = 0; row < count; ++row) {
ini.beginGroup(QString("CalibPointInfo_%1").arg(row));
ini.setValue("dEyeX", m_tableEyeToHand->item(row, 0) ? m_tableEyeToHand->item(row, 0)->text() : "0");
ini.setValue("dEyeY", m_tableEyeToHand->item(row, 1) ? m_tableEyeToHand->item(row, 1)->text() : "0");
ini.setValue("dEyeZ", m_tableEyeToHand->item(row, 2) ? m_tableEyeToHand->item(row, 2)->text() : "0");
ini.setValue("dRobotX", m_tableEyeToHand->item(row, 3) ? m_tableEyeToHand->item(row, 3)->text() : "0");
ini.setValue("dRobotY", m_tableEyeToHand->item(row, 4) ? m_tableEyeToHand->item(row, 4)->text() : "0");
ini.setValue("dRobotZ", m_tableEyeToHand->item(row, 5) ? m_tableEyeToHand->item(row, 5)->text() : "0");
ini.endGroup();
}
} else if (mode == 1) {
int count = m_tableEyeInHand->rowCount();
ini.setValue("nPointCount", count);
ini.endGroup();
for (int row = 0; row < count; ++row) {
ini.beginGroup(QString("CalibPointInfo_%1").arg(row));
ini.setValue("dEndX", m_tableEyeInHand->item(row, 0) ? m_tableEyeInHand->item(row, 0)->text() : "0");
ini.setValue("dEndY", m_tableEyeInHand->item(row, 1) ? m_tableEyeInHand->item(row, 1)->text() : "0");
ini.setValue("dEndZ", m_tableEyeInHand->item(row, 2) ? m_tableEyeInHand->item(row, 2)->text() : "0");
ini.setValue("dEndRoll", m_tableEyeInHand->item(row, 3) ? m_tableEyeInHand->item(row, 3)->text() : "0");
ini.setValue("dEndPitch", m_tableEyeInHand->item(row, 4) ? m_tableEyeInHand->item(row, 4)->text() : "0");
ini.setValue("dEndYaw", m_tableEyeInHand->item(row, 5) ? m_tableEyeInHand->item(row, 5)->text() : "0");
ini.setValue("dCamX", m_tableEyeInHand->item(row, 6) ? m_tableEyeInHand->item(row, 6)->text() : "0");
ini.setValue("dCamY", m_tableEyeInHand->item(row, 7) ? m_tableEyeInHand->item(row, 7)->text() : "0");
ini.setValue("dCamZ", m_tableEyeInHand->item(row, 8) ? m_tableEyeInHand->item(row, 8)->text() : "0");
ini.endGroup();
}
} else {
int count = m_tableTCP->rowCount();
ini.setValue("nPoseCount", count);
ini.setValue("eCalibMode", m_tcpModeCombo->currentIndex());
ini.setValue("eEulerOrder", m_tcpEulerOrderCombo->currentIndex());
ini.setValue("nRefPoseIndex", m_tcpRefPoseIndex->value());
ini.setValue("dWorldRx", m_tcpWorldRx->value());
ini.setValue("dWorldRy", m_tcpWorldRy->value());
ini.setValue("dWorldRz", m_tcpWorldRz->value());
ini.endGroup();
for (int row = 0; row < count; ++row) {
ini.beginGroup(QString("CalibPoseInfo_%1").arg(row));
ini.setValue("dX", m_tableTCP->item(row, 0) ? m_tableTCP->item(row, 0)->text() : "0");
ini.setValue("dY", m_tableTCP->item(row, 1) ? m_tableTCP->item(row, 1)->text() : "0");
ini.setValue("dZ", m_tableTCP->item(row, 2) ? m_tableTCP->item(row, 2)->text() : "0");
ini.setValue("dRx", m_tableTCP->item(row, 3) ? m_tableTCP->item(row, 3)->text() : "0");
ini.setValue("dRy", m_tableTCP->item(row, 4) ? m_tableTCP->item(row, 4)->text() : "0");
ini.setValue("dRz", m_tableTCP->item(row, 5) ? m_tableTCP->item(row, 5)->text() : "0");
ini.endGroup();
}
}
}
void CalibDataWidget::loadCalibData(const QString& filePath)
{
QSettings ini(filePath, QSettings::IniFormat);
ini.setIniCodec("UTF-8");
ini.beginGroup("CommInfo");
int calibType = ini.value("eCalibType", -1).toInt();
if (calibType == 0) {
// Eye-To-Hand
int count = ini.value("nPointCount", 0).toInt();
ini.endGroup();
m_cbCalibType->setCurrentIndex(0);
m_tableEyeToHand->setRowCount(0);
for (int i = 0; i < count; ++i) {
ini.beginGroup(QString("CalibPointInfo_%1").arg(i));
int row = m_tableEyeToHand->rowCount();
m_tableEyeToHand->insertRow(row);
m_tableEyeToHand->setItem(row, 0, new QTableWidgetItem(ini.value("dEyeX", "0").toString()));
m_tableEyeToHand->setItem(row, 1, new QTableWidgetItem(ini.value("dEyeY", "0").toString()));
m_tableEyeToHand->setItem(row, 2, new QTableWidgetItem(ini.value("dEyeZ", "0").toString()));
m_tableEyeToHand->setItem(row, 3, new QTableWidgetItem(ini.value("dRobotX", "0").toString()));
m_tableEyeToHand->setItem(row, 4, new QTableWidgetItem(ini.value("dRobotY", "0").toString()));
m_tableEyeToHand->setItem(row, 5, new QTableWidgetItem(ini.value("dRobotZ", "0").toString()));
ini.endGroup();
}
} else if (calibType == 1) {
// Eye-In-Hand
int count = ini.value("nPointCount", 0).toInt();
ini.endGroup();
m_cbCalibType->setCurrentIndex(1);
m_tableEyeInHand->setRowCount(0);
for (int i = 0; i < count; ++i) {
ini.beginGroup(QString("CalibPointInfo_%1").arg(i));
int row = m_tableEyeInHand->rowCount();
m_tableEyeInHand->insertRow(row);
m_tableEyeInHand->setItem(row, 0, new QTableWidgetItem(ini.value("dEndX", "0").toString()));
m_tableEyeInHand->setItem(row, 1, new QTableWidgetItem(ini.value("dEndY", "0").toString()));
m_tableEyeInHand->setItem(row, 2, new QTableWidgetItem(ini.value("dEndZ", "0").toString()));
m_tableEyeInHand->setItem(row, 3, new QTableWidgetItem(ini.value("dEndRoll", "0").toString()));
m_tableEyeInHand->setItem(row, 4, new QTableWidgetItem(ini.value("dEndPitch", "0").toString()));
m_tableEyeInHand->setItem(row, 5, new QTableWidgetItem(ini.value("dEndYaw", "0").toString()));
m_tableEyeInHand->setItem(row, 6, new QTableWidgetItem(ini.value("dCamX", "0").toString()));
m_tableEyeInHand->setItem(row, 7, new QTableWidgetItem(ini.value("dCamY", "0").toString()));
m_tableEyeInHand->setItem(row, 8, new QTableWidgetItem(ini.value("dCamZ", "0").toString()));
ini.endGroup();
}
} else if (calibType == 2) {
// TCP
int count = ini.value("nPoseCount", 0).toInt();
m_tcpModeCombo->setCurrentIndex(ini.value("eCalibMode", 0).toInt());
m_tcpEulerOrderCombo->setCurrentIndex(ini.value("eEulerOrder", 5).toInt());
m_tcpRefPoseIndex->setValue(ini.value("nRefPoseIndex", 0).toInt());
m_tcpWorldRx->setValue(ini.value("dWorldRx", 0).toDouble());
m_tcpWorldRy->setValue(ini.value("dWorldRy", 0).toDouble());
m_tcpWorldRz->setValue(ini.value("dWorldRz", 0).toDouble());
ini.endGroup();
m_cbCalibType->setCurrentIndex(2);
m_tableTCP->setRowCount(0);
for (int i = 0; i < count; ++i) {
ini.beginGroup(QString("CalibPoseInfo_%1").arg(i));
int row = m_tableTCP->rowCount();
m_tableTCP->insertRow(row);
m_tableTCP->setItem(row, 0, new QTableWidgetItem(ini.value("dX", "0").toString()));
m_tableTCP->setItem(row, 1, new QTableWidgetItem(ini.value("dY", "0").toString()));
m_tableTCP->setItem(row, 2, new QTableWidgetItem(ini.value("dZ", "0").toString()));
m_tableTCP->setItem(row, 3, new QTableWidgetItem(ini.value("dRx", "0").toString()));
m_tableTCP->setItem(row, 4, new QTableWidgetItem(ini.value("dRy", "0").toString()));
m_tableTCP->setItem(row, 5, new QTableWidgetItem(ini.value("dRz", "0").toString()));
ini.endGroup();
}
} else {
ini.endGroup();
}
}

View File

@ -16,11 +16,10 @@
#include <QFileDialog>
#include <QFile>
#include <QTextStream>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QLabel>
#include <QScrollArea>
#include <QSettings>
#include <QDateTime>
CalibViewMainWindow::CalibViewMainWindow(QWidget* parent)
: QMainWindow(parent)
@ -49,7 +48,7 @@ CalibViewMainWindow::CalibViewMainWindow(QWidget* parent)
SpinBoxPasteHelper::install(this);
setWindowTitle("CalibView - 手眼标定测试工具");
resize(1000, 600);
resize(1200, 650);
updateStatusBar("就绪");
}
@ -213,6 +212,16 @@ void CalibViewMainWindow::createMenuBar()
fileMenu->addSeparator();
QAction* actSaveData = fileMenu->addAction("保存标定数据(&D)");
actSaveData->setShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_S));
connect(actSaveData, &QAction::triggered, this, &CalibViewMainWindow::onSaveCalibData);
QAction* actLoadData = fileMenu->addAction("加载标定数据(&A)");
actLoadData->setShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_O));
connect(actLoadData, &QAction::triggered, this, &CalibViewMainWindow::onLoadCalibData);
fileMenu->addSeparator();
QAction* actExit = fileMenu->addAction("退出(&X)");
actExit->setShortcut(QKeySequence::Quit);
connect(actExit, &QAction::triggered, this, &QMainWindow::close);
@ -507,112 +516,116 @@ void CalibViewMainWindow::onSaveResult()
return;
}
QString defaultName = QString("CalibResult_%1.ini")
.arg(QDateTime::currentDateTime().toString("yyyyMMdd_HHmmss"));
QString fileName = QFileDialog::getSaveFileName(this,
"保存标定结果", "", "JSON文件 (*.json)");
"保存标定结果", defaultName, "INI文件 (*.ini)");
if (fileName.isEmpty()) {
return;
}
QJsonObject root;
QSettings ini(fileName, QSettings::IniFormat);
ini.setIniCodec("UTF-8");
// 保存旋转矩阵
QJsonArray rotArray;
for (int i = 0; i < 9; ++i) {
rotArray.append(m_currentResult.R.data[i]);
}
root["rotation"] = rotArray;
// [CommInfo]
ini.beginGroup("CommInfo");
ini.setValue("sCalibTime", QDateTime::currentDateTime().toString("yyyy-MM-dd-HH-mm-ss"));
ini.setValue("dError", m_currentResult.error);
ini.endGroup();
// 保存平移向量
QJsonArray transArray;
for (int i = 0; i < 3; ++i) {
transArray.append(m_currentResult.T.data[i]);
}
root["translation"] = transArray;
// [CalibMatrixInfo_0] - 4x4 齐次矩阵
ini.beginGroup("CalibMatrixInfo_0");
// 第0行: R[0,0] R[0,1] R[0,2] Tx
ini.setValue("dCalibMatrix_0", m_currentResult.R.data[0]);
ini.setValue("dCalibMatrix_1", m_currentResult.R.data[1]);
ini.setValue("dCalibMatrix_2", m_currentResult.R.data[2]);
ini.setValue("dCalibMatrix_3", m_currentResult.T.data[0]);
// 第1行: R[1,0] R[1,1] R[1,2] Ty
ini.setValue("dCalibMatrix_4", m_currentResult.R.data[3]);
ini.setValue("dCalibMatrix_5", m_currentResult.R.data[4]);
ini.setValue("dCalibMatrix_6", m_currentResult.R.data[5]);
ini.setValue("dCalibMatrix_7", m_currentResult.T.data[1]);
// 第2行: R[2,0] R[2,1] R[2,2] Tz
ini.setValue("dCalibMatrix_8", m_currentResult.R.data[6]);
ini.setValue("dCalibMatrix_9", m_currentResult.R.data[7]);
ini.setValue("dCalibMatrix_10", m_currentResult.R.data[8]);
ini.setValue("dCalibMatrix_11", m_currentResult.T.data[2]);
// 第3行: 0 0 0 1
ini.setValue("dCalibMatrix_12", 0.0);
ini.setValue("dCalibMatrix_13", 0.0);
ini.setValue("dCalibMatrix_14", 0.0);
ini.setValue("dCalibMatrix_15", 1.0);
ini.endGroup();
// 保存质心
QJsonObject centerEye;
centerEye["x"] = m_currentResult.centerEye.x;
centerEye["y"] = m_currentResult.centerEye.y;
centerEye["z"] = m_currentResult.centerEye.z;
root["centerEye"] = centerEye;
// [CenterInfo] - 质心
ini.beginGroup("CenterInfo");
ini.setValue("dCenterEyeX", m_currentResult.centerEye.x);
ini.setValue("dCenterEyeY", m_currentResult.centerEye.y);
ini.setValue("dCenterEyeZ", m_currentResult.centerEye.z);
ini.setValue("dCenterRobotX", m_currentResult.centerRobot.x);
ini.setValue("dCenterRobotY", m_currentResult.centerRobot.y);
ini.setValue("dCenterRobotZ", m_currentResult.centerRobot.z);
ini.endGroup();
QJsonObject centerRobot;
centerRobot["x"] = m_currentResult.centerRobot.x;
centerRobot["y"] = m_currentResult.centerRobot.y;
centerRobot["z"] = m_currentResult.centerRobot.z;
root["centerRobot"] = centerRobot;
// 保存误差
root["error"] = m_currentResult.error;
QFile file(fileName);
if (file.open(QIODevice::WriteOnly)) {
QJsonDocument doc(root);
file.write(doc.toJson(QJsonDocument::Indented));
file.close();
appendLog(QString("结果已保存到: %1").arg(fileName));
updateStatusBar("结果已保存");
} else {
QMessageBox::critical(this, "错误", "无法保存文件");
}
}
void CalibViewMainWindow::onLoadResult()
{
QString fileName = QFileDialog::getOpenFileName(this,
"加载标定结果", "", "JSON文件 (*.json)");
"加载标定结果", "", "INI文件 (*.ini)");
if (fileName.isEmpty()) {
return;
}
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly)) {
QMessageBox::critical(this, "错误", "无法打开文件");
QSettings ini(fileName, QSettings::IniFormat);
ini.setIniCodec("UTF-8");
// 校验是否包含标定矩阵
ini.beginGroup("CalibMatrixInfo_0");
bool hasMatrix = ini.contains("dCalibMatrix_0");
if (!hasMatrix) {
ini.endGroup();
QMessageBox::warning(this, "警告", "该文件不包含有效的标定结果");
return;
}
QByteArray data = file.readAll();
file.close();
QJsonDocument doc = QJsonDocument::fromJson(data);
if (doc.isNull()) {
QMessageBox::critical(this, "错误", "无效的JSON文件");
return;
}
QJsonObject root = doc.object();
// 加载旋转矩阵
QJsonArray rotArray = root["rotation"].toArray();
if (rotArray.size() == 9) {
for (int i = 0; i < 9; ++i) {
m_currentResult.R.data[i] = rotArray[i].toDouble();
}
}
// 加载平移向量
QJsonArray transArray = root["translation"].toArray();
if (transArray.size() == 3) {
for (int i = 0; i < 3; ++i) {
m_currentResult.T.data[i] = transArray[i].toDouble();
}
}
// 加载质心
QJsonObject centerEye = root["centerEye"].toObject();
m_currentResult.centerEye.x = centerEye["x"].toDouble();
m_currentResult.centerEye.y = centerEye["y"].toDouble();
m_currentResult.centerEye.z = centerEye["z"].toDouble();
QJsonObject centerRobot = root["centerRobot"].toObject();
m_currentResult.centerRobot.x = centerRobot["x"].toDouble();
m_currentResult.centerRobot.y = centerRobot["y"].toDouble();
m_currentResult.centerRobot.z = centerRobot["z"].toDouble();
// 加载 4x4 齐次矩阵 → 拆分为 R[3x3] + T[3x1]
// 第0行
m_currentResult.R.data[0] = ini.value("dCalibMatrix_0", 0).toDouble();
m_currentResult.R.data[1] = ini.value("dCalibMatrix_1", 0).toDouble();
m_currentResult.R.data[2] = ini.value("dCalibMatrix_2", 0).toDouble();
m_currentResult.T.data[0] = ini.value("dCalibMatrix_3", 0).toDouble();
// 第1行
m_currentResult.R.data[3] = ini.value("dCalibMatrix_4", 0).toDouble();
m_currentResult.R.data[4] = ini.value("dCalibMatrix_5", 0).toDouble();
m_currentResult.R.data[5] = ini.value("dCalibMatrix_6", 0).toDouble();
m_currentResult.T.data[1] = ini.value("dCalibMatrix_7", 0).toDouble();
// 第2行
m_currentResult.R.data[6] = ini.value("dCalibMatrix_8", 0).toDouble();
m_currentResult.R.data[7] = ini.value("dCalibMatrix_9", 0).toDouble();
m_currentResult.R.data[8] = ini.value("dCalibMatrix_10", 0).toDouble();
m_currentResult.T.data[2] = ini.value("dCalibMatrix_11", 0).toDouble();
ini.endGroup();
// 加载误差
m_currentResult.error = root["error"].toDouble();
ini.beginGroup("CommInfo");
m_currentResult.error = ini.value("dError", 0).toDouble();
ini.endGroup();
// 加载质心
ini.beginGroup("CenterInfo");
m_currentResult.centerEye.x = ini.value("dCenterEyeX", 0).toDouble();
m_currentResult.centerEye.y = ini.value("dCenterEyeY", 0).toDouble();
m_currentResult.centerEye.z = ini.value("dCenterEyeZ", 0).toDouble();
m_currentResult.centerRobot.x = ini.value("dCenterRobotX", 0).toDouble();
m_currentResult.centerRobot.y = ini.value("dCenterRobotY", 0).toDouble();
m_currentResult.centerRobot.z = ini.value("dCenterRobotZ", 0).toDouble();
ini.endGroup();
m_hasResult = true;
m_resultWidget->showCalibResult(m_currentResult);
@ -674,3 +687,55 @@ void CalibViewMainWindow::onChessboardDetected(
.arg(x, 0, 'f', 2).arg(y, 0, 'f', 2).arg(z, 0, 'f', 2)
.arg(rx, 0, 'f', 2).arg(ry, 0, 'f', 2).arg(rz, 0, 'f', 2));
}
void CalibViewMainWindow::onSaveCalibData()
{
// 根据当前模式生成默认文件名
static const char* typeNames[] = { "EyeToHand", "EyeInHand", "TCP" };
int typeIndex = m_dataWidget->getCalibTypeIndex();
QString typeName = typeNames[typeIndex];
QString defaultName = QString("CalibData_%1_%2.ini")
.arg(typeName)
.arg(QDateTime::currentDateTime().toString("yyyyMMdd_HHmmss"));
QString fileName = QFileDialog::getSaveFileName(this,
"保存标定数据", defaultName, "INI文件 (*.ini)");
if (fileName.isEmpty()) {
return;
}
m_dataWidget->saveCalibData(fileName);
appendLog(QString("标定数据已保存到: %1").arg(fileName));
updateStatusBar("标定数据已保存");
}
void CalibViewMainWindow::onLoadCalibData()
{
QString fileName = QFileDialog::getOpenFileName(this,
"加载标定数据", "", "INI文件 (*.ini)");
if (fileName.isEmpty()) {
return;
}
// 校验文件是否包含标定数据
QSettings check(fileName, QSettings::IniFormat);
check.beginGroup("CommInfo");
int calibType = check.value("eCalibType", -1).toInt();
QString saveTime = check.value("sCalibTime").toString();
check.endGroup();
if (calibType < 0 || calibType > 2) {
QMessageBox::warning(this, "警告", "该文件不包含有效的标定数据");
return;
}
m_dataWidget->loadCalibData(fileName);
static const char* typeNames[] = { "EyeToHand", "EyeInHand", "TCP" };
appendLog(QString("已加载标定数据: %1 (保存时间: %2, 模式: %3)")
.arg(fileName).arg(saveTime).arg(typeNames[calibType]));
updateStatusBar("标定数据已加载");
}

2
Utils

@ -1 +1 @@
Subproject commit 7071da62403ea703981ffe77fff948266b66fdf3
Subproject commit 8c8dd5a5a26a316dbcd523eddfb8bd56727c62d2