手眼标定完善
This commit is contained in:
parent
11c615d0c9
commit
ce4bd9348a
46
GrabBagPrj/CalibView.iss
Normal file
46
GrabBagPrj/CalibView.iss
Normal 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]
|
||||
34
GrabBagPrj/pkg_calibview.bat
Normal file
34
GrabBagPrj/pkg_calibview.bat
Normal 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
|
||||
@ -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"
|
||||
|
||||
|
||||
@ -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 标定模式改变信号
|
||||
|
||||
@ -75,6 +75,16 @@ private slots:
|
||||
*/
|
||||
void onLoadResult();
|
||||
|
||||
/**
|
||||
* @brief 保存标定数据(表格中的坐标数据)
|
||||
*/
|
||||
void onSaveCalibData();
|
||||
|
||||
/**
|
||||
* @brief 加载标定数据(表格中的坐标数据)
|
||||
*/
|
||||
void onLoadCalibData();
|
||||
|
||||
/**
|
||||
* @brief 打开 RobotView 窗口
|
||||
*/
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
2
Utils
@ -1 +1 @@
|
||||
Subproject commit 7071da62403ea703981ffe77fff948266b66fdf3
|
||||
Subproject commit 8c8dd5a5a26a316dbcd523eddfb8bd56727c62d2
|
||||
Loading…
x
Reference in New Issue
Block a user