GrabBag/AppUtils/CloudUtils/Src/PointCloudImageUtils.cpp

1334 lines
49 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 "PointCloudImageUtils.h"
#include <QPainter>
#include <cmath>
#include <algorithm>
#include <limits>
#include "VrLog.h"
#ifndef PI
#define PI 3.14159265358979323846
#endif
// 线特征类型定义 - 用于点云可视化颜色区分
#ifndef LINE_FEATURE_UNDEF
#define LINE_FEATURE_UNDEF 0
#define LINE_FEATURE_L_JUMP_H2L 1
#define LINE_FEATURE_L_JUMP_L2H 2
#define LINE_FEATURE_L_SLOPE_H2L 3
#define LINE_FEATURE_L_SLOPE_L2H 4
#define LINE_FEATURE_V_SLOPE 5
#define LINE_FEATURE_LINE_ENDING_0 6
#define LINE_FEATURE_LINE_ENDING_1 7
#endif
QImage PointCloudImageUtils::GeneratePointCloudImage(SVzNL3DLaserLine* scanData,
int lineNum,
const std::vector<DetectionTargetInfo>& objOps)
{
if (!scanData || lineNum <= 0) {
return QImage(); // 返回空图像
}
// 统计X和Y的范围
double xMin = 0.0, xMax = -1.0;
double yMin = 0.0, yMax = -1.0;
CalculatePointCloudRange(scanData, lineNum, xMin, xMax, yMin, yMax);
// 检查范围是否有效
if (xMax < xMin || yMax < yMin) {
return QImage(); // 返回空图像
}
// 创建图像
int imgRows = 992;
int imgCols = 1056;
int x_skip = 16;
int y_skip = 16;
// 计算投影比例
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);
// 绘制点云
for (int line = 0; line < lineNum; line++) {
for (int i = 0; i < scanData[line].nPositionCnt; i++) {
SVzNL3DPosition* pt3D = &scanData[line].p3DPosition[i];
if (pt3D->pt3D.z < 1e-4) continue;
// 解析点索引信息
int vType = pt3D->nPointIdx & 0xff;
int hType = vType >> 4;
int objId = (pt3D->nPointIdx >> 16) & 0xff;
vType = vType & 0x0f;
// 根据线特征类型确定颜色和大小
QColor pointColor;
int pointSize = 1;
GetLineFeatureStyle(vType, hType, objId, pointColor, pointSize);
int px = (int)((pt3D->pt3D.x - xMin) / x_scale + x_skip);
int py = (int)((pt3D->pt3D.y - yMin) / y_scale + y_skip);
if (px >= 0 && px < imgCols && py >= 0 && py < imgRows) {
painter.setPen(QPen(pointColor, 1));
painter.drawPoint(px, py);
}
}
}
// 绘制检测目标和方向线
DrawDetectionTargets(painter, objOps, xMin, x_scale, x_skip, yMin, y_scale, y_skip, imgCols, imgRows);
return image;
}
QImage PointCloudImageUtils::GeneratePointCloudImage(SVzNLXYZRGBDLaserLine* scanData,
int lineNum,
const std::vector<DetectionTargetInfo>& objOps)
{
int imgRows = 992;
int imgCols = 1056;
QImage image(imgCols, imgRows, QImage::Format_RGB888);
image.fill(Qt::black);
if (!scanData || lineNum <= 0) {
return image; // 返回空图像
}
// 统计X和Y的范围
double xMin = 0.0, xMax = -1.0;
double yMin = 0.0, yMax = -1.0;
for (int line = 0; line < lineNum; line++) {
for (int i = 0; i < scanData[line].nPointCnt; i++) {
SVzNLPointXYZRGBA* pt3D = &scanData[line].p3DPoint[i];
if (pt3D->z < 1e-4) continue;
// 更新X范围
if (xMax < xMin) {
xMin = xMax = pt3D->x;
} else {
if (xMin > pt3D->x) xMin = pt3D->x;
if (xMax < pt3D->x) xMax = pt3D->x;
}
// 更新Y范围
if (yMax < yMin) {
yMin = yMax = pt3D->y;
} else {
if (yMin > pt3D->y) yMin = pt3D->y;
if (yMax < pt3D->y) yMax = pt3D->y;
}
}
}
// 检查范围是否有效
if (xMax < xMin || yMax < yMin) {
return image; // 返回空图像
}
// 创建图像
int x_skip = 16;
int y_skip = 16;
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;
QPainter painter(&image);
// 绘制点云
for (int line = 0; line < lineNum; line++) {
for (int i = 0; i < scanData[line].nPointCnt; i++) {
SVzNLPointXYZRGBA* pt3D = &scanData[line].p3DPoint[i];
if (pt3D->z < 1e-4) continue;
// 解析RGB颜色
int nRGB = pt3D->nRGB;
int r = nRGB & 0xff;
nRGB >>= 8;
int g = nRGB & 0xff;
nRGB >>= 8;
int b = nRGB & 0xff;
QColor pointColor(r, g, b);
int px = (int)((pt3D->x - xMin) / x_scale + x_skip);
int py = (int)((pt3D->y - yMin) / y_scale + y_skip);
if (px >= 0 && px < imgCols && py >= 0 && py < imgRows) {
painter.setPen(QPen(pointColor, 1));
painter.drawPoint(px, py);
}
}
}
// 绘制检测目标和方向线
DrawDetectionTargets(painter, objOps, xMin, x_scale, x_skip, yMin, y_scale, y_skip, imgCols, imgRows);
return image;
}
// 新的点云图像生成函数 - 基于X、Y范围创建图像
QImage PointCloudImageUtils::GeneratePointCloudImage(SVzNLXYZRGBDLaserLine* scanData, int lineNum)
{
if (!scanData || lineNum <= 0) {
return QImage();
}
// 1. 遍历X, Y的范围计算最小和最大值
double xMin = std::numeric_limits<double>::max();
double xMax = std::numeric_limits<double>::lowest();
double yMin = std::numeric_limits<double>::max();
double yMax = std::numeric_limits<double>::lowest();
for (int i = 0; i < lineNum; i++) {
if (scanData[i].p3DPoint && scanData[i].nPointCnt > 0) {
for (int j = 0; j < scanData[i].nPointCnt; j++) {
const SVzNLPointXYZRGBA& point = scanData[i].p3DPoint[j];
// 更新X范围
if (point.x < xMin) xMin = point.x;
if (point.x > xMax) xMax = point.x;
// 更新Y范围
if (point.y < yMin) yMin = point.y;
if (point.y > yMax) yMax = point.y;
}
}
}
// 检查范围是否有效
if (xMin >= xMax || yMin >= yMax) {
return QImage();
}
// 2. 根据范围创建图像大小
// 设置图像分辨率,可以根据需要调整
const int imageWidth = xMax - xMin;
const int imageHeight = yMax - yMin;
// 3. 创建默认黑底图像
QImage image(imageWidth, imageHeight, QImage::Format_RGB888);
image.fill(Qt::black);
// 4. 遍历点云数据将3D X, Y对应的颜色值赋值给图像
for (int i = 0; i < lineNum; i++) {
if (scanData[i].p3DPoint && scanData[i].nPointCnt > 0) {
for (int j = 0; j < scanData[i].nPointCnt; j++) {
const SVzNLPointXYZRGBA& point = scanData[i].p3DPoint[j];
// 将3D坐标转换为图像坐标
int imageX = static_cast<int>((point.x - xMin));
int imageY = static_cast<int>((point.y - yMin));
// 确保坐标在图像范围内
if (imageX >= 0 && imageX < imageWidth &&
imageY >= 0 && imageY < imageHeight) {
// 解析RGB颜色 - 参考_RGBDto2DImage函数的解析方式
int nRGB = point.nRGB;
int r = nRGB & 0xff;
nRGB >>= 8;
int g = nRGB & 0xff;
nRGB >>= 8;
int b = nRGB & 0xff;
QColor pointColor(r, g, b);
// 设置图像像素颜色
image.setPixel(imageX, imageY, pointColor.rgb());
}
}
}
}
return image;
}
void PointCloudImageUtils::GetLineFeatureStyle(int vType, int hType, int objId,
QColor& pointColor, int& pointSize)
{
pointSize = 1;
// 优先根据垂直方向特征设置颜色
if (LINE_FEATURE_L_JUMP_H2L == vType) {
pointColor = QColor(255, 97, 0); // 橙色
pointSize = 2;
}
else if (LINE_FEATURE_L_JUMP_L2H == vType) {
pointColor = QColor(255, 255, 0); // 黄色
pointSize = 2;
}
else if (LINE_FEATURE_V_SLOPE == vType) {
pointColor = QColor(255, 0, 255); // 紫色
pointSize = 2;
}
else if (LINE_FEATURE_L_SLOPE_H2L == vType) {
pointColor = QColor(160, 82, 45); // 褐色
pointSize = 2;
}
else if ((LINE_FEATURE_LINE_ENDING_0 == vType) || (LINE_FEATURE_LINE_ENDING_1 == vType)) {
pointColor = QColor(255, 0, 0); // 红色
pointSize = 2;
}
else if (LINE_FEATURE_L_SLOPE_L2H == vType) {
pointColor = QColor(233, 150, 122); // 浅褐色
pointSize = 2;
}
// 检查水平方向特征
else if (LINE_FEATURE_L_JUMP_H2L == hType) {
pointColor = QColor(0, 0, 255); // 蓝色
pointSize = 2;
}
else if (LINE_FEATURE_L_JUMP_L2H == hType) {
pointColor = QColor(0, 255, 255); // 青色
pointSize = 2;
}
else if (LINE_FEATURE_V_SLOPE == hType) {
pointColor = QColor(0, 255, 0); // 绿色
pointSize = 2;
}
else if (LINE_FEATURE_L_SLOPE_H2L == hType) {
pointColor = QColor(85, 107, 47); // 橄榄绿
pointSize = 2;
}
else if (LINE_FEATURE_L_SLOPE_L2H == hType) {
pointColor = QColor(0, 255, 154); // 浅绿色
pointSize = 2;
}
else if ((LINE_FEATURE_LINE_ENDING_0 == hType) || (LINE_FEATURE_LINE_ENDING_1 == hType)) {
pointColor = QColor(255, 0, 0); // 红色
pointSize = 3;
}
// 检查是否为目标对象
else if (objId > 0) {
pointColor = GetObjectColor(objId);
pointSize = 1;
}
// 默认颜色
else {
pointColor = QColor(150, 150, 150); // 深灰色
pointSize = 1;
}
}
QColor PointCloudImageUtils::GetObjectColor(int index)
{
QColor objColors[8] = {
QColor(245,222,179), QColor(210,105,30), QColor(240,230,140), QColor(135,206,235),
QColor(250,235,215), QColor(189,252,201), QColor(221,160,221), QColor(188,143,143)
};
return objColors[index % 8];
}
void PointCloudImageUtils::CalculatePointCloudRange(SVzNL3DLaserLine* scanData, int lineNum,
double& xMin, double& xMax,
double& yMin, double& yMax)
{
xMin = 0.0; xMax = -1.0;
yMin = 0.0; yMax = -1.0;
for (int line = 0; line < lineNum; line++) {
for (int i = 0; i < scanData[line].nPositionCnt; i++) {
SVzNL3DPosition* pt3D = &scanData[line].p3DPosition[i];
if (pt3D->pt3D.z < 1e-4) continue;
// 更新X范围
if (xMax < xMin) {
xMin = xMax = pt3D->pt3D.x;
} else {
if (xMin > pt3D->pt3D.x) xMin = pt3D->pt3D.x;
if (xMax < pt3D->pt3D.x) xMax = pt3D->pt3D.x;
}
// 更新Y范围
if (yMax < yMin) {
yMin = yMax = pt3D->pt3D.y;
} else {
if (yMin > pt3D->pt3D.y) yMin = pt3D->pt3D.y;
if (yMax < pt3D->pt3D.y) yMax = pt3D->pt3D.y;
}
}
}
}
void PointCloudImageUtils::DrawDetectionTargets(QPainter& painter,
const std::vector<DetectionTargetInfo>& objOps,
double xMin, double xScale, int xSkip,
double yMin, double yScale, int ySkip,
int imgCols, int imgRows)
{
// 绘制检测目标和方向线
for (size_t i = 0; i < objOps.size(); i++) {
QColor objColor = (i == 0) ? QColor(255, 0, 0) : QColor(255, 255, 0);
int size = (i == 0) ? 20 : 10;
int px = (int)((objOps[i].centerPos.x - xMin) / xScale + xSkip);
int py = (int)((objOps[i].centerPos.y - yMin) / yScale + ySkip);
if (px >= 0 && px < imgCols && py >= 0 && py < imgRows) {
// 绘制抓取点圆圈
painter.setPen(QPen(objColor, 2));
painter.setBrush(QBrush(objColor));
painter.drawEllipse(px - size/2, py - size/2, size, size);
// 绘制方向线
const double deg2rad = PI / 180.0;
// 使用检测目标实际2D尺寸的较大值的一半作为方向线长度
double maxSize = std::max(objOps[i].objSize.dHeight, objOps[i].objSize.dWidth);
double R = std::max(20.0, maxSize / 6.0);
const double yaw = objOps[i].centerPos.z_yaw * deg2rad;
double cy = cos(yaw);
double sy = sin(yaw);
// 计算方向线的端点
double x1 = objOps[i].centerPos.x + R * cy;
double y1 = objOps[i].centerPos.y - R * sy;
double x2 = objOps[i].centerPos.x - R * cy;
double y2 = objOps[i].centerPos.y + R * sy;
int px1 = (int)((x1 - xMin) / xScale + xSkip);
int py1 = (int)((y1 - yMin) / yScale + ySkip);
int px2 = (int)((x2 - xMin) / xScale + xSkip);
int py2 = (int)((y2 - yMin) / yScale + ySkip);
// 绘制方向线
painter.setPen(QPen(objColor, 2));
painter.drawLine(px1, py1, px2, py2);
// 根据orienFlag绘制箭头
if (objOps[i].orienFlag == 1) {
// 绿色箭头 - 正面
painter.setPen(QPen(QColor(0, 255, 0), 2));
// 计算箭头端点
double arrowLen = R / 3;
double arrowAngle = 30 * deg2rad;
double ca = cos(arrowAngle);
double sa = sin(arrowAngle);
double x3 = x1 - arrowLen * ca * cy + arrowLen * sa * sy;
double y3 = y1 + arrowLen * ca * sy + arrowLen * sa * cy;
double x4 = x1 - arrowLen * ca * cy - arrowLen * sa * sy;
double y4 = y1 + arrowLen * ca * sy - arrowLen * sa * cy;
int px3 = (int)((x3 - xMin) / xScale + xSkip);
int py3 = (int)((y3 - yMin) / yScale + ySkip);
int px4 = (int)((x4 - xMin) / xScale + xSkip);
int py4 = (int)((y4 - yMin) / yScale + ySkip);
painter.drawLine(px1, py1, px3, py3);
painter.drawLine(px1, py1, px4, py4);
}
else if (objOps[i].orienFlag == 2) {
// 蓝色箭头 - 反面
painter.setPen(QPen(QColor(0, 0, 255), 2));
// 计算箭头端点
double arrowLen = R / 3;
double arrowAngle = 30 * deg2rad;
double ca = cos(arrowAngle);
double sa = sin(arrowAngle);
double x3 = x1 - arrowLen * ca * cy + arrowLen * sa * sy;
double y3 = y1 + arrowLen * ca * sy + arrowLen * sa * cy;
double x4 = x1 - arrowLen * ca * cy - arrowLen * sa * sy;
double y4 = y1 + arrowLen * ca * sy - arrowLen * sa * cy;
int px3 = (int)((x3 - xMin) / xScale + xSkip);
int py3 = (int)((y3 - yMin) / yScale + ySkip);
int px4 = (int)((x4 - xMin) / xScale + xSkip);
int py4 = (int)((y4 - yMin) / yScale + ySkip);
painter.drawLine(px1, py1, px3, py3);
painter.drawLine(px1, py1, px4, py4);
}
// 绘制目标编号
painter.setPen(QPen(Qt::white, 1));
painter.setFont(QFont("Arial", 15, QFont::Bold));
painter.drawText(px + 15, py - 10, QString("%1").arg(i + 1));
}
}
}
QImage PointCloudImageUtils::GeneratePointCloudImage(const std::vector<std::vector<SVzNL3DPosition>>& scanLines,
const std::vector<std::vector<SVzNL3DPoint>>& weldResults,
int imageWidth, int imageHeight)
{
if (scanLines.empty()) {
return QImage();
}
// 计算点云范围
double xMin, xMax, yMin, yMax;
CalculateScanLinesRange(scanLines, xMin, xMax, yMin, yMax);
// 检查范围是否有效
if (xMax <= xMin || yMax <= yMin) {
return QImage();
}
// 创建图像
QImage image(imageWidth, imageHeight, QImage::Format_RGB888);
image.fill(Qt::black);
QPainter painter(&image);
// 绘制点云数据
DrawScanLinesPointCloud(painter, scanLines, xMin, xMax, yMin, yMax, imageWidth, imageHeight);
// 绘制检测结果
DrawLapWeldResults(painter, weldResults, xMin, xMax, yMin, yMax, imageWidth, imageHeight);
return image;
}
void PointCloudImageUtils::CalculateScanLinesRange(const std::vector<std::vector<SVzNL3DPosition>>& scanLines,
double& xMin, double& xMax,
double& yMin, double& yMax)
{
xMin = std::numeric_limits<double>::max();
xMax = std::numeric_limits<double>::lowest();
yMin = std::numeric_limits<double>::max();
yMax = std::numeric_limits<double>::lowest();
for (const auto& scanLine : scanLines) {
for (const auto& point : scanLine) {
if (point.pt3D.z < 1e-4) continue;
if (point.pt3D.x < xMin) xMin = point.pt3D.x;
if (point.pt3D.x > xMax) xMax = point.pt3D.x;
if (point.pt3D.y < yMin) yMin = point.pt3D.y;
if (point.pt3D.y > yMax) yMax = point.pt3D.y;
}
}
}
void PointCloudImageUtils::DrawScanLinesPointCloud(QPainter& painter,
const std::vector<std::vector<SVzNL3DPosition>>& scanLines,
double xMin, double xMax, double yMin, double yMax,
int imageWidth, int imageHeight)
{
double xScale = (xMax - xMin) / imageWidth;
double yScale = (yMax - yMin) / imageHeight;
for (const auto& scanLine : scanLines) {
for (const auto& point : scanLine) {
if (point.pt3D.z < 1e-4) continue;
// 解析点索引信息
int vType = point.nPointIdx & 0xff;
int hType = vType >> 4;
int objId = (point.nPointIdx >> 16) & 0xff;
vType = vType & 0x0f;
// 根据线特征类型确定颜色和大小
QColor pointColor;
int pointSize = 1;
GetLineFeatureStyle(vType, hType, objId, pointColor, pointSize);
// 计算图像坐标
int px = (int)((point.pt3D.x - xMin) / xScale);
int py = (int)((point.pt3D.y - yMin) / yScale);
if (px >= 0 && px < imageWidth && py >= 0 && py < imageHeight) {
painter.setPen(QPen(pointColor, pointSize));
painter.drawPoint(px, py);
}
}
}
}
void PointCloudImageUtils::DrawLapWeldResults(QPainter& painter,
const std::vector<std::vector<SVzNL3DPoint>>& weldResults,
double xMin, double xMax, double yMin, double yMax,
int imageWidth, int imageHeight)
{
if (weldResults.empty()) return;
double xScale = (xMax - xMin) / imageWidth;
double yScale = (yMax - yMin) / imageHeight;
// 使用不同颜色绘制每条焊缝
QColor weldColors[] = {
QColor(255, 0, 0), // 红色
QColor(0, 255, 0), // 绿色
QColor(0, 0, 255), // 蓝色
QColor(255, 255, 0), // 黄色
QColor(255, 0, 255), // 紫色
QColor(0, 255, 255), // 青色
QColor(255, 128, 0), // 橙色
QColor(128, 255, 0) // 浅绿色
};
int numColors = sizeof(weldColors) / sizeof(weldColors[0]);
for (size_t i = 0; i < weldResults.size(); i++) {
const auto& weldLine = weldResults[i];
if (weldLine.empty()) continue;
QColor weldColor = weldColors[i % numColors];
painter.setPen(QPen(weldColor, 3));
// 绘制焊缝线段
for (size_t j = 1; j < weldLine.size(); j++) {
int px1 = (int)((weldLine[j-1].x - xMin) / xScale);
int py1 = (int)((weldLine[j-1].y - yMin) / yScale);
int px2 = (int)((weldLine[j].x - xMin) / xScale);
int py2 = (int)((weldLine[j].y - yMin) / yScale);
if (px1 >= 0 && px1 < imageWidth && py1 >= 0 && py1 < imageHeight &&
px2 >= 0 && px2 < imageWidth && py2 >= 0 && py2 < imageHeight) {
painter.drawLine(px1, py1, px2, py2);
}
}
// 在起点和终点绘制标记
if (!weldLine.empty()) {
// 起点标记 - 圆形
int startX = (int)((weldLine[0].x - xMin) / xScale);
int startY = (int)((weldLine[0].y - yMin) / yScale);
if (startX >= 0 && startX < imageWidth && startY >= 0 && startY < imageHeight) {
painter.setPen(QPen(weldColor, 2));
painter.setBrush(QBrush(weldColor));
painter.drawEllipse(startX - 5, startY - 5, 10, 10);
}
// 终点标记 - 方形
int endX = (int)((weldLine.back().x - xMin) / xScale);
int endY = (int)((weldLine.back().y - yMin) / yScale);
if (endX >= 0 && endX < imageWidth && endY >= 0 && endY < imageHeight) {
painter.setPen(QPen(weldColor, 2));
painter.setBrush(QBrush(weldColor));
painter.drawRect(endX - 4, endY - 4, 8, 8);
}
}
}
}
// Workpiece点云和角点检测结果转图像 - 将角点画成圆点
QImage PointCloudImageUtils::GeneratePointCloudRetPointImage(const std::vector<std::vector<SVzNL3DPosition>>& scanLines,
const std::vector<std::vector<SVzNL3DPoint>>& cornerPoints,
double margin)
{
if (scanLines.empty()) {
return QImage();
}
// 固定图像尺寸,与其他函数保持一致
int imgRows = 992;
int imgCols = 1056;
int x_skip = 50;
int y_skip = 50;
// 计算点云范围
double xMin, xMax, yMin, yMax;
CalculateScanLinesRange(scanLines, xMin, xMax, yMin, yMax);
// 检查范围是否有效
if (xMax <= xMin || yMax <= yMin) {
return QImage();
}
// 在3D范围上扩展边界如果指定
if (margin > 0.0) {
xMin -= margin;
xMax += margin;
yMin -= margin;
yMax += margin;
}
// 创建图像
QImage image(imgCols, imgRows, QImage::Format_RGB888);
image.fill(Qt::black);
QPainter painter(&image);
// 计算投影比例
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;
// 使用统一的比例尺,并计算居中偏移
double scale = std::max(x_scale, y_scale);
x_scale = scale;
y_scale = scale;
// 计算点云在图像中居中的偏移量
double cloudWidth = (xMax - xMin) / scale; // 点云在图像中的像素宽度
double cloudHeight = (yMax - yMin) / scale; // 点云在图像中的像素高度
int x_offset = x_skip + (int)((x_cols - cloudWidth) / 2); // X方向居中偏移
int y_offset = y_skip + (int)((y_rows - cloudHeight) / 2); // Y方向居中偏移
// 绘制点云数据
for (const auto& scanLine : scanLines) {
for (const auto& point : scanLine) {
if (point.pt3D.z < 1e-4) continue;
// 解析点索引信息
int vType = point.nPointIdx & 0xff;
int hType = vType >> 4;
int objId = (point.nPointIdx >> 16) & 0xff;
vType = vType & 0x0f;
// 根据线特征类型确定颜色和大小
QColor pointColor;
int pointSize = 1;
GetLineFeatureStyle(vType, hType, objId, pointColor, pointSize);
// 计算图像坐标(使用居中偏移)
int px = (int)((point.pt3D.x - xMin) / x_scale + x_offset);
int py = (int)((point.pt3D.y - yMin) / y_scale + y_offset);
if (px >= 0 && px < imgCols && py >= 0 && py < imgRows) {
painter.setPen(QPen(pointColor, pointSize));
painter.drawPoint(px, py);
}
}
}
// 绘制角点作为圆点
if (!cornerPoints.empty()) {
// 定义不同组角点的颜色
QColor cornerColors[] = {
QColor(255, 0, 0), // 红色
QColor(0, 255, 0), // 绿色
QColor(0, 0, 255), // 蓝色
QColor(255, 255, 0), // 黄色
QColor(255, 0, 255), // 紫色
QColor(0, 255, 255), // 青色
QColor(255, 128, 0), // 橙色
QColor(128, 255, 0) // 浅绿色
};
int numColors = sizeof(cornerColors) / sizeof(cornerColors[0]);
for (size_t i = 0; i < cornerPoints.size(); i++) {
const auto& cornerGroup = cornerPoints[i];
if (cornerGroup.empty()) continue;
QColor cornerColor = cornerColors[i % numColors];
// 绘制每个角点
for (size_t j = 0; j < cornerGroup.size(); j++) {
const SVzNL3DPoint& corner = cornerGroup[j];
// 跳过全0的点
if (fabs(corner.x) < 0.0001 && fabs(corner.y) < 0.0001 && fabs(corner.z) < 0.0001) {
continue;
}
// 计算图像坐标(使用居中偏移)
int px = (int)((corner.x - xMin) / x_scale + x_offset);
int py = (int)((corner.y - yMin) / y_scale + y_offset);
if (px >= 0 && px < imgCols && py >= 0 && py < imgRows) {
// 绘制圆点标记
int circleSize = 10; // 圆点直径
painter.setPen(QPen(cornerColor, 2));
painter.setBrush(QBrush(cornerColor));
painter.drawEllipse(px - circleSize/2, py - circleSize/2, circleSize, circleSize);
// 绘制角点编号,确保不超出图像边界
painter.setPen(QPen(Qt::white, 1));
QFont font("Arial", 16, QFont::Bold);
painter.setFont(font);
QString numberText = QString("%1").arg(j + 1);
painter.drawText(px + 8, py + 8, numberText);
}
}
}
}
return image;
}
QImage PointCloudImageUtils::GeneratePointCloudImageWithParticles(
const std::vector<std::vector<SVzNL3DPosition>>& scanLines,
const std::vector<SimpleParticleInfo>& particles,
int cameraIndex)
{
// 创建空图像
int imageWidth = 800;
int imageHeight = 600;
QImage image(imageWidth, imageHeight, QImage::Format_RGB888);
image.fill(Qt::black);
if (scanLines.empty()) {
return image;
}
// 计算点云范围
double xMin = std::numeric_limits<double>::max();
double xMax = std::numeric_limits<double>::lowest();
double yMin = std::numeric_limits<double>::max();
double yMax = std::numeric_limits<double>::lowest();
for (const auto& line : scanLines) {
for (const auto& pt : line) {
if (pt.pt3D.z < 1e-4) continue; // 跳过无效点
xMin = std::min(xMin, static_cast<double>(pt.pt3D.x));
xMax = std::max(xMax, static_cast<double>(pt.pt3D.x));
yMin = std::min(yMin, static_cast<double>(pt.pt3D.y));
yMax = std::max(yMax, static_cast<double>(pt.pt3D.y));
}
}
// 检查范围是否有效
if (xMax <= xMin || yMax <= yMin) {
return image;
}
// 计算缩放比例
double xScale = (xMax - xMin) / imageWidth;
double yScale = (yMax - yMin) / imageHeight;
double scale = std::max(xScale, yScale);
QPainter painter(&image);
painter.setRenderHint(QPainter::Antialiasing, true);
// 绘制点云数据(灰色)
painter.setPen(QColor(128, 128, 128));
for (const auto& line : scanLines) {
for (const auto& pt : line) {
if (pt.pt3D.z < 1e-4) continue; // 跳过无效点
int px = static_cast<int>((pt.pt3D.x - xMin) / scale);
int py = static_cast<int>((pt.pt3D.y - yMin) / scale);
if (px >= 0 && px < imageWidth && py >= 0 && py < imageHeight) {
painter.drawPoint(px, py);
}
}
}
// 绘制颗粒(用不同颜色标记)
QColor colors[] = {
QColor(255, 0, 0), // 红色
QColor(0, 255, 0), // 绿色
QColor(0, 0, 255), // 蓝色
QColor(255, 255, 0), // 黄色
QColor(255, 0, 255), // 紫色
QColor(0, 255, 255), // 青色
QColor(255, 128, 0), // 橙色
QColor(128, 255, 0) // 浅绿色
};
int numColors = sizeof(colors) / sizeof(colors[0]);
for (size_t i = 0; i < particles.size(); i++) {
const auto& particle = particles[i];
QColor color = colors[i % numColors];
// 绘制颗粒的8个顶点和边界框
QVector<QPoint> points2D;
for (int j = 0; j < 8; j++) {
int px = static_cast<int>((particle.vertix[j].x - xMin) / scale);
int py = static_cast<int>((particle.vertix[j].y - yMin) / scale);
if (px >= 0 && px < imageWidth && py >= 0 && py < imageHeight) {
points2D.append(QPoint(px, py));
// 绘制顶点
painter.setPen(QPen(color, 3));
painter.setBrush(color);
painter.drawEllipse(QPoint(px, py), 3, 3);
}
}
// 绘制边界框(连接相邻顶点)
if (points2D.size() >= 4) {
painter.setPen(QPen(color, 1));
painter.setBrush(Qt::NoBrush);
// 假设8个顶点按照立方体的顺序排列
// 底面4个顶点0,1,2,3 顶面4个顶点4,5,6,7
int edges[12][2] = {
{0,1}, {1,2}, {2,3}, {3,0}, // 底面
{4,5}, {5,6}, {6,7}, {7,4}, // 顶面
{0,4}, {1,5}, {2,6}, {3,7} // 竖边
};
for (int j = 0; j < 12; j++) {
int idx1 = edges[j][0];
int idx2 = edges[j][1];
if (idx1 < points2D.size() && idx2 < points2D.size()) {
painter.drawLine(points2D[idx1], points2D[idx2]);
}
}
}
// 计算颗粒中心位置并标注序号
if (!points2D.empty()) {
QPoint center(0, 0);
for (const auto& pt : points2D) {
center.setX(center.x() + pt.x());
center.setY(center.y() + pt.y());
}
center.setX(center.x() / points2D.size());
center.setY(center.y() / points2D.size());
// 绘制序号
painter.setPen(color);
painter.setFont(QFont("Arial", 10, QFont::Bold));
painter.drawText(center, QString::number(i + 1));
}
}
return image;
}
QImage PointCloudImageUtils::GenerateWheelArchImage(
const std::vector<std::vector<SVzNL3DPosition>>& scanLines,
const SVzNL3DPoint& wheelArchPos,
const SVzNL3DPoint& wheelUpPos,
const SVzNL3DPoint& wheelDownPos,
double archToCenterHeight,
bool hasResult)
{
// 固定图像尺寸
int imgRows = 992;
int imgCols = 1056;
int x_skip = 50;
int y_skip = 50;
// 创建图像
QImage image(imgCols, imgRows, QImage::Format_RGB888);
image.fill(Qt::black);
if (scanLines.empty()) {
return image;
}
// 计算点云范围
double xMin, xMax, yMin, yMax;
CalculateScanLinesRange(scanLines, xMin, xMax, yMin, yMax);
// 检查范围是否有效
if (xMax <= xMin || yMax <= yMin) {
return image;
}
QPainter painter(&image);
painter.setRenderHint(QPainter::Antialiasing, true);
// 计算投影比例
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;
// 绘制点云数据(灰色)
for (const auto& scanLine : scanLines) {
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(QColor(128, 128, 128), 1));
painter.drawPoint(px, py);
}
}
}
// 如果有检测结果,绘制轮眉检测结果
if (hasResult) {
// 绘制轮眉位置(红色大圆点)
if (fabs(wheelArchPos.x) > 0.0001 || fabs(wheelArchPos.y) > 0.0001) {
int px = (int)((wheelArchPos.x - xMin) / x_scale + x_skip);
int py = (int)((wheelArchPos.y - yMin) / y_scale + y_skip);
if (px >= 0 && px < imgCols && py >= 0 && py < imgRows) {
painter.setPen(QPen(QColor(255, 0, 0), 2));
painter.setBrush(QBrush(QColor(255, 0, 0)));
painter.drawEllipse(px - 8, py - 8, 16, 16);
// 标注"轮眉"
painter.setPen(QPen(Qt::white, 1));
painter.setFont(QFont("Arial", 24, QFont::Bold));
painter.drawText(px + 12, py + 5, QString::fromUtf8("轮眉"));
}
}
// 绘制上点位置(绿色圆点)
if (fabs(wheelUpPos.x) > 0.0001 || fabs(wheelUpPos.y) > 0.0001) {
int px = (int)((wheelUpPos.x - xMin) / x_scale + x_skip);
int py = (int)((wheelUpPos.y - yMin) / y_scale + y_skip);
if (px >= 0 && px < imgCols && py >= 0 && py < imgRows) {
painter.setPen(QPen(QColor(0, 255, 0), 2));
painter.setBrush(QBrush(QColor(0, 255, 0)));
painter.drawEllipse(px - 6, py - 6, 12, 12);
// 标注"上点"
painter.setPen(QPen(Qt::white, 1));
painter.setFont(QFont("Arial", 24, QFont::Bold));
painter.drawText(px + 10, py + 5, QString::fromUtf8("上点"));
}
}
// 绘制下点位置(蓝色圆点)
if (fabs(wheelDownPos.x) > 0.0001 || fabs(wheelDownPos.y) > 0.0001) {
int px = (int)((wheelDownPos.x - xMin) / x_scale + x_skip);
int py = (int)((wheelDownPos.y - yMin) / y_scale + y_skip);
if (px >= 0 && px < imgCols && py >= 0 && py < imgRows) {
painter.setPen(QPen(QColor(0, 0, 255), 2));
painter.setBrush(QBrush(QColor(0, 0, 255)));
painter.drawEllipse(px - 6, py - 6, 12, 12);
// 标注"下点"
painter.setPen(QPen(Qt::white, 1));
painter.setFont(QFont("Arial", 24, QFont::Bold));
painter.drawText(px + 10, py + 5, QString::fromUtf8("下点"));
}
}
// 绘制连线(从上点到轮眉到下点)
if ((fabs(wheelUpPos.x) > 0.0001 || fabs(wheelUpPos.y) > 0.0001) &&
(fabs(wheelArchPos.x) > 0.0001 || fabs(wheelArchPos.y) > 0.0001)) {
int px1 = (int)((wheelUpPos.x - xMin) / x_scale + x_skip);
int py1 = (int)((wheelUpPos.y - yMin) / y_scale + y_skip);
int px2 = (int)((wheelArchPos.x - xMin) / x_scale + x_skip);
int py2 = (int)((wheelArchPos.y - yMin) / y_scale + y_skip);
painter.setPen(QPen(QColor(255, 255, 0), 2, Qt::DashLine));
painter.drawLine(px1, py1, px2, py2);
}
if ((fabs(wheelArchPos.x) > 0.0001 || fabs(wheelArchPos.y) > 0.0001) &&
(fabs(wheelDownPos.x) > 0.0001 || fabs(wheelDownPos.y) > 0.0001)) {
int px1 = (int)((wheelArchPos.x - xMin) / x_scale + x_skip);
int py1 = (int)((wheelArchPos.y - yMin) / y_scale + y_skip);
int px2 = (int)((wheelDownPos.x - xMin) / x_scale + x_skip);
int py2 = (int)((wheelDownPos.y - yMin) / y_scale + y_skip);
painter.setPen(QPen(QColor(255, 255, 0), 2, Qt::DashLine));
painter.drawLine(px1, py1, px2, py2);
}
// 在图像右上角显示拱高信息
// painter.setPen(QPen(Qt::white, 1));
// painter.setFont(QFont("Arial", 14, QFont::Bold));
// QString heightText = QString::fromUtf8("拱高: %1 mm").arg(archToCenterHeight, 0, 'f', 2);
// painter.drawText(imgCols - 200, 40, heightText);
}
return image;
}
QImage PointCloudImageUtils::GenerateChannelSpaceImage(
const std::vector<std::vector<SVzNL3DPosition>>& scanLines,
int imageWidth, int imageHeight)
{
// 创建图像
QImage image(imageWidth, imageHeight, QImage::Format_RGB888);
image.fill(Qt::black);
if (scanLines.empty()) {
return image;
}
int x_skip = 30;
int y_skip = 30;
// 计算点云范围
double xMin, xMax, yMin, yMax;
CalculateScanLinesRange(scanLines, xMin, xMax, yMin, yMax);
// 检查范围是否有效
if (xMax <= xMin || yMax <= yMin) {
return image;
}
// 计算投影比例
double y_rows = (double)(imageHeight - y_skip * 2);
double x_cols = (double)(imageWidth - x_skip * 2);
double x_scale = (xMax - xMin) / x_cols;
double y_scale = (yMax - yMin) / y_rows;
// 使用统一的比例尺
double scale = std::max(x_scale, y_scale);
// 计算点云总数和密度,动态调整点的绘制大小
int totalPoints = 0;
for (const auto& scanLine : scanLines) {
for (const auto& point : scanLine) {
if (point.pt3D.z > 1e-4) totalPoints++;
}
}
// 根据点云密度计算基础点大小(点少则画大,点多则画小)
int basePointSize = 2;
if (totalPoints < 10000) {
basePointSize = 4;
} else if (totalPoints < 50000) {
basePointSize = 3;
} else {
basePointSize = 2;
}
QPainter painter(&image);
painter.setRenderHint(QPainter::Antialiasing, false);
// 预定义8种颜色参考 HC_chanelSpaceMeasure_test.cpp 的 objColor
QColor objColors[8] = {
QColor(245, 222, 179), // 淡黄色
QColor(210, 105, 30), // 巧克力色
QColor(240, 230, 140), // 黄褐色
QColor(135, 206, 235), // 天蓝色
QColor(250, 235, 215), // 古董白
QColor(189, 252, 201), // 薄荷色
QColor(221, 160, 221), // 梅红色
QColor(188, 143, 143) // 玫瑰红色
};
// 绘制点云数据 - 根据算法标记的nPointIdx着色
for (const auto& scanLine : scanLines) {
for (const auto& point : scanLine) {
if (point.pt3D.z < 1e-4) continue;
// 根据nPointIdx确定颜色和大小
// 参考 _outputRGBDScan_RGBD: nPointIdx > 0 使用objColor, 否则灰色
QColor pointColor;
int pointSize = basePointSize;
if (point.nPointIdx > 0 && point.nPointIdx <= 8) {
pointColor = objColors[point.nPointIdx - 1];
pointSize = basePointSize + 2; // 特征点更大
} else if (point.nPointIdx > 8) {
// nPointIdx > 8 使用取模方式循环颜色
pointColor = objColors[(point.nPointIdx - 1) % 8];
pointSize = basePointSize + 2;
} else {
// nPointIdx == 0 或负数,使用灰色
pointColor = QColor(180, 180, 180);
pointSize = basePointSize;
}
// 计算图像坐标
int px = (int)((point.pt3D.x - xMin) / scale + x_skip);
int py = (int)((point.pt3D.y - yMin) / scale + y_skip);
if (px >= 0 && px < imageWidth && py >= 0 && py < imageHeight) {
// 使用填充矩形绘制点,避免稀疏
painter.fillRect(px - pointSize/2, py - pointSize/2,
pointSize, pointSize, pointColor);
}
}
}
return image;
}
int PointCloudImageUtils::GenerateDepthImage(
const std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& detectionDataCache,
QImage& outImage)
{
if (detectionDataCache.empty()) {
return -1;
}
// 固定图像尺寸
int imgRows = 992;
int imgCols = 1056;
int x_skip = 50;
int y_skip = 50;
// 计算点云范围
double xMin = std::numeric_limits<double>::max();
double xMax = std::numeric_limits<double>::lowest();
double yMin = std::numeric_limits<double>::max();
double yMax = std::numeric_limits<double>::lowest();
double zMin = std::numeric_limits<double>::max();
double zMax = std::numeric_limits<double>::lowest();
for (const auto& dataPair : detectionDataCache) {
EVzResultDataType dataType = dataPair.first;
const SVzLaserLineData& lineData = dataPair.second;
if (!lineData.p3DPoint || lineData.nPointCount <= 0) continue;
// 根据数据类型处理点云数据
if (dataType == keResultDataType_Position) {
SVzNL3DPosition* positions = static_cast<SVzNL3DPosition*>(lineData.p3DPoint);
for (int i = 0; i < lineData.nPointCount; i++) {
const SVzNL3DPosition& point = positions[i];
if (point.pt3D.z < 1e-4) continue;
if (point.pt3D.x < xMin) xMin = point.pt3D.x;
if (point.pt3D.x > xMax) xMax = point.pt3D.x;
if (point.pt3D.y < yMin) yMin = point.pt3D.y;
if (point.pt3D.y > yMax) yMax = point.pt3D.y;
if (point.pt3D.z < zMin) zMin = point.pt3D.z;
if (point.pt3D.z > zMax) zMax = point.pt3D.z;
}
} else if (dataType == keResultDataType_PointXYZRGBA) {
SVzNLPointXYZRGBA* points = static_cast<SVzNLPointXYZRGBA*>(lineData.p3DPoint);
for (int i = 0; i < lineData.nPointCount; i++) {
const SVzNLPointXYZRGBA& point = points[i];
if (point.z < 1e-4) continue;
if (point.x < xMin) xMin = point.x;
if (point.x > xMax) xMax = point.x;
if (point.y < yMin) yMin = point.y;
if (point.y > yMax) yMax = point.y;
if (point.z < zMin) zMin = point.z;
if (point.z > zMax) zMax = point.z;
}
}
}
// 检查范围是否有效
if (xMax <= xMin || yMax <= yMin) {
return -2;
}
// 创建图像
outImage = QImage(imgCols, imgRows, QImage::Format_RGB888);
outImage.fill(Qt::black);
QPainter painter(&outImage);
// 计算投影比例
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;
double zRange = zMax - zMin;
if (zRange < 1e-4) zRange = 1.0; // 防止除0
// 绘制点云数据 - 使用Z值映射颜色深度图
for (const auto& dataPair : detectionDataCache) {
EVzResultDataType dataType = dataPair.first;
const SVzLaserLineData& lineData = dataPair.second;
if (!lineData.p3DPoint || lineData.nPointCount <= 0) continue;
if (dataType == keResultDataType_Position) {
SVzNL3DPosition* positions = static_cast<SVzNL3DPosition*>(lineData.p3DPoint);
for (int i = 0; i < lineData.nPointCount; i++) {
const SVzNL3DPosition& point = positions[i];
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) {
// 根据Z值计算颜色深度图效果
double normalizedZ = (point.pt3D.z - zMin) / zRange;
normalizedZ = std::max(0.0, std::min(1.0, normalizedZ));
// 使用热力图颜色映射:蓝->青->绿->黄->红
QColor color;
if (normalizedZ < 0.25) {
int r = 0;
int g = (int)(normalizedZ * 4 * 255);
int b = 255;
color = QColor(r, g, b);
} else if (normalizedZ < 0.5) {
int r = 0;
int g = 255;
int b = (int)((0.5 - normalizedZ) * 4 * 255);
color = QColor(r, g, b);
} else if (normalizedZ < 0.75) {
int r = (int)((normalizedZ - 0.5) * 4 * 255);
int g = 255;
int b = 0;
color = QColor(r, g, b);
} else {
int r = 255;
int g = (int)((1.0 - normalizedZ) * 4 * 255);
int b = 0;
color = QColor(r, g, b);
}
painter.setPen(QPen(color, 1));
painter.drawPoint(px, py);
}
}
} else if (dataType == keResultDataType_PointXYZRGBA) {
SVzNLPointXYZRGBA* points = static_cast<SVzNLPointXYZRGBA*>(lineData.p3DPoint);
for (int i = 0; i < lineData.nPointCount; i++) {
const SVzNLPointXYZRGBA& point = points[i];
if (point.z < 1e-4) continue;
// 计算图像坐标
int px = (int)((point.x - xMin) / x_scale + x_skip);
int py = (int)((point.y - yMin) / y_scale + y_skip);
if (px >= 0 && px < imgCols && py >= 0 && py < imgRows) {
// 根据Z值计算颜色深度图效果
double normalizedZ = (point.z - zMin) / zRange;
normalizedZ = std::max(0.0, std::min(1.0, normalizedZ));
// 使用热力图颜色映射:蓝->青->绿->黄->红
QColor color;
if (normalizedZ < 0.25) {
int r = 0;
int g = (int)(normalizedZ * 4 * 255);
int b = 255;
color = QColor(r, g, b);
} else if (normalizedZ < 0.5) {
int r = 0;
int g = 255;
int b = (int)((0.5 - normalizedZ) * 4 * 255);
color = QColor(r, g, b);
} else if (normalizedZ < 0.75) {
int r = (int)((normalizedZ - 0.5) * 4 * 255);
int g = 255;
int b = 0;
color = QColor(r, g, b);
} else {
int r = 255;
int g = (int)((1.0 - normalizedZ) * 4 * 255);
int b = 0;
color = QColor(r, g, b);
}
painter.setPen(QPen(color, 1));
painter.drawPoint(px, py);
}
}
}
}
return 0;
}