feat(CloudView): 添加机械臂视角选项
feat(CloudUtils): 为点云图像生成添加旋转功能
This commit is contained in:
parent
36645c6ceb
commit
7631f8aadc
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,16 @@ 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);
|
||||||
|
|
||||||
// 孔洞检测图像生成 - 热力图底图 + 孔洞标记
|
// 孔洞检测图像生成 - 热力图底图 + 孔洞标记
|
||||||
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);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// 定义线特征颜色和大小获取函数
|
// 定义线特征颜色和大小获取函数
|
||||||
|
|||||||
@ -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,56 @@ 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)
|
||||||
{
|
{
|
||||||
// 先生成热力图底图
|
// 先生成热力图底图(透传旋转参数)
|
||||||
QImage image = GenerateHeatmapImage(scanLines);
|
QImage image = GenerateHeatmapImage(scanLines, rotateX_deg, rotateY_deg);
|
||||||
if (image.isNull() || holes.empty()) {
|
if (image.isNull() || holes.empty()) {
|
||||||
return image;
|
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 一致)
|
// 固定图像尺寸(与 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;
|
||||||
|
|
||||||
// 重新计算投影参数(与底图一致)
|
// 重新计算投影参数(与底图一致,需要旋转后的范围)
|
||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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 +1521,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) {
|
||||||
// 将物理半径转换为像素半径
|
// 将物理半径转换为像素半径
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
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