332 lines
14 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 "AlgorithmParamConverter.h"
#include "PoseAxesBuilder.h"
#include "DiscHolePoseTCPProtocol.h"
#include "workpieceHolePositioning_Export.h"
#include <QColor>
namespace {
QImage BuildDiscHolePointCloudImage(const std::vector<std::vector<SVzNL3DPosition>>& xyzData,
const WD_HolePositionInfo& holeInfo,
bool hasResult)
{
PointCloudCanvas canvas = PointCloudCanvas::Create(xyzData);
if (!canvas.isValid()) {
return QImage();
}
if (hasResult) {
constexpr double kNormalLineLength = 60.0;
const QColor centerColor(0, 255, 0);
const QColor normalColor(0, 180, 255); // 蓝色:法向
// 绘制孔中心点
canvas.drawPoint(holeInfo.center.x, holeInfo.center.y, centerColor, 10);
// 绘制法向量方向线
const double normalEndX = holeInfo.center.x + kNormalLineLength * holeInfo.normDir.x;
const double normalEndY = holeInfo.center.y + kNormalLineLength * holeInfo.normDir.y;
canvas.drawLine(holeInfo.center.x, holeInfo.center.y, normalEndX, normalEndY, normalColor, 2);
canvas.drawText(normalEndX, normalEndY, QStringLiteral("Z"), normalColor, 14, 6, -6);
}
return canvas.image().copy();
}
QImage BuildDiscRackPointCloudImage(const std::vector<std::vector<SVzNL3DPosition>>& xyzData,
const SSG_pointPose& poseInfo,
bool hasResult)
{
PointCloudCanvas canvas = PointCloudCanvas::Create(xyzData);
if (!canvas.isValid()) {
return QImage();
}
if (hasResult) {
constexpr double kAxisLineLength = 60.0;
const QColor centerColor(0, 255, 0);
const QColor xAxisColor(255, 0, 0); // 红色X 轴
const QColor yAxisColor(0, 255, 0); // 绿色Y 轴
const QColor zAxisColor(0, 180, 255); // 蓝色Z 轴
// 绘制架子中心点
canvas.drawPoint(poseInfo.point.x, poseInfo.point.y, centerColor, 10);
// 绘制 X 轴方向线
const double xEndX = poseInfo.point.x + kAxisLineLength * poseInfo.pose_x.x;
const double xEndY = poseInfo.point.y + kAxisLineLength * poseInfo.pose_x.y;
canvas.drawLine(poseInfo.point.x, poseInfo.point.y, xEndX, xEndY, xAxisColor, 2);
canvas.drawText(xEndX, xEndY, QStringLiteral("X"), xAxisColor, 14, 6, -6);
// 绘制 Y 轴方向线
const double yEndX = poseInfo.point.x + kAxisLineLength * poseInfo.pose_y.x;
const double yEndY = poseInfo.point.y + kAxisLineLength * poseInfo.pose_y.y;
canvas.drawLine(poseInfo.point.x, poseInfo.point.y, yEndX, yEndY, yAxisColor, 2);
canvas.drawText(yEndX, yEndY, QStringLiteral("Y"), yAxisColor, 14, 6, -6);
// 绘制 Z 轴方向线
const double zEndX = poseInfo.point.x + kAxisLineLength * poseInfo.pose_z.x;
const double zEndY = poseInfo.point.y + kAxisLineLength * poseInfo.pose_z.y;
canvas.drawLine(poseInfo.point.x, poseInfo.point.y, zEndX, zEndY, zAxisColor, 2);
canvas.drawText(zEndX, zEndY, QStringLiteral("Z"), zAxisColor, 14, 6, -6);
}
return canvas.image().copy();
}
void SaveDebugImageIfNeeded(int cameraIndex,
const VrDebugParam& debugParam,
const QImage& image,
const QString& prefix)
{
if (!debugParam.enableDebug || !debugParam.saveDebugImage || image.isNull()) {
return;
}
const std::string timeStamp = CVrDateUtils::GetNowTime();
const std::string fileName = debugParam.debugOutputPath + "/" +
prefix.toStdString() + "_" + std::to_string(cameraIndex) + "_" + timeStamp + ".png";
LOG_INFO("[Algo Thread] Debug image saved image : %s\n", fileName.c_str());
image.save(QString::fromStdString(fileName));
}
}
DetectPresenter::DetectPresenter(/* args */)
{
LOG_DEBUG("DetectPresenter Init algo ver: %s\n", wd_workpieceHolePositioningVersion());
}
DetectPresenter::~DetectPresenter()
{
}
QString DetectPresenter::GetAlgoVersion()
{
return QString(wd_workpieceHolePositioningVersion());
}
int DetectPresenter::DetectDiscHole(
int cameraIndex,
std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& laserLines,
const VrAlgorithmParams& algorithmParams,
const VrDebugParam& debugParam,
LaserDataLoader& dataLoader,
const double clibMatrix[16],
const RobotPose6D& robotPose,
const HandEyeExtrinsic& extrinsic,
int poseOutputOrder,
DetectionResult& detectionResult)
{
if (laserLines.empty()) {
LOG_WARNING("No laser lines data available for disc hole detection\n");
return ERR_CODE(DEV_DATA_INVALID);
}
std::vector<std::vector<SVzNL3DPosition>> xyzData;
int convertResult = dataLoader.ConvertToSVzNL3DPosition(laserLines, xyzData);
if (convertResult != SUCCESS || xyzData.empty()) {
LOG_WARNING("Failed to convert disc hole data to XYZ format or no XYZ data available\n");
return ERR_CODE(DEV_DATA_INVALID);
}
// 砂轮盘孔定位算法参数(仅使用 cornerParam
const DiscHoleAlgorithmParams algoParams = AlgorithmParamConverter::ToDiscHoleAlgorithmParams(algorithmParams);
const SSG_cornerParam& cornerParam = algoParams.cornerParam;
if (debugParam.enableDebug && debugParam.printDetailLog) {
LOG_INFO("[Algo Thread] DiscHole 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] DiscHole Corner: cornerTh=%.1f, scale=%.1f, minEndingGap=%.1f, minEndingGap_z=%.1f, jumpCornerTh_1=%.1f, jumpCornerTh_2=%.1f\n",
cornerParam.cornerTh, cornerParam.scale, cornerParam.minEndingGap,
cornerParam.minEndingGap_z, cornerParam.jumpCornerTh_1, cornerParam.jumpCornerTh_2);
LOG_INFO("[Algo Thread] DiscHole 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;
CVrTimeUtils oTimeUtils;
LOG_DEBUG("before sx_getDiscHolePose\n");
WD_HolePositionInfo holeInfo = sx_getDiscHolePose(xyzData, cornerParam, &errCode);
LOG_DEBUG("after sx_getDiscHolePose\n");
LOG_INFO("sx_getDiscHolePose: err=%d runtime=%.3fms\n", errCode, oTimeUtils.GetElapsedTimeInMilliSec());
ERR_CODE_RETURN(errCode);
detectionResult.success = true;
detectionResult.errorCode = 0;
detectionResult.message = QStringLiteral("砂轮盘孔检测成功");
// 使用 NormalDirToPoseAngles从 WD_HolePositionInfo 的 normDir+center 计算完整姿态
// (内部封装了手眼变换流水线:法兰姿态构造 -> eye-in-hand 变换 -> rot/outRot 补偿 -> 欧拉提取)
double outX, outY, outZ, outRoll, outPitch, outYaw;
if (!PoseAxesBuilder::NormalDirToPoseAngles(
holeInfo.center,
holeInfo.normDir,
clibMatrix,
extrinsic,
robotPose,
outX, outY, outZ,
outRoll, outPitch, outYaw,
poseOutputOrder)) {
LOG_WARNING("[Algo Thread] DiscHole pose transform failed: invalid normal or transform axes\n");
detectionResult.success = false;
detectionResult.errorCode = ERR_CODE(DEV_DATA_INVALID);
detectionResult.message = QStringLiteral("砂轮盘孔姿态计算失败");
return detectionResult.errorCode;
}
DiscHolePosition pos;
pos.x = outX;
pos.y = outY;
pos.z = outZ;
pos.roll = outRoll;
pos.pitch = outPitch;
pos.yaw = outYaw;
detectionResult.positions.push_back(pos);
DiscHoleInfo info;
info.centerX = holeInfo.center.x;
info.centerY = holeInfo.center.y;
info.centerZ = holeInfo.center.z;
info.normDirX = holeInfo.normDir.x;
info.normDirY = holeInfo.normDir.y;
info.normDirZ = holeInfo.normDir.z;
detectionResult.discHoleInfoList.push_back(info);
if (debugParam.enableDebug && debugParam.printDetailLog) {
LOG_INFO("[Algo Thread] DiscHole Eye Center: X=%.2f, Y=%.2f, Z=%.2f\n",
holeInfo.center.x, holeInfo.center.y, holeInfo.center.z);
LOG_INFO("[Algo Thread] DiscHole Eye NormDir: X=%.3f, Y=%.3f, Z=%.3f\n",
holeInfo.normDir.x, holeInfo.normDir.y, holeInfo.normDir.z);
LOG_INFO("[Algo Thread] DiscHole Robot Coords: X=%.2f, Y=%.2f, Z=%.2f, RPY=%.2f/%.2f/%.2f\n",
pos.x, pos.y, pos.z, pos.roll, pos.pitch, pos.yaw);
}
detectionResult.image = BuildDiscHolePointCloudImage(xyzData, holeInfo, true);
SaveDebugImageIfNeeded(cameraIndex, debugParam, detectionResult.image, QStringLiteral("DiscHole_Image"));
return SUCCESS;
}
int DetectPresenter::DetectDiscRack(
int cameraIndex,
std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& laserLines,
const VrAlgorithmParams& algorithmParams,
const VrDebugParam& debugParam,
LaserDataLoader& dataLoader,
const double clibMatrix[16],
const RobotPose6D& robotPose,
const HandEyeExtrinsic& extrinsic,
int poseOutputOrder,
DetectionResult& detectionResult)
{
if (laserLines.empty()) {
LOG_WARNING("No laser lines data available for disc rack detection\n");
return ERR_CODE(DEV_DATA_INVALID);
}
std::vector<std::vector<SVzNL3DPosition>> xyzData;
int convertResult = dataLoader.ConvertToSVzNL3DPosition(laserLines, xyzData);
if (convertResult != SUCCESS || xyzData.empty()) {
LOG_WARNING("Failed to convert disc rack data to XYZ format or no XYZ data available\n");
return ERR_CODE(DEV_DATA_INVALID);
}
// 砂轮盘架子定位算法参数(仅使用 cornerParam
const DiscHoleAlgorithmParams algoParams = AlgorithmParamConverter::ToDiscHoleAlgorithmParams(algorithmParams);
const SSG_cornerParam& cornerParam = algoParams.cornerParam;
if (debugParam.enableDebug && debugParam.printDetailLog) {
LOG_INFO("[Algo Thread] DiscRack 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] DiscRack Corner: cornerTh=%.1f, scale=%.1f, minEndingGap=%.1f, minEndingGap_z=%.1f, jumpCornerTh_1=%.1f, jumpCornerTh_2=%.1f\n",
cornerParam.cornerTh, cornerParam.scale, cornerParam.minEndingGap,
cornerParam.minEndingGap_z, cornerParam.jumpCornerTh_1, cornerParam.jumpCornerTh_2);
LOG_INFO("[Algo Thread] DiscRack 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;
CVrTimeUtils oTimeUtils;
LOG_DEBUG("before sx_getDiscRackCenterPosition\n");
SSG_pointPose pointPose = sx_getDiscRackCenterPosition(xyzData, cornerParam, &errCode);
LOG_DEBUG("after sx_getDiscRackCenterPosition\n");
LOG_INFO("sx_getDiscRackCenterPosition: err=%d runtime=%.3fms\n", errCode, oTimeUtils.GetElapsedTimeInMilliSec());
ERR_CODE_RETURN(errCode);
detectionResult.success = true;
detectionResult.errorCode = 0;
detectionResult.message = QStringLiteral("砂轮盘架子检测成功");
// 使用 PointPoseToEuler从 SSG_pointPose 的三轴向量 (pose_x/y/z) 构造旋转矩阵并提取欧拉
// (内部封装了手眼变换流水线:法兰姿态构造 -> eye-in-hand 变换 -> rot/outRot 补偿 -> 欧拉提取)
double outX, outY, outZ, outRoll, outPitch, outYaw;
if (!PoseAxesBuilder::PointPoseToEuler(
pointPose,
clibMatrix,
extrinsic,
robotPose,
outX, outY, outZ,
outRoll, outPitch, outYaw,
poseOutputOrder)) {
LOG_WARNING("[Algo Thread] DiscRack pose transform failed: invalid pose axes or transform axes\n");
detectionResult.success = false;
detectionResult.errorCode = ERR_CODE(DEV_DATA_INVALID);
detectionResult.message = QStringLiteral("砂轮盘架子姿态计算失败");
return detectionResult.errorCode;
}
DiscHolePosition pos;
pos.x = outX;
pos.y = outY;
pos.z = outZ;
pos.roll = outRoll;
pos.pitch = outPitch;
pos.yaw = outYaw;
detectionResult.positions.push_back(pos);
DiscRackInfo info;
info.centerX = pointPose.point.x;
info.centerY = pointPose.point.y;
info.centerZ = pointPose.point.z;
info.roll = outRoll;
info.pitch = outPitch;
info.yaw = outYaw;
detectionResult.discRackInfoList.push_back(info);
if (debugParam.enableDebug && debugParam.printDetailLog) {
LOG_INFO("[Algo Thread] DiscRack Eye Point: X=%.2f, Y=%.2f, Z=%.2f\n",
pointPose.point.x, pointPose.point.y, pointPose.point.z);
LOG_INFO("[Algo Thread] DiscRack Eye PoseX: X=%.3f, Y=%.3f, Z=%.3f\n",
pointPose.pose_x.x, pointPose.pose_x.y, pointPose.pose_x.z);
LOG_INFO("[Algo Thread] DiscRack Eye PoseY: X=%.3f, Y=%.3f, Z=%.3f\n",
pointPose.pose_y.x, pointPose.pose_y.y, pointPose.pose_y.z);
LOG_INFO("[Algo Thread] DiscRack Eye PoseZ: X=%.3f, Y=%.3f, Z=%.3f\n",
pointPose.pose_z.x, pointPose.pose_z.y, pointPose.pose_z.z);
LOG_INFO("[Algo Thread] DiscRack Robot Coords: X=%.2f, Y=%.2f, Z=%.2f, RPY=%.2f/%.2f/%.2f\n",
pos.x, pos.y, pos.z, pos.roll, pos.pitch, pos.yaw);
}
detectionResult.image = BuildDiscRackPointCloudImage(xyzData, pointPose, true);
SaveDebugImageIfNeeded(cameraIndex, debugParam, detectionResult.image, QStringLiteral("DiscRack_Image"));
return SUCCESS;
}