386 lines
14 KiB
C++
Raw Permalink 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 "DiscHolePosePresenter.h"
#include "StyledMessageBox.h"
#include <QDoubleValidator>
#include <QIntValidator>
#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;
}
}
DialogAlgoArg::DialogAlgoArg(QWidget *parent)
: QDialog(parent)
, ui(new Ui::DialogAlgoArg)
{
ui->setupUi(this);
setWindowTitle(QStringLiteral("算法参数设置"));
initNumericEditors();
initHandEyeCalibTab();
initNetworkConfigTab();
}
DialogAlgoArg::~DialogAlgoArg()
{
delete ui;
}
void DialogAlgoArg::SetPresenter(DiscHolePosePresenter* 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 个参数DiscHolePose 仅使用 cornerParam
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::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 VrCornerParam corner = m_presenter->GetCornerParams();
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);
}
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);
for (const auto& info : cameraInfos) {
// 以 discHole set 作为「该相机的公共默认」展示
const VrDetectionConfigSet* savedSet =
configResult.FindDetectionConfig(info.cameraIndex, DETECTION_TYPE_DISC_HOLE);
if (savedSet) {
const auto& savedMatrix = savedSet->handEyeMatrix;
m_handEyeCalibWidget->setCalibData(info.cameraIndex,
savedMatrix.matrix,
!IsIdentityMatrix(savedMatrix.matrix));
m_handEyeCalibWidget->setExtrinsicData(info.cameraIndex,
savedMatrix.eulerOrder,
savedMatrix.rotX,
savedMatrix.rotY,
savedMatrix.rotZ,
0.0,
savedMatrix.offsetX,
savedMatrix.offsetY,
savedMatrix.offsetZ);
} else {
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();
// DiscHolePose 仅使用角点检测参数
VrCornerParam sharedCornerParam = systemConfig.configResult.algorithmParams.cornerParam;
if (!tryGetDouble(ui->spinCornerTh, QStringLiteral("拐角阈值"), sharedCornerParam.cornerTh)) return false;
if (!tryGetDouble(ui->spinScale, QStringLiteral("窗口比例因子"), sharedCornerParam.scale)) return false;
if (!tryGetDouble(ui->spinMinEndingGap, QStringLiteral("Y方向最小结束间隙"), sharedCornerParam.minEndingGap)) return false;
if (!tryGetDouble(ui->spinMinEndingGapZ, QStringLiteral("Z方向最小结束间隙"), sharedCornerParam.minEndingGap_z)) return false;
if (!tryGetDouble(ui->spinJumpCornerTh1, QStringLiteral("跳变拐角阈值1"), sharedCornerParam.jumpCornerTh_1)) return false;
if (!tryGetDouble(ui->spinJumpCornerTh2, QStringLiteral("跳变拐角阈值2"), sharedCornerParam.jumpCornerTh_2)) return false;
// 把「公共默认」角点参数写入全部 (camera, type) 槽位
for (auto& item : systemConfig.configResult.detectionConfigList) {
item.algorithmParams.cornerParam = sharedCornerParam;
}
if (m_handEyeCalibWidget) {
int cameraCount = std::max(1, static_cast<int>(systemConfig.configResult.cameraList.size()));
// 兜底:保证 (camera N, discHole) + (camera N, discRack) 槽位都存在
for (int camIdx = 1; camIdx <= cameraCount; ++camIdx) {
systemConfig.configResult.EnsureDetectionConfig(camIdx, DETECTION_TYPE_DISC_HOLE);
systemConfig.configResult.EnsureDetectionConfig(camIdx, DETECTION_TYPE_DISC_RACK);
}
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);
// 同一矩阵写入该相机的两个 setdiscHole + discRack
const DetectionType types[2] = { DETECTION_TYPE_DISC_HOLE, DETECTION_TYPE_DISC_RACK };
for (DetectionType t : types) {
VrDetectionConfigSet* setPtr = systemConfig.configResult.FindDetectionConfig(camIdx, t);
if (!setPtr) continue;
setPtr->handEyeMatrix.cameraIndex = camIdx;
if (hasMatrix) {
std::memcpy(setPtr->handEyeMatrix.matrix, matrix, sizeof(double) * 16);
}
if (hasExtrinsic) {
setPtr->handEyeMatrix.eulerOrder = eulerOrder;
setPtr->handEyeMatrix.rotX = rotX;
setPtr->handEyeMatrix.rotY = rotY;
setPtr->handEyeMatrix.rotZ = rotZ;
setPtr->handEyeMatrix.offsetX = offsetX;
setPtr->handEyeMatrix.offsetY = offsetY;
setPtr->handEyeMatrix.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);
}
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("保存手眼标定参数失败。"));
}
}