2026-01-09 01:15:57 +08:00

387 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 "BQ_workpieceCornerExtraction_Export.h"
#include <fstream>
#include <QPainter>
#include <QPen>
#include <QColor>
DetectPresenter::DetectPresenter(/* args */)
{
LOG_DEBUG("DetectPresenter Init algo ver: %s\n", wd_BQWorkpieceCornerVersion());
}
DetectPresenter::~DetectPresenter()
{
}
int DetectPresenter::DetectWorkpiece(
int cameraIndex,
std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& laserLines,
const VrAlgorithmParams& algorithmParams,
const VrDebugParam& debugParam,
LaserDataLoader& dataLoader,
const double clibMatrix[16],
DetectionResult& 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);
}
// 工件角点提取参数
SSX_BQworkpiecePara bqWorkpieceParam;
bqWorkpieceParam.lineLen = algorithmParams.workpieceParam.lineLen;
SSG_cornerParam cornerParam;
cornerParam.minEndingGap = algorithmParams.cornerParam.minEndingGap;
cornerParam.minEndingGap_z = algorithmParams.cornerParam.minEndingGap_z;
cornerParam.scale = algorithmParams.cornerParam.scale;
cornerParam.cornerTh = algorithmParams.cornerParam.cornerTh;
cornerParam.jumpCornerTh_1 = algorithmParams.cornerParam.jumpCornerTh_1;
cornerParam.jumpCornerTh_2 = algorithmParams.cornerParam.jumpCornerTh_2;
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;
SSG_outlierFilterParam filterParam;
filterParam.continuityTh = algorithmParams.filterParam.continuityTh;
filterParam.outlierTh = algorithmParams.filterParam.outlierTh;
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] 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);
// 打印工件角点提取参数
LOG_INFO("[Algo Thread] BQ Workpiece: lineLen=%.1f\n", bqWorkpieceParam.lineLen);
}
// 准备平面校准参数
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++){
sx_BQ_lineDataR(xyzData[i], cameraCalibParam->planeCalib, groundH);
}
}
int errCode = 0;
CVrTimeUtils oTimeUtils;
LOG_DEBUG("before sx_BQ_getWorkpieceCorners \n");
// 调用工件角点提取算法
std::vector<SSX_debugInfo> debugContours;
SSX_BQworkpieceResult bqResult = sx_BQ_getWorkpieceCorners(
xyzData,
cornerParam,
filterParam,
growParam,
groundCalibPara,
bqWorkpieceParam,
#if _OUTPUT_DEBUG_DATA
debugContours,
#endif
&errCode
);
LOG_DEBUG("after sx_BQ_getWorkpieceCorners \n");
LOG_INFO("sx_BQ_getWorkpieceCorners: workpieceType=%d err=%d runtime=%.3fms\n", bqResult.workpieceType, errCode, oTimeUtils.GetElapsedTimeInMilliSec());
ERR_CODE_RETURN(errCode);
// 保存工件类型到检测结果
detectionResult.workpieceType = bqResult.workpieceType;
// 保存长度信息到检测结果
detectionResult.len_A1 = bqResult.len135_A1;
detectionResult.len_A2 = bqResult.len225_A2;
detectionResult.len_B1 = bqResult.len45_B1;
detectionResult.len_B2 = bqResult.len315_B2;
// 构建用于可视化的角点数组
// 按照逆时针顺序排列:左侧(从下到上) -> 顶部(从左到右) -> 右侧(从上到下) -> 底部(从右到左)
std::vector<std::vector<SVzNL3DPoint>> objOps;
std::vector<SVzNL3DPoint> allCorners;
// 1. 添加左侧角点(从下到上,逆时针)
for (int i = 0; i < 3; i++) {
SVzNL3DPoint pt;
pt.x = bqResult.corner_1[i].x;
pt.y = bqResult.corner_1[i].y;
pt.z = bqResult.corner_1[i].z;
allCorners.push_back(pt);
}
// 2. 添加顶部角点(从左到右,逆时针)
for (int i = 0; i < 3; i++) {
SVzNL3DPoint pt;
pt.x = bqResult.corner_2[i].x;
pt.y = bqResult.corner_2[i].y;
pt.z = bqResult.corner_2[i].z;
allCorners.push_back(pt);
}
// 3. 添加右侧角点(从上到下,逆时针)
for (int i = 0; i < 3; i++) {
SVzNL3DPoint pt;
pt.x = bqResult.corner_3[i].x;
pt.y = bqResult.corner_3[i].y;
pt.z = bqResult.corner_3[i].z;
allCorners.push_back(pt);
}
// 4. 添加底部角点(从右到左,逆时针)
for (int i = 0; i < 3; i++) {
SVzNL3DPoint pt;
pt.x = bqResult.corner_4[i].x;
pt.y = bqResult.corner_4[i].y;
pt.z = bqResult.corner_4[i].z;
allCorners.push_back(pt);
}
// 5. 添加中心点(用于绘制)
allCorners.push_back(bqResult.center);
objOps.push_back(allCorners);
// 从点云数据生成投影图像10cm边界
detectionResult.image = PointCloudImageUtils::GeneratePointCloudRetPointImage(xyzData, objOps, 100.0);
// 在图像上绘制连接线:从每组的中间点到中心点
if (!detectionResult.image.isNull() && allCorners.size() >= 4) {
QPainter painter(&detectionResult.image);
painter.setRenderHint(QPainter::Antialiasing);
// 计算点云范围与PointCloudImageUtils相同的方式
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);
}
}
// 扩展边界与GeneratePointCloudRetPointImage相同
double margin = 100.0; // 10cm = 100mm
xMin -= margin;
xMax += margin;
yMin -= margin;
yMax += margin;
// 使用与GeneratePointCloudRetPointImage相同的参数
int imgRows = detectionResult.image.height();
int imgCols = detectionResult.image.width();
int x_skip = 50;
int y_skip = 50;
// 计算投影比例与PointCloudImageUtils相同的方式
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;
// 转换3D坐标到图像坐标的lambda函数
auto toImageCoord = [&](const SVzNL3DPoint& pt) -> QPointF {
int px = (int)((pt.x - xMin) / x_scale + x_skip);
int py = (int)((pt.y - yMin) / y_scale + y_skip);
return QPointF(px, py);
};
// 绘制连接线
QPen pen(QColor(255, 0, 0), 2);
painter.setPen(pen);
// 中心点是最后一个点
int centerIdx = allCorners.size() - 1;
QPointF centerPt = toImageCoord(allCorners[centerIdx]);
// 计算组数除去中心点后每3个点为一组
int numPoints = allCorners.size() - 1;
int numGroups = numPoints / 3;
// 连接每组的中间点(索引为 i*3+1到中心点但跳过全0的组
for (int i = 0; i < numGroups; i++) {
int startIdx = i * 3;
int midIdx = i * 3 + 1;
int endIdx = i * 3 + 2;
// 检查这一组的三个点是否都是0
bool allZero = true;
for (int j = startIdx; j <= endIdx && j < numPoints; j++) {
const auto& pt = allCorners[j];
if (fabs(pt.x) > 0.0001 || fabs(pt.y) > 0.0001 || fabs(pt.z) > 0.0001) {
allZero = false;
break;
}
}
// 如果这一组不是全0绘制连线
if (!allZero && midIdx < numPoints) {
QPointF pt = toImageCoord(allCorners[midIdx]);
painter.drawLine(pt, centerPt);
}
}
}
// 转换检测结果为UI显示格式使用机械臂坐标系数据
for (size_t i = 0; i < objOps.size(); i++) {
for(size_t j = 0; j < objOps[i].size(); j++){
const SVzNL3DPoint& obj = objOps[i][j];
// 进行坐标转换:从算法坐标系转换到机械臂坐标系
SVzNL3DPoint targetObj;
targetObj.x = obj.x;
targetObj.y = obj.y;
targetObj.z = obj.z;
SVzNL3DPoint robotObj;
CVrConvert::EyeToRobot(targetObj, robotObj, clibMatrix);
// 创建位置数据(使用转换后的机械臂坐标)
WorkpiecePosition pos;
pos.x = robotObj.x; // 机械臂坐标X
pos.y = robotObj.y; // 机械臂坐标Y
pos.z = robotObj.z; // 机械臂坐标Z
pos.roll = 0.0; // 搭接焊缝检测不提供姿态角
pos.pitch = 0.0; // 搭接焊缝检测不提供姿态角
pos.yaw = 0.0; // 搭接焊缝检测不提供姿态角
detectionResult.positions.push_back(pos);
if(debugParam.enableDebug && debugParam.printDetailLog){
LOG_INFO("[Algo Thread] Object %zu Eye Coords: X=%.2f, Y=%.2f, Z=%.2f\n", j, obj.x, obj.y, obj.z);
LOG_INFO("[Algo Thread] Object %zu Robot Coords: X=%.2f, Y=%.2f, Z=%.2f, Roll=%.2f, Pitch=%.2f, Yaw=%.2f\n",
j, pos.x, pos.y, pos.z, pos.roll, pos.pitch, pos.yaw);
}
}
}
// 转换中心点坐标到机械臂坐标系
SVzNL3DPoint centerEye;
centerEye.x = bqResult.center.x;
centerEye.y = bqResult.center.y;
centerEye.z = bqResult.center.z;
SVzNL3DPoint centerRobot;
CVrConvert::EyeToRobot(centerEye, centerRobot, clibMatrix);
// 保存中心点到检测结果
detectionResult.center.x = centerRobot.x;
detectionResult.center.y = centerRobot.y;
detectionResult.center.z = centerRobot.z;
detectionResult.center.roll = 0.0;
detectionResult.center.pitch = 0.0;
detectionResult.center.yaw = 0.0;
if(debugParam.enableDebug && debugParam.printDetailLog){
LOG_INFO("[Algo Thread] Center Eye Coords: X=%.2f, Y=%.2f, Z=%.2f\n", centerEye.x, centerEye.y, centerEye.z);
LOG_INFO("[Algo Thread] Center Robot Coords: X=%.2f, Y=%.2f, Z=%.2f\n", centerRobot.x, centerRobot.y, centerRobot.z);
LOG_INFO("[Algo Thread] Lengths: A1=%.2f, A2=%.2f, B1=%.2f, B2=%.2f\n",
detectionResult.len_A1, detectionResult.len_A2, detectionResult.len_B1, detectionResult.len_B2);
}
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;
}