Compare commits
3 Commits
d6049e7846
...
5e978eaea4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e978eaea4 | ||
|
|
7631f8aadc | ||
|
|
36645c6ceb |
1
.gitignore
vendored
1
.gitignore
vendored
@ -27,6 +27,7 @@ Release/
|
|||||||
build/
|
build/
|
||||||
build-*/
|
build-*/
|
||||||
*-build-*/
|
*-build-*/
|
||||||
|
**/TestData
|
||||||
|
|
||||||
# IDE 配置文件
|
# IDE 配置文件
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|||||||
@ -94,12 +94,18 @@ public:
|
|||||||
|
|
||||||
// 点云灰度图生成 - 灰色着色 + 自适应点大小填充扫描线间隙(通用接口)
|
// 点云灰度图生成 - 灰色着色 + 自适应点大小填充扫描线间隙(通用接口)
|
||||||
static QImage GenerateHeatmapImage(
|
static QImage GenerateHeatmapImage(
|
||||||
const std::vector<std::vector<SVzNL3DPosition>>& scanLines);
|
const std::vector<std::vector<SVzNL3DPosition>>& scanLines,
|
||||||
|
double rotateX_deg = 0.0,
|
||||||
|
double rotateY_deg = 0.0);
|
||||||
|
|
||||||
// 孔洞检测图像生成 - 热力图底图 + 孔洞标记
|
// 孔洞检测图像生成 - 热力图底图 + 孔洞标记
|
||||||
|
// useZGradient: 为true时根据点云Z值范围生成灰度渐变底图,否则使用统一灰色底图
|
||||||
static QImage GenerateHoleDetectionImage(
|
static QImage GenerateHoleDetectionImage(
|
||||||
const std::vector<std::vector<SVzNL3DPosition>>& scanLines,
|
const std::vector<std::vector<SVzNL3DPosition>>& scanLines,
|
||||||
const std::vector<HoleMarkerInfo>& holes);
|
const std::vector<HoleMarkerInfo>& holes,
|
||||||
|
double rotateX_deg = 0.0,
|
||||||
|
double rotateY_deg = 0.0,
|
||||||
|
bool useZGradient = false);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// 定义线特征颜色和大小获取函数
|
// 定义线特征颜色和大小获取函数
|
||||||
|
|||||||
@ -128,6 +128,12 @@ int LaserDataLoader::LoadLaserScanData(const std::string& fileName,
|
|||||||
return ERR_CODE(DATA_ERR_INVALID);
|
return ERR_CODE(DATA_ERR_INVALID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 防止数组越界:跳过超出声明点数的数据行
|
||||||
|
if (nLaserPointIdx >= sLaserData.nPointCount) {
|
||||||
|
LOG_WARN("nLaserPointIdx(%d) >= nPointCount(%d), skip\n", nLaserPointIdx, sLaserData.nPointCount);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (eDataType == keResultDataType_PointXYZRGBA) {
|
if (eDataType == keResultDataType_PointXYZRGBA) {
|
||||||
SVzNLPointXYZRGBA* pRGBAPoints = static_cast<SVzNLPointXYZRGBA*>(sLaserData.p3DPoint);
|
SVzNLPointXYZRGBA* pRGBAPoints = static_cast<SVzNLPointXYZRGBA*>(sLaserData.p3DPoint);
|
||||||
SVzNL2DLRPoint* p2DPoints = static_cast<SVzNL2DLRPoint*>(sLaserData.p2DPoint);
|
SVzNL2DLRPoint* p2DPoints = static_cast<SVzNL2DLRPoint*>(sLaserData.p2DPoint);
|
||||||
|
|||||||
@ -1333,21 +1333,57 @@ int PointCloudImageUtils::GenerateDepthImage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
QImage PointCloudImageUtils::GenerateHeatmapImage(
|
QImage PointCloudImageUtils::GenerateHeatmapImage(
|
||||||
const std::vector<std::vector<SVzNL3DPosition>>& scanLines)
|
const std::vector<std::vector<SVzNL3DPosition>>& scanLines,
|
||||||
|
double rotateX_deg,
|
||||||
|
double rotateY_deg)
|
||||||
{
|
{
|
||||||
if (scanLines.empty()) {
|
if (scanLines.empty()) {
|
||||||
return QImage();
|
return QImage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 预计算旋转参数
|
||||||
|
double cosX = cos(rotateX_deg * PI / 180.0);
|
||||||
|
double sinX = sin(rotateX_deg * PI / 180.0);
|
||||||
|
double cosY = cos(rotateY_deg * PI / 180.0);
|
||||||
|
double sinY = sin(rotateY_deg * PI / 180.0);
|
||||||
|
bool needRotate = (fabs(rotateX_deg) > 1e-6 || fabs(rotateY_deg) > 1e-6);
|
||||||
|
|
||||||
|
// 旋转点的 lambda:先绕X旋转,再绕Y旋转
|
||||||
|
auto rotatePoint = [&](double x, double y, double z, double& rx, double& ry, double& rz) {
|
||||||
|
// 绕X旋转
|
||||||
|
double y1 = y * cosX - z * sinX;
|
||||||
|
double z1 = y * sinX + z * cosX;
|
||||||
|
// 绕Y旋转
|
||||||
|
rx = x * cosY + z1 * sinY;
|
||||||
|
ry = y1;
|
||||||
|
rz = -x * sinY + z1 * cosY;
|
||||||
|
};
|
||||||
|
|
||||||
// 固定图像尺寸
|
// 固定图像尺寸
|
||||||
int imgRows = 992;
|
int imgRows = 992;
|
||||||
int imgCols = 1056;
|
int imgCols = 1056;
|
||||||
int x_skip = 50;
|
int x_skip = 50;
|
||||||
int y_skip = 50;
|
int y_skip = 50;
|
||||||
|
|
||||||
// 计算 XY 范围(复用已有方法)
|
// 计算 XY 范围(考虑旋转后的坐标)
|
||||||
double xMin, xMax, yMin, yMax;
|
double xMin, xMax, yMin, yMax;
|
||||||
|
if (!needRotate) {
|
||||||
CalculateScanLinesRange(scanLines, xMin, xMax, yMin, yMax);
|
CalculateScanLinesRange(scanLines, xMin, xMax, yMin, yMax);
|
||||||
|
} else {
|
||||||
|
xMin = yMin = std::numeric_limits<double>::max();
|
||||||
|
xMax = yMax = -std::numeric_limits<double>::max();
|
||||||
|
for (const auto& scanLine : scanLines) {
|
||||||
|
for (const auto& point : scanLine) {
|
||||||
|
if (point.pt3D.z < 1e-4) continue;
|
||||||
|
double rx, ry, rz;
|
||||||
|
rotatePoint(point.pt3D.x, point.pt3D.y, point.pt3D.z, rx, ry, rz);
|
||||||
|
if (rx < xMin) xMin = rx;
|
||||||
|
if (rx > xMax) xMax = rx;
|
||||||
|
if (ry < yMin) yMin = ry;
|
||||||
|
if (ry > yMax) yMax = ry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (xMax <= xMin || yMax <= yMin) {
|
if (xMax <= xMin || yMax <= yMin) {
|
||||||
return QImage();
|
return QImage();
|
||||||
@ -1385,8 +1421,19 @@ QImage PointCloudImageUtils::GenerateHeatmapImage(
|
|||||||
for (const auto& point : scanLine) {
|
for (const auto& point : scanLine) {
|
||||||
if (point.pt3D.z < 1e-4) continue;
|
if (point.pt3D.z < 1e-4) continue;
|
||||||
|
|
||||||
int px = (int)((point.pt3D.x - xMin) / x_scale + x_skip);
|
double projX, projY;
|
||||||
int py = (int)((point.pt3D.y - yMin) / y_scale + y_skip);
|
if (needRotate) {
|
||||||
|
double rx, ry, rz;
|
||||||
|
rotatePoint(point.pt3D.x, point.pt3D.y, point.pt3D.z, rx, ry, rz);
|
||||||
|
projX = rx;
|
||||||
|
projY = ry;
|
||||||
|
} else {
|
||||||
|
projX = point.pt3D.x;
|
||||||
|
projY = point.pt3D.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
int px = (int)((projX - xMin) / x_scale + x_skip);
|
||||||
|
int py = (int)((projY - yMin) / y_scale + y_skip);
|
||||||
|
|
||||||
if (px >= 0 && px < imgCols && py >= 0 && py < imgRows) {
|
if (px >= 0 && px < imgCols && py >= 0 && py < imgRows) {
|
||||||
painter.fillRect(px, py, pointSize, pointSize, grayColor);
|
painter.fillRect(px, py, pointSize, pointSize, grayColor);
|
||||||
@ -1399,23 +1446,181 @@ QImage PointCloudImageUtils::GenerateHeatmapImage(
|
|||||||
|
|
||||||
QImage PointCloudImageUtils::GenerateHoleDetectionImage(
|
QImage PointCloudImageUtils::GenerateHoleDetectionImage(
|
||||||
const std::vector<std::vector<SVzNL3DPosition>>& scanLines,
|
const std::vector<std::vector<SVzNL3DPosition>>& scanLines,
|
||||||
const std::vector<HoleMarkerInfo>& holes)
|
const std::vector<HoleMarkerInfo>& holes,
|
||||||
|
double rotateX_deg,
|
||||||
|
double rotateY_deg,
|
||||||
|
bool useZGradient)
|
||||||
{
|
{
|
||||||
// 先生成热力图底图
|
QImage image;
|
||||||
QImage image = GenerateHeatmapImage(scanLines);
|
|
||||||
if (image.isNull() || holes.empty()) {
|
if (useZGradient) {
|
||||||
return image;
|
// 根据Z值范围生成灰度渐变底图
|
||||||
|
if (scanLines.empty()) {
|
||||||
|
return QImage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 预计算旋转参数
|
||||||
|
double cosX = cos(rotateX_deg * PI / 180.0);
|
||||||
|
double sinX = sin(rotateX_deg * PI / 180.0);
|
||||||
|
double cosY = cos(rotateY_deg * PI / 180.0);
|
||||||
|
double sinY = sin(rotateY_deg * PI / 180.0);
|
||||||
|
bool needRotate = (fabs(rotateX_deg) > 1e-6 || fabs(rotateY_deg) > 1e-6);
|
||||||
|
|
||||||
|
auto rotatePoint = [&](double x, double y, double z, double& rx, double& ry, double& rz) {
|
||||||
|
double y1 = y * cosX - z * sinX;
|
||||||
|
double z1 = y * sinX + z * cosX;
|
||||||
|
rx = x * cosY + z1 * sinY;
|
||||||
|
ry = y1;
|
||||||
|
rz = -x * sinY + z1 * cosY;
|
||||||
|
};
|
||||||
|
|
||||||
// 固定图像尺寸(与 GenerateHeatmapImage 一致)
|
// 固定图像尺寸(与 GenerateHeatmapImage 一致)
|
||||||
int imgRows = 992;
|
int imgRows = 992;
|
||||||
int imgCols = 1056;
|
int imgCols = 1056;
|
||||||
int x_skip = 50;
|
int x_skip = 50;
|
||||||
int y_skip = 50;
|
int y_skip = 50;
|
||||||
|
|
||||||
// 重新计算投影参数(与底图一致)
|
// 计算 XY 范围和 Z 范围
|
||||||
double xMin, xMax, yMin, yMax;
|
double xMin, xMax, yMin, yMax;
|
||||||
|
double zMin = std::numeric_limits<double>::max();
|
||||||
|
double zMax = -std::numeric_limits<double>::max();
|
||||||
|
|
||||||
|
if (!needRotate) {
|
||||||
CalculateScanLinesRange(scanLines, xMin, xMax, yMin, yMax);
|
CalculateScanLinesRange(scanLines, xMin, xMax, yMin, yMax);
|
||||||
|
for (const auto& scanLine : scanLines) {
|
||||||
|
for (const auto& point : scanLine) {
|
||||||
|
if (point.pt3D.z < 1e-4) continue;
|
||||||
|
if (point.pt3D.z < zMin) zMin = point.pt3D.z;
|
||||||
|
if (point.pt3D.z > zMax) zMax = point.pt3D.z;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
xMin = yMin = std::numeric_limits<double>::max();
|
||||||
|
xMax = yMax = -std::numeric_limits<double>::max();
|
||||||
|
for (const auto& scanLine : scanLines) {
|
||||||
|
for (const auto& point : scanLine) {
|
||||||
|
if (point.pt3D.z < 1e-4) continue;
|
||||||
|
double rx, ry, rz;
|
||||||
|
rotatePoint(point.pt3D.x, point.pt3D.y, point.pt3D.z, rx, ry, rz);
|
||||||
|
if (rx < xMin) xMin = rx;
|
||||||
|
if (rx > xMax) xMax = rx;
|
||||||
|
if (ry < yMin) yMin = ry;
|
||||||
|
if (ry > yMax) yMax = ry;
|
||||||
|
if (point.pt3D.z < zMin) zMin = point.pt3D.z;
|
||||||
|
if (point.pt3D.z > zMax) zMax = point.pt3D.z;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xMax <= xMin || yMax <= yMin) {
|
||||||
|
return QImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
// 自适应点大小
|
||||||
|
int lineCount = static_cast<int>(scanLines.size());
|
||||||
|
int pointSize = 2;
|
||||||
|
if (lineCount > 1) {
|
||||||
|
double lineSpacing = (yMax - yMin) / (lineCount - 1);
|
||||||
|
pointSize = (int)std::ceil(lineSpacing / x_scale);
|
||||||
|
if (pointSize < 2) pointSize = 2;
|
||||||
|
if (pointSize > 6) pointSize = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建图像
|
||||||
|
image = QImage(imgCols, imgRows, QImage::Format_RGB888);
|
||||||
|
image.fill(Qt::black);
|
||||||
|
|
||||||
|
QPainter painter(&image);
|
||||||
|
|
||||||
|
double zRange = zMax - zMin;
|
||||||
|
for (const auto& scanLine : scanLines) {
|
||||||
|
for (const auto& point : scanLine) {
|
||||||
|
if (point.pt3D.z < 1e-4) continue;
|
||||||
|
|
||||||
|
double projX, projY;
|
||||||
|
if (needRotate) {
|
||||||
|
double rx, ry, rz;
|
||||||
|
rotatePoint(point.pt3D.x, point.pt3D.y, point.pt3D.z, rx, ry, rz);
|
||||||
|
projX = rx;
|
||||||
|
projY = ry;
|
||||||
|
} else {
|
||||||
|
projX = point.pt3D.x;
|
||||||
|
projY = point.pt3D.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
int px = (int)((projX - xMin) / x_scale + x_skip);
|
||||||
|
int py = (int)((projY - yMin) / y_scale + y_skip);
|
||||||
|
|
||||||
|
if (px >= 0 && px < imgCols && py >= 0 && py < imgRows) {
|
||||||
|
// Z值映射到灰度:zMin → 0(黑), zMax → 255(白)
|
||||||
|
int gray = 0;
|
||||||
|
if (zRange > 1e-6) {
|
||||||
|
gray = (int)(255.0 * (point.pt3D.z - zMin) / zRange);
|
||||||
|
if (gray < 0) gray = 0;
|
||||||
|
if (gray > 255) gray = 255;
|
||||||
|
}
|
||||||
|
painter.fillRect(px, py, pointSize, pointSize, QColor(gray, gray, gray));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 使用统一灰色底图
|
||||||
|
image = GenerateHeatmapImage(scanLines, rotateX_deg, rotateY_deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (image.isNull() || holes.empty()) {
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 预计算旋转参数(用于孔洞中心坐标变换)
|
||||||
|
double cosX = cos(rotateX_deg * PI / 180.0);
|
||||||
|
double sinX = sin(rotateX_deg * PI / 180.0);
|
||||||
|
double cosY = cos(rotateY_deg * PI / 180.0);
|
||||||
|
double sinY = sin(rotateY_deg * PI / 180.0);
|
||||||
|
bool needRotate = (fabs(rotateX_deg) > 1e-6 || fabs(rotateY_deg) > 1e-6);
|
||||||
|
|
||||||
|
auto rotatePoint = [&](double x, double y, double z, double& rx, double& ry, double& rz) {
|
||||||
|
double y1 = y * cosX - z * sinX;
|
||||||
|
double z1 = y * sinX + z * cosX;
|
||||||
|
rx = x * cosY + z1 * sinY;
|
||||||
|
ry = y1;
|
||||||
|
rz = -x * sinY + z1 * cosY;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 固定图像尺寸(与 GenerateHeatmapImage 一致)
|
||||||
|
int imgRows = 992;
|
||||||
|
int imgCols = 1056;
|
||||||
|
int x_skip = 50;
|
||||||
|
int y_skip = 50;
|
||||||
|
|
||||||
|
// 重新计算投影参数(与底图一致,需要旋转后的范围)
|
||||||
|
double xMin, xMax, yMin, yMax;
|
||||||
|
if (!needRotate) {
|
||||||
|
CalculateScanLinesRange(scanLines, xMin, xMax, yMin, yMax);
|
||||||
|
} else {
|
||||||
|
xMin = yMin = std::numeric_limits<double>::max();
|
||||||
|
xMax = yMax = -std::numeric_limits<double>::max();
|
||||||
|
for (const auto& scanLine : scanLines) {
|
||||||
|
for (const auto& point : scanLine) {
|
||||||
|
if (point.pt3D.z < 1e-4) continue;
|
||||||
|
double rx, ry, rz;
|
||||||
|
rotatePoint(point.pt3D.x, point.pt3D.y, point.pt3D.z, rx, ry, rz);
|
||||||
|
if (rx < xMin) xMin = rx;
|
||||||
|
if (rx > xMax) xMax = rx;
|
||||||
|
if (ry < yMin) yMin = ry;
|
||||||
|
if (ry > yMax) yMax = ry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
double y_rows = (double)(imgRows - y_skip * 2);
|
double y_rows = (double)(imgRows - y_skip * 2);
|
||||||
double x_cols = (double)(imgCols - x_skip * 2);
|
double x_cols = (double)(imgCols - x_skip * 2);
|
||||||
@ -1441,8 +1646,19 @@ QImage PointCloudImageUtils::GenerateHoleDetectionImage(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int px = (int)((hole.center.x - xMin) / x_scale + x_skip);
|
double projX, projY;
|
||||||
int py = (int)((hole.center.y - yMin) / y_scale + y_skip);
|
if (needRotate) {
|
||||||
|
double rx, ry, rz;
|
||||||
|
rotatePoint(hole.center.x, hole.center.y, hole.center.z, rx, ry, rz);
|
||||||
|
projX = rx;
|
||||||
|
projY = ry;
|
||||||
|
} else {
|
||||||
|
projX = hole.center.x;
|
||||||
|
projY = hole.center.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
int px = (int)((projX - xMin) / x_scale + x_skip);
|
||||||
|
int py = (int)((projY - yMin) / y_scale + y_skip);
|
||||||
|
|
||||||
if (px >= 0 && px < imgCols && py >= 0 && py < imgRows) {
|
if (px >= 0 && px < imgCols && py >= 0 && py < imgRows) {
|
||||||
// 将物理半径转换为像素半径
|
// 将物理半径转换为像素半径
|
||||||
|
|||||||
@ -303,6 +303,9 @@ private:
|
|||||||
QLineEdit* m_editRz2;
|
QLineEdit* m_editRz2;
|
||||||
QPushButton* m_btnShowPose2;
|
QPushButton* m_btnShowPose2;
|
||||||
|
|
||||||
|
// 姿态坐标轴缩放
|
||||||
|
QLineEdit* m_editPoseScale;
|
||||||
|
|
||||||
// 欧拉角旋转顺序选择
|
// 欧拉角旋转顺序选择
|
||||||
QComboBox* m_comboEulerOrder;
|
QComboBox* m_comboEulerOrder;
|
||||||
|
|
||||||
|
|||||||
@ -287,6 +287,7 @@ private:
|
|||||||
std::vector<float> colors;
|
std::vector<float> colors;
|
||||||
std::vector<int> lineIndices; // 每个点所属的线索引
|
std::vector<int> lineIndices; // 每个点所属的线索引
|
||||||
std::vector<int> originalIndices; // 每个显示点在原始点云中的索引(用于计算原始index)
|
std::vector<int> originalIndices; // 每个显示点在原始点云中的索引(用于计算原始index)
|
||||||
|
std::vector<int> pointInLineIndices; // 每个显示点在所属线中的索引(预计算,支持不等长线)
|
||||||
bool hasColor;
|
bool hasColor;
|
||||||
bool hasLineInfo; // 是否有线信息
|
bool hasLineInfo; // 是否有线信息
|
||||||
QString name;
|
QString name;
|
||||||
@ -322,6 +323,7 @@ private:
|
|||||||
, colors(std::move(other.colors))
|
, colors(std::move(other.colors))
|
||||||
, lineIndices(std::move(other.lineIndices))
|
, lineIndices(std::move(other.lineIndices))
|
||||||
, originalIndices(std::move(other.originalIndices))
|
, originalIndices(std::move(other.originalIndices))
|
||||||
|
, pointInLineIndices(std::move(other.pointInLineIndices))
|
||||||
, hasColor(other.hasColor)
|
, hasColor(other.hasColor)
|
||||||
, hasLineInfo(other.hasLineInfo)
|
, hasLineInfo(other.hasLineInfo)
|
||||||
, name(std::move(other.name))
|
, name(std::move(other.name))
|
||||||
@ -351,6 +353,7 @@ private:
|
|||||||
colors = std::move(other.colors);
|
colors = std::move(other.colors);
|
||||||
lineIndices = std::move(other.lineIndices);
|
lineIndices = std::move(other.lineIndices);
|
||||||
originalIndices = std::move(other.originalIndices);
|
originalIndices = std::move(other.originalIndices);
|
||||||
|
pointInLineIndices = std::move(other.pointInLineIndices);
|
||||||
hasColor = other.hasColor;
|
hasColor = other.hasColor;
|
||||||
hasLineInfo = other.hasLineInfo;
|
hasLineInfo = other.hasLineInfo;
|
||||||
name = std::move(other.name);
|
name = std::move(other.name);
|
||||||
|
|||||||
@ -248,6 +248,7 @@ QWidget* CloudViewMainWindow::createViewToolbar()
|
|||||||
{":/common/resource/view_right.png", "右视", 180.0f, -90.0f, 0.0f},
|
{":/common/resource/view_right.png", "右视", 180.0f, -90.0f, 0.0f},
|
||||||
{":/common/resource/view_top.png", "俯视 (XZ面)", 90.0f, 0.0f, 0.0f},
|
{":/common/resource/view_top.png", "俯视 (XZ面)", 90.0f, 0.0f, 0.0f},
|
||||||
{":/common/resource/view_bottom.png", "仰视", -90.0f, 0.0f, 0.0f},
|
{":/common/resource/view_bottom.png", "仰视", -90.0f, 0.0f, 0.0f},
|
||||||
|
{":/common/resource/view_robot.png", "机械臂", -90.0f, 90.0f, 0.0f},
|
||||||
};
|
};
|
||||||
|
|
||||||
int btnSize = 32;
|
int btnSize = 32;
|
||||||
@ -595,6 +596,16 @@ QGroupBox* CloudViewMainWindow::createMeasureGroup()
|
|||||||
connect(m_editRy2, &QLineEdit::returnPressed, this, &CloudViewMainWindow::onShowPose2);
|
connect(m_editRy2, &QLineEdit::returnPressed, this, &CloudViewMainWindow::onShowPose2);
|
||||||
connect(m_editRz2, &QLineEdit::returnPressed, this, &CloudViewMainWindow::onShowPose2);
|
connect(m_editRz2, &QLineEdit::returnPressed, this, &CloudViewMainWindow::onShowPose2);
|
||||||
|
|
||||||
|
// 姿态坐标轴缩放输入
|
||||||
|
QHBoxLayout* scaleLayout = new QHBoxLayout();
|
||||||
|
scaleLayout->setSpacing(5);
|
||||||
|
scaleLayout->addWidget(new QLabel("坐标轴长度:", group));
|
||||||
|
m_editPoseScale = new QLineEdit("10.0", group);
|
||||||
|
m_editPoseScale->setMaximumWidth(60);
|
||||||
|
scaleLayout->addWidget(m_editPoseScale);
|
||||||
|
scaleLayout->addStretch();
|
||||||
|
layout->addLayout(scaleLayout);
|
||||||
|
|
||||||
// 分隔线
|
// 分隔线
|
||||||
QFrame* line3 = new QFrame(group);
|
QFrame* line3 = new QFrame(group);
|
||||||
line3->setFrameShape(QFrame::HLine);
|
line3->setFrameShape(QFrame::HLine);
|
||||||
@ -1587,8 +1598,9 @@ void CloudViewMainWindow::onShowPose1()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 固定大小为10
|
// 读取坐标轴缩放
|
||||||
float scale = 10.0f;
|
float scale = m_editPoseScale->text().toFloat(&ok);
|
||||||
|
if (!ok || scale <= 0) scale = 25.0f;
|
||||||
|
|
||||||
// 清除之前的姿态点
|
// 清除之前的姿态点
|
||||||
m_glWidget->clearPosePoints();
|
m_glWidget->clearPosePoints();
|
||||||
@ -1660,8 +1672,9 @@ void CloudViewMainWindow::onShowPose2()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 固定大小为10
|
// 读取坐标轴缩放
|
||||||
float scale = 10.0f;
|
float scale = m_editPoseScale->text().toFloat(&ok);
|
||||||
|
if (!ok || scale <= 0) scale = 25.0f;
|
||||||
|
|
||||||
// 清除之前的姿态点
|
// 清除之前的姿态点
|
||||||
m_glWidget->clearPosePoints();
|
m_glWidget->clearPosePoints();
|
||||||
@ -2022,12 +2035,6 @@ void CloudViewMainWindow::onViewAnglesChanged(float rotX, float rotY, float rotZ
|
|||||||
|
|
||||||
void CloudViewMainWindow::onPoint1CoordChanged()
|
void CloudViewMainWindow::onPoint1CoordChanged()
|
||||||
{
|
{
|
||||||
auto selectedPoints = m_glWidget->getSelectedPoints();
|
|
||||||
if (selectedPoints.isEmpty() || !selectedPoints[0].valid) {
|
|
||||||
QMessageBox::warning(this, "提示", "请先选择点1");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 读取编辑框中的坐标
|
// 读取编辑框中的坐标
|
||||||
bool ok = true;
|
bool ok = true;
|
||||||
float x = m_editPoint1X->text().toFloat(&ok);
|
float x = m_editPoint1X->text().toFloat(&ok);
|
||||||
@ -2048,17 +2055,16 @@ void CloudViewMainWindow::onPoint1CoordChanged()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新选中点的坐标
|
// 直接设置选中点坐标(无论是否已有鼠标选点)
|
||||||
m_glWidget->updateSelectedPointCoord(0, x, y, z);
|
m_glWidget->setSelectedPointCoord(0, x, y, z);
|
||||||
|
updateSelectedPointsDisplay();
|
||||||
|
|
||||||
// 如果启用了测距且有两个点,重新计算距离
|
// 如果启用了测距且有两个点,重新计算距离
|
||||||
if (m_glWidget->isMeasureDistanceEnabled() && selectedPoints.size() >= 2 && selectedPoints[1].valid) {
|
|
||||||
auto updatedPoints = m_glWidget->getSelectedPoints();
|
auto updatedPoints = m_glWidget->getSelectedPoints();
|
||||||
if (updatedPoints.size() >= 2) {
|
if (m_glWidget->isMeasureDistanceEnabled() && updatedPoints.size() >= 2 && updatedPoints[1].valid) {
|
||||||
float distance = m_glWidget->calculateDistance(updatedPoints[0], updatedPoints[1]);
|
float distance = m_glWidget->calculateDistance(updatedPoints[0], updatedPoints[1]);
|
||||||
m_lblDistance->setText(QString("%1 mm").arg(distance, 0, 'f', 3));
|
m_lblDistance->setText(QString("%1 mm").arg(distance, 0, 'f', 3));
|
||||||
statusBar()->showMessage(QString("点1坐标已更新,距离: %1 mm").arg(distance, 0, 'f', 3));
|
statusBar()->showMessage(QString("点1坐标已更新,距离: %1 mm").arg(distance, 0, 'f', 3));
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
statusBar()->showMessage(QString("点1坐标已更新为 (%1, %2, %3)").arg(x, 0, 'f', 3).arg(y, 0, 'f', 3).arg(z, 0, 'f', 3));
|
statusBar()->showMessage(QString("点1坐标已更新为 (%1, %2, %3)").arg(x, 0, 'f', 3).arg(y, 0, 'f', 3).arg(z, 0, 'f', 3));
|
||||||
}
|
}
|
||||||
@ -2068,12 +2074,6 @@ void CloudViewMainWindow::onPoint1CoordChanged()
|
|||||||
|
|
||||||
void CloudViewMainWindow::onPoint2CoordChanged()
|
void CloudViewMainWindow::onPoint2CoordChanged()
|
||||||
{
|
{
|
||||||
auto selectedPoints = m_glWidget->getSelectedPoints();
|
|
||||||
if (selectedPoints.size() < 2 || !selectedPoints[1].valid) {
|
|
||||||
QMessageBox::warning(this, "提示", "请先选择点2");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 读取编辑框中的坐标
|
// 读取编辑框中的坐标
|
||||||
bool ok = true;
|
bool ok = true;
|
||||||
float x = m_editPoint2X->text().toFloat(&ok);
|
float x = m_editPoint2X->text().toFloat(&ok);
|
||||||
@ -2094,17 +2094,16 @@ void CloudViewMainWindow::onPoint2CoordChanged()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新选中点的坐标
|
// 直接设置选中点坐标(无论是否已有鼠标选点)
|
||||||
m_glWidget->updateSelectedPointCoord(1, x, y, z);
|
m_glWidget->setSelectedPointCoord(1, x, y, z);
|
||||||
|
updateSelectedPointsDisplay();
|
||||||
|
|
||||||
// 如果启用了测距,重新计算距离
|
// 如果启用了测距,重新计算距离
|
||||||
if (m_glWidget->isMeasureDistanceEnabled()) {
|
|
||||||
auto updatedPoints = m_glWidget->getSelectedPoints();
|
auto updatedPoints = m_glWidget->getSelectedPoints();
|
||||||
if (updatedPoints.size() >= 2) {
|
if (m_glWidget->isMeasureDistanceEnabled() && updatedPoints.size() >= 2 && updatedPoints[0].valid) {
|
||||||
float distance = m_glWidget->calculateDistance(updatedPoints[0], updatedPoints[1]);
|
float distance = m_glWidget->calculateDistance(updatedPoints[0], updatedPoints[1]);
|
||||||
m_lblDistance->setText(QString("%1 mm").arg(distance, 0, 'f', 3));
|
m_lblDistance->setText(QString("%1 mm").arg(distance, 0, 'f', 3));
|
||||||
statusBar()->showMessage(QString("点2坐标已更新,距离: %1 mm").arg(distance, 0, 'f', 3));
|
statusBar()->showMessage(QString("点2坐标已更新,距离: %1 mm").arg(distance, 0, 'f', 3));
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
statusBar()->showMessage(QString("点2坐标已更新为 (%1, %2, %3)").arg(x, 0, 'f', 3).arg(y, 0, 'f', 3).arg(z, 0, 'f', 3));
|
statusBar()->showMessage(QString("点2坐标已更新为 (%1, %2, %3)").arg(x, 0, 'f', 3).arg(y, 0, 'f', 3).arg(z, 0, 'f', 3));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -255,13 +255,27 @@ void PointCloudGLWidget::addPointCloud(const PointCloudXYZ& cloud, const QString
|
|||||||
data.pointsPerLine = 0;
|
data.pointsPerLine = 0;
|
||||||
|
|
||||||
const float EPSILON = 1e-6f;
|
const float EPSILON = 1e-6f;
|
||||||
|
int prevLineIdx = -1;
|
||||||
|
int ptInLineCounter = 0;
|
||||||
for (size_t i = 0; i < cloud.points.size(); ++i) {
|
for (size_t i = 0; i < cloud.points.size(); ++i) {
|
||||||
|
// 跟踪线内索引(对所有点递增,包括被过滤的零点)
|
||||||
|
int lineIdx = -1;
|
||||||
|
if (data.hasLineInfo && i < cloud.lineIndices.size()) {
|
||||||
|
lineIdx = cloud.lineIndices[i];
|
||||||
|
}
|
||||||
|
if (lineIdx != prevLineIdx) {
|
||||||
|
ptInLineCounter = 0;
|
||||||
|
prevLineIdx = lineIdx;
|
||||||
|
}
|
||||||
|
|
||||||
const auto& pt = cloud.points[i];
|
const auto& pt = cloud.points[i];
|
||||||
if (!std::isfinite(pt.x) || !std::isfinite(pt.y) || !std::isfinite(pt.z)) {
|
if (!std::isfinite(pt.x) || !std::isfinite(pt.y) || !std::isfinite(pt.z)) {
|
||||||
|
ptInLineCounter++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// 显示时过滤 (0,0,0) 点
|
// 显示时过滤 (0,0,0) 点
|
||||||
if (std::fabs(pt.x) < EPSILON && std::fabs(pt.y) < EPSILON && std::fabs(pt.z) < EPSILON) {
|
if (std::fabs(pt.x) < EPSILON && std::fabs(pt.y) < EPSILON && std::fabs(pt.z) < EPSILON) {
|
||||||
|
ptInLineCounter++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
data.vertices.push_back(pt.x);
|
data.vertices.push_back(pt.x);
|
||||||
@ -271,14 +285,18 @@ void PointCloudGLWidget::addPointCloud(const PointCloudXYZ& cloud, const QString
|
|||||||
// 保存原始索引
|
// 保存原始索引
|
||||||
data.originalIndices.push_back(static_cast<int>(i));
|
data.originalIndices.push_back(static_cast<int>(i));
|
||||||
|
|
||||||
|
// 保存线内索引(点在所属线中的位置)
|
||||||
|
data.pointInLineIndices.push_back(ptInLineCounter);
|
||||||
|
|
||||||
// 保存线索引
|
// 保存线索引
|
||||||
if (data.hasLineInfo && i < cloud.lineIndices.size()) {
|
if (lineIdx >= 0) {
|
||||||
int lineIdx = cloud.lineIndices[i];
|
|
||||||
data.lineIndices.push_back(lineIdx);
|
data.lineIndices.push_back(lineIdx);
|
||||||
if (lineIdx + 1 > data.totalLines) {
|
if (lineIdx + 1 > data.totalLines) {
|
||||||
data.totalLines = lineIdx + 1;
|
data.totalLines = lineIdx + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ptInLineCounter++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算每线点数(假设网格化点云)
|
// 计算每线点数(假设网格化点云)
|
||||||
@ -328,9 +346,31 @@ void PointCloudGLWidget::addPointCloud(const PointCloudXYZRGB& cloud, const QStr
|
|||||||
// 用于按点大小分组的临时 map
|
// 用于按点大小分组的临时 map
|
||||||
std::map<float, std::vector<size_t>> sizeGroupMap;
|
std::map<float, std::vector<size_t>> sizeGroupMap;
|
||||||
|
|
||||||
|
const float EPSILON = 1e-6f;
|
||||||
|
int prevLineIdx = -1;
|
||||||
|
int ptInLineCounter = 0;
|
||||||
|
|
||||||
for (size_t i = 0; i < cloud.points.size(); ++i) {
|
for (size_t i = 0; i < cloud.points.size(); ++i) {
|
||||||
const auto& pt = cloud.points[i];
|
const auto& pt = cloud.points[i];
|
||||||
|
|
||||||
|
// 跟踪线内索引(对所有点递增,包括被过滤的零点)
|
||||||
|
int lineIdx = -1;
|
||||||
|
if (data.hasLineInfo && i < cloud.lineIndices.size()) {
|
||||||
|
lineIdx = cloud.lineIndices[i];
|
||||||
|
}
|
||||||
|
if (lineIdx != prevLineIdx) {
|
||||||
|
ptInLineCounter = 0;
|
||||||
|
prevLineIdx = lineIdx;
|
||||||
|
}
|
||||||
|
|
||||||
if (!std::isfinite(pt.x) || !std::isfinite(pt.y) || !std::isfinite(pt.z)) {
|
if (!std::isfinite(pt.x) || !std::isfinite(pt.y) || !std::isfinite(pt.z)) {
|
||||||
|
ptInLineCounter++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示时过滤 (0,0,0) 点
|
||||||
|
if (std::fabs(pt.x) < EPSILON && std::fabs(pt.y) < EPSILON && std::fabs(pt.z) < EPSILON) {
|
||||||
|
ptInLineCounter++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -347,9 +387,11 @@ void PointCloudGLWidget::addPointCloud(const PointCloudXYZRGB& cloud, const QStr
|
|||||||
// 保存原始索引
|
// 保存原始索引
|
||||||
data.originalIndices.push_back(static_cast<int>(i));
|
data.originalIndices.push_back(static_cast<int>(i));
|
||||||
|
|
||||||
|
// 保存线内索引(点在所属线中的位置)
|
||||||
|
data.pointInLineIndices.push_back(ptInLineCounter);
|
||||||
|
|
||||||
// 保存线索引
|
// 保存线索引
|
||||||
if (data.hasLineInfo && i < cloud.lineIndices.size()) {
|
if (lineIdx >= 0) {
|
||||||
int lineIdx = cloud.lineIndices[i];
|
|
||||||
data.lineIndices.push_back(lineIdx);
|
data.lineIndices.push_back(lineIdx);
|
||||||
if (lineIdx + 1 > data.totalLines) {
|
if (lineIdx + 1 > data.totalLines) {
|
||||||
data.totalLines = lineIdx + 1;
|
data.totalLines = lineIdx + 1;
|
||||||
@ -360,6 +402,8 @@ void PointCloudGLWidget::addPointCloud(const PointCloudXYZRGB& cloud, const QStr
|
|||||||
if (pt.pointSize > 1.0f) {
|
if (pt.pointSize > 1.0f) {
|
||||||
sizeGroupMap[pt.pointSize].push_back(pointIndex);
|
sizeGroupMap[pt.pointSize].push_back(pointIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ptInLineCounter++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构建自定义点大小分组
|
// 构建自定义点大小分组
|
||||||
@ -843,10 +887,9 @@ SelectedPointInfo PointCloudGLWidget::pickPoint(int screenX, int screenY)
|
|||||||
// 计算点在线中的原始索引
|
// 计算点在线中的原始索引
|
||||||
if (bestLineIndex >= 0 && bestCloudIndex >= 0) {
|
if (bestLineIndex >= 0 && bestCloudIndex >= 0) {
|
||||||
const auto& cloudData = m_pointClouds[bestCloudIndex];
|
const auto& cloudData = m_pointClouds[bestCloudIndex];
|
||||||
// 使用原始索引计算:原始索引 % 每线点数
|
// 使用预计算的线内索引(支持不等长线)
|
||||||
if (bestIndex < cloudData.originalIndices.size() && cloudData.pointsPerLine > 0) {
|
if (bestIndex < cloudData.pointInLineIndices.size()) {
|
||||||
int originalIdx = cloudData.originalIndices[bestIndex];
|
result.pointIndexInLine = cloudData.pointInLineIndices[bestIndex];
|
||||||
result.pointIndexInLine = originalIdx % cloudData.pointsPerLine;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
7
CloudView/Version.md
Normal file
7
CloudView/Version.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# 1.1.3
|
||||||
|
- 修复崩溃
|
||||||
|
- 修复选点序号不对
|
||||||
|
- 视图修改为左侧
|
||||||
|
|
||||||
|
# 1.1.2
|
||||||
|
- 线回撤选中
|
||||||
Loading…
x
Reference in New Issue
Block a user