344 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 "CoordinateTransform.h"
#include "ScrewPositionTCPProtocol.h"
#include "rodAndBarDetection_Export.h"
#include <QColor>
#include <cmath>
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
namespace {
QImage BuildScrewPointCloudImage(const std::vector<std::vector<SVzNL3DPosition>>& xyzData,
const std::vector<SSX_rodPoseInfo>& screwInfo)
{
PointCloudCanvas canvas = PointCloudCanvas::Create(xyzData);
if (!canvas.isValid()) {
return QImage();
}
constexpr double kAxisLineLength = 60.0;
const QColor pointColor(0, 255, 0);
const QColor lineColor(255, 0, 0);
const QColor textColor(255, 255, 0);
for (size_t i = 0; i < screwInfo.size(); ++i) {
const auto& screw = screwInfo[i];
canvas.drawPoint(screw.center.x, screw.center.y, pointColor, 8);
canvas.drawText(screw.center.x, screw.center.y, QString::number(i + 1), textColor, 16, 10, -10);
canvas.drawLine(screw.center.x - kAxisLineLength * screw.axialDir.x,
screw.center.y - kAxisLineLength * screw.axialDir.y,
screw.center.x + kAxisLineLength * screw.axialDir.x,
screw.center.y + kAxisLineLength * screw.axialDir.y,
lineColor, 2);
}
return canvas.image().copy();
}
QImage BuildToolDiskPointCloudImage(const std::vector<std::vector<SVzNL3DPosition>>& xyzData,
const SSX_pointPoseInfo& poseInfo,
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(255, 0, 0);
const QColor textColor(255, 255, 0);
// 绘制定位盘中心点
canvas.drawPoint(poseInfo.center.x, poseInfo.center.y, centerColor, 10);
canvas.drawText(poseInfo.center.x, poseInfo.center.y,
QStringLiteral("center"), textColor, 14, 10, -10);
// 绘制法向量方向线
canvas.drawLine(poseInfo.center.x,
poseInfo.center.y,
poseInfo.center.x + kNormalLineLength * poseInfo.normalDir.x,
poseInfo.center.y + kNormalLineLength * poseInfo.normalDir.y,
normalColor, 2);
}
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));
}
CTHomogeneousMatrix BuildEyeInHandTransform(const double clibMatrix[16], const RobotPose6D& robotPose)
{
CTHomogeneousMatrix handEyeMatrix;
for (int row = 0; row < 4; ++row) {
for (int col = 0; col < 4; ++col) {
handEyeMatrix.at(row, col) = clibMatrix[row * 4 + col];
}
}
const CTRobotPose flangePose = CTRobotPose::fromDegrees(
robotPose.x,
robotPose.y,
robotPose.z,
robotPose.rx,
robotPose.ry,
robotPose.rz);
return flangePose.toHomogeneousMatrix() * handEyeMatrix;
}
CTVec3D NormalizeVector(const CTVec3D& vector)
{
const double length = vector.norm();
if (length < 1e-8) {
return CTVec3D();
}
return vector * (1.0 / length);
}
CTVec3D ToCTVec3D(const SVzNL3DPoint& point)
{
return CTVec3D(point.x, point.y, point.z);
}
void VectorToPitchYaw(const CTVec3D& direction, double& pitch, double& yaw)
{
const double xyLen = std::sqrt(direction.x * direction.x + direction.y * direction.y);
pitch = std::atan2(-direction.z, xyLen) * 180.0 / M_PI;
yaw = std::atan2(direction.y, direction.x) * 180.0 / M_PI;
}
}
DetectPresenter::DetectPresenter(/* args */)
{
LOG_DEBUG("DetectPresenter Init algo ver: %s\n", wd_rodAndBarDetectionVersion());
}
DetectPresenter::~DetectPresenter()
{
}
QString DetectPresenter::GetAlgoVersion()
{
return QString(wd_rodAndBarDetectionVersion());
}
int DetectPresenter::DetectScrew(
int cameraIndex,
std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& laserLines,
const VrAlgorithmParams& algorithmParams,
const VrDebugParam& debugParam,
LaserDataLoader& dataLoader,
const double clibMatrix[16],
const RobotPose6D& robotPose,
DetectionResult& detectionResult)
{
if (laserLines.empty()) {
LOG_WARNING("No laser lines data available\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 data to XYZ format or no XYZ data available\n");
return ERR_CODE(DEV_DATA_INVALID);
}
const ScrewDetectAlgorithmParams algoParams = AlgorithmParamConverter::ToScrewDetectAlgorithmParams(algorithmParams);
const double rodDiameter = algoParams.rodDiameter;
const bool isHorizonScan = algoParams.isHorizonScan;
const SSG_cornerParam& cornerParam = algoParams.cornerParam;
const SSG_treeGrowParam& growParam = algoParams.growParam;
const SSG_outlierFilterParam& filterParam = algoParams.filterParam;
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] Screw: rodDiameter=%.1f, isHorizonScan=%s\n", rodDiameter, isHorizonScan ? "true" : "false");
LOG_INFO("[Algo Thread] 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] 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);
}
int errCode = 0;
CVrTimeUtils oTimeUtils;
LOG_DEBUG("before sx_hexHeadScrewMeasure \n");
std::vector<SSX_rodPoseInfo> screwInfo;
sx_hexHeadScrewMeasure(
xyzData,
isHorizonScan,
cornerParam,
filterParam,
growParam,
rodDiameter,
screwInfo,
&errCode);
LOG_DEBUG("after sx_hexHeadScrewMeasure \n");
LOG_INFO("sx_hexHeadScrewMeasure: detected %zu screws, err=%d runtime=%.3fms\n",
screwInfo.size(), errCode, oTimeUtils.GetElapsedTimeInMilliSec());
ERR_CODE_RETURN(errCode);
detectionResult.success = true;
detectionResult.errorCode = 0;
detectionResult.message = QStringLiteral("\u68c0\u6d4b\u6210\u529f");
detectionResult.image = BuildScrewPointCloudImage(xyzData, screwInfo);
const CTHomogeneousMatrix eyeInHandTransform = BuildEyeInHandTransform(clibMatrix, robotPose);
if (debugParam.enableDebug && debugParam.printDetailLog) {
LOG_INFO("[Algo Thread] Robot flange pose: X=%.3f, Y=%.3f, Z=%.3f, RX=%.3f, RY=%.3f, RZ=%.3f\n",
robotPose.x, robotPose.y, robotPose.z,
robotPose.rx, robotPose.ry, robotPose.rz);
}
for (size_t i = 0; i < screwInfo.size(); ++i) {
const auto& screw = screwInfo[i];
const CTVec3D robotCenter = eyeInHandTransform.transformPoint(ToCTVec3D(screw.center));
const CTVec3D robotAxialDir = NormalizeVector(eyeInHandTransform.transformVector(ToCTVec3D(screw.axialDir)));
ScrewPosition pos;
pos.x = robotCenter.x;
pos.y = robotCenter.y;
pos.z = robotCenter.z;
// 从轴向量计算欧拉角
pos.roll = 0.0;
VectorToPitchYaw(robotAxialDir, pos.pitch, pos.yaw);
detectionResult.positions.push_back(pos);
ScrewInfo info;
info.centerX = robotCenter.x;
info.centerY = robotCenter.y;
info.centerZ = robotCenter.z;
info.axialDirX = robotAxialDir.x;
info.axialDirY = robotAxialDir.y;
info.axialDirZ = robotAxialDir.z;
info.rotateAngle = pos.roll;
detectionResult.screwInfoList.push_back(info);
if (debugParam.enableDebug && debugParam.printDetailLog) {
LOG_INFO("[Algo Thread] Screw %zu Eye Coords: X=%.2f, Y=%.2f, Z=%.2f\n", i, screw.center.x, screw.center.y, screw.center.z);
LOG_INFO("[Algo Thread] Screw %zu Robot Coords: X=%.2f, Y=%.2f, Z=%.2f, RPY=%.2f/%.2f/%.2f\n", i, pos.x, pos.y, pos.z, pos.roll, pos.pitch, pos.yaw);
LOG_INFO("[Algo Thread] Screw %zu Axial Dir Eye: X=%.3f, Y=%.3f, Z=%.3f\n", i, screw.axialDir.x, screw.axialDir.y, screw.axialDir.z);
LOG_INFO("[Algo Thread] Screw %zu Axial Dir Robot: X=%.3f, Y=%.3f, Z=%.3f\n", i, robotAxialDir.x, robotAxialDir.y, robotAxialDir.z);
}
}
SaveDebugImageIfNeeded(cameraIndex, debugParam, detectionResult.image, QStringLiteral("Image"));
return SUCCESS;
}
int DetectPresenter::DetectToolDisk(
int cameraIndex,
std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& laserLines,
const VrAlgorithmParams& algorithmParams,
const VrDebugParam& debugParam,
LaserDataLoader& dataLoader,
const double clibMatrix[16],
const RobotPose6D& robotPose,
DetectionResult& detectionResult)
{
if (laserLines.empty()) {
LOG_WARNING("No laser lines data available for tool disk 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 tool disk data to XYZ format or no XYZ data available\n");
return ERR_CODE(DEV_DATA_INVALID);
}
// 构造算法参数(与螺杆检测共享 cornerParam
const ScrewDetectAlgorithmParams algoParams = AlgorithmParamConverter::ToScrewDetectAlgorithmParams(algorithmParams);
const SSG_cornerParam& cornerParam = algoParams.cornerParam;
if (debugParam.enableDebug && debugParam.printDetailLog) {
LOG_INFO("[Algo Thread] ToolDisk 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] ToolDisk Corner: cornerTh=%.1f, scale=%.1f, minEndingGap=%.1f, minEndingGap_z=%.1f\n",
cornerParam.cornerTh, cornerParam.scale, cornerParam.minEndingGap, cornerParam.minEndingGap_z);
}
int errCode = 0;
CVrTimeUtils oTimeUtils;
LOG_DEBUG("before sx_getLocationPlatePose\n");
SSX_pointPoseInfo poseInfo = sx_getLocationPlatePose(
xyzData,
cornerParam,
&errCode);
LOG_DEBUG("after sx_getLocationPlatePose\n");
LOG_INFO("sx_getLocationPlatePose: err=%d runtime=%.3fms\n",
errCode, oTimeUtils.GetElapsedTimeInMilliSec());
ERR_CODE_RETURN(errCode);
detectionResult.success = true;
detectionResult.errorCode = 0;
detectionResult.message = QStringLiteral("\u5de5\u5177\u76d8\u68c0\u6d4b\u6210\u529f");
detectionResult.image = BuildToolDiskPointCloudImage(xyzData, poseInfo, true);
const CTHomogeneousMatrix eyeInHandTransform = BuildEyeInHandTransform(clibMatrix, robotPose);
// 将定位盘中心点通过手眼标定转换为机器人坐标
const CTVec3D robotCenter = eyeInHandTransform.transformPoint(ToCTVec3D(poseInfo.center));
const CTVec3D robotNormalDir = NormalizeVector(eyeInHandTransform.transformVector(ToCTVec3D(poseInfo.normalDir)));
ScrewPosition pos;
pos.x = robotCenter.x;
pos.y = robotCenter.y;
pos.z = robotCenter.z;
// 法向量转欧拉角roll=0, pitch=atan2(-nz, sqrt(nx^2+ny^2)), yaw=atan2(ny, nx)
pos.roll = 0.0;
VectorToPitchYaw(robotNormalDir, pos.pitch, pos.yaw);
detectionResult.positions.push_back(pos);
if (debugParam.enableDebug && debugParam.printDetailLog) {
LOG_INFO("[Algo Thread] ToolDisk Eye Coords: X=%.2f, Y=%.2f, Z=%.2f\n", poseInfo.center.x, poseInfo.center.y, poseInfo.center.z);
LOG_INFO("[Algo Thread] ToolDisk Robot Coords: X=%.2f, Y=%.2f, Z=%.2f\n", pos.x, pos.y, pos.z);
LOG_INFO("[Algo Thread] ToolDisk NormalDir Eye: X=%.3f, Y=%.3f, Z=%.3f\n", poseInfo.normalDir.x, poseInfo.normalDir.y, poseInfo.normalDir.z);
LOG_INFO("[Algo Thread] ToolDisk NormalDir Robot: X=%.3f, Y=%.3f, Z=%.3f\n", robotNormalDir.x, robotNormalDir.y, robotNormalDir.z);
LOG_INFO("[Algo Thread] ToolDisk Euler: Roll=%.2f, Pitch=%.2f, Yaw=%.2f\n", pos.roll, pos.pitch, pos.yaw);
}
SaveDebugImageIfNeeded(cameraIndex, debugParam, detectionResult.image, QStringLiteral("ToolDisk_Image"));
return SUCCESS;
}