#include "HandEyeCalibWidget.h" #include #include #include #include #include #include #include HandEyeCalibWidget::HandEyeCalibWidget(QWidget *parent) : QWidget(parent) , m_comboCamera(nullptr) , m_labelStatus(nullptr) , m_btnLoad(nullptr) , m_btnSave(nullptr) , m_matrixEditable(false) { memset(m_matrixEdits, 0, sizeof(m_matrixEdits)); setupUI(); } HandEyeCalibWidget::~HandEyeCalibWidget() { } // ========== 公开接口 ========== void HandEyeCalibWidget::setCameraList(const QVector& cameras) { m_comboCamera->blockSignals(true); m_comboCamera->clear(); for (int i = 0; i < cameras.size(); ++i) { m_comboCamera->addItem(cameras[i].displayName, QVariant(cameras[i].cameraIndex)); } m_comboCamera->blockSignals(false); if (cameras.isEmpty()) { onCameraSelectionChanged(-1); } else { // 默认选中第一个相机 m_comboCamera->setCurrentIndex(0); onCameraSelectionChanged(0); } } void HandEyeCalibWidget::setCalibData(int cameraIndex, const double matrix[16], bool isCalibrated) { HandEyeCalibData* data = findCalibData(cameraIndex); if (!data) { HandEyeCalibData newData; newData.cameraIndex = cameraIndex; m_calibDataCache.append(newData); data = &m_calibDataCache.last(); } memcpy(data->matrix, matrix, sizeof(double) * 16); data->isCalibrated = isCalibrated; // 如果当前正在显示这个相机,刷新 UI if (currentCameraIndex() == cameraIndex) { if (isCalibrated) { displayMatrix(matrix); updateCalibStatus(true); } else { displayIdentityMatrix(); updateCalibStatus(false); } } } bool HandEyeCalibWidget::getCalibData(int cameraIndex, double outMatrix[16], bool& outIsCalibrated) const { // 如果当前显示的就是这个相机,且矩阵可编辑,优先从 UI 读取 if (m_matrixEditable && currentCameraIndex() == cameraIndex) { double uiMatrix[16]; if (readMatrixFromUI(uiMatrix)) { memcpy(outMatrix, uiMatrix, sizeof(double) * 16); outIsCalibrated = true; return true; } } const HandEyeCalibData* data = findCalibData(cameraIndex); if (!data) { return false; } memcpy(outMatrix, data->matrix, sizeof(double) * 16); outIsCalibrated = data->isCalibrated; return true; } int HandEyeCalibWidget::currentCameraIndex() const { if (!m_comboCamera || m_comboCamera->count() == 0 || m_comboCamera->currentIndex() < 0) { return -1; } return m_comboCamera->currentData().toInt(); } void HandEyeCalibWidget::setDefaultFilePath(const QString& path) { m_defaultFilePath = path; } void HandEyeCalibWidget::setMatrixEditable(bool editable) { m_matrixEditable = editable; for (int r = 0; r < 4; ++r) { for (int c = 0; c < 4; ++c) { if (m_matrixEdits[r][c]) { m_matrixEdits[r][c]->setReadOnly(!editable); } } } } // ========== 私有槽 ========== void HandEyeCalibWidget::onCameraSelectionChanged(int index) { if (index < 0 || !m_comboCamera || m_comboCamera->count() == 0) { m_btnLoad->setEnabled(false); m_btnSave->setEnabled(false); displayIdentityMatrix(); updateCalibStatus(false); return; } m_btnLoad->setEnabled(true); m_btnSave->setEnabled(true); int camIdx = m_comboCamera->itemData(index).toInt(); const HandEyeCalibData* data = findCalibData(camIdx); if (data && data->isCalibrated) { displayMatrix(data->matrix); updateCalibStatus(true); } else { displayIdentityMatrix(); updateCalibStatus(false); } } void HandEyeCalibWidget::onLoadCalibMatrixClicked() { int camIdx = currentCameraIndex(); if (camIdx < 0) { return; } QString startDir = m_defaultFilePath.isEmpty() ? QString() : m_defaultFilePath; QString fileName = QFileDialog::getOpenFileName(this, QString::fromUtf8("选择手眼标定矩阵文件"), startDir, QString::fromUtf8("INI Files (*.ini);;All Files (*)")); if (fileName.isEmpty()) { return; } // 用 QSettings 读取 [CalibMatrixInfo_0] section QSettings settings(fileName, QSettings::IniFormat); settings.setIniCodec("UTF-8"); settings.beginGroup("CalibMatrixInfo_0"); double matrix[16]; for (int i = 0; i < 16; ++i) { int row = i / 4; int col = i % 4; double defaultVal = (row == col) ? 1.0 : 0.0; QString key = QString("dCalibMatrix_%1").arg(i); matrix[i] = settings.value(key, defaultVal).toDouble(); } settings.endGroup(); // 更新缓存 setCalibData(camIdx, matrix, true); // 发射信号通知外部 emit calibMatrixLoaded(camIdx, matrix); } void HandEyeCalibWidget::onSaveCalibMatrixClicked() { int camIdx = currentCameraIndex(); if (camIdx < 0) { return; } // 如果可编辑,先从 UI 读取最新值更新到缓存 if (m_matrixEditable) { double uiMatrix[16]; if (readMatrixFromUI(uiMatrix)) { setCalibData(camIdx, uiMatrix, true); } } const HandEyeCalibData* data = findCalibData(camIdx); if (data && data->isCalibrated) { emit saveCalibRequested(camIdx, data->matrix); } } // ========== 私有方法 ========== void HandEyeCalibWidget::setupUI() { // 主样式(继承父容器暗色主题) QString comboStyle = "QComboBox { color: rgb(221, 225, 233); background-color: rgb(47, 48, 52); " "border: 1px solid rgb(70, 72, 78); padding: 6px; }" "QComboBox QAbstractItemView { color: rgb(221, 225, 233); background-color: rgb(47, 48, 52); " "selection-background-color: rgb(70, 100, 150); }"; QString editStyle = "QLineEdit { color: rgb(221, 225, 233); background-color: rgb(47, 48, 52); " "border: 1px solid rgb(70, 72, 78); padding: 4px; }"; QString labelStyle = "color: rgb(221, 225, 233);"; QString btnLoadStyle = "QPushButton { background-color: rgb(60, 120, 180); color: rgb(221, 225, 233); " "border: none; border-radius: 4px; padding: 8px 16px; }" "QPushButton:hover { background-color: rgb(80, 140, 200); }" "QPushButton:pressed { background-color: rgb(40, 100, 160); }" "QPushButton:disabled { background-color: rgb(80, 80, 80); color: rgb(120, 120, 120); }"; QString btnSaveStyle = "QPushButton { background-color: rgb(60, 150, 80); color: rgb(221, 225, 233); " "border: none; border-radius: 4px; padding: 8px 16px; }" "QPushButton:hover { background-color: rgb(80, 170, 100); }" "QPushButton:pressed { background-color: rgb(40, 130, 60); }" "QPushButton:disabled { background-color: rgb(80, 80, 80); color: rgb(120, 120, 120); }"; QVBoxLayout* mainLayout = new QVBoxLayout(this); mainLayout->setContentsMargins(20, 10, 20, 10); // 相机选择行 QHBoxLayout* cameraRow = new QHBoxLayout(); QLabel* labelCamera = new QLabel(QString::fromUtf8("选择相机:"), this); labelCamera->setStyleSheet(labelStyle); QFont labelFont; labelFont.setPointSize(16); labelCamera->setFont(labelFont); labelCamera->setMinimumWidth(120); m_comboCamera = new QComboBox(this); QFont comboFont; comboFont.setPointSize(14); m_comboCamera->setFont(comboFont); m_comboCamera->setStyleSheet(comboStyle); cameraRow->addWidget(labelCamera); cameraRow->addWidget(m_comboCamera); mainLayout->addLayout(cameraRow); // 标定状态 m_labelStatus = new QLabel(QString::fromUtf8("标定状态: 未标定"), this); QFont statusFont; statusFont.setPointSize(14); m_labelStatus->setFont(statusFont); m_labelStatus->setStyleSheet("color: rgb(180, 180, 180); padding: 4px 0;"); mainLayout->addWidget(m_labelStatus); // 矩阵标题 QLabel* labelMatrixTitle = new QLabel(QString::fromUtf8("4x4 变换矩阵:"), this); QFont titleFont; titleFont.setPointSize(14); labelMatrixTitle->setFont(titleFont); labelMatrixTitle->setStyleSheet(labelStyle); mainLayout->addWidget(labelMatrixTitle); // 4x4 矩阵 Grid QGridLayout* gridLayout = new QGridLayout(); QFont editFont; editFont.setPointSize(12); for (int r = 0; r < 4; ++r) { for (int c = 0; c < 4; ++c) { m_matrixEdits[r][c] = new QLineEdit(this); m_matrixEdits[r][c]->setFont(editFont); m_matrixEdits[r][c]->setReadOnly(true); // 默认只读 m_matrixEdits[r][c]->setStyleSheet(editStyle); gridLayout->addWidget(m_matrixEdits[r][c], r, c); } } mainLayout->addLayout(gridLayout); // 按钮行 QHBoxLayout* btnRow = new QHBoxLayout(); m_btnLoad = new QPushButton(QString::fromUtf8("加载标定矩阵"), this); QFont btnFont; btnFont.setPointSize(14); btnFont.setBold(true); m_btnLoad->setFont(btnFont); m_btnLoad->setStyleSheet(btnLoadStyle); m_btnLoad->setEnabled(false); m_btnSave = new QPushButton(QString::fromUtf8("保存标定"), this); m_btnSave->setFont(btnFont); m_btnSave->setStyleSheet(btnSaveStyle); m_btnSave->setEnabled(false); btnRow->addWidget(m_btnLoad); btnRow->addWidget(m_btnSave); mainLayout->addLayout(btnRow); // 底部弹性空间 mainLayout->addStretch(); // 连接信号 connect(m_comboCamera, static_cast(&QComboBox::currentIndexChanged), this, &HandEyeCalibWidget::onCameraSelectionChanged); connect(m_btnLoad, &QPushButton::clicked, this, &HandEyeCalibWidget::onLoadCalibMatrixClicked); connect(m_btnSave, &QPushButton::clicked, this, &HandEyeCalibWidget::onSaveCalibMatrixClicked); } void HandEyeCalibWidget::displayMatrix(const double* matrix) { if (!matrix) return; for (int r = 0; r < 4; ++r) { for (int c = 0; c < 4; ++c) { m_matrixEdits[r][c]->setText(QString::number(matrix[r * 4 + c], 'f', 6)); } } } void HandEyeCalibWidget::clearMatrix() { for (int r = 0; r < 4; ++r) { for (int c = 0; c < 4; ++c) { m_matrixEdits[r][c]->setText(""); } } } void HandEyeCalibWidget::displayIdentityMatrix() { static const double identity[16] = { 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 }; displayMatrix(identity); } void HandEyeCalibWidget::updateCalibStatus(bool isCalibrated) { if (isCalibrated) { m_labelStatus->setText(QString::fromUtf8("标定状态: 已标定")); m_labelStatus->setStyleSheet("color: rgb(100, 200, 100); padding: 4px 0;"); } else { m_labelStatus->setText(QString::fromUtf8("标定状态: 未标定")); m_labelStatus->setStyleSheet("color: rgb(180, 180, 180); padding: 4px 0;"); } } bool HandEyeCalibWidget::readMatrixFromUI(double outMatrix[16]) const { for (int r = 0; r < 4; ++r) { for (int c = 0; c < 4; ++c) { if (!m_matrixEdits[r][c]) return false; QString text = m_matrixEdits[r][c]->text().trimmed(); if (text.isEmpty()) return false; bool ok = false; outMatrix[r * 4 + c] = text.toDouble(&ok); if (!ok) return false; } } return true; } HandEyeCalibData* HandEyeCalibWidget::findCalibData(int cameraIndex) { for (int i = 0; i < m_calibDataCache.size(); ++i) { if (m_calibDataCache[i].cameraIndex == cameraIndex) { return &m_calibDataCache[i]; } } return nullptr; } const HandEyeCalibData* HandEyeCalibWidget::findCalibData(int cameraIndex) const { for (int i = 0; i < m_calibDataCache.size(); ++i) { if (m_calibDataCache[i].cameraIndex == cameraIndex) { return &m_calibDataCache[i]; } } return nullptr; }