GrabBag/AppUtils/UICommon/Src/HandEyeCalibWidget.cpp

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;
}