#include "DetectPresenter.h" #include "workpieceHolePositioning_Export.h" #include "SG_baseDataType.h" #include #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>& 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>) std::vector> 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 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(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> 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(holeInfo.center.x); holeData.centerY = static_cast(holeInfo.center.y); holeData.centerZ = static_cast(holeInfo.center.z); holeData.normalX = static_cast(holeInfo.normDir.x); holeData.normalY = static_cast(holeInfo.normDir.y); holeData.normalZ = static_cast(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(holeInfo.center.x); visCenter.y = static_cast(holeInfo.center.y); visCenter.z = static_cast(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 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); } detectionResult.image = PointCloudImageUtils::GenerateHoleDetectionImage( xyzData, holes, 0.0, // 绕X轴旋转角度(度),可按需调整视图 0.0); // 绕Y轴旋转角度(度),可按需调整视图 } 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; }