329 lines
12 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "DetectPresenter.h"
#include "HoleDetection.h"
#include "HoleDetectionParams.h"
#include "AlgoParamConverter.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 (HoleDetection)\n");
}
DetectPresenter::~DetectPresenter()
{
}
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格式
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);
}
// 将 xyzData 转换为 SVzNLPointXYZ* 网格数据
int rows = static_cast<int>(xyzData.size());
int cols = 0;
for (const auto& row : xyzData) {
if (static_cast<int>(row.size()) > cols) {
cols = static_cast<int>(row.size());
}
}
if (rows <= 0 || cols <= 0) {
LOG_WARNING("Invalid grid dimensions: rows=%d, cols=%d\n", rows, cols);
return ERR_CODE(DEV_DATA_INVALID);
}
LOG_INFO("[Algo Thread] Grid dimensions: rows=%d, cols=%d\n", rows, cols);
// 分配网格数组(行优先)
SVzNLPointXYZ* points = new SVzNLPointXYZ[rows * cols];
memset(points, 0, sizeof(SVzNLPointXYZ) * rows * cols);
// 填充有效点,缺失点用 {0,0,0} 填充
for (int r = 0; r < rows; r++) {
int rowCols = static_cast<int>(xyzData[r].size());
for (int c = 0; c < cols; c++) {
int idx = r * cols + c;
if (c < rowCols) {
points[idx].x = static_cast<float>(xyzData[r][c].pt3D.x);
points[idx].y = static_cast<float>(xyzData[r][c].pt3D.y);
points[idx].z = static_cast<float>(xyzData[r][c].pt3D.z);
} else {
points[idx].x = 0.0f;
points[idx].y = 0.0f;
points[idx].z = 0.0f;
}
}
}
// 使用 AlgoParamConverter 进行参数转换
SHoleDetectionParams detectionParams = AlgoParamConverter::ToAlgoParam(algorithmParams.detectionParam);
SHoleFilterParams filterParams = AlgoParamConverter::ToAlgoParam(algorithmParams.filterParam);
if (debugParam.enableDebug && debugParam.printDetailLog) {
AlgoParamConverter::LogAlgoParams("[Algo Thread]", detectionParams, filterParams, clibMatrix);
}
CVrTimeUtils oTimeUtils;
LOG_DEBUG("before DetectMultipleHoles\n");
// 调用孔洞检测算法
SMultiHoleResult multiResult;
int errCode = DetectMultipleHoles(
points,
rows,
cols,
detectionParams,
filterParams,
&multiResult
);
LOG_DEBUG("after DetectMultipleHoles\n");
LOG_INFO("DetectMultipleHoles: found %d holes (candidates=%d, filtered=%d), err=%d runtime=%.3fms\n",
multiResult.holeCount, multiResult.totalCandidates, multiResult.filteredCount,
errCode, oTimeUtils.GetElapsedTimeInMilliSec());
// 记录统计信息到检测结果
detectionResult.totalCandidates = multiResult.totalCandidates;
detectionResult.filteredCount = multiResult.filteredCount;
if (errCode != HD_SUCCESS) {
LOG_ERROR("DetectMultipleHoles failed with error code: %d\n", errCode);
delete[] points;
FreeMultiHoleResult(&multiResult);
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 (int i = 0; i < multiResult.holeCount; i++) {
const SHoleResult& hole = multiResult.holes[i];
// 保存孔洞详细信息
HoleResultData holeData;
holeData.centerX = hole.center.x;
holeData.centerY = hole.center.y;
holeData.centerZ = hole.center.z;
holeData.normalX = hole.normal.x;
holeData.normalY = hole.normal.y;
holeData.normalZ = hole.normal.z;
holeData.radius = hole.radius;
holeData.depth = hole.depth;
holeData.eccentricity = hole.eccentricity;
holeData.radiusVariance = hole.radiusVariance;
holeData.angularSpan = hole.angularSpan;
holeData.qualityScore = hole.qualityScore;
detectionResult.holeDetails.push_back(holeData);
// 六轴转换:位置+姿态(转换到机械臂坐标系)
// 1. 位置转换:使用齐次变换矩阵进行坐标变换
CTVec3D ptEye(hole.center.x, hole.center.y, hole.center.z);
CTVec3D ptRobot = T_total.transformPoint(ptEye);
// 2. 姿态转换:从法向量构建旋转矩阵
// Z轴 = 法向量(归一化)
CTVec3D Z_axis(hole.normal.x, hole.normal.y, hole.normal.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
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);
// 保存可视化数据
holeVisData.push_back(std::make_pair(hole.center, hole.radius));
if (debugParam.enableDebug && debugParam.printDetailLog) {
LOG_INFO("[Algo Thread] Hole[%d] Normal (eye): (%.3f, %.3f, %.3f)\n",
i, hole.normal.x, hole.normal.y, hole.normal.z);
LOG_INFO("[Algo Thread] Hole[%d] 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[%d] Center Eye Coords: X=%.2f, Y=%.2f, Z=%.2f\n",
i, hole.center.x, hole.center.y, hole.center.z);
LOG_INFO("[Algo Thread] Hole[%d] 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);
LOG_INFO("[Algo Thread] Hole[%d] Radius=%.2f, Depth=%.2f, Quality=%.3f\n",
i, hole.radius, hole.depth, hole.qualityScore);
}
}
// 释放算法结果内存
FreeMultiHoleResult(&multiResult);
// 从点云数据生成投影图像(热力图着色 + 孔位标记)
{
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;
holes.push_back(h);
}
detectionResult.image = PointCloudImageUtils::GenerateHoleDetectionImage(xyzData, holes);
}
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");
}
}
// 释放网格数组
delete[] points;
return nRet;
}