336 lines
14 KiB
C++
Raw Normal View History

2026-03-26 08:30:31 +08:00
#include "DetectPresenter.h"
#include "workpieceHolePositioning_Export.h"
#include "SG_baseDataType.h"
#include <fstream>
#include "PointCloudImageUtils.h"
#include "CoordinateTransform.h"
#include "VrConvert.h"
#include "VrTimeUtils.h"
#include "VrDateUtils.h"
// 辅助函数:向量叉积
static CTVec3D crossProduct(const CTVec3D& a, const CTVec3D& b) {
return CTVec3D(
a.y * b.z - a.z * b.y,
a.z * b.x - a.x * b.z,
a.x * b.y - a.y * b.x
);
}
DetectPresenter::DetectPresenter()
{
LOG_DEBUG("DetectPresenter Init (HolePitPosition)\n");
}
DetectPresenter::~DetectPresenter()
{
}
std::string DetectPresenter::GetAlgoVersion() const
{
const char* ver = wd_workpieceHolePositioningVersion();
return ver ? std::string(ver) : std::string("unknown");
}
int DetectPresenter::DetectHoles(
int cameraIndex,
std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& laserLines,
const VrAlgorithmParams& algorithmParams,
const VrDebugParam& debugParam,
LaserDataLoader& dataLoader,
const double clibMatrix[16],
int eulerOrder,
int dirVectorInvert,
const RobotFlangePose& robotPose,
HoleDetectionResult& detectionResult)
{
if (laserLines.empty()) {
LOG_WARNING("No laser lines data available\n");
return ERR_CODE(DEV_DATA_INVALID);
}
// 保存debug数据
std::string timeStamp = CVrDateUtils::GetNowTime();
if (debugParam.enableDebug && debugParam.savePointCloud) {
LOG_INFO("[Algo Thread] Debug mode is enabled, saving point cloud data\n");
std::string fileName = debugParam.debugOutputPath + "/Laserline_" + std::to_string(cameraIndex) + "_" + timeStamp + ".txt";
dataLoader.SaveLaserScanData(fileName, laserLines, laserLines.size(), 0.0, 0, 0);
}
int nRet = SUCCESS;
// 转换为算法需要的XYZ格式vector<vector<SVzNL3DPosition>>
std::vector<std::vector<SVzNL3DPosition>> xyzData;
int convertResult = dataLoader.ConvertToSVzNL3DPosition(laserLines, xyzData);
if (convertResult != SUCCESS || xyzData.empty()) {
LOG_WARNING("Failed to convert data to XYZ format or no XYZ data available\n");
return ERR_CODE(DEV_DATA_INVALID);
}
LOG_INFO("[Algo Thread] Converted %zu scan lines for wd_HolePositioning\n", xyzData.size());
// ========== 参数转换VrAlgorithmParams -> 算法结构体 ==========
// 线段参数
SSG_lineSegParam lineSegPara;
lineSegPara.distScale = algorithmParams.lineSegParam.distScale;
lineSegPara.segGapTh_y = algorithmParams.lineSegParam.segGapTh_y;
lineSegPara.segGapTh_z = algorithmParams.lineSegParam.segGapTh_z;
// 角点参数
SSG_cornerParam cornerParam;
cornerParam.cornerTh = algorithmParams.cornerParam.cornerTh;
cornerParam.scale = algorithmParams.cornerParam.scale;
cornerParam.minEndingGap = algorithmParams.cornerParam.minEndingGap;
cornerParam.minEndingGap_z = algorithmParams.cornerParam.minEndingGap_z;
cornerParam.jumpCornerTh_1 = algorithmParams.cornerParam.jumpCornerTh_1;
cornerParam.jumpCornerTh_2 = algorithmParams.cornerParam.jumpCornerTh_2;
// 噪声滤除参数
SSG_outlierFilterParam filterParam;
filterParam.continuityTh = algorithmParams.outlierFilterParam.continuityTh;
filterParam.outlierTh = algorithmParams.outlierFilterParam.outlierTh;
// 树生长参数
SSG_treeGrowParam growParam;
growParam.maxLineSkipNum = algorithmParams.treeGrowParam.maxLineSkipNum;
growParam.yDeviation_max = algorithmParams.treeGrowParam.yDeviation_max;
growParam.maxSkipDistance = algorithmParams.treeGrowParam.maxSkipDistance;
growParam.zDeviation_max = algorithmParams.treeGrowParam.zDeviation_max;
growParam.minLTypeTreeLen = algorithmParams.treeGrowParam.minLTypeTreeLen;
growParam.minVTypeTreeLen = algorithmParams.treeGrowParam.minVTypeTreeLen;
if (debugParam.enableDebug && debugParam.printDetailLog) {
LOG_INFO("[Algo Thread] lineSegPara: distScale=%.2f, segGapTh_y=%.2f, segGapTh_z=%.2f\n",
lineSegPara.distScale, lineSegPara.segGapTh_y, lineSegPara.segGapTh_z);
LOG_INFO("[Algo Thread] cornerParam: cornerTh=%.2f, scale=%.2f, minEndingGap=%.2f, minEndingGap_z=%.2f, jumpCornerTh_1=%.2f, jumpCornerTh_2=%.2f\n",
cornerParam.cornerTh, cornerParam.scale, cornerParam.minEndingGap, cornerParam.minEndingGap_z,
cornerParam.jumpCornerTh_1, cornerParam.jumpCornerTh_2);
LOG_INFO("[Algo Thread] filterParam: continuityTh=%.2f, outlierTh=%.2f\n",
filterParam.continuityTh, filterParam.outlierTh);
LOG_INFO("[Algo Thread] growParam: maxLineSkipNum=%d, yDeviation_max=%.2f, maxSkipDistance=%.2f, zDeviation_max=%.2f, minLTypeTreeLen=%.2f, minVTypeTreeLen=%.2f\n",
growParam.maxLineSkipNum, growParam.yDeviation_max, growParam.maxSkipDistance,
growParam.zDeviation_max, growParam.minLTypeTreeLen, growParam.minVTypeTreeLen);
// 打印手眼标定矩阵
LOG_INFO("[Algo Thread] Hand-Eye Calibration Matrix:\n");
for (int r = 0; r < 4; ++r) {
LOG_INFO(" [%.4f, %.4f, %.4f, %.4f]\n",
clibMatrix[r * 4 + 0], clibMatrix[r * 4 + 1], clibMatrix[r * 4 + 2], clibMatrix[r * 4 + 3]);
}
}
CVrTimeUtils oTimeUtils;
LOG_DEBUG("before wd_HolePositioning\n");
// ========== 调用 wd_HolePositioning 算法 ==========
std::vector<WD_HolePositionInfo> holePositioning;
int errCode = 0;
wd_HolePositioning(xyzData, lineSegPara, cornerParam, filterParam, growParam, holePositioning, &errCode);
LOG_DEBUG("after wd_HolePositioning\n");
LOG_INFO("wd_HolePositioning: found %zu holes, err=%d runtime=%.3fms\n",
holePositioning.size(), errCode, oTimeUtils.GetElapsedTimeInMilliSec());
// 记录统计信息到检测结果
detectionResult.totalCandidates = static_cast<int>(holePositioning.size());
detectionResult.filteredCount = 0;
if (errCode != 0) {
LOG_ERROR("wd_HolePositioning failed with error code: %d\n", errCode);
return errCode;
}
// ========== 构建手眼标定齐次变换矩阵 ==========
// T_end_to_cam从4x4 clibMatrix数组
CTHomogeneousMatrix handEyeHM;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
handEyeHM.at(i, j) = clibMatrix[i * 4 + j];
}
}
// 构建机器人法兰位姿齐次变换矩阵 T_base_to_end
CTRobotPose ctRobotPose = CTRobotPose::fromDegrees(
robotPose.x, robotPose.y, robotPose.z,
robotPose.roll, robotPose.pitch, robotPose.yaw);
CTHomogeneousMatrix T_robot = ctRobotPose.toHomogeneousMatrix();
// 眼在手上T_total = T_base_to_end * T_end_to_cam
CTHomogeneousMatrix T_total = T_robot * handEyeHM;
// 构建用于可视化的孔洞中心点列表
std::vector<std::pair<SVzNL3DPointF, float>> holeVisData; // center + radius
// ========== 处理每个检测到的孔洞 ==========
for (size_t i = 0; i < holePositioning.size(); i++) {
const WD_HolePositionInfo& holeInfo = holePositioning[i];
// 保存孔洞详细信息center和normDir来自算法结果
HoleResultData holeData;
holeData.centerX = static_cast<float>(holeInfo.center.x);
holeData.centerY = static_cast<float>(holeInfo.center.y);
holeData.centerZ = static_cast<float>(holeInfo.center.z);
holeData.normalX = static_cast<float>(holeInfo.normDir.x);
holeData.normalY = static_cast<float>(holeInfo.normDir.y);
holeData.normalZ = static_cast<float>(holeInfo.normDir.z);
// wd_HolePositioning 不输出 radius/depth/eccentricity 等设为0
holeData.radius = 0.0f;
holeData.depth = 0.0f;
holeData.eccentricity = 0.0f;
holeData.radiusVariance = 0.0f;
holeData.angularSpan = 0.0f;
holeData.qualityScore = 0.0f;
detectionResult.holeDetails.push_back(holeData);
// ========== 六轴转换:位置+姿态(转换到机械臂坐标系) ==========
// 1. 位置转换:使用齐次变换矩阵进行坐标变换
CTVec3D ptEye(holeInfo.center.x, holeInfo.center.y, holeInfo.center.z);
CTVec3D ptRobot = T_total.transformPoint(ptEye);
// 2. 姿态转换:从法向量构建旋转矩阵
// Z轴 = 法向量(归一化)
CTVec3D Z_axis(holeInfo.normDir.x, holeInfo.normDir.y, holeInfo.normDir.z);
double normZ = Z_axis.norm();
if (normZ > 1e-6) {
Z_axis = Z_axis * (1.0 / normZ);
}
// 选择 X 轴:使用上方向 (0,1,0) 叉乘 Z_axis
CTVec3D up(0.0, 1.0, 0.0);
CTVec3D X_axis = crossProduct(up, Z_axis);
double normX = X_axis.norm();
// 如果 up 和 Z_axis 平行,使用 (1,0,0) 作为备选
if (normX < 1e-6) {
up = CTVec3D(1.0, 0.0, 0.0);
X_axis = crossProduct(up, Z_axis);
normX = X_axis.norm();
}
if (normX > 1e-6) {
X_axis = X_axis * (1.0 / normX);
}
// Y轴 = Z x X
CTVec3D Y_axis = crossProduct(Z_axis, X_axis);
// 构建方向向量列表eye坐标系
CTVec3D dirVectors_eye[3];
dirVectors_eye[0] = X_axis;
dirVectors_eye[1] = Y_axis;
dirVectors_eye[2] = Z_axis;
// 根据配置决定方向向量反向
switch (dirVectorInvert) {
case DIR_INVERT_NONE:
break;
case DIR_INVERT_XY:
dirVectors_eye[0] = dirVectors_eye[0] * (-1.0);
dirVectors_eye[1] = dirVectors_eye[1] * (-1.0);
break;
case DIR_INVERT_XZ:
dirVectors_eye[0] = dirVectors_eye[0] * (-1.0);
dirVectors_eye[2] = dirVectors_eye[2] * (-1.0);
break;
case DIR_INVERT_YZ:
default:
dirVectors_eye[1] = dirVectors_eye[1] * (-1.0);
dirVectors_eye[2] = dirVectors_eye[2] * (-1.0);
break;
}
// 使用齐次变换矩阵对方向向量进行旋转变换(仅旋转,不平移)
CTVec3D dirVectors_robot[3];
for (int j = 0; j < 3; j++) {
dirVectors_robot[j] = T_total.transformVector(dirVectors_eye[j]);
}
// 构建旋转矩阵
CTRotationMatrix R_pose;
R_pose.at(0, 0) = dirVectors_robot[0].x;
R_pose.at(0, 1) = dirVectors_robot[1].x;
R_pose.at(0, 2) = dirVectors_robot[2].x;
R_pose.at(1, 0) = dirVectors_robot[0].y;
R_pose.at(1, 1) = dirVectors_robot[1].y;
R_pose.at(1, 2) = dirVectors_robot[2].y;
R_pose.at(2, 0) = dirVectors_robot[0].z;
R_pose.at(2, 1) = dirVectors_robot[1].z;
R_pose.at(2, 2) = dirVectors_robot[2].z;
// 使用 CCoordinateTransform::rotationMatrixToEuler 转换为欧拉角外旋ZYX即RZ-RY-RX
CTEulerAngles robotRpyRad = CCoordinateTransform::rotationMatrixToEuler(R_pose, CTEulerOrder::sZYX);
double roll_deg, pitch_deg, yaw_deg;
robotRpyRad.toDegrees(roll_deg, pitch_deg, yaw_deg);
// 将机器人坐标系下的位姿添加到positions列表
HolePosition centerRobotPos;
centerRobotPos.x = ptRobot.x;
centerRobotPos.y = ptRobot.y;
centerRobotPos.z = ptRobot.z;
centerRobotPos.roll = roll_deg;
centerRobotPos.pitch = pitch_deg;
centerRobotPos.yaw = yaw_deg;
detectionResult.positions.push_back(centerRobotPos);
// 保存可视化数据半径设为0因为 wd_HolePositioning 不输出半径)
SVzNL3DPointF visCenter;
visCenter.x = static_cast<float>(holeInfo.center.x);
visCenter.y = static_cast<float>(holeInfo.center.y);
visCenter.z = static_cast<float>(holeInfo.center.z);
holeVisData.push_back(std::make_pair(visCenter, 0.0f));
if (debugParam.enableDebug && debugParam.printDetailLog) {
LOG_INFO("[Algo Thread] Hole[%zu] Normal (eye): (%.3f, %.3f, %.3f)\n",
i, holeInfo.normDir.x, holeInfo.normDir.y, holeInfo.normDir.z);
LOG_INFO("[Algo Thread] Hole[%zu] Direction vectors (robot): X=(%.3f,%.3f,%.3f), Y=(%.3f,%.3f,%.3f), Z=(%.3f,%.3f,%.3f)\n",
i,
dirVectors_robot[0].x, dirVectors_robot[0].y, dirVectors_robot[0].z,
dirVectors_robot[1].x, dirVectors_robot[1].y, dirVectors_robot[1].z,
dirVectors_robot[2].x, dirVectors_robot[2].y, dirVectors_robot[2].z);
LOG_INFO("[Algo Thread] Hole[%zu] Center Eye Coords: X=%.2f, Y=%.2f, Z=%.2f\n",
i, holeInfo.center.x, holeInfo.center.y, holeInfo.center.z);
LOG_INFO("[Algo Thread] Hole[%zu] Center Robot Coords: X=%.2f, Y=%.2f, Z=%.2f, R=%.4f, P=%.4f, Y=%.4f\n",
i, ptRobot.x, ptRobot.y, ptRobot.z,
centerRobotPos.roll, centerRobotPos.pitch, centerRobotPos.yaw);
}
}
// ========== 生成可视化图像 ==========
{
std::vector<HoleMarkerInfo> holes;
for (const auto& vis : holeVisData) {
HoleMarkerInfo h;
h.center.x = vis.first.x;
h.center.y = vis.first.y;
h.center.z = vis.first.z;
h.radius = vis.second; // 半径为0
holes.push_back(h);
}
2026-03-26 13:40:17 +08:00
detectionResult.image = PointCloudImageUtils::GenerateHoleDetectionImage(
xyzData, holes,
0.0, // 绕X轴旋转角度可按需调整视图
0.0); // 绕Y轴旋转角度可按需调整视图
2026-03-26 08:30:31 +08:00
}
if (debugParam.enableDebug && debugParam.saveDebugImage) {
std::string fileName = debugParam.debugOutputPath + "/Image_" + std::to_string(cameraIndex) + "_" + timeStamp + ".png";
LOG_INFO("[Algo Thread] Debug image saved image : %s\n", fileName.c_str());
if (!detectionResult.image.isNull()) {
QString qFileName = QString::fromStdString(fileName);
detectionResult.image.save(qFileName);
} else {
LOG_WARNING("[Algo Thread] No valid image to save for debug\n");
}
}
return nRet;
}