2026-02-01 14:51:16 +08:00

377 lines
16 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 "workpieceHolePositioning_Export.h"
#include <fstream>
#include <QPainter>
#include <QPen>
#include <QColor>
#include "CoordinateTransform.h"
#include "VrConvert.h"
DetectPresenter::DetectPresenter(/* args */)
{
LOG_DEBUG("DetectPresenter Init algo ver: %s\n", wd_workpieceHolePositioningVersion());
}
DetectPresenter::~DetectPresenter()
{
}
int DetectPresenter::DetectWorkpieceHole(
int cameraIndex,
std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& laserLines,
const VrAlgorithmParams& algorithmParams,
const VrDebugParam& debugParam,
LaserDataLoader& dataLoader,
const double clibMatrix[16],
int eulerOrder,
WorkpieceHoleDetectionResult& detectionResult)
{
if (laserLines.empty()) {
LOG_WARNING("No laser lines data available\n");
return ERR_CODE(DEV_DATA_INVALID);
}
// 获取当前相机的校准参数
VrCameraPlaneCalibParam cameraCalibParamValue;
const VrCameraPlaneCalibParam* cameraCalibParam = nullptr;
if (algorithmParams.planeCalibParam.GetCameraCalibParam(cameraIndex, cameraCalibParamValue)) {
cameraCalibParam = &cameraCalibParamValue;
}
// 保存debug数据
std::string timeStamp = CVrDateUtils::GetNowTime();
if(debugParam.enableDebug && debugParam.savePointCloud){
LOG_INFO("[Algo Thread] Debug mode is enabled, saving point cloud data\n");
// 获取当前时间戳格式为YYYYMMDDHHMMSS
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);
}
// 工件孔参数
WD_workpieceHoleParam workpiecePara;
workpiecePara.workpieceType = algorithmParams.workpieceHoleParam.workpieceType;
workpiecePara.holeDiameter = algorithmParams.workpieceHoleParam.holeDiameter;
workpiecePara.holeDist_L = algorithmParams.workpieceHoleParam.holeDist_L;
workpiecePara.holeDist_W = algorithmParams.workpieceHoleParam.holeDist_W;
// 线段分割参数
SSG_lineSegParam lineSegPara;
lineSegPara.distScale = algorithmParams.lineSegParam.distScale;
lineSegPara.segGapTh_y = algorithmParams.lineSegParam.segGapTh_y;
lineSegPara.segGapTh_z = algorithmParams.lineSegParam.segGapTh_z;
// 滤波参数
SSG_outlierFilterParam filterParam;
filterParam.continuityTh = algorithmParams.filterParam.continuityTh;
filterParam.outlierTh = algorithmParams.filterParam.outlierTh;
// 树生长参数
SSG_treeGrowParam growParam;
growParam.yDeviation_max = algorithmParams.growParam.yDeviation_max;
growParam.zDeviation_max = algorithmParams.growParam.zDeviation_max;
growParam.maxLineSkipNum = algorithmParams.growParam.maxLineSkipNum;
growParam.maxSkipDistance = algorithmParams.growParam.maxSkipDistance;
growParam.minLTypeTreeLen = algorithmParams.growParam.minLTypeTreeLen;
growParam.minVTypeTreeLen = algorithmParams.growParam.minVTypeTreeLen;
if(debugParam.enableDebug && debugParam.printDetailLog)
{
LOG_INFO("[Algo Thread] clibMatrix: \n\t[%.3f, %.3f, %.3f, %.3f] \n\t[ %.3f, %.3f, %.3f, %.3f] \n\t[ %.3f, %.3f, %.3f, %.3f] \n\t[ %.3f, %.3f, %.3f, %.3f]\n",
clibMatrix[0], clibMatrix[1], clibMatrix[2], clibMatrix[3],
clibMatrix[4], clibMatrix[5], clibMatrix[6], clibMatrix[7],
clibMatrix[8], clibMatrix[9], clibMatrix[10], clibMatrix[11],
clibMatrix[12], clibMatrix[13], clibMatrix[14], clibMatrix[15]);
// 打印工件孔参数
LOG_INFO("[Algo Thread] WorkpieceHole: type=%d, holeDiameter=%.1f, holeDist_L=%.1f, holeDist_W=%.1f\n",
workpiecePara.workpieceType, workpiecePara.holeDiameter, workpiecePara.holeDist_L, workpiecePara.holeDist_W);
// 打印线段分割参数
LOG_INFO("[Algo Thread] LineSeg: distScale=%.1f, segGapTh_y=%.1f, segGapTh_z=%.1f\n",
lineSegPara.distScale, lineSegPara.segGapTh_y, lineSegPara.segGapTh_z);
// 打印树生长参数
LOG_INFO("[Algo Thread] Tree Grow: yDeviation_max=%.1f, zDeviation_max=%.1f, maxLineSkipNum=%d, maxSkipDistance=%.1f, minLTypeTreeLen=%.1f, minVTypeTreeLen=%.1f\n",
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);
}
// 准备平面校准参数
SSG_planeCalibPara groundCalibPara;
if(cameraCalibParam){
memcpy(groundCalibPara.planeCalib, cameraCalibParam->planeCalib, sizeof(double) * 9);
memcpy(groundCalibPara.invRMatrix, cameraCalibParam->invRMatrix, sizeof(double) * 9);
groundCalibPara.planeHeight = cameraCalibParam->planeHeight;
} else {
// 使用默认单位矩阵
double identity[9] = {1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0};
memcpy(groundCalibPara.planeCalib, identity, sizeof(double) * 9);
memcpy(groundCalibPara.invRMatrix, identity, sizeof(double) * 9);
groundCalibPara.planeHeight = -1.0;
}
if(debugParam.enableDebug && debugParam.printDetailLog)
{
LOG_INFO("Plane height: %.3f\n", groundCalibPara.planeHeight);
LOG_INFO(" Plane calibration matrix: [%f, %f, %f; %f, %f, %f; %f, %f, %f]\n",
groundCalibPara.planeCalib[0], groundCalibPara.planeCalib[1], groundCalibPara.planeCalib[2],
groundCalibPara.planeCalib[3], groundCalibPara.planeCalib[4], groundCalibPara.planeCalib[5],
groundCalibPara.planeCalib[6], groundCalibPara.planeCalib[7], groundCalibPara.planeCalib[8]);
LOG_INFO(" Plane invRMatrix matrix: [%f, %f, %f; %f, %f, %f; %f, %f, %f]\n",
groundCalibPara.invRMatrix[0], groundCalibPara.invRMatrix[1], groundCalibPara.invRMatrix[2],
groundCalibPara.invRMatrix[3], groundCalibPara.invRMatrix[4], groundCalibPara.invRMatrix[5],
groundCalibPara.invRMatrix[6], groundCalibPara.invRMatrix[7], groundCalibPara.invRMatrix[8]);
}
// 数据预处理:调平和去除地面(使用当前相机的调平参数)
if(cameraCalibParam){
LOG_DEBUG("Processing data with plane calibration\n");
double groundH = -1;
for(size_t i = 0; i < xyzData.size(); i++){
wd_lineDataR(xyzData[i], cameraCalibParam->planeCalib, groundH);
}
}
int errCode = 0;
CVrTimeUtils oTimeUtils;
LOG_DEBUG("before wd_workpieceHolePositioning \n");
// 调用工件孔定位算法
std::vector<WD_workpieceInfo> workpiecePositioning;
wd_workpieceHolePositioning(
xyzData,
workpiecePara,
lineSegPara,
filterParam,
growParam,
groundCalibPara,
workpiecePositioning,
&errCode
);
LOG_DEBUG("after wd_workpieceHolePositioning \n");
LOG_INFO("wd_workpieceHolePositioning: found %zu workpieces, err=%d runtime=%.3fms\n", workpiecePositioning.size(), errCode, oTimeUtils.GetElapsedTimeInMilliSec());
ERR_CODE_RETURN(errCode);
// 直接使用4x4齐次变换矩阵避免欧拉角转换误差
CTHomogeneousMatrix calibHMatrix;
memcpy(calibHMatrix.data, clibMatrix, sizeof(double) * 16);
// 构建用于可视化的点数组
std::vector<std::vector<SVzNL3DPoint>> objOps;
// 处理每个工件的检测结果
for (size_t i = 0; i < workpiecePositioning.size(); i++) {
const WD_workpieceInfo& workpiece = workpiecePositioning[i];
// 保存工件类型(仅保存第一个工件的类型)
if (i == 0) {
detectionResult.workpieceType = workpiece.workpieceType;
}
// 六轴转换:位置+姿态(转换到机械臂坐标系)
SVzNL3DPoint centerEye = workpiece.center;
// 从方向向量构建旋转矩阵
// 算法输出的 x_dir, y_dir, z_dir 是工件坐标系的三个轴在相机坐标系下的方向
CTRotationMatrix rotMatrix;
// 第一列X轴方向
rotMatrix.at(0, 0) = workpiece.x_dir.x;
rotMatrix.at(1, 0) = workpiece.x_dir.y;
rotMatrix.at(2, 0) = workpiece.x_dir.z;
// 第二列Y轴方向
rotMatrix.at(0, 1) = workpiece.y_dir.x;
rotMatrix.at(1, 1) = workpiece.y_dir.y;
rotMatrix.at(2, 1) = workpiece.y_dir.z;
// 第三列Z轴方向
rotMatrix.at(0, 2) = workpiece.z_dir.x;
rotMatrix.at(1, 2) = workpiece.z_dir.y;
rotMatrix.at(2, 2) = workpiece.z_dir.z;
// 将旋转矩阵转换为欧拉角(使用配置的旋转顺序)
CTEulerAngles eulerAngles = CCoordinateTransform::rotationMatrixToEuler(rotMatrix, static_cast<CTEulerOrder>(eulerOrder));
// 构建相机坐标系下的完整位姿
CTCameraPose cameraPose;
cameraPose.x = centerEye.x;
cameraPose.y = centerEye.y;
cameraPose.z = centerEye.z;
cameraPose.rx = eulerAngles.roll;
cameraPose.ry = eulerAngles.pitch;
cameraPose.rz = eulerAngles.yaw;
// 进行六轴Eye-to-Hand转换位置+姿态)
// 使用配置的旋转顺序
CTRobotPose robotPose;
CCoordinateTransform::sixAxisEyeToHandCalcGraspPose(cameraPose, calibHMatrix, static_cast<CTEulerOrder>(eulerOrder), robotPose);
// 将机器人坐标系下的位姿添加到positions列表
HolePosition centerRobotPos;
centerRobotPos.x = robotPose.x;
centerRobotPos.y = robotPose.y;
centerRobotPos.z = robotPose.z;
centerRobotPos.roll = CCoordinateTransform::radToDeg(robotPose.rx);
centerRobotPos.pitch = CCoordinateTransform::radToDeg(robotPose.ry);
centerRobotPos.yaw = CCoordinateTransform::radToDeg(robotPose.rz);
detectionResult.positions.push_back(centerRobotPos);
// 添加中心点到可视化列表
std::vector<SVzNL3DPoint> holePoints;
for(size_t j = 0; j < workpiece.holes.size(); j++){
SVzNL3DPoint holeEye = workpiece.holes[j];
holePoints.push_back(holeEye);
}
holePoints.push_back(centerEye);
objOps.push_back(holePoints);
if(debugParam.enableDebug && debugParam.printDetailLog){
LOG_INFO("[Algo Thread] Direction vectors: X=(%.3f,%.3f,%.3f), Y=(%.3f,%.3f,%.3f), Z=(%.3f,%.3f,%.3f)\n",
workpiece.x_dir.x, workpiece.x_dir.y, workpiece.x_dir.z,
workpiece.y_dir.x, workpiece.y_dir.y, workpiece.y_dir.z,
workpiece.z_dir.x, workpiece.z_dir.y, workpiece.z_dir.z);
LOG_INFO("[Algo Thread] Center Eye Coords: X=%.2f, Y=%.2f, Z=%.2f, R=%.4f, P=%.4f, Y=%.4f\n",
cameraPose.x, cameraPose.y, cameraPose.z,
CCoordinateTransform::radToDeg(cameraPose.rx), CCoordinateTransform::radToDeg(cameraPose.ry), CCoordinateTransform::radToDeg(cameraPose.rz));
LOG_INFO("[Algo Thread] Center Robot Coords: X=%.2f, Y=%.2f, Z=%.2f, R=%.4f, P=%.4f, Y=%.4f\n",
robotPose.x, robotPose.y, robotPose.z,
CCoordinateTransform::radToDeg(robotPose.rx), CCoordinateTransform::radToDeg(robotPose.ry), CCoordinateTransform::radToDeg(robotPose.rz));
}
}
// 从点云数据生成投影图像(单色灰色点云 + 红色孔位标记)
{
// 固定图像尺寸
int imgRows = 992;
int imgCols = 1056;
int x_skip = 50;
int y_skip = 50;
// 计算点云范围
double xMin = 1e10, xMax = -1e10, yMin = 1e10, yMax = -1e10;
for (const auto& line : xyzData) {
for (const auto& pt : line) {
if (pt.pt3D.z < 1e-4) continue;
xMin = std::min(xMin, (double)pt.pt3D.x);
xMax = std::max(xMax, (double)pt.pt3D.x);
yMin = std::min(yMin, (double)pt.pt3D.y);
yMax = std::max(yMax, (double)pt.pt3D.y);
}
}
// 不扩展边界,直接使用点云范围
// 计算投影比例
double y_rows = (double)(imgRows - y_skip * 2);
double x_cols = (double)(imgCols - x_skip * 2);
double x_scale = (xMax - xMin) / x_cols;
double y_scale = (yMax - yMin) / y_rows;
if (x_scale < y_scale)
x_scale = y_scale;
else
y_scale = x_scale;
// 创建图像
QImage image(imgCols, imgRows, QImage::Format_RGB888);
image.fill(Qt::black);
QPainter painter(&image);
painter.setRenderHint(QPainter::Antialiasing);
// 绘制点云数据 - 使用单色灰色
QColor grayColor(150, 150, 150);
for (const auto& scanLine : xyzData) {
for (const auto& point : scanLine) {
if (point.pt3D.z < 1e-4) continue;
int px = (int)((point.pt3D.x - xMin) / x_scale + x_skip);
int py = (int)((point.pt3D.y - yMin) / y_scale + y_skip);
if (px >= 0 && px < imgCols && py >= 0 && py < imgRows) {
painter.setPen(QPen(grayColor, 1));
painter.drawPoint(px, py);
}
}
}
// 绘制孔位标记 - 使用红色
if (!objOps.empty()) {
QColor holeColor(255, 0, 0); // 红色
painter.setPen(QPen(holeColor, 1));
painter.setBrush(QBrush(holeColor));
for (size_t i = 0; i < objOps.size(); i++) {
const auto& holeGroup = objOps[i];
size_t groupSize = holeGroup.size();
for (size_t j = 0; j < groupSize; j++) {
const SVzNL3DPoint& hole = holeGroup[j];
// 跳过全0的点
if (fabs(hole.x) < 0.0001 && fabs(hole.y) < 0.0001 && fabs(hole.z) < 0.0001) {
continue;
}
int px = (int)((hole.x - xMin) / x_scale + x_skip);
int py = (int)((hole.y - yMin) / y_scale + y_skip);
if (px >= 0 && px < imgCols && py >= 0 && py < imgRows) {
// 绘制圆点标记
int circleSize = 2;
painter.drawEllipse(px - circleSize/2, py - circleSize/2, circleSize, circleSize);
// 只有中心点(最后一个点)才绘制编号
if (j == groupSize - 1) {
painter.setPen(QPen(Qt::white, 1));
QFont font("Arial", 14, QFont::Bold);
painter.setFont(font);
painter.drawText(px + 8, py + 6, QString("%1").arg(i + 1));
// 恢复画笔
painter.setPen(QPen(holeColor, 1));
painter.setBrush(QBrush(holeColor));
}
}
}
}
}
detectionResult.image = image;
}
if(debugParam.enableDebug && debugParam.saveDebugImage){
// 获取当前时间戳格式为YYYYMMDDHHMMSS
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;
}