454 lines
16 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "dialogalgoarg.h"
#include "ui_dialogalgoarg.h"
#include "HandEyeCalibWidget.h"
#include "NetworkConfigWidget.h"
#include "PathManager.h"
#include "TireHolePosePresenter.h"
#include "StyledMessageBox.h"
#include <QDoubleValidator>
#include <QGroupBox>
#include <QIntValidator>
#include <QLabel>
#include <QLineEdit>
#include <algorithm>
#include <cmath>
#include <cstring>
namespace {
bool IsIdentityMatrix(const double matrix[16])
{
for (int i = 0; i < 16; ++i) {
const double expected = (i / 4 == i % 4) ? 1.0 : 0.0;
if (std::fabs(matrix[i] - expected) > 1e-9) {
return false;
}
}
return true;
}
} // namespace
DialogAlgoArg::DialogAlgoArg(QWidget *parent)
: QDialog(parent)
, ui(new Ui::DialogAlgoArg)
{
ui->setupUi(this);
setWindowTitle(QStringLiteral("轮胎孔定位 - 算法参数设置"));
initNumericEditors();
initTireParamEditors();
initHandEyeCalibTab();
initNetworkConfigTab();
}
DialogAlgoArg::~DialogAlgoArg()
{
delete ui;
}
void DialogAlgoArg::SetPresenter(TireHolePosePresenter* presenter)
{
m_presenter = presenter;
loadParams();
loadHandEyeCalibConfig();
loadNetworkConfig();
}
void DialogAlgoArg::initNumericEditors()
{
auto setupDoubleEditor = [](QLineEdit* edit, double min, double max, int decimals) {
auto* validator = new QDoubleValidator(min, max, decimals, edit);
validator->setNotation(QDoubleValidator::StandardNotation);
edit->setValidator(validator);
};
// 角点检测的 6 个参数
setupDoubleEditor(ui->spinCornerTh, 0.0, 180.0, 6);
setupDoubleEditor(ui->spinScale, 0.0, 200.0, 6);
setupDoubleEditor(ui->spinMinEndingGap, 0.0, 100.0, 6);
setupDoubleEditor(ui->spinMinEndingGapZ, 0.0, 100.0, 6);
setupDoubleEditor(ui->spinJumpCornerTh1, 0.0, 100.0, 6);
setupDoubleEditor(ui->spinJumpCornerTh2, 0.0, 180.0, 6);
}
void DialogAlgoArg::initTireParamEditors()
{
// 在 tabWidget 中新增独立的"轮胎参数"Tab 页
if (!ui->tabWidget || m_editTireDiameter) {
return;
}
// 创建 Tab 页容器
auto* tabTire = new QWidget();
tabTire->setObjectName(QStringLiteral("tabTire"));
tabTire->setStyleSheet(ui->tabCorner->styleSheet());
// 轮胎参数 GroupBox
auto* groupBoxTire = new QGroupBox(QStringLiteral("轮胎参数 (WD_tireParam)"), tabTire);
groupBoxTire->setGeometry(10, 20, 671, 200);
QFont groupFont = ui->groupBox_corner->font();
groupBoxTire->setFont(groupFont);
// 轮胎直径
auto* labelDiameter = new QLabel(QStringLiteral("轮胎直径(diameter):"), groupBoxTire);
labelDiameter->setGeometry(30, 50, 220, 32);
labelDiameter->setFont(groupFont);
m_editTireDiameter = new QLineEdit(groupBoxTire);
m_editTireDiameter->setObjectName(QStringLiteral("spinTireDiameter"));
m_editTireDiameter->setGeometry(280, 50, 200, 35);
m_editTireDiameter->setFont(groupFont);
m_editTireDiameter->setText(QStringLiteral("500"));
// 轮胎宽度
auto* labelThickness = new QLabel(QStringLiteral("轮胎宽度(thickness):"), groupBoxTire);
labelThickness->setGeometry(30, 110, 220, 32);
labelThickness->setFont(groupFont);
m_editTireThickness = new QLineEdit(groupBoxTire);
m_editTireThickness->setObjectName(QStringLiteral("spinTireThickness"));
m_editTireThickness->setGeometry(280, 110, 200, 35);
m_editTireThickness->setFont(groupFont);
m_editTireThickness->setText(QStringLiteral("140"));
// 设置验证器
auto setupDouble = [](QLineEdit* edit, double min, double max, int decimals) {
auto* validator = new QDoubleValidator(min, max, decimals, edit);
validator->setNotation(QDoubleValidator::StandardNotation);
edit->setValidator(validator);
};
setupDouble(m_editTireDiameter, 0.0, 10000.0, 6);
setupDouble(m_editTireThickness, 0.0, 10000.0, 6);
// 在"角点检测"Tab 之后插入"轮胎参数"Tab索引 1
ui->tabWidget->insertTab(1, tabTire, QStringLiteral("轮胎参数"));
}
void DialogAlgoArg::initHandEyeCalibTab()
{
if (!ui || !ui->verticalLayout_handEyeCalibHost || m_handEyeCalibWidget) {
return;
}
m_handEyeCalibWidget = new HandEyeCalibWidget(this);
m_handEyeCalibWidget->setMatrixEditable(true);
m_handEyeCalibWidget->setExtrinsicControlsVisible(true);
m_handEyeCalibWidget->setDefaultFilePath(PathManager::GetInstance().GetAppConfigDirectory());
ui->verticalLayout_handEyeCalibHost->addWidget(m_handEyeCalibWidget);
connect(m_handEyeCalibWidget, &HandEyeCalibWidget::calibMatrixLoaded,
this, &DialogAlgoArg::onCalibMatrixLoaded);
connect(m_handEyeCalibWidget, &HandEyeCalibWidget::saveCalibRequested,
this, &DialogAlgoArg::onSaveCalibRequested);
}
void DialogAlgoArg::initNetworkConfigTab()
{
if (!ui || !ui->verticalLayout_networkConfigHost || m_networkConfigWidget) {
return;
}
m_networkConfigWidget = new NetworkConfigWidget(false, true, this);
// 欧拉角顺序、方向向量调整、长边对应轴已迁移到手眼标定页
m_networkConfigWidget->setExtrinsicControlsVisible(false);
ui->verticalLayout_networkConfigHost->addWidget(m_networkConfigWidget);
}
void DialogAlgoArg::setDoubleEditorText(QLineEdit* edit, double value)
{
if (edit) {
edit->setText(QString::number(value, 'g', 12));
}
}
void DialogAlgoArg::setIntEditorText(QLineEdit* edit, int value)
{
if (edit) {
edit->setText(QString::number(value));
}
}
bool DialogAlgoArg::tryGetDouble(QLineEdit* edit, const QString& label, double& value)
{
bool ok = false;
value = edit->text().trimmed().toDouble(&ok);
if (!ok) {
StyledMessageBox::warning(this, QStringLiteral("错误"),
QStringLiteral("\"%1\" 输入的数值无效。").arg(label));
edit->setFocus();
edit->selectAll();
return false;
}
return true;
}
bool DialogAlgoArg::tryGetInt(QLineEdit* edit, const QString& label, int& value)
{
bool ok = false;
value = edit->text().trimmed().toInt(&ok);
if (!ok) {
StyledMessageBox::warning(this, QStringLiteral("错误"),
QStringLiteral("\"%1\" 输入的整数无效。").arg(label));
edit->setFocus();
edit->selectAll();
return false;
}
return true;
}
void DialogAlgoArg::loadParams()
{
if (!m_presenter || !m_presenter->GetConfigManager()) {
return;
}
const ConfigResult configResult = m_presenter->GetConfigManager()->GetConfigResult();
const VrCornerParam corner = configResult.algorithmParams.cornerParam;
const VrTireParam tire = configResult.algorithmParams.tireParam;
setDoubleEditorText(ui->spinCornerTh, corner.cornerTh);
setDoubleEditorText(ui->spinScale, corner.scale);
setDoubleEditorText(ui->spinMinEndingGap, corner.minEndingGap);
setDoubleEditorText(ui->spinMinEndingGapZ, corner.minEndingGap_z);
setDoubleEditorText(ui->spinJumpCornerTh1, corner.jumpCornerTh_1);
setDoubleEditorText(ui->spinJumpCornerTh2, corner.jumpCornerTh_2);
setDoubleEditorText(m_editTireDiameter, tire.diameter);
setDoubleEditorText(m_editTireThickness, tire.thickness);
}
void DialogAlgoArg::loadHandEyeCalibConfig()
{
if (!m_presenter || !m_handEyeCalibWidget || !m_presenter->GetConfigManager()) {
return;
}
const ConfigResult configResult = m_presenter->GetConfigManager()->GetConfigResult();
QVector<HandEyeCalibCameraInfo> cameraInfos;
if (configResult.cameraList.empty()) {
HandEyeCalibCameraInfo defaultCam;
defaultCam.cameraIndex = 1;
defaultCam.displayName = QStringLiteral("相机 1");
cameraInfos.append(defaultCam);
} else {
for (size_t i = 0; i < configResult.cameraList.size(); ++i) {
HandEyeCalibCameraInfo info;
info.cameraIndex = static_cast<int>(i + 1);
info.displayName = QString::fromStdString(configResult.cameraList[i].name);
cameraInfos.append(info);
}
}
m_handEyeCalibWidget->setCameraList(cameraInfos);
// 从 handEyeCalibMatrixList 加载每台相机的手眼标定矩阵(类比 WorkpieceHole 风格)
for (const auto& info : cameraInfos) {
const VrHandEyeCalibMatrix* savedMatrix =
configResult.FindHandEyeMatrix(info.cameraIndex);
if (savedMatrix) {
m_handEyeCalibWidget->setCalibData(info.cameraIndex,
savedMatrix->matrix,
!IsIdentityMatrix(savedMatrix->matrix));
m_handEyeCalibWidget->setExtrinsicData(info.cameraIndex,
savedMatrix->eulerOrder,
savedMatrix->rotX,
savedMatrix->rotY,
savedMatrix->rotZ,
savedMatrix->approachOffset,
savedMatrix->offsetX,
savedMatrix->offsetY,
savedMatrix->offsetZ);
} else {
// 未配置时回退到 GetClibMatrixPresenter 会返回单位阵)
const CalibMatrix calibMatrix = m_presenter->GetClibMatrix(info.cameraIndex - 1);
m_handEyeCalibWidget->setCalibData(info.cameraIndex,
calibMatrix.clibMatrix,
!IsIdentityMatrix(calibMatrix.clibMatrix));
m_handEyeCalibWidget->setExtrinsicData(info.cameraIndex, 11, 0.0, 0.0, 0.0, 0.0);
}
}
}
void DialogAlgoArg::loadNetworkConfig()
{
if (!m_presenter || !m_presenter->GetConfigManager() || !m_networkConfigWidget) {
return;
}
const ConfigResult configResult = m_presenter->GetConfigManager()->GetConfigResult();
NetworkConfigData netConfig;
netConfig.tcpServerPort = configResult.tcpPort;
netConfig.poseOutputOrder = configResult.poseOutputOrder;
netConfig.byteOrder = configResult.byteOrder;
m_networkConfigWidget->setConfig(netConfig);
}
bool DialogAlgoArg::saveParams()
{
if (!m_presenter || !m_presenter->GetConfigManager()) {
return false;
}
SystemConfig systemConfig = m_presenter->GetConfigManager()->GetConfig();
// —— 读取共享算法参数(类比 WorkpieceHole 风格:直接写入 algorithmParams——
VrCornerParam& corner = systemConfig.configResult.algorithmParams.cornerParam;
if (!tryGetDouble(ui->spinCornerTh, QStringLiteral("拐角阈值"), corner.cornerTh)) return false;
if (!tryGetDouble(ui->spinScale, QStringLiteral("窗口比例因子"), corner.scale)) return false;
if (!tryGetDouble(ui->spinMinEndingGap, QStringLiteral("Y方向最小结束间隙"), corner.minEndingGap)) return false;
if (!tryGetDouble(ui->spinMinEndingGapZ, QStringLiteral("Z方向最小结束间隙"), corner.minEndingGap_z)) return false;
if (!tryGetDouble(ui->spinJumpCornerTh1, QStringLiteral("跳变拐角阈值1"), corner.jumpCornerTh_1)) return false;
if (!tryGetDouble(ui->spinJumpCornerTh2, QStringLiteral("跳变拐角阈值2"), corner.jumpCornerTh_2)) return false;
VrTireParam& tire = systemConfig.configResult.algorithmParams.tireParam;
if (!tryGetDouble(m_editTireDiameter, QStringLiteral("轮胎直径"), tire.diameter)) return false;
if (!tryGetDouble(m_editTireThickness, QStringLiteral("轮胎宽度"), tire.thickness)) return false;
// —— 保存手眼标定矩阵到 handEyeCalibMatrixList类比 WorkpieceHole 风格)——
if (m_handEyeCalibWidget) {
const int cameraCount = std::max(1, static_cast<int>(systemConfig.configResult.cameraList.size()));
// 保证每台相机的手眼标定槽位都存在
for (int camIdx = 1; camIdx <= cameraCount; ++camIdx) {
systemConfig.configResult.EnsureHandEyeMatrix(camIdx);
}
for (int camIdx = 1; camIdx <= cameraCount; ++camIdx) {
bool isCalibrated = false;
double matrix[16];
int eulerOrder = 11;
double rotX = 0.0, rotY = 0.0, rotZ = 0.0;
double approachOffset = 0.0;
double offsetX = 0.0, offsetY = 0.0, offsetZ = 0.0;
const bool hasMatrix = m_handEyeCalibWidget->getCalibData(camIdx, matrix, isCalibrated) && isCalibrated;
const bool hasExtrinsic = m_handEyeCalibWidget->getExtrinsicData(
camIdx, eulerOrder, rotX, rotY, rotZ, approachOffset, offsetX, offsetY, offsetZ);
// 查找或创建该相机的手眼矩阵
VrHandEyeCalibMatrix* hePtr = systemConfig.configResult.FindHandEyeMatrix(camIdx);
if (!hePtr) {
systemConfig.configResult.EnsureHandEyeMatrix(camIdx);
hePtr = systemConfig.configResult.FindHandEyeMatrix(camIdx);
}
if (hePtr) {
hePtr->cameraIndex = camIdx;
if (hasMatrix) {
std::memcpy(hePtr->matrix, matrix, sizeof(double) * 16);
}
if (hasExtrinsic) {
hePtr->eulerOrder = eulerOrder;
hePtr->rotX = rotX;
hePtr->rotY = rotY;
hePtr->rotZ = rotZ;
hePtr->offsetX = offsetX;
hePtr->offsetY = offsetY;
hePtr->offsetZ = offsetZ;
}
}
}
}
// —— 网络配置 ——
if (m_networkConfigWidget) {
const NetworkConfigData netConfig = m_networkConfigWidget->getConfig();
if (netConfig.tcpServerPort <= 0 || netConfig.tcpServerPort > 65535) {
StyledMessageBox::warning(this, QStringLiteral("错误"),
QStringLiteral("TCP端口必须在 1 到 65535 之间。"));
return false;
}
systemConfig.configResult.tcpPort = static_cast<uint16_t>(netConfig.tcpServerPort);
systemConfig.configResult.poseOutputOrder = netConfig.poseOutputOrder;
systemConfig.configResult.byteOrder = netConfig.byteOrder;
}
if (!m_presenter->GetConfigManager()->UpdateFullConfig(systemConfig)) {
StyledMessageBox::warning(this, QStringLiteral("失败"),
QStringLiteral("更新配置缓存失败。"));
return false;
}
const QString configPath = PathManager::GetInstance().GetConfigFilePath();
if (!m_presenter->GetConfigManager()->SaveConfigToFile(configPath.toStdString())) {
StyledMessageBox::warning(this, QStringLiteral("失败"),
QStringLiteral("保存配置文件失败。"));
return false;
}
m_presenter->OnConfigChanged(systemConfig.configResult);
return true;
}
void DialogAlgoArg::resetParams()
{
setDoubleEditorText(ui->spinCornerTh, 60.0);
setDoubleEditorText(ui->spinScale, 10.0);
setDoubleEditorText(ui->spinMinEndingGap, 10.0);
setDoubleEditorText(ui->spinMinEndingGapZ, 5.0);
setDoubleEditorText(ui->spinJumpCornerTh1, 15.0);
setDoubleEditorText(ui->spinJumpCornerTh2, 60.0);
setDoubleEditorText(m_editTireDiameter, 500.0);
setDoubleEditorText(m_editTireThickness, 140.0);
}
void DialogAlgoArg::on_btnOK_clicked()
{
if (saveParams()) {
accept();
}
}
void DialogAlgoArg::on_btnCancel_clicked()
{
reject();
}
void DialogAlgoArg::on_btnApply_clicked()
{
if (saveParams()) {
StyledMessageBox::information(this, QStringLiteral("提示"),
QStringLiteral("参数已应用。"));
}
}
void DialogAlgoArg::on_btnReset_clicked()
{
const auto ret = StyledMessageBox::question(this, QStringLiteral("确认重置"),
QStringLiteral("确定要重置为默认参数吗?"), QMessageBox::Yes | QMessageBox::No);
if (ret == QMessageBox::Yes) {
resetParams();
}
}
void DialogAlgoArg::onCalibMatrixLoaded(int cameraIndex, const double* matrix)
{
Q_UNUSED(cameraIndex);
Q_UNUSED(matrix);
StyledMessageBox::information(this, QStringLiteral("提示"),
QStringLiteral("已从文件导入手眼标定矩阵。"));
}
void DialogAlgoArg::onSaveCalibRequested(int cameraIndex, const double* matrix)
{
Q_UNUSED(cameraIndex);
Q_UNUSED(matrix);
if (saveParams()) {
StyledMessageBox::information(this, QStringLiteral("成功"),
QStringLiteral("手眼标定参数已保存。"));
} else {
StyledMessageBox::warning(this, QStringLiteral("失败"),
QStringLiteral("保存手眼标定参数失败。"));
}
}