398 lines
12 KiB
C++
398 lines
12 KiB
C++
#include "HandEyeCalibWidget.h"
|
|
|
|
#include <QVBoxLayout>
|
|
#include <QHBoxLayout>
|
|
#include <QGridLayout>
|
|
#include <QGroupBox>
|
|
#include <QFileDialog>
|
|
#include <QSettings>
|
|
#include <cstring>
|
|
|
|
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<HandEyeCalibCameraInfo>& 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<void(QComboBox::*)(int)>(&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;
|
|
}
|