工具盘姿态转换增加参数旋转到万向锁以外

This commit is contained in:
杰仔 2026-04-23 09:01:05 +08:00
parent d108a48a20
commit c103a63519
16 changed files with 1040 additions and 42 deletions

View File

@ -22,7 +22,10 @@ struct RobotPose6D;
*
* eulerOrder 姿 (rx/ry/rz )
* 姿 ( roll/pitch/yaw)
* rotX/Y/Z Eye Rx*Ry*Rz
* rotX/Y/Z Eye Rx*Ry*Rz
* outRotX/Y/Z Robot Rx*Ry*Rz
* "算法定义的工具系""机器人期望的 TCP 系"姿
* 0 姿
* approachOffset 沿姿mm
*/
struct HandEyeExtrinsic
@ -32,6 +35,9 @@ struct HandEyeExtrinsic
double rotY = 0.0;
double rotZ = 0.0;
double approachOffset = 0.0;
double outRotX = 0.0;
double outRotY = 0.0;
double outRotZ = 15.0;
};
class DetectPresenter

View File

@ -458,8 +458,9 @@ int DetectPresenter::DetectScrew(
growParam.yDeviation_max, growParam.zDeviation_max, growParam.maxLineSkipNum,
growParam.maxSkipDistance, growParam.minLTypeTreeLen, growParam.minVTypeTreeLen);
LOG_INFO("[Algo Thread] Filter: continuityTh=%.1f, outlierTh=%.1f\n", filterParam.continuityTh, filterParam.outlierTh);
LOG_INFO("[Algo Thread] Pose Config: eulerOrder=%d, poseOutputOrder=%d, rotX=%.3f, rotY=%.3f, rotZ=%.3f\n",
extrinsic.eulerOrder, poseOutputOrder, extrinsic.rotX, extrinsic.rotY, extrinsic.rotZ);
LOG_INFO("[Algo Thread] Pose Config: eulerOrder=%d, poseOutputOrder=%d, rotX=%.3f, rotY=%.3f, rotZ=%.3f, outRotX=%.3f, outRotY=%.3f, outRotZ=%.3f\n",
extrinsic.eulerOrder, poseOutputOrder, extrinsic.rotX, extrinsic.rotY, extrinsic.rotZ,
extrinsic.outRotX, extrinsic.outRotY, extrinsic.outRotZ);
}
int errCode = 0;
@ -550,6 +551,9 @@ int DetectPresenter::DetectScrew(
LOG_WARNING("[Algo Thread] Screw %zu: 轴变换到机器人坐标系失败,已跳过\n", i);
continue;
}
// Robot 系下对工具三轴做后补偿:把"算法工具系"修正到"机器人期望 TCP 系"
// 同时让最终输出欧拉角远离万向节锁;默认 0 不启用。
ApplyAxesRotation(robotAxes, extrinsic.outRotX, extrinsic.outRotY, extrinsic.outRotZ);
const CTRotationMatrix rotation = BuildRotationMatrix(robotAxes);
ScrewPosition pos;
@ -626,8 +630,9 @@ int DetectPresenter::DetectToolDisk(
LOG_INFO("[Algo Thread] ToolDisk Corner: cornerTh=%.1f, scale=%.1f, minEndingGap=%.1f, minEndingGap_z=%.1f\n",
cornerParam.cornerTh, cornerParam.scale, cornerParam.minEndingGap, cornerParam.minEndingGap_z);
LOG_INFO("[Algo Thread] ToolDisk Pose Config: eulerOrder=%d, poseOutputOrder=%d, rotX=%.3f, rotY=%.3f, rotZ=%.3f\n",
extrinsic.eulerOrder, poseOutputOrder, extrinsic.rotX, extrinsic.rotY, extrinsic.rotZ);
LOG_INFO("[Algo Thread] ToolDisk Pose Config: eulerOrder=%d, poseOutputOrder=%d, rotX=%.3f, rotY=%.3f, rotZ=%.3f, outRotX=%.3f, outRotY=%.3f, outRotZ=%.3f\n",
extrinsic.eulerOrder, poseOutputOrder, extrinsic.rotX, extrinsic.rotY, extrinsic.rotZ,
extrinsic.outRotX, extrinsic.outRotY, extrinsic.outRotZ);
}
int errCode = 0;
@ -691,6 +696,9 @@ int DetectPresenter::DetectToolDisk(
LOG_WARNING("[Algo Thread] ToolDisk: 轴变换到机器人坐标系失败\n");
return ERR_CODE(DEV_DATA_INVALID);
}
// Robot 系下对工具三轴做后补偿:把"算法工具系"修正到"机器人期望 TCP 系"
// 同时让最终输出欧拉角远离万向节锁;默认 0 不启用。
ApplyAxesRotation(robotAxes, extrinsic.outRotX, extrinsic.outRotY, extrinsic.outRotZ);
const CTRotationMatrix robotRotation = BuildRotationMatrix(robotAxes);
RotationMatrixToConfiguredEulerDegrees(robotRotation, order, pos.roll, pos.pitch, pos.yaw);
// 拍照姿态作参考,把 gimbal lock / 多解区的解拉回拍照姿态附近,减少机械臂旋转量。

View File

@ -236,7 +236,7 @@ int ScrewPositionPresenter::ProcessAlgoDetection(std::vector<std::pair<EVzResult
const VrDebugParam debugParam = configResult.debugParam;
const int poseOutputOrder = configResult.poseOutputOrder;
// 每台相机独立:欧拉角顺序 + 绕 XYZ 三轴的补偿旋转 + 接近点偏移
// 每台相机独立:欧拉角顺序 + 绕 XYZ 三轴的补偿旋转 + 接近点偏移 + Robot 系后补偿
HandEyeExtrinsic extrinsic;
for (const auto& calibMatrix : configResult.handEyeCalibMatrixList) {
if (calibMatrix.cameraIndex == m_currentCameraIndex) {
@ -245,13 +245,18 @@ int ScrewPositionPresenter::ProcessAlgoDetection(std::vector<std::pair<EVzResult
extrinsic.rotY = calibMatrix.rotY;
extrinsic.rotZ = calibMatrix.rotZ;
extrinsic.approachOffset = calibMatrix.approachOffset;
extrinsic.outRotX = calibMatrix.outRotX;
extrinsic.outRotY = calibMatrix.outRotY;
extrinsic.outRotZ = calibMatrix.outRotZ;
break;
}
}
LOG_INFO("[Algo Thread] camera=%d eulerOrder=%d poseOutputOrder=%d rot=(%.3f, %.3f, %.3f) approachOffset=%.3f\n",
LOG_INFO("[Algo Thread] camera=%d eulerOrder=%d poseOutputOrder=%d rot=(%.3f, %.3f, %.3f) outRot=(%.3f, %.3f, %.3f) approachOffset=%.3f\n",
m_currentCameraIndex, extrinsic.eulerOrder, poseOutputOrder,
extrinsic.rotX, extrinsic.rotY, extrinsic.rotZ, extrinsic.approachOffset);
extrinsic.rotX, extrinsic.rotY, extrinsic.rotZ,
extrinsic.outRotX, extrinsic.outRotY, extrinsic.outRotZ,
extrinsic.approachOffset);
DetectionResult detectionResult;
detectionResult.cameraIndex = m_currentCameraIndex;

View File

@ -4,7 +4,7 @@
#define SCREWPOSITION_APP_NAME "螺杆定位"
#define SCREWPOSITION_VERSION_STRING "1.1.5"
#define SCREWPOSITION_BUILD_STRING "2"
#define SCREWPOSITION_BUILD_STRING "3"
#define SCREWPOSITION_FULL_VERSION_STRING "V" SCREWPOSITION_VERSION_STRING "_" SCREWPOSITION_BUILD_STRING
// 获取版本信息的便捷函数

View File

@ -8,7 +8,9 @@
*
* X/Y/Z
* 姿姿
* Eye Rx(rotX) * Ry(rotY) * Rz(rotZ)
* rotX/Y/Z : Eye Rx(rotX)*Ry(rotY)*Rz(rotZ)
* outRotX/Y/Z: Robot Rx*Ry*Rz
* "算法定义的工具系""机器人期望的 TCP 系"
*/
struct VrHandEyeCalibMatrix
{
@ -20,6 +22,9 @@ struct VrHandEyeCalibMatrix
double rotZ;
/* 接近点偏移 (mm): approach point offset along -axialDir (screw) / -normalDir (tool disk). */
double approachOffset;
double outRotX;
double outRotY;
double outRotZ;
VrHandEyeCalibMatrix()
: cameraIndex(1)
@ -28,6 +33,9 @@ struct VrHandEyeCalibMatrix
, rotY(0.0)
, rotZ(0.0)
, approachOffset(0.0)
, outRotX(0.0)
, outRotY(0.0)
, outRotZ(15.0)
{
double identity[16] = {
1.0, 0.0, 0.0, 0.0,

View File

@ -38,6 +38,12 @@ static void LoadSingleHandEyeCalibMatrix(XMLElement* handEyeElement,
calibMatrix.rotZ = handEyeElement->DoubleAttribute("rotZ");
if (handEyeElement->Attribute("approachOffset"))
calibMatrix.approachOffset = handEyeElement->DoubleAttribute("approachOffset");
if (handEyeElement->Attribute("outRotX"))
calibMatrix.outRotX = handEyeElement->DoubleAttribute("outRotX");
if (handEyeElement->Attribute("outRotY"))
calibMatrix.outRotY = handEyeElement->DoubleAttribute("outRotY");
if (handEyeElement->Attribute("outRotZ"))
calibMatrix.outRotZ = handEyeElement->DoubleAttribute("outRotZ");
}
// ============ 手眼标定矩阵 ============
@ -98,6 +104,9 @@ void SaveHandEyeCalibMatrixs(XMLDocument& doc,
handEyeElement->SetAttribute("rotX", calibMatrix.rotX);
handEyeElement->SetAttribute("rotY", calibMatrix.rotY);
handEyeElement->SetAttribute("rotZ", calibMatrix.rotZ);
handEyeElement->SetAttribute("outRotX", calibMatrix.outRotX);
handEyeElement->SetAttribute("outRotY", calibMatrix.outRotY);
handEyeElement->SetAttribute("outRotZ", calibMatrix.outRotZ);
handEyeElement->SetAttribute("approachOffset", calibMatrix.approachOffset);
containerElement->InsertEndChild(handEyeElement);
}

View File

@ -15,8 +15,8 @@
| 9 | 颗粒尺寸检测 | ParticleSize | 1.0.0.0 |
| 10 | 双目标记检测 | BinocularMarkServer | 1.0.0.4 |
| 11 | 铁路隧道槽道测量 | TunnelChannel | 1.0.0.3 |
| 12 | 螺杆定位 | ScrewPosition | 1.1.5.2 |
| 12 | 螺杆定位 | ScrewPosition | 1.1.5.3 |
| 13 | 包裹拆线位置定位 | BagThreadPosition | 1.0.0.4 |
| 14 | 工件孔定位 | WorkpieceHole | 1.1.2.1 |
| 14 | 工件孔定位 | WorkpieceHole | 1.1.3.1 |
| 16 | 坑孔定位 | HolePitPosition | 无 |

View File

@ -337,6 +337,20 @@ struct HECEyeInHandData
: endPose(pose), targetInCamera(target) {}
};
/**
* @brief 姿
* 姿姿
*/
struct HECEyeInHandPoseData
{
HECHomogeneousMatrix endPose; // 机器人末端位姿 (相对于基座)
HECHomogeneousMatrix boardInCamera; // 标定板在相机坐标系的位姿
HECEyeInHandPoseData() = default;
HECEyeInHandPoseData(const HECHomogeneousMatrix& end, const HECHomogeneousMatrix& board)
: endPose(end), boardInCamera(board) {}
};
/**
* @brief 姿
* 姿姿

View File

@ -53,6 +53,9 @@ public:
virtual int CalculateEyeInHand(const std::vector<HECEyeInHandData>& calibData,
HECCalibResult& result) = 0;
virtual int CalculateEyeInHandWithPose(const std::vector<HECEyeInHandPoseData>& calibData,
HECCalibResult& result) = 0;
virtual HECTCPCalibResult CalculateTCP(const HECTCPCalibData& data) = 0;
virtual int CalculateEyeToHandWithPose(const std::vector<HECEyeToHandData>& calibData,

View File

@ -1,7 +1,3 @@
#ifndef HANDEYECALIB_LIBRARY
#define HANDEYECALIB_LIBRARY
#endif
#include "HandEyeCalib.h"
#include "VrError.h"
#include "VrLog.h"
@ -623,6 +619,162 @@ int HandEyeCalib::CalculateEyeInHand(
return SUCCESS;
}
int HandEyeCalib::CalculateEyeInHandWithPose(
const std::vector<HECEyeInHandPoseData>& calibData,
HECCalibResult& result)
{
const int N = static_cast<int>(calibData.size());
if (N < 3) {
return ERR_CODE(APP_ERR_PARAM);
}
// Eye-In-Hand:
// T_base_to_end_i * X * T_board_in_cam_i = T_base_to_board
// X 为相机到末端的固定变换,构造 A_i_j * X = X * B_i_j.
std::vector<Eigen::Matrix3d> A_rot, B_rot;
std::vector<Eigen::Vector3d> A_trans, B_trans;
for (int i = 0; i < N; ++i) {
for (int j = i + 1; j < N; ++j) {
Eigen::Matrix4d T_end_i = Eigen::Matrix4d::Identity();
Eigen::Matrix4d T_end_j = Eigen::Matrix4d::Identity();
Eigen::Matrix4d T_board_i = Eigen::Matrix4d::Identity();
Eigen::Matrix4d T_board_j = Eigen::Matrix4d::Identity();
for (int r = 0; r < 4; ++r) {
for (int c = 0; c < 4; ++c) {
T_end_i(r, c) = calibData[i].endPose.at(r, c);
T_end_j(r, c) = calibData[j].endPose.at(r, c);
T_board_i(r, c) = calibData[i].boardInCamera.at(r, c);
T_board_j(r, c) = calibData[j].boardInCamera.at(r, c);
}
}
const Eigen::Matrix4d A = T_end_i.inverse() * T_end_j;
const Eigen::Matrix4d B = T_board_i * T_board_j.inverse();
A_rot.push_back(A.block<3, 3>(0, 0));
A_trans.push_back(A.block<3, 1>(0, 3));
B_rot.push_back(B.block<3, 3>(0, 0));
B_trans.push_back(B.block<3, 1>(0, 3));
}
}
const int numPairs = static_cast<int>(A_rot.size());
Eigen::MatrixXd M(9 * numPairs, 9);
M.setZero();
for (int i = 0; i < numPairs; ++i) {
const Eigen::Matrix3d& Ai = A_rot[i];
const Eigen::Matrix3d& Bi = B_rot[i];
for (int r = 0; r < 3; ++r) {
for (int c = 0; c < 3; ++c) {
const int row = i * 9 + r * 3 + c;
for (int k = 0; k < 3; ++k) {
M(row, k * 3 + c) += Ai(r, k);
M(row, r * 3 + k) -= Bi(k, c);
}
}
}
}
Eigen::JacobiSVD<Eigen::MatrixXd> svd(M, Eigen::ComputeFullV);
const Eigen::VectorXd x = svd.matrixV().col(8);
Eigen::Matrix3d R_raw;
R_raw << x(0), x(1), x(2),
x(3), x(4), x(5),
x(6), x(7), x(8);
Eigen::JacobiSVD<Eigen::Matrix3d> svd_R(R_raw, Eigen::ComputeFullU | Eigen::ComputeFullV);
Eigen::Matrix3d R_cam_to_end = svd_R.matrixU() * svd_R.matrixV().transpose();
if (R_cam_to_end.determinant() < 0) {
R_cam_to_end = -R_cam_to_end;
}
Eigen::MatrixXd C(3 * numPairs, 3);
Eigen::VectorXd d(3 * numPairs);
for (int i = 0; i < numPairs; ++i) {
C.block<3, 3>(i * 3, 0) = A_rot[i] - Eigen::Matrix3d::Identity();
d.segment<3>(i * 3) = R_cam_to_end * B_trans[i] - A_trans[i];
}
const Eigen::Vector3d t_cam_to_end =
C.bdcSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(d);
double totalError = 0.0;
double maxErr = 0.0;
double minErr = std::numeric_limits<double>::max();
Eigen::Matrix4d T_cam_to_end = Eigen::Matrix4d::Identity();
T_cam_to_end.block<3, 3>(0, 0) = R_cam_to_end;
T_cam_to_end.block<3, 1>(0, 3) = t_cam_to_end;
std::vector<Eigen::Vector3d> boardInBasePoints;
boardInBasePoints.reserve(N);
for (int i = 0; i < N; ++i) {
Eigen::Matrix4d T_end = Eigen::Matrix4d::Identity();
Eigen::Matrix4d T_board = Eigen::Matrix4d::Identity();
for (int r = 0; r < 4; ++r) {
for (int c = 0; c < 4; ++c) {
T_end(r, c) = calibData[i].endPose.at(r, c);
T_board(r, c) = calibData[i].boardInCamera.at(r, c);
}
}
const Eigen::Matrix4d T_board_in_base = T_end * T_cam_to_end * T_board;
boardInBasePoints.push_back(T_board_in_base.block<3, 1>(0, 3));
}
Eigen::Vector3d meanBoardInBase = Eigen::Vector3d::Zero();
for (const auto& p : boardInBasePoints) {
meanBoardInBase += p;
}
meanBoardInBase /= N;
for (const auto& p : boardInBasePoints) {
const double error = (p - meanBoardInBase).norm();
totalError += error;
if (error > maxErr) maxErr = error;
if (error < minErr) minErr = error;
}
result.error = totalError / N;
result.maxError = maxErr;
result.minError = minErr;
result.centerEye = HECPoint3D(0, 0, 0);
result.centerRobot = HECPoint3D(meanBoardInBase(0), meanBoardInBase(1), meanBoardInBase(2));
#if 0
// 统一对外返回 camera -> end与点法和业务侧 sixAxisEyeInHandBuildTransform 保持一致。
const Eigen::Matrix3d R_cam_to_end = R_end_to_cam.transpose();
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
result.R.at(i, j) = R_cam_to_end(i, j);
}
}
result.T = HECTranslationVector(t_cam_to_end(0), t_cam_to_end(1), t_cam_to_end(2));
return SUCCESS;
}
#endif
// Return the hand-eye matrix in camera -> end form.
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
result.R.at(i, j) = R_cam_to_end(i, j);
}
}
result.T = HECTranslationVector(t_cam_to_end(0), t_cam_to_end(1), t_cam_to_end(2));
return SUCCESS;
}
Eigen::Matrix3d HandEyeCalib::eulerToRotationMatrix(double rx, double ry, double rz, HECEulerOrder order)
{
// 将角度转为弧度

View File

@ -51,6 +51,9 @@ public:
int CalculateEyeInHand(const std::vector<HECEyeInHandData>& calibData,
HECCalibResult& result) override;
int CalculateEyeInHandWithPose(const std::vector<HECEyeInHandPoseData>& calibData,
HECCalibResult& result) override;
HECTCPCalibResult CalculateTCP(const HECTCPCalibData& data) override;
int CalculateEyeToHandWithPose(const std::vector<HECEyeToHandData>& calibData,

View File

@ -9,8 +9,10 @@
#include <QTextEdit>
#include <QSpinBox>
#include <QDoubleSpinBox>
#include <QComboBox>
#include <vector>
#include <QString>
#include "IHandEyeCalib.h"
class IChessboardDetector;
class IHandEyeCalib;
@ -23,7 +25,8 @@ struct BatchVerifyItem
QString leftImagePath; // 左目图像路径
QString rightImagePath; // 右目图像路径
double robotX, robotY, robotZ; // 机械臂坐标
double robotRx, robotRy, robotRz; // 机械臂姿态
double robotRx, robotRy, robotRz; // 机械臂姿态(欧拉角,度)
double robotQw, robotQx, robotQy, robotQz; // 机械臂姿态(四元数)
// 检测结果
bool detected;
@ -33,6 +36,16 @@ struct BatchVerifyItem
// 验证结果
double errorX, errorY, errorZ; // 误差
double errorTotal; // 总误差
BatchVerifyItem()
: robotX(0), robotY(0), robotZ(0)
, robotRx(0), robotRy(0), robotRz(0)
, robotQw(1), robotQx(0), robotQy(0), robotQz(0)
, detected(false)
, camX(0), camY(0), camZ(0)
, camRx(0), camRy(0), camRz(0)
, errorX(0), errorY(0), errorZ(0)
, errorTotal(0) {}
};
/**
@ -97,10 +110,21 @@ private:
bool detectImagePair(BatchVerifyItem& item);
/**
* @brief
* @brief
*/
void calculateError(BatchVerifyItem& item);
/**
* @brief Eye-In-Hand
*/
void calculateEyeInHandErrors();
/**
* @brief
*/
static void quatToRotationMatrix(double qw, double qx, double qy, double qz,
HECRotationMatrix& R);
/**
* @brief
*/
@ -136,6 +160,13 @@ private:
QDoubleSpinBox* m_sbCx;
QDoubleSpinBox* m_sbCy;
// 标定类型选择
QComboBox* m_cbCalibType;
// 标定结果
HECCalibResult m_calibResult;
bool m_calibSuccess;
// 数据
std::vector<BatchVerifyItem> m_items;
QString m_currentDirectory;

View File

@ -42,6 +42,25 @@ public:
*/
void getEyeInHandData(std::vector<HECEyeInHandData>& calibData) const;
/**
* @brief Eye-In-Hand 姿 Park
*/
void getEyeInHandPoseData(std::vector<HECEyeInHandPoseData>& calibData) const;
/**
* @brief Eye-In-Hand 姿
*/
void getEyeInHandPoseData(std::vector<HECEyeInHandPoseData>& calibData,
HECEulerOrder robotEulerOrder) const;
/**
* @brief /姿 Eye-In-Hand 姿
*/
void getEyeInHandPoseData(std::vector<HECEyeInHandPoseData>& calibData,
HECEulerOrder robotEulerOrder,
HECEulerOrder boardEulerOrder,
bool invertBoardPose) const;
/**
* @brief
*/

View File

@ -45,6 +45,8 @@ BatchVerifyDialog::BatchVerifyDialog(IChessboardDetector* detector,
, m_sbFy(nullptr)
, m_sbCx(nullptr)
, m_sbCy(nullptr)
, m_cbCalibType(nullptr)
, m_calibSuccess(false)
, m_isVerifying(false)
{
setupUI();
@ -123,6 +125,13 @@ void BatchVerifyDialog::setupUI()
m_sbCy->setValue(512.0);
paramLayout->addWidget(m_sbCy, 2, 1);
// 标定类型选择
paramLayout->addWidget(new QLabel("标定类型:", this), 2, 2);
m_cbCalibType = new QComboBox(this);
m_cbCalibType->addItem("Eye-In-Hand (眼在手上)");
m_cbCalibType->addItem("Eye-To-Hand (眼在手外)");
paramLayout->addWidget(m_cbCalibType, 2, 3, 1, 3);
mainLayout->addWidget(paramGroup);
// 控制按钮
@ -287,6 +296,11 @@ bool BatchVerifyDialog::scanDirectory(const QString& dirPath)
double qy = rotation["y"].toDouble();
double qz = rotation["z"].toDouble();
item.robotQw = qw;
item.robotQx = qx;
item.robotQy = qy;
item.robotQz = qz;
// 四元数转欧拉角 (ZYX顺序单位度)
// Roll (X轴旋转)
double sinr_cosp = 2.0 * (qw * qx + qy * qz);
@ -332,11 +346,14 @@ void BatchVerifyDialog::onStartVerify()
}
m_isVerifying = true;
m_calibSuccess = false;
m_btnStartVerify->setEnabled(false);
m_btnStopVerify->setEnabled(true);
m_btnExport->setEnabled(false);
appendLog("开始批量验证...");
int calibType = m_cbCalibType->currentIndex();
appendLog(QString("开始批量验证 (模式: %1)...")
.arg(calibType == 0 ? "Eye-In-Hand" : "Eye-To-Hand"));
// 设置检测参数
m_detector->SetDetectionFlags(true, true, false);
@ -344,33 +361,50 @@ void BatchVerifyDialog::onStartVerify()
int successCount = 0;
int totalCount = m_items.size();
// 阶段1检测所有图像对
for (size_t i = 0; i < m_items.size(); ++i) {
if (!m_isVerifying) {
appendLog("验证已停止");
break;
}
appendLog(QString("正在验证第 %1/%2 组数据...").arg(i + 1).arg(totalCount));
appendLog(QString("正在检测第 %1/%2 组数据...").arg(i + 1).arg(totalCount));
if (detectImagePair(m_items[i])) {
calculateError(m_items[i]);
successCount++;
}
// 更新进度
m_progressBar->setValue((i + 1) * 100 / totalCount);
// 更新进度检测阶段占50%
m_progressBar->setValue((i + 1) * 50 / totalCount);
updateTable();
// 处理事件,保持界面响应
QApplication::processEvents();
}
// 阶段2误差计算
if (calibType == 0) {
// Eye-In-Hand先标定再计算一致性误差
appendLog(QString("检测完成: %1/%2 成功,正在执行 Eye-In-Hand 标定...").arg(successCount).arg(totalCount));
calculateEyeInHandErrors();
} else {
// Eye-To-Hand直接比较相机坐标系已标定到基座坐标系的情况
for (size_t i = 0; i < m_items.size(); ++i) {
if (m_items[i].detected) {
calculateError(m_items[i]);
}
}
}
m_progressBar->setValue(100);
updateTable();
m_isVerifying = false;
m_btnStartVerify->setEnabled(true);
m_btnStopVerify->setEnabled(false);
m_btnExport->setEnabled(true);
appendLog(QString("验证完成: 成功 %1/%2").arg(successCount).arg(totalCount));
appendLog(QString("验证完成: 检测成功 %1/%2").arg(successCount).arg(totalCount));
}
void BatchVerifyDialog::onStopVerify()
@ -457,7 +491,7 @@ void BatchVerifyDialog::calculateError(BatchVerifyItem& item)
return;
}
// 计算位置误差
// 直接比较模式(仅适用于相机坐标系已对齐到机器人基座坐标系的情况)
item.errorX = item.camX - item.robotX;
item.errorY = item.camY - item.robotY;
item.errorZ = item.camZ - item.robotZ;
@ -466,6 +500,128 @@ void BatchVerifyDialog::calculateError(BatchVerifyItem& item)
item.errorZ * item.errorZ);
}
void BatchVerifyDialog::quatToRotationMatrix(double qw, double qx, double qy, double qz,
HECRotationMatrix& R)
{
R.at(0, 0) = 1 - 2*(qy*qy + qz*qz);
R.at(0, 1) = 2*(qx*qy - qz*qw);
R.at(0, 2) = 2*(qx*qz + qy*qw);
R.at(1, 0) = 2*(qx*qy + qz*qw);
R.at(1, 1) = 1 - 2*(qx*qx + qz*qz);
R.at(1, 2) = 2*(qy*qz - qx*qw);
R.at(2, 0) = 2*(qx*qz - qy*qw);
R.at(2, 1) = 2*(qy*qz + qx*qw);
R.at(2, 2) = 1 - 2*(qx*qx + qy*qy);
}
void BatchVerifyDialog::calculateEyeInHandErrors()
{
// 收集所有成功检测的数据
std::vector<HECEyeInHandData> calibData;
std::vector<int> detectedIndices;
for (size_t i = 0; i < m_items.size(); ++i) {
if (!m_items[i].detected) continue;
// 从四元数构建末端位姿矩阵
HECRotationMatrix R_end;
quatToRotationMatrix(m_items[i].robotQw, m_items[i].robotQx,
m_items[i].robotQy, m_items[i].robotQz, R_end);
HECTranslationVector T_end(m_items[i].robotX, m_items[i].robotY, m_items[i].robotZ);
HECHomogeneousMatrix endPose(R_end, T_end);
HECPoint3D target(m_items[i].camX, m_items[i].camY, m_items[i].camZ);
calibData.push_back(HECEyeInHandData(endPose, target));
detectedIndices.push_back(static_cast<int>(i));
}
if (calibData.size() < 3) {
appendLog("有效检测数据不足3组无法进行 Eye-In-Hand 标定验证");
return;
}
appendLog(QString("使用 %1 组数据进行 Eye-In-Hand 标定...").arg(calibData.size()));
// 执行手眼标定
int ret = m_calib->CalculateEyeInHand(calibData, m_calibResult);
if (ret != 0) {
appendLog(QString("Eye-In-Hand 标定失败,错误码: %1").arg(ret));
return;
}
m_calibSuccess = true;
// 输出标定结果
appendLog("=== Eye-In-Hand 标定结果 ===");
appendLog(QString("旋转矩阵 R:"));
appendLog(QString(" [%1, %2, %3]")
.arg(m_calibResult.R.at(0,0), 0, 'f', 6)
.arg(m_calibResult.R.at(0,1), 0, 'f', 6)
.arg(m_calibResult.R.at(0,2), 0, 'f', 6));
appendLog(QString(" [%1, %2, %3]")
.arg(m_calibResult.R.at(1,0), 0, 'f', 6)
.arg(m_calibResult.R.at(1,1), 0, 'f', 6)
.arg(m_calibResult.R.at(1,2), 0, 'f', 6));
appendLog(QString(" [%1, %2, %3]")
.arg(m_calibResult.R.at(2,0), 0, 'f', 6)
.arg(m_calibResult.R.at(2,1), 0, 'f', 6)
.arg(m_calibResult.R.at(2,2), 0, 'f', 6));
appendLog(QString("平移向量 T: [%1, %2, %3] mm")
.arg(m_calibResult.T.at(0), 0, 'f', 3)
.arg(m_calibResult.T.at(1), 0, 'f', 3)
.arg(m_calibResult.T.at(2), 0, 'f', 3));
appendLog(QString("标定平均误差: %1 mm").arg(m_calibResult.error, 0, 'f', 4));
appendLog(QString("标定最大误差: %1 mm").arg(m_calibResult.maxError, 0, 'f', 4));
appendLog(QString("标定最小误差: %1 mm").arg(m_calibResult.minError, 0, 'f', 4));
// 使用标定结果计算每个观测映射到基座坐标系的点
// P_base_i = T_end_i * X * P_cam_i标定板固定所有映射点应趋于同一位置
HECHomogeneousMatrix X(m_calibResult.R, m_calibResult.T);
int N = static_cast<int>(calibData.size());
std::vector<HECPoint3D> mappedPositions(N);
for (int i = 0; i < N; ++i) {
// P_base = T_end * X * P_cam
HECPoint3D p_in_end = X.transformPoint(calibData[i].targetInCamera);
mappedPositions[i] = calibData[i].endPose.transformPoint(p_in_end);
}
// 计算映射点的均值
HECPoint3D meanPos(0, 0, 0);
for (const auto& p : mappedPositions) {
meanPos += p;
}
meanPos = meanPos / static_cast<double>(N);
appendLog(QString("标定板在基座坐标系的映射位置: (%1, %2, %3)")
.arg(meanPos.x, 0, 'f', 3)
.arg(meanPos.y, 0, 'f', 3)
.arg(meanPos.z, 0, 'f', 3));
// 计算每个样本的一致性误差
for (int i = 0; i < N; ++i) {
int idx = detectedIndices[i];
m_items[idx].errorX = mappedPositions[i].x - meanPos.x;
m_items[idx].errorY = mappedPositions[i].y - meanPos.y;
m_items[idx].errorZ = mappedPositions[i].z - meanPos.z;
m_items[idx].errorTotal = (mappedPositions[i] - meanPos).norm();
}
// 计算并显示一致性误差统计
double totalErr = 0, maxErr = 0, minErr = 1e9;
for (int i = 0; i < N; ++i) {
double e = m_items[detectedIndices[i]].errorTotal;
totalErr += e;
if (e > maxErr) maxErr = e;
if (e < minErr) minErr = e;
}
appendLog(QString("一致性误差 - 平均: %1 mm, 最大: %2 mm, 最小: %3 mm")
.arg(totalErr / N, 0, 'f', 4)
.arg(maxErr, 0, 'f', 4)
.arg(minErr, 0, 'f', 4));
}
void BatchVerifyDialog::updateTable()
{
m_tableResults->setRowCount(m_items.size());
@ -523,6 +679,30 @@ void BatchVerifyDialog::onExportResults()
QTextStream out(&file);
out.setCodec("UTF-8");
// Eye-In-Hand 模式下先输出标定结果
if (m_cbCalibType->currentIndex() == 0 && m_calibSuccess) {
out << "# Eye-In-Hand 标定结果\n";
out << "# 旋转矩阵 R:\n";
out << QString("# [%1, %2, %3]\n")
.arg(m_calibResult.R.at(0,0), 0, 'f', 6)
.arg(m_calibResult.R.at(0,1), 0, 'f', 6)
.arg(m_calibResult.R.at(0,2), 0, 'f', 6);
out << QString("# [%1, %2, %3]\n")
.arg(m_calibResult.R.at(1,0), 0, 'f', 6)
.arg(m_calibResult.R.at(1,1), 0, 'f', 6)
.arg(m_calibResult.R.at(1,2), 0, 'f', 6);
out << QString("# [%1, %2, %3]\n")
.arg(m_calibResult.R.at(2,0), 0, 'f', 6)
.arg(m_calibResult.R.at(2,1), 0, 'f', 6)
.arg(m_calibResult.R.at(2,2), 0, 'f', 6);
out << QString("# 平移向量 T: [%1, %2, %3] mm\n")
.arg(m_calibResult.T.at(0), 0, 'f', 3)
.arg(m_calibResult.T.at(1), 0, 'f', 3)
.arg(m_calibResult.T.at(2), 0, 'f', 3);
out << QString("# 标定平均误差: %1 mm\n").arg(m_calibResult.error, 0, 'f', 4);
out << "#\n";
}
// 写入表头
out << "序号,左目图像,右目图像,机械臂X,机械臂Y,机械臂Z,检测X,检测Y,检测Z,误差X,误差Y,误差Z,总误差\n";

View File

@ -146,12 +146,12 @@ void CalibDataWidget::setupUI()
modeLayout->addSpacing(20);
QLabel* lblEuler = new QLabel("欧拉角顺序:", this);
m_cbEulerOrder = new QComboBox(this);
m_cbEulerOrder->addItem("ZYX (默认)");
m_cbEulerOrder->addItem("XYZ");
m_cbEulerOrder->addItem("XZY");
m_cbEulerOrder->addItem("YXZ");
m_cbEulerOrder->addItem("YZX");
m_cbEulerOrder->addItem("ZXY");
m_cbEulerOrder->addItem("ZYX (Rz-Ry-Rx)");
m_cbEulerOrder->addItem("XYZ (Rx-Ry-Rz)");
m_cbEulerOrder->addItem("XZY (Rx-Rz-Ry)");
m_cbEulerOrder->addItem("YXZ (Ry-Rx-Rz)");
m_cbEulerOrder->addItem("YZX (Ry-Rz-Rx)");
m_cbEulerOrder->addItem("ZXY (Rz-Rx-Ry)");
modeLayout->addWidget(lblEuler);
modeLayout->addWidget(m_cbEulerOrder);
@ -494,6 +494,206 @@ void CalibDataWidget::getEyeInHandData(std::vector<HECEyeInHandData>& calibData)
DestroyHandEyeCalibInstance(calib);
}
void CalibDataWidget::getEyeInHandPoseData(std::vector<HECEyeInHandPoseData>& calibData) const
{
calibData.clear();
IHandEyeCalib* calib = CreateHandEyeCalibInstance();
if (!calib) {
return;
}
const HECEulerOrder eulerOrder = getEulerOrder();
const int rowCount = m_tableHandEye->rowCount();
for (int row = 0; row < rowCount; ++row) {
HECEyeInHandPoseData data;
const double endX = m_tableHandEye->item(row, 0) ?
m_tableHandEye->item(row, 0)->text().toDouble() : 0;
const double endY = m_tableHandEye->item(row, 1) ?
m_tableHandEye->item(row, 1)->text().toDouble() : 0;
const double endZ = m_tableHandEye->item(row, 2) ?
m_tableHandEye->item(row, 2)->text().toDouble() : 0;
const double endRoll = m_tableHandEye->item(row, 3) ?
m_tableHandEye->item(row, 3)->text().toDouble() * M_PI / 180.0 : 0;
const double endPitch = m_tableHandEye->item(row, 4) ?
m_tableHandEye->item(row, 4)->text().toDouble() * M_PI / 180.0 : 0;
const double endYaw = m_tableHandEye->item(row, 5) ?
m_tableHandEye->item(row, 5)->text().toDouble() * M_PI / 180.0 : 0;
HECEulerAngles endAngles(endRoll, endPitch, endYaw);
HECRotationMatrix R_end;
calib->EulerToRotationMatrix(endAngles, eulerOrder, R_end);
data.endPose = HECHomogeneousMatrix(R_end, HECTranslationVector(endX, endY, endZ));
const double boardX = m_tableHandEye->item(row, 6) ?
m_tableHandEye->item(row, 6)->text().toDouble() : 0;
const double boardY = m_tableHandEye->item(row, 7) ?
m_tableHandEye->item(row, 7)->text().toDouble() : 0;
const double boardZ = m_tableHandEye->item(row, 8) ?
m_tableHandEye->item(row, 8)->text().toDouble() : 0;
double boardRoll = m_tableHandEye->item(row, 9) ?
m_tableHandEye->item(row, 9)->text().toDouble() : 0;
double boardPitch = m_tableHandEye->item(row, 10) ?
m_tableHandEye->item(row, 10)->text().toDouble() : 0;
double boardYaw = m_tableHandEye->item(row, 11) ?
m_tableHandEye->item(row, 11)->text().toDouble() : 0;
if (m_cbCamAngleUnit->currentIndex() == 1) {
boardRoll *= M_PI / 180.0;
boardPitch *= M_PI / 180.0;
boardYaw *= M_PI / 180.0;
}
// ChessboardDetector::DetectChessboardWithPose 固定按 ZYX 顺序输出相机观测姿态。
// 这里不能复用机器人姿态的欧拉角顺序,否则会把旧文件中的 dExtrinsic_3..5 解释错。
HECEulerAngles boardAngles(boardRoll, boardPitch, boardYaw);
HECEulerAngles boardAnglesFixed(boardRoll, boardPitch, boardYaw);
HECRotationMatrix R_board;
calib->EulerToRotationMatrix(boardAnglesFixed, HECEulerOrder::XYZ, R_board);
data.boardInCamera = HECHomogeneousMatrix(R_board, HECTranslationVector(boardX, boardY, boardZ));
calibData.push_back(data);
}
DestroyHandEyeCalibInstance(calib);
}
void CalibDataWidget::getEyeInHandPoseData(std::vector<HECEyeInHandPoseData>& calibData,
HECEulerOrder robotEulerOrder) const
{
calibData.clear();
IHandEyeCalib* calib = CreateHandEyeCalibInstance();
if (!calib) {
return;
}
const int rowCount = m_tableHandEye->rowCount();
for (int row = 0; row < rowCount; ++row) {
HECEyeInHandPoseData data;
const double endX = m_tableHandEye->item(row, 0) ?
m_tableHandEye->item(row, 0)->text().toDouble() : 0;
const double endY = m_tableHandEye->item(row, 1) ?
m_tableHandEye->item(row, 1)->text().toDouble() : 0;
const double endZ = m_tableHandEye->item(row, 2) ?
m_tableHandEye->item(row, 2)->text().toDouble() : 0;
const double endRoll = m_tableHandEye->item(row, 3) ?
m_tableHandEye->item(row, 3)->text().toDouble() * M_PI / 180.0 : 0;
const double endPitch = m_tableHandEye->item(row, 4) ?
m_tableHandEye->item(row, 4)->text().toDouble() * M_PI / 180.0 : 0;
const double endYaw = m_tableHandEye->item(row, 5) ?
m_tableHandEye->item(row, 5)->text().toDouble() * M_PI / 180.0 : 0;
HECEulerAngles endAngles(endRoll, endPitch, endYaw);
HECRotationMatrix R_end;
calib->EulerToRotationMatrix(endAngles, robotEulerOrder, R_end);
data.endPose = HECHomogeneousMatrix(R_end, HECTranslationVector(endX, endY, endZ));
const double boardX = m_tableHandEye->item(row, 6) ?
m_tableHandEye->item(row, 6)->text().toDouble() : 0;
const double boardY = m_tableHandEye->item(row, 7) ?
m_tableHandEye->item(row, 7)->text().toDouble() : 0;
const double boardZ = m_tableHandEye->item(row, 8) ?
m_tableHandEye->item(row, 8)->text().toDouble() : 0;
double boardRoll = m_tableHandEye->item(row, 9) ?
m_tableHandEye->item(row, 9)->text().toDouble() : 0;
double boardPitch = m_tableHandEye->item(row, 10) ?
m_tableHandEye->item(row, 10)->text().toDouble() : 0;
double boardYaw = m_tableHandEye->item(row, 11) ?
m_tableHandEye->item(row, 11)->text().toDouble() : 0;
if (m_cbCamAngleUnit->currentIndex() == 1) {
boardRoll *= M_PI / 180.0;
boardPitch *= M_PI / 180.0;
boardYaw *= M_PI / 180.0;
}
HECEulerAngles boardAngles(boardRoll, boardPitch, boardYaw);
HECRotationMatrix R_board;
calib->EulerToRotationMatrix(boardAngles, HECEulerOrder::XYZ, R_board);
data.boardInCamera = HECHomogeneousMatrix(R_board, HECTranslationVector(boardX, boardY, boardZ));
calibData.push_back(data);
}
DestroyHandEyeCalibInstance(calib);
}
void CalibDataWidget::getEyeInHandPoseData(std::vector<HECEyeInHandPoseData>& calibData,
HECEulerOrder robotEulerOrder,
HECEulerOrder boardEulerOrder,
bool invertBoardPose) const
{
calibData.clear();
IHandEyeCalib* calib = CreateHandEyeCalibInstance();
if (!calib) {
return;
}
const int rowCount = m_tableHandEye->rowCount();
for (int row = 0; row < rowCount; ++row) {
HECEyeInHandPoseData data;
const double endX = m_tableHandEye->item(row, 0) ?
m_tableHandEye->item(row, 0)->text().toDouble() : 0;
const double endY = m_tableHandEye->item(row, 1) ?
m_tableHandEye->item(row, 1)->text().toDouble() : 0;
const double endZ = m_tableHandEye->item(row, 2) ?
m_tableHandEye->item(row, 2)->text().toDouble() : 0;
const double endRoll = m_tableHandEye->item(row, 3) ?
m_tableHandEye->item(row, 3)->text().toDouble() * M_PI / 180.0 : 0;
const double endPitch = m_tableHandEye->item(row, 4) ?
m_tableHandEye->item(row, 4)->text().toDouble() * M_PI / 180.0 : 0;
const double endYaw = m_tableHandEye->item(row, 5) ?
m_tableHandEye->item(row, 5)->text().toDouble() * M_PI / 180.0 : 0;
HECEulerAngles endAngles(endRoll, endPitch, endYaw);
HECRotationMatrix R_end;
calib->EulerToRotationMatrix(endAngles, robotEulerOrder, R_end);
data.endPose = HECHomogeneousMatrix(R_end, HECTranslationVector(endX, endY, endZ));
const double boardX = m_tableHandEye->item(row, 6) ?
m_tableHandEye->item(row, 6)->text().toDouble() : 0;
const double boardY = m_tableHandEye->item(row, 7) ?
m_tableHandEye->item(row, 7)->text().toDouble() : 0;
const double boardZ = m_tableHandEye->item(row, 8) ?
m_tableHandEye->item(row, 8)->text().toDouble() : 0;
double boardRoll = m_tableHandEye->item(row, 9) ?
m_tableHandEye->item(row, 9)->text().toDouble() : 0;
double boardPitch = m_tableHandEye->item(row, 10) ?
m_tableHandEye->item(row, 10)->text().toDouble() : 0;
double boardYaw = m_tableHandEye->item(row, 11) ?
m_tableHandEye->item(row, 11)->text().toDouble() : 0;
if (m_cbCamAngleUnit->currentIndex() == 1) {
boardRoll *= M_PI / 180.0;
boardPitch *= M_PI / 180.0;
boardYaw *= M_PI / 180.0;
}
HECEulerAngles boardAngles(boardRoll, boardPitch, boardYaw);
HECRotationMatrix R_board;
calib->EulerToRotationMatrix(boardAngles, boardEulerOrder, R_board);
data.boardInCamera = HECHomogeneousMatrix(R_board, HECTranslationVector(boardX, boardY, boardZ));
if (invertBoardPose) {
data.boardInCamera = data.boardInCamera.inverse();
}
calibData.push_back(data);
}
DestroyHandEyeCalibInstance(calib);
}
void CalibDataWidget::getEyeToHandPoseData(std::vector<HECEyeToHandData>& calibData) const
{
calibData.clear();
@ -560,9 +760,11 @@ void CalibDataWidget::getEyeToHandPoseData(std::vector<HECEyeToHandData>& calibD
}
// 标定板姿态与机器人姿态统一使用当前选中的欧拉角顺序
// ChessboardDetector::DetectChessboardWithPose 固定按 ZYX 顺序输出相机观测姿态。
HECEulerAngles boardAngles(boardRoll, boardPitch, boardYaw);
HECEulerAngles boardAnglesFixed(boardRoll, boardPitch, boardYaw);
HECRotationMatrix R_board;
calib->EulerToRotationMatrix(boardAngles, eulerOrder, R_board);
calib->EulerToRotationMatrix(boardAnglesFixed, HECEulerOrder::XYZ, R_board);
HECTranslationVector T_board(boardX, boardY, boardZ);
data.boardInCamera = HECHomogeneousMatrix(R_board, T_board);
@ -863,17 +1065,17 @@ void CalibDataWidget::saveCalibData(const QString& filePath) const
int mode = m_cbCalibType->currentIndex();
const int eulerOrderIndex = m_cbEulerOrder->currentIndex();
QString axisOrder = "RzRyRx";
// 兼容旧格式:轴序字符串直接表示姿态角的应用顺序。
// 例如 RxRyRz 表示 RX=roll, RY=pitch, RZ=yaw。
QString axisOrder;
switch (eulerOrderIndex) {
case 0: axisOrder = "RzRyRx"; break;
case 1: axisOrder = "RxRyRz"; break;
case 2: axisOrder = "RxRzRy"; break;
case 3: axisOrder = "RyRxRz"; break;
case 4: axisOrder = "RyRzRx"; break;
case 5: axisOrder = "RzRxRy"; break;
case 0:
default:
axisOrder = "RzRyRx";
break;
default: axisOrder = "RzRyRx"; break;
}
ini.beginGroup("CommInfo");
@ -1008,8 +1210,11 @@ void CalibDataWidget::loadCalibData(const QString& filePath)
} else {
QString axisOrder = ini.value("eRobotPosAxisOrder", "RzRyRx").toString();
// 映射到UI的欧拉角顺序选择
if (axisOrder == "RxRyRz") {
// 旧文件里保存的是轴顺序标签,不是矩阵乘法顺序。
// RxRyRz 表示 RX-roll / RY-pitch / RZ-yaw。
if (axisOrder == "RzRyRx") {
m_cbEulerOrder->setCurrentIndex(0); // ZYX
} else if (axisOrder == "RxRyRz") {
m_cbEulerOrder->setCurrentIndex(1); // XYZ
} else if (axisOrder == "RxRzRy") {
m_cbEulerOrder->setCurrentIndex(2); // XZY
@ -1020,7 +1225,7 @@ void CalibDataWidget::loadCalibData(const QString& filePath)
} else if (axisOrder == "RzRxRy") {
m_cbEulerOrder->setCurrentIndex(5); // ZXY
} else {
m_cbEulerOrder->setCurrentIndex(0); // ZYX / RzRyRx
m_cbEulerOrder->setCurrentIndex(0); // 默认 ZYX / RzRyRx
}
}

View File

@ -23,6 +23,9 @@
#include <QSettings>
#include <QDateTime>
#include <algorithm>
#include <cmath>
namespace {
int parseCalibType(const QVariant& value)
@ -621,6 +624,7 @@ void CalibViewMainWindow::onEyeToHandCalibWithPose()
}
}
#if 0
void CalibViewMainWindow::onEyeInHandCalib()
{
if (!m_calib) {
@ -628,8 +632,151 @@ void CalibViewMainWindow::onEyeInHandCalib()
return;
}
std::vector<HECEyeInHandData> calibData;
m_dataWidget->getEyeInHandData(calibData);
std::vector<HECEyeInHandPoseData> poseData;
m_dataWidget->getEyeInHandPoseData(poseData);
bool hasBoardPose = false;
for (const auto& sample : poseData) {
for (int r = 0; r < 3 && !hasBoardPose; ++r) {
for (int c = 0; c < 3; ++c) {
const double expected = (r == c) ? 1.0 : 0.0;
if (std::abs(sample.boardInCamera.at(r, c) - expected) > 1e-6) {
hasBoardPose = true;
break;
}
}
}
if (hasBoardPose) {
break;
}
}
std::vector<HECEyeInHandData> pointData;
if (!hasBoardPose) {
m_dataWidget->getEyeInHandData(pointData);
}
/* legacy pose-calibration block kept commented out while rewriting around
if (hasBoardPose) {
appendLog("开始 Eye-In-Hand 标定...");
appendLog(QString("输入数据组数: %1").arg(poseData.size()));
const HECEulerOrder eulerOrder = m_dataWidget->getEulerOrder();
QString eulerFormula;
switch (eulerOrder) {
case HECEulerOrder::XYZ: eulerFormula = "Rz*Ry*Rx"; break;
case HECEulerOrder::XZY: eulerFormula = "Ry*Rz*Rx"; break;
case HECEulerOrder::YXZ: eulerFormula = "Rz*Rx*Ry"; break;
case HECEulerOrder::YZX: eulerFormula = "Rx*Rz*Ry"; break;
case HECEulerOrder::ZXY: eulerFormula = "Ry*Rx*Rz"; break;
case HECEulerOrder::ZYX: eulerFormula = "Rx*Ry*Rz"; break;
}
appendLog(QString("欧拉角顺序: %1 (R=%2)")
.arg(eulerOrderToString(eulerOrder))
.arg(eulerFormula));
appendLog("使用 Park/Tsai-Lenz 算法,利用完整位姿信息(位置+姿态)");
const int ret = m_calib->CalculateEyeInHandWithPose(poseData, m_currentResult);
if (ret == 0) {
m_hasResult = true;
m_resultWidget->showCalibResult(m_currentResult);
#if 0
appendLog(QString("标定成功,误差: %1 mm")
.arg(m_currentResult.error, 0, 'f', 4));
updateStatusBar("Eye-In-Hand 标定完成Park 方法)");
#endif
appendLog(QString("标定成功,误差: %1 mm")
.arg(m_currentResult.error, 0, 'f', 4));
updateStatusBar("Eye-In-Hand Park calibration finished");
emit calibrationCompleted(m_currentResult);
} else {
appendLog(QString("标定失败,错误码: %1").arg(ret));
QMessageBox::critical(this, "错误", QString("标定失败,错误码: %1").arg(ret));
}
return;
}
*/
if (hasBoardPose) {
appendLog("开始 Eye-In-Hand 标定...");
appendLog(QString("输入数据组数: %1").arg(poseData.size()));
const HECEulerOrder eulerOrder = m_dataWidget->getEulerOrder();
QString eulerFormula;
switch (eulerOrder) {
case HECEulerOrder::XYZ: eulerFormula = "Rz*Ry*Rx"; break;
case HECEulerOrder::XZY: eulerFormula = "Ry*Rz*Rx"; break;
case HECEulerOrder::YXZ: eulerFormula = "Rz*Rx*Ry"; break;
case HECEulerOrder::YZX: eulerFormula = "Rx*Rz*Ry"; break;
case HECEulerOrder::ZXY: eulerFormula = "Ry*Rx*Rz"; break;
case HECEulerOrder::ZYX: eulerFormula = "Rx*Ry*Rz"; break;
}
appendLog(QString("欧拉角顺序: %1 (R=%2)")
.arg(eulerOrderToString(eulerOrder))
.arg(eulerFormula));
appendLog("使用 Park/Tsai-Lenz 算法,利用完整位姿信息(位置+姿态)");
const int ret = m_calib->CalculateEyeInHandWithPose(poseData, m_currentResult);
if (ret == 0) {
m_hasResult = true;
m_resultWidget->showCalibResult(m_currentResult);
appendLog(QString("标定成功,误差: %1 mm")
.arg(m_currentResult.error, 0, 'f', 4));
if (m_currentResult.error > 20.0) {
appendLog("当前误差仍然偏大,开始枚举机器人欧拉角顺序做诊断...");
const HECEulerOrder probeOrders[] = {
HECEulerOrder::XYZ,
HECEulerOrder::XZY,
HECEulerOrder::YXZ,
HECEulerOrder::YZX,
HECEulerOrder::ZXY,
HECEulerOrder::ZYX
};
for (const HECEulerOrder probeOrder : probeOrders) {
std::vector<HECEyeInHandPoseData> probeData;
m_dataWidget->getEyeInHandPoseData(probeData, probeOrder);
if (probeData.size() < 3) {
continue;
}
HECCalibResult probeResult;
const int probeRet = m_calib->CalculateEyeInHandWithPose(probeData, probeResult);
if (probeRet == 0) {
appendLog(QString("顺序试探 %1 -> %2 mm")
.arg(eulerOrderToString(probeOrder))
.arg(probeResult.error, 0, 'f', 4));
} else {
appendLog(QString("顺序试探 %1 -> 失败(%2)")
.arg(eulerOrderToString(probeOrder))
.arg(probeRet));
}
}
}
#if 0
updateStatusBar("Eye-In-Hand 标定完成Park 方法)");
emit calibrationCompleted(m_currentResult);
} else {
appendLog(QString("标定失败,错误码: %1").arg(ret));
QMessageBox::critical(this, "错误", QString("标定失败,错误码: %1").arg(ret));
}
return;
}
#endif
updateStatusBar("Eye-In-Hand Park calibration finished");
emit calibrationCompleted(m_currentResult);
} else {
appendLog(QString("标定失败,错误码: %1").arg(ret));
QMessageBox::critical(this, "错误", QString("标定失败,错误码: %1").arg(ret));
}
return;
}
const std::vector<HECEyeInHandData>& calibData = pointData;
if (calibData.size() < 3) {
QMessageBox::warning(this, "警告", "至少需要3组数据进行标定");
@ -639,6 +786,17 @@ void CalibViewMainWindow::onEyeInHandCalib()
appendLog("开始 Eye-In-Hand 标定...");
appendLog(QString("输入数据组数: %1").arg(calibData.size()));
HECEulerOrder eulerOrder = m_dataWidget->getEulerOrder();
const char* eulerOrderNames[] = {"XYZ", "XZY", "YXZ", "YZX", "ZXY", "ZYX"};
int orderIdx = static_cast<int>(eulerOrder);
appendLog(QString("欧拉角顺序: %1 (R=%2)")
.arg(eulerOrderNames[orderIdx])
.arg(orderIdx == 0 ? "Rz*Ry*Rx" :
orderIdx == 1 ? "Ry*Rz*Rx" :
orderIdx == 2 ? "Rz*Rx*Ry" :
orderIdx == 3 ? "Rx*Rz*Ry" :
orderIdx == 4 ? "Ry*Rx*Rz" : "Rx*Ry*Rz"));
int ret = m_calib->CalculateEyeInHand(calibData, m_currentResult);
if (ret == 0) {
@ -654,6 +812,203 @@ void CalibViewMainWindow::onEyeInHandCalib()
}
}
#endif
void CalibViewMainWindow::onEyeInHandCalib()
{
if (!m_calib) {
QMessageBox::critical(this, "Error", "Calibration instance is not initialized");
return;
}
auto orderFormula = [](HECEulerOrder order) -> const char* {
switch (order) {
case HECEulerOrder::XYZ: return "Rz*Ry*Rx";
case HECEulerOrder::XZY: return "Ry*Rz*Rx";
case HECEulerOrder::YXZ: return "Rz*Rx*Ry";
case HECEulerOrder::YZX: return "Rx*Rz*Ry";
case HECEulerOrder::ZXY: return "Ry*Rx*Rz";
case HECEulerOrder::ZYX: return "Rx*Ry*Rz";
}
return "unknown";
};
std::vector<HECEyeInHandPoseData> poseData;
m_dataWidget->getEyeInHandPoseData(poseData);
bool hasBoardPose = false;
for (const auto& sample : poseData) {
if (std::abs(sample.boardInCamera.at(0, 3)) > 1e-6 ||
std::abs(sample.boardInCamera.at(1, 3)) > 1e-6 ||
std::abs(sample.boardInCamera.at(2, 3)) > 1e-6) {
hasBoardPose = true;
break;
}
for (int r = 0; r < 3 && !hasBoardPose; ++r) {
for (int c = 0; c < 3; ++c) {
const double expected = (r == c) ? 1.0 : 0.0;
if (std::abs(sample.boardInCamera.at(r, c) - expected) > 1e-6) {
hasBoardPose = true;
break;
}
}
}
if (hasBoardPose) {
break;
}
}
if (hasBoardPose) {
const HECEulerOrder robotOrder = m_dataWidget->getEulerOrder();
appendLog("Starting Eye-In-Hand calibration...");
appendLog(QString("Input sample count: %1").arg(poseData.size()));
appendLog(QString("Robot Euler order: %1 (%2)")
.arg(eulerOrderToString(robotOrder))
.arg(orderFormula(robotOrder)));
appendLog("Board pose assumption: boardInCamera, Euler order = XYZ");
appendLog("Algorithm: Park/Tsai-Lenz with full pose data");
const int ret = m_calib->CalculateEyeInHandWithPose(poseData, m_currentResult);
if (ret != 0) {
appendLog(QString("Calibration failed, error code: %1").arg(ret));
QMessageBox::critical(this, "Error", QString("Calibration failed, error code: %1").arg(ret));
return;
}
m_hasResult = true;
m_resultWidget->showCalibResult(m_currentResult);
appendLog(QString("Calibration succeeded, error: %1 mm")
.arg(m_currentResult.error, 0, 'f', 4));
struct ProbeResult {
HECEulerOrder robotOrder;
HECEulerOrder boardOrder;
bool invertBoardPose = false;
double error = 0.0;
double maxError = 0.0;
double minError = 0.0;
int ret = -1;
};
if (m_currentResult.error > 20.0) {
appendLog("Running exhaustive Eye-In-Hand pose diagnostics...");
const HECEulerOrder allOrders[] = {
HECEulerOrder::XYZ,
HECEulerOrder::XZY,
HECEulerOrder::YXZ,
HECEulerOrder::YZX,
HECEulerOrder::ZXY,
HECEulerOrder::ZYX
};
std::vector<ProbeResult> probeResults;
probeResults.reserve(72);
for (const HECEulerOrder probeRobotOrder : allOrders) {
for (const HECEulerOrder probeBoardOrder : allOrders) {
for (const bool invertBoardPose : { false, true }) {
std::vector<HECEyeInHandPoseData> probeData;
m_dataWidget->getEyeInHandPoseData(
probeData,
probeRobotOrder,
probeBoardOrder,
invertBoardPose);
if (probeData.size() < 3) {
continue;
}
ProbeResult probe;
probe.robotOrder = probeRobotOrder;
probe.boardOrder = probeBoardOrder;
probe.invertBoardPose = invertBoardPose;
HECCalibResult probeCalibResult;
probe.ret = m_calib->CalculateEyeInHandWithPose(probeData, probeCalibResult);
if (probe.ret == 0) {
probe.error = probeCalibResult.error;
probe.maxError = probeCalibResult.maxError;
probe.minError = probeCalibResult.minError;
}
probeResults.push_back(probe);
}
}
}
std::sort(probeResults.begin(), probeResults.end(),
[](const ProbeResult& lhs, const ProbeResult& rhs) {
if (lhs.ret != 0 && rhs.ret != 0) {
return false;
}
if (lhs.ret != 0) {
return false;
}
if (rhs.ret != 0) {
return true;
}
return lhs.error < rhs.error;
});
int loggedCount = 0;
for (const ProbeResult& probe : probeResults) {
if (probe.ret != 0) {
continue;
}
appendLog(QString("Probe #%1: robot=%2(%3), board=%4(%5), pose=%6, err=%7 mm, max=%8, min=%9")
.arg(loggedCount + 1)
.arg(eulerOrderToString(probe.robotOrder))
.arg(orderFormula(probe.robotOrder))
.arg(eulerOrderToString(probe.boardOrder))
.arg(orderFormula(probe.boardOrder))
.arg(probe.invertBoardPose ? "cameraInBoard->inverse" : "boardInCamera")
.arg(probe.error, 0, 'f', 4)
.arg(probe.maxError, 0, 'f', 4)
.arg(probe.minError, 0, 'f', 4));
++loggedCount;
if (loggedCount >= 12) {
break;
}
}
}
updateStatusBar("Eye-In-Hand pose calibration finished");
emit calibrationCompleted(m_currentResult);
return;
}
std::vector<HECEyeInHandData> calibData;
m_dataWidget->getEyeInHandData(calibData);
if (calibData.size() < 3) {
QMessageBox::warning(this, "Warning", "At least 3 samples are required");
return;
}
const HECEulerOrder eulerOrder = m_dataWidget->getEulerOrder();
appendLog("Starting Eye-In-Hand point calibration...");
appendLog(QString("Input sample count: %1").arg(calibData.size()));
appendLog(QString("Robot Euler order: %1 (%2)")
.arg(eulerOrderToString(eulerOrder))
.arg(orderFormula(eulerOrder)));
appendLog("Algorithm: fixed-target point method");
const int ret = m_calib->CalculateEyeInHand(calibData, m_currentResult);
if (ret == 0) {
m_hasResult = true;
m_resultWidget->showCalibResult(m_currentResult);
appendLog(QString("Calibration succeeded, error: %1 mm")
.arg(m_currentResult.error, 0, 'f', 4));
updateStatusBar("Eye-In-Hand point calibration finished");
emit calibrationCompleted(m_currentResult);
} else {
appendLog(QString("Calibration failed, error code: %1").arg(ret));
QMessageBox::critical(this, "Error", QString("Calibration failed, error code: %1").arg(ret));
}
}
void CalibViewMainWindow::onTCPCalib()
{
if (!m_calib) {