WoodAlgo/Algo/DetectHole/src/PlaneSegmentation.cpp
2026-03-30 00:22:24 +08:00

1416 lines
48 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 "PlaneSegmentation.h"
#include "HoleDetection.h"
#include <algorithm>
#include <cmath>
#include <iostream>
#include <map>
#include <queue>
// 辅助函数:检查点是否有效
static bool IsValidPoint(const SVzNLPointXYZ& pt) {
constexpr float kEpsilon = 1e-6f;
if (std::abs(pt.x) < kEpsilon &&
std::abs(pt.y) < kEpsilon &&
std::abs(pt.z) < kEpsilon) {
return false;
}
return std::isfinite(pt.x) && std::isfinite(pt.y) && std::isfinite(pt.z);
}
int SegmentPlanesByZHistogram(
const SVzNLPointXYZ* points,
int pointCount,
const ZHistogramSegmentationParams& params,
std::vector<ZHistogramPeak>& outPeaks,
int* errCode
) {
if (points == nullptr || pointCount < 3 || errCode == nullptr) {
if (errCode) *errCode = HD_ERR_INVALID_INPUT;
return 0;
}
outPeaks.clear();
// ========== 步骤1计算Z值范围 ==========
float minZ = std::numeric_limits<float>::max();
float maxZ = std::numeric_limits<float>::lowest();
int validCount = 0;
for (int i = 0; i < pointCount; i++) {
if (!IsValidPoint(points[i])) continue;
float z = points[i].z;
if (z < minZ) minZ = z;
if (z > maxZ) maxZ = z;
validCount++;
}
if (validCount < params.minPeakPoints) {
*errCode = HD_ERR_INVALID_INPUT;
return 0;
}
std::cout << "[Z-Histogram] Z range: [" << minZ << ", " << maxZ
<< "], valid points: " << validCount << std::endl;
// ========== 步骤2创建直方图 ==========
float zRange = maxZ - minZ;
int numBins = static_cast<int>(std::ceil(zRange / params.binSize)) + 1;
if (numBins < 2) {
std::cout << "[Z-Histogram] All points in same Z layer" << std::endl;
*errCode = HD_ERR_NO_PITS_DETECTED;
return 0;
}
std::vector<int> histogram(numBins, 0);
std::vector<std::vector<int>> binPointIndices(numBins);
// 统计每个bin的点数
for (int i = 0; i < pointCount; i++) {
if (!IsValidPoint(points[i])) continue;
float z = points[i].z;
int binIdx = static_cast<int>((z - minZ) / params.binSize);
if (binIdx >= numBins) binIdx = numBins - 1;
if (binIdx < 0) binIdx = 0;
histogram[binIdx]++;
binPointIndices[binIdx].push_back(i);
}
// ========== 步骤3平滑直方图可选==========
if (params.smoothingWindow > 1) {
std::vector<int> smoothed(numBins, 0);
int halfWindow = params.smoothingWindow / 2;
for (int i = 0; i < numBins; i++) {
int sum = 0;
int count = 0;
for (int j = std::max(0, i - halfWindow);
j <= std::min(numBins - 1, i + halfWindow); j++) {
sum += histogram[j];
count++;
}
smoothed[i] = sum / count;
}
histogram = smoothed;
}
// ========== 步骤4检测峰值 ==========
std::vector<int> peakBins;
for (int i = 1; i < numBins - 1; i++) {
// 局部最大值检测
if (histogram[i] > histogram[i-1] && histogram[i] > histogram[i+1]) {
// 检查峰值是否足够显著
if (histogram[i] >= params.minPeakPoints) {
peakBins.push_back(i);
}
}
}
// 边界检查
if (numBins > 0 && histogram[0] >= params.minPeakPoints) {
if (numBins == 1 || histogram[0] > histogram[1]) {
peakBins.insert(peakBins.begin(), 0);
}
}
if (numBins > 1 && histogram[numBins-1] >= params.minPeakPoints) {
if (histogram[numBins-1] > histogram[numBins-2]) {
peakBins.push_back(numBins - 1);
}
}
std::cout << "[Z-Histogram] Found " << peakBins.size()
<< " initial peaks" << std::endl;
if (peakBins.empty()) {
*errCode = HD_ERR_NO_PITS_DETECTED;
return 0;
}
// ========== 步骤5合并相邻峰值 ==========
std::vector<int> mergedPeakBins;
int mergeBinThreshold = static_cast<int>(params.peakMergeThreshold / params.binSize);
int i = 0;
while (i < static_cast<int>(peakBins.size())) {
int startBin = peakBins[i];
int endBin = startBin;
int maxBin = startBin;
int maxCount = histogram[startBin];
// 查找相邻的峰值
int j = i + 1;
while (j < static_cast<int>(peakBins.size()) &&
(peakBins[j] - endBin) <= mergeBinThreshold) {
endBin = peakBins[j];
if (histogram[peakBins[j]] > maxCount) {
maxCount = histogram[peakBins[j]];
maxBin = peakBins[j];
}
j++;
}
mergedPeakBins.push_back(maxBin);
i = j;
}
std::cout << "[Z-Histogram] After merging: " << mergedPeakBins.size()
<< " peaks" << std::endl;
// ========== 步骤6为每个峰值收集点并计算统计信息 ==========
for (int peakBin : mergedPeakBins) {
ZHistogramPeak peak;
// 确定峰值的Z范围包含相邻的bin
int startBin = std::max(0, peakBin - mergeBinThreshold / 2);
int endBin = std::min(numBins - 1, peakBin + mergeBinThreshold / 2);
peak.zMin = minZ + startBin * params.binSize;
peak.zMax = minZ + (endBin + 1) * params.binSize;
// 收集该范围内的所有点
float sumZ = 0.0f;
for (int b = startBin; b <= endBin; b++) {
for (int idx : binPointIndices[b]) {
peak.pointIndices.push_back(idx);
sumZ += points[idx].z;
}
}
peak.pointCount = static_cast<int>(peak.pointIndices.size());
if (peak.pointCount < params.minPeakPoints) {
continue;
}
// 计算平均Z值
peak.zValue = sumZ / peak.pointCount;
outPeaks.push_back(peak);
std::cout << " Peak " << outPeaks.size()
<< ": Z=" << peak.zValue
<< " (" << peak.zMin << " - " << peak.zMax << ")"
<< ", points=" << peak.pointCount << std::endl;
}
// 按Z值排序
std::sort(outPeaks.begin(), outPeaks.end(),
[](const ZHistogramPeak& a, const ZHistogramPeak& b) {
return a.zValue < b.zValue;
});
*errCode = HD_SUCCESS;
return static_cast<int>(outPeaks.size());
}
// ========== 改进版:自适应倾斜平面分割 ==========
int SegmentTiltedPlanesByHistogram(
const SVzNLPointXYZ* points,
int pointCount,
const ZHistogramSegmentationParams& params,
std::vector<ZHistogramPeak>& outPeaks,
int* errCode
) {
if (points == nullptr || pointCount < 3 || errCode == nullptr) {
if (errCode) *errCode = HD_ERR_INVALID_INPUT;
return 0;
}
outPeaks.clear();
// ========== 步骤1估计主方向使用PCA或简单的法向量估计==========
// 这里使用简化方法假设主平面接近水平先用Z直方图粗分割
// 计算点云的质心
double cx = 0.0, cy = 0.0, cz = 0.0;
int validCount = 0;
for (int i = 0; i < pointCount; i++) {
if (!IsValidPoint(points[i])) continue;
cx += points[i].x;
cy += points[i].y;
cz += points[i].z;
validCount++;
}
if (validCount < params.minPeakPoints) {
*errCode = HD_ERR_INVALID_INPUT;
return 0;
}
cx /= validCount;
cy /= validCount;
cz /= validCount;
// ========== 步骤2计算协方差矩阵简化版PCA==========
double cxx = 0, cyy = 0, czz = 0;
double cxy = 0, cxz = 0, cyz = 0;
for (int i = 0; i < pointCount; i++) {
if (!IsValidPoint(points[i])) continue;
double dx = points[i].x - cx;
double dy = points[i].y - cy;
double dz = points[i].z - cz;
cxx += dx * dx;
cyy += dy * dy;
czz += dz * dz;
cxy += dx * dy;
cxz += dx * dz;
cyz += dy * dz;
}
// 简化:假设主平面法向量接近 (0, 0, 1)
// 如果 czz 远大于 cxx 和 cyy说明Z方向变化大适合用Z直方图
// 否则需要投影到主方向
double zVariance = czz / validCount;
double xyVariance = (cxx + cyy) / validCount;
std::cout << "[Tilted-Histogram] Z variance: " << zVariance
<< ", XY variance: " << xyVariance << std::endl;
if (zVariance > xyVariance * 0.1) {
// Z方向有足够变化使用标准Z直方图
std::cout << "[Tilted-Histogram] Using standard Z-histogram" << std::endl;
return SegmentPlanesByZHistogram(points, pointCount, params, outPeaks, errCode);
}
// ========== 步骤3对于接近水平的平面使用更精细的分层 ==========
std::cout << "[Tilted-Histogram] Planes are nearly horizontal, using fine-grained segmentation" << std::endl;
// 使用更小的bin size
ZHistogramSegmentationParams fineParams = params;
fineParams.binSize = params.binSize * 0.5f; // 减小bin大小
fineParams.smoothingWindow = std::max(1, params.smoothingWindow - 1);
return SegmentPlanesByZHistogram(points, pointCount, fineParams, outPeaks, errCode);
}
// ========== Z值波动统计函数 ==========
int ComputeZFluctuation(
const SVzNLPointXYZ* points,
int pointCount,
const ZFluctuationParams& params,
ZFluctuationStats* outStats,
int* errCode
) {
if (points == nullptr || pointCount < 3 || outStats == nullptr || errCode == nullptr) {
if (errCode) *errCode = HD_ERR_INVALID_INPUT;
return HD_ERR_INVALID_INPUT;
}
// ========== 步骤1收集所有有效点的Z值 ==========
std::vector<float> zValues;
zValues.reserve(pointCount);
for (int i = 0; i < pointCount; i++) {
if (!IsValidPoint(points[i])) continue;
zValues.push_back(points[i].z);
}
if (zValues.size() < 3) {
*errCode = HD_ERR_INVALID_INPUT;
return HD_ERR_INVALID_INPUT;
}
std::cout << "[Z-Fluctuation] Total valid points: " << zValues.size() << std::endl;
// 排序(用于计算中位数和百分位数)
std::vector<float> sortedZ = zValues;
std::sort(sortedZ.begin(), sortedZ.end());
// ========== 步骤2计算初始统计量 ==========
double sumZ = 0.0;
for (float z : zValues) {
sumZ += z;
}
float meanZ = static_cast<float>(sumZ / zValues.size());
double sumSqDiff = 0.0;
for (float z : zValues) {
double diff = z - meanZ;
sumSqDiff += diff * diff;
}
float stdDevZ = static_cast<float>(std::sqrt(sumSqDiff / zValues.size()));
// 计算中位数
float medianZ;
size_t n = sortedZ.size();
if (n % 2 == 0) {
medianZ = (sortedZ[n/2 - 1] + sortedZ[n/2]) / 2.0f;
} else {
medianZ = sortedZ[n/2];
}
// 计算四分位数和IQR
float q1 = sortedZ[n / 4];
float q3 = sortedZ[3 * n / 4];
float iqr = q3 - q1;
std::cout << "[Z-Fluctuation] Initial stats: mean=" << meanZ
<< ", stdDev=" << stdDevZ
<< ", median=" << medianZ
<< ", IQR=" << iqr << std::endl;
// ========== 步骤3根据选择的方法检测离群点 ==========
std::vector<bool> isInlier(zValues.size(), true);
int outlierCount = 0;
switch (params.method) {
case ZFluctuationParams::SIGMA_3: {
// 3-sigma规则去除距离均值超过N倍标准差的点
float threshold = params.sigmaMultiplier * stdDevZ;
for (size_t i = 0; i < zValues.size(); i++) {
if (std::abs(zValues[i] - meanZ) > threshold) {
isInlier[i] = false;
outlierCount++;
}
}
std::cout << "[Z-Fluctuation] Using 3-sigma method (threshold="
<< threshold << ")" << std::endl;
break;
}
case ZFluctuationParams::IQR: {
// IQR方法去除超出 [Q1-k*IQR, Q3+k*IQR] 范围的点
float lowerBound = q1 - params.iqrMultiplier * iqr;
float upperBound = q3 + params.iqrMultiplier * iqr;
for (size_t i = 0; i < zValues.size(); i++) {
if (zValues[i] < lowerBound || zValues[i] > upperBound) {
isInlier[i] = false;
outlierCount++;
}
}
std::cout << "[Z-Fluctuation] Using IQR method (bounds=["
<< lowerBound << ", " << upperBound << "])" << std::endl;
break;
}
case ZFluctuationParams::PERCENTILE: {
// 百分位数方法:只保留指定百分位数范围内的点
size_t lowIdx = static_cast<size_t>(params.percentileLow / 100.0f * n);
size_t highIdx = static_cast<size_t>(params.percentileHigh / 100.0f * n);
float lowerBound = sortedZ[lowIdx];
float upperBound = sortedZ[highIdx];
for (size_t i = 0; i < zValues.size(); i++) {
if (zValues[i] < lowerBound || zValues[i] > upperBound) {
isInlier[i] = false;
outlierCount++;
}
}
std::cout << "[Z-Fluctuation] Using Percentile method (bounds=["
<< lowerBound << ", " << upperBound << "])" << std::endl;
break;
}
}
std::cout << "[Z-Fluctuation] Detected " << outlierCount << " outliers ("
<< (outlierCount * 100.0f / zValues.size()) << "%)" << std::endl;
// ========== 步骤4用内点重新计算统计信息 ==========
std::vector<float> inlierZ;
inlierZ.reserve(zValues.size() - outlierCount);
for (size_t i = 0; i < zValues.size(); i++) {
if (isInlier[i]) {
inlierZ.push_back(zValues[i]);
}
}
if (inlierZ.size() < 3) {
std::cout << "[Z-Fluctuation] Warning: Too few inliers, using all points" << std::endl;
inlierZ = zValues;
outlierCount = 0;
}
// 重新计算统计量
sumZ = 0.0;
float minZ = std::numeric_limits<float>::max();
float maxZ = std::numeric_limits<float>::lowest();
for (float z : inlierZ) {
sumZ += z;
if (z < minZ) minZ = z;
if (z > maxZ) maxZ = z;
}
meanZ = static_cast<float>(sumZ / inlierZ.size());
sumSqDiff = 0.0;
for (float z : inlierZ) {
double diff = z - meanZ;
sumSqDiff += diff * diff;
}
stdDevZ = static_cast<float>(std::sqrt(sumSqDiff / inlierZ.size()));
// 重新计算中位数和IQR
std::sort(inlierZ.begin(), inlierZ.end());
n = inlierZ.size();
if (n % 2 == 0) {
medianZ = (inlierZ[n/2 - 1] + inlierZ[n/2]) / 2.0f;
} else {
medianZ = inlierZ[n/2];
}
q1 = inlierZ[n / 4];
q3 = inlierZ[3 * n / 4];
iqr = q3 - q1;
// ========== 步骤5填充输出结构 ==========
outStats->meanZ = meanZ;
outStats->stdDevZ = stdDevZ;
outStats->minZ = minZ;
outStats->maxZ = maxZ;
outStats->rangeZ = maxZ - minZ;
outStats->validPointCount = static_cast<int>(inlierZ.size());
outStats->outlierCount = outlierCount;
outStats->outlierRatio = static_cast<float>(outlierCount) / zValues.size();
outStats->medianZ = medianZ;
outStats->iqrZ = iqr;
// ========== 输出结果 ==========
std::cout << "\n[Z-Fluctuation] Final Statistics (after outlier removal):\n";
std::cout << " Mean Z: " << outStats->meanZ << " mm\n";
std::cout << " Std Dev: " << outStats->stdDevZ << " mm\n";
std::cout << " Median Z: " << outStats->medianZ << " mm\n";
std::cout << " Z Range: [" << outStats->minZ << ", " << outStats->maxZ << "] mm\n";
std::cout << " Z Span: " << outStats->rangeZ << " mm\n";
std::cout << " IQR: " << outStats->iqrZ << " mm\n";
std::cout << " Valid Points: " << outStats->validPointCount << "\n";
std::cout << " Outliers: " << outStats->outlierCount
<< " (" << (outStats->outlierRatio * 100) << "%)\n";
*errCode = HD_SUCCESS;
return HD_SUCCESS;
}
// 简化版本:使用默认参数
int ComputeZFluctuation(
const SVzNLPointXYZ* points,
int pointCount,
ZFluctuationStats* outStats,
int* errCode
) {
ZFluctuationParams params; // 使用默认参数IQR方法
return ComputeZFluctuation(points, pointCount, params, outStats, errCode);
}
// ========== 点云相邻点间距计算函数 ==========
int ComputePointSpacing(
const SVzNLPointXYZ* points,
int rows,
int cols,
PointSpacingStats* outStats,
int* errCode
) {
if (points == nullptr || rows <= 0 || cols <= 0 ||
outStats == nullptr || errCode == nullptr) {
if (errCode) *errCode = HD_ERR_INVALID_INPUT;
return HD_ERR_INVALID_INPUT;
}
std::cout << "[Point-Spacing] Analyzing " << rows << "x" << cols
<< " grid..." << std::endl;
// ========== 步骤1收集行内相邻点距离同一激光线YZ平面==========
std::vector<float> rowDistances;
rowDistances.reserve(rows * cols);
for (int r = 0; r < rows; r++) {
int prevValidCol = -1;
SVzNLPointXYZ prevPoint;
for (int c = 0; c < cols; c++) {
int idx = r * cols + c;
const SVzNLPointXYZ& pt = points[idx];
// 跳过无效点
if (!IsValidPoint(pt)) {
continue;
}
// 如果有前一个有效点,计算距离
if (prevValidCol >= 0) {
// 行内距离使用YZ平面
float dy = pt.y - prevPoint.y;
float dz = pt.z - prevPoint.z;
float dist = std::sqrt(dy * dy + dz * dz);
// 过滤明显异常的距离(可能是噪点)
// 假设合理的间距在 0.01mm 到 100mm 之间
if (dist > 0.01f && dist < 100.0f) {
rowDistances.push_back(dist);
}
}
prevValidCol = c;
prevPoint = pt;
}
}
std::cout << "[Point-Spacing] Collected " << rowDistances.size()
<< " row-wise distances" << std::endl;
// ========== 步骤2收集列内相邻点距离相邻激光线XZ平面==========
std::vector<float> colDistances;
colDistances.reserve(rows * cols);
for (int c = 0; c < cols; c++) {
int prevValidRow = -1;
SVzNLPointXYZ prevPoint;
for (int r = 0; r < rows; r++) {
int idx = r * cols + c;
const SVzNLPointXYZ& pt = points[idx];
// 跳过无效点
if (!IsValidPoint(pt)) {
continue;
}
// 如果有前一个有效点,计算距离
if (prevValidRow >= 0) {
// 列内距离使用XZ平面
float dx = pt.x - prevPoint.x;
float dz = pt.z - prevPoint.z;
float dist = std::sqrt(dx * dx + dz * dz);
// 过滤明显异常的距离
if (dist > 0.01f && dist < 100.0f) {
colDistances.push_back(dist);
}
}
prevValidRow = r;
prevPoint = pt;
}
}
std::cout << "[Point-Spacing] Collected " << colDistances.size()
<< " column-wise distances" << std::endl;
// ========== 步骤3检查是否有足够的样本 ==========
if (rowDistances.empty() && colDistances.empty()) {
std::cerr << "[Point-Spacing] Error: No valid distances found" << std::endl;
*errCode = HD_ERR_INVALID_INPUT;
return HD_ERR_INVALID_INPUT;
}
// ========== 步骤4使用IQR方法去除离群点行内距离==========
float rowSpacing = 0.0f;
float rowSpacingMedian = 0.0f;
float rowSpacingStdDev = 0.0f;
float rowSpacingMin = 0.0f;
float rowSpacingMax = 0.0f;
int rowSampleCount = 0;
if (!rowDistances.empty()) {
// 排序
std::vector<float> sortedRowDist = rowDistances;
std::sort(sortedRowDist.begin(), sortedRowDist.end());
// 计算中位数和IQR
size_t n = sortedRowDist.size();
float median = (n % 2 == 0)
? (sortedRowDist[n/2 - 1] + sortedRowDist[n/2]) / 2.0f
: sortedRowDist[n/2];
float q1 = sortedRowDist[n / 4];
float q3 = sortedRowDist[3 * n / 4];
float iqr = q3 - q1;
// 使用IQR去除离群点
float lowerBound = q1 - 1.5f * iqr;
float upperBound = q3 + 1.5f * iqr;
std::vector<float> inlierRowDist;
for (float d : rowDistances) {
if (d >= lowerBound && d <= upperBound) {
inlierRowDist.push_back(d);
}
}
std::cout << "[Point-Spacing] Row distances: removed "
<< (rowDistances.size() - inlierRowDist.size())
<< " outliers (IQR bounds: [" << lowerBound << ", "
<< upperBound << "])" << std::endl;
// 计算统计量
if (!inlierRowDist.empty()) {
double sum = 0.0;
rowSpacingMin = inlierRowDist[0];
rowSpacingMax = inlierRowDist[0];
for (float d : inlierRowDist) {
sum += d;
if (d < rowSpacingMin) rowSpacingMin = d;
if (d > rowSpacingMax) rowSpacingMax = d;
}
rowSpacing = static_cast<float>(sum / inlierRowDist.size());
rowSpacingMedian = median;
rowSampleCount = static_cast<int>(inlierRowDist.size());
// 计算标准差
double sumSqDiff = 0.0;
for (float d : inlierRowDist) {
double diff = d - rowSpacing;
sumSqDiff += diff * diff;
}
rowSpacingStdDev = static_cast<float>(
std::sqrt(sumSqDiff / inlierRowDist.size())
);
}
}
// ========== 步骤5使用IQR方法去除离群点列内距离==========
float colSpacing = 0.0f;
float colSpacingMedian = 0.0f;
float colSpacingStdDev = 0.0f;
float colSpacingMin = 0.0f;
float colSpacingMax = 0.0f;
int colSampleCount = 0;
if (!colDistances.empty()) {
// 排序
std::vector<float> sortedColDist = colDistances;
std::sort(sortedColDist.begin(), sortedColDist.end());
// 计算中位数和IQR
size_t n = sortedColDist.size();
float median = (n % 2 == 0)
? (sortedColDist[n/2 - 1] + sortedColDist[n/2]) / 2.0f
: sortedColDist[n/2];
float q1 = sortedColDist[n / 4];
float q3 = sortedColDist[3 * n / 4];
float iqr = q3 - q1;
// 使用IQR去除离群点
float lowerBound = q1 - 1.5f * iqr;
float upperBound = q3 + 1.5f * iqr;
std::vector<float> inlierColDist;
for (float d : colDistances) {
if (d >= lowerBound && d <= upperBound) {
inlierColDist.push_back(d);
}
}
std::cout << "[Point-Spacing] Column distances: removed "
<< (colDistances.size() - inlierColDist.size())
<< " outliers (IQR bounds: [" << lowerBound << ", "
<< upperBound << "])" << std::endl;
// 计算统计量
if (!inlierColDist.empty()) {
double sum = 0.0;
colSpacingMin = inlierColDist[0];
colSpacingMax = inlierColDist[0];
for (float d : inlierColDist) {
sum += d;
if (d < colSpacingMin) colSpacingMin = d;
if (d > colSpacingMax) colSpacingMax = d;
}
colSpacing = static_cast<float>(sum / inlierColDist.size());
colSpacingMedian = median;
colSampleCount = static_cast<int>(inlierColDist.size());
// 计算标准差
double sumSqDiff = 0.0;
for (float d : inlierColDist) {
double diff = d - colSpacing;
sumSqDiff += diff * diff;
}
colSpacingStdDev = static_cast<float>(
std::sqrt(sumSqDiff / inlierColDist.size())
);
}
}
// ========== 步骤6填充输出结构 ==========
outStats->rowSpacing = rowSpacing;
outStats->colSpacing = colSpacing;
outStats->rowSpacingMedian = rowSpacingMedian;
outStats->colSpacingMedian = colSpacingMedian;
outStats->rowSpacingStdDev = rowSpacingStdDev;
outStats->colSpacingStdDev = colSpacingStdDev;
outStats->rowSampleCount = rowSampleCount;
outStats->colSampleCount = colSampleCount;
outStats->rowSpacingMin = rowSpacingMin;
outStats->rowSpacingMax = rowSpacingMax;
outStats->colSpacingMin = colSpacingMin;
outStats->colSpacingMax = colSpacingMax;
// 计算典型间距:取两个方向中位数的最大值
outStats->typicalSpacing = std::max(rowSpacingMedian, colSpacingMedian);
// ========== 输出结果 ==========
std::cout << "\n[Point-Spacing] Results:\n";
std::cout << " ★ Typical spacing (max of row/col): "
<< outStats->typicalSpacing << " mm\n";
std::cout << "\n";
std::cout << " Row-wise (same laser line, YZ plane):\n";
std::cout << " Median spacing: " << rowSpacingMedian << " mm\n";
std::cout << " Mean spacing: " << rowSpacing << " mm\n";
std::cout << " Std deviation: " << rowSpacingStdDev << " mm\n";
std::cout << " Range: [" << rowSpacingMin << ", "
<< rowSpacingMax << "] mm\n";
std::cout << " Sample count: " << rowSampleCount << "\n";
std::cout << "\n";
std::cout << " Column-wise (adjacent laser lines, XZ plane):\n";
std::cout << " Median spacing: " << colSpacingMedian << " mm\n";
std::cout << " Mean spacing: " << colSpacing << " mm\n";
std::cout << " Std deviation: " << colSpacingStdDev << " mm\n";
std::cout << " Range: [" << colSpacingMin << ", "
<< colSpacingMax << "] mm\n";
std::cout << " Sample count: " << colSampleCount << "\n";
// 评估采样均匀性
if (rowSampleCount > 0) {
float rowCV = rowSpacingStdDev / rowSpacing; // 变异系数
std::cout << " Row uniformity: ";
if (rowCV < 0.1f) {
std::cout << "Excellent (CV=" << rowCV << ")\n";
} else if (rowCV < 0.2f) {
std::cout << "Good (CV=" << rowCV << ")\n";
} else if (rowCV < 0.3f) {
std::cout << "Fair (CV=" << rowCV << ")\n";
} else {
std::cout << "Poor (CV=" << rowCV << ") - uneven sampling\n";
}
}
if (colSampleCount > 0) {
float colCV = colSpacingStdDev / colSpacing;
std::cout << " Column uniformity: ";
if (colCV < 0.1f) {
std::cout << "Excellent (CV=" << colCV << ")\n";
} else if (colCV < 0.2f) {
std::cout << "Good (CV=" << colCV << ")\n";
} else if (colCV < 0.3f) {
std::cout << "Fair (CV=" << colCV << ")\n";
} else {
std::cout << "Poor (CV=" << colCV << ") - uneven sampling\n";
}
}
*errCode = HD_SUCCESS;
return HD_SUCCESS;
}
// ========== RANSAC 平面分割实现 ==========
// 用3个点拟合平面方程 ax + by + cz + d = 0
static bool FitPlaneFrom3Points(
const SVzNLPointXYZ& p1,
const SVzNLPointXYZ& p2,
const SVzNLPointXYZ& p3,
float& a, float& b, float& c, float& d
) {
// v1 = p2 - p1, v2 = p3 - p1
float v1x = p2.x - p1.x, v1y = p2.y - p1.y, v1z = p2.z - p1.z;
float v2x = p3.x - p1.x, v2y = p3.y - p1.y, v2z = p3.z - p1.z;
// normal = v1 x v2
a = v1y * v2z - v1z * v2y;
b = v1z * v2x - v1x * v2z;
c = v1x * v2y - v1y * v2x;
float len = std::sqrt(a * a + b * b + c * c);
if (len < 1e-10f) return false;
a /= len;
b /= len;
c /= len;
d = -(a * p1.x + b * p1.y + c * p1.z);
return true;
}
int SegmentPlanesByRansac(
const SVzNLPointXYZ* points,
int rows,
int cols,
const RansacPlaneSegmentationParams& params,
std::vector<PlaneSegment>& outPlanes,
int* errCode
) {
int pointCount = rows * cols;
if (points == nullptr || pointCount < 3 || errCode == nullptr) {
if (errCode) *errCode = HD_ERR_INVALID_INPUT;
return 0;
}
outPlanes.clear();
// 收集所有有效点的索引
std::vector<int> validIndices;
validIndices.reserve(pointCount);
for (int i = 0; i < pointCount; i++) {
if (IsValidPoint(points[i])) {
validIndices.push_back(i);
}
}
if (static_cast<int>(validIndices.size()) < params.minPlanePoints) {
*errCode = HD_ERR_INSUFFICIENT_POINTS;
return 0;
}
std::cout << "[RANSAC] Starting plane segmentation with "
<< validIndices.size() << " valid points"
<< " (grid " << rows << "x" << cols << ")" << std::endl;
// 标记已被分配到平面的点
std::vector<bool> used(pointCount, false);
// 简单的伪随机数生成器(确定性,便于调试)
unsigned int seed = 42u;
auto nextRandom = [&seed](int maxVal) -> int {
seed = seed * 1103515245u + 12345u;
return static_cast<int>((seed >> 16) % static_cast<unsigned int>(maxVal));
};
// 4-连通邻域偏移: 上、下、左、右
const int dr[4] = { -1, 1, 0, 0 };
const int dc[4] = { 0, 0, -1, 1 };
for (int planeIdx = 0; planeIdx < params.maxPlanes; planeIdx++) {
// 收集剩余未分配的有效点索引
std::vector<int> remaining;
remaining.reserve(validIndices.size());
for (int idx : validIndices) {
if (!used[idx]) {
remaining.push_back(idx);
}
}
int remainCount = static_cast<int>(remaining.size());
if (remainCount < params.minPlanePoints) {
break;
}
// ===== RANSAC迭代寻找最佳平面 =====
float bestA = 0, bestB = 0, bestC = 0, bestD = 0;
int bestInlierCount = 0;
for (int iter = 0; iter < params.maxIterations; iter++) {
// 随机选取3个不同的点
int i1 = nextRandom(remainCount);
int i2 = nextRandom(remainCount);
int i3 = nextRandom(remainCount);
if (i1 == i2 || i1 == i3 || i2 == i3) continue;
const SVzNLPointXYZ& p1 = points[remaining[i1]];
const SVzNLPointXYZ& p2 = points[remaining[i2]];
const SVzNLPointXYZ& p3 = points[remaining[i3]];
float a, b, c, d;
if (!FitPlaneFrom3Points(p1, p2, p3, a, b, c, d)) continue;
// 统计内点数
int inlierCount = 0;
for (int idx : remaining) {
float dist = std::abs(
a * points[idx].x + b * points[idx].y +
c * points[idx].z + d
);
if (dist <= params.distanceThreshold) {
inlierCount++;
}
}
if (inlierCount > bestInlierCount) {
bestInlierCount = inlierCount;
bestA = a;
bestB = b;
bestC = c;
bestD = d;
}
}
// 检查最佳平面是否有足够的内点
if (bestInlierCount < params.minPlanePoints) {
break;
}
// ===== 提取RANSAC初始内点作为种子 =====
std::vector<int> seedIndices;
seedIndices.reserve(bestInlierCount);
for (int idx : remaining) {
float dist = std::abs(
bestA * points[idx].x + bestB * points[idx].y +
bestC * points[idx].z + bestD
);
if (dist <= params.distanceThreshold) {
seedIndices.push_back(idx);
}
}
// ===== BFS区域生长从种子点出发沿网格扩展 =====
// 使用局部Z差阈值而非全局点-平面距离,从而能追踪曲面
std::vector<bool> inPlane(pointCount, false);
std::queue<int> bfsQueue;
// 将所有种子点加入队列
for (int idx : seedIndices) {
if (!used[idx]) {
inPlane[idx] = true;
bfsQueue.push(idx);
}
}
while (!bfsQueue.empty()) {
int curIdx = bfsQueue.front();
bfsQueue.pop();
int curRow = curIdx / cols;
int curCol = curIdx % cols;
// 遍历4-连通邻居
for (int d = 0; d < 4; d++) {
int nr = curRow + dr[d];
int nc = curCol + dc[d];
if (nr < 0 || nr >= rows || nc < 0 || nc >= cols) continue;
int nIdx = nr * cols + nc;
if (used[nIdx] || inPlane[nIdx]) continue;
if (!IsValidPoint(points[nIdx])) continue;
// 局部Z差检查当前点与邻居点的Z值差
float zDiff = std::abs(points[nIdx].z - points[curIdx].z);
if (zDiff <= params.growthZThreshold) {
inPlane[nIdx] = true;
bfsQueue.push(nIdx);
}
}
}
// ===== 收集生长后的平面点 =====
PlaneSegment plane;
plane.a = bestA;
plane.b = bestB;
plane.c = bestC;
plane.d = bestD;
plane.normalAngleDeg = 0.0f;
for (int i = 0; i < pointCount; i++) {
if (inPlane[i]) {
plane.pointIndices.push_back(i);
used[i] = true;
}
}
plane.pointCount = static_cast<int>(plane.pointIndices.size());
if (plane.pointCount < params.minPlanePoints) {
// 生长后点数不足跳过但保持used标记不变以避免再次处理
std::cout << " Plane " << (planeIdx + 1)
<< ": skipped (only " << plane.pointCount
<< " points after region growing)" << std::endl;
continue;
}
outPlanes.push_back(plane);
std::cout << " Plane " << (planeIdx + 1)
<< ": normal=(" << bestA << ", " << bestB << ", " << bestC << ")"
<< ", d=" << bestD
<< ", RANSAC seeds=" << static_cast<int>(seedIndices.size())
<< ", after growth=" << plane.pointCount << std::endl;
}
std::cout << "[RANSAC] Found " << outPlanes.size() << " planes (before merge)" << std::endl;
// ===== 后处理:将小平面合并到兼容的大平面中 =====
// 解决孔洞穿透问题孔洞对面的点因BFS不可达而被分为单独小平面
// 但实际上它们属于同一物理表面。
if (outPlanes.size() > 1) {
// 按点数降序排序,最大平面在前
std::sort(outPlanes.begin(), outPlanes.end(),
[](const PlaneSegment& a, const PlaneSegment& b) {
return a.pointCount > b.pointCount;
});
// 为每个平面计算Z值统计中位数和范围
struct PlaneZStats {
float medianZ;
float minZ;
float maxZ;
};
std::vector<PlaneZStats> planeStats(outPlanes.size());
for (size_t i = 0; i < outPlanes.size(); i++) {
std::vector<float> zValues;
zValues.reserve(outPlanes[i].pointIndices.size());
for (int idx : outPlanes[i].pointIndices) {
zValues.push_back(points[idx].z);
}
std::sort(zValues.begin(), zValues.end());
size_t n = zValues.size();
planeStats[i].medianZ = (n % 2 == 0)
? (zValues[n/2 - 1] + zValues[n/2]) / 2.0f
: zValues[n/2];
planeStats[i].minZ = zValues.front();
planeStats[i].maxZ = zValues.back();
}
// 尝试将小平面合并到大平面
constexpr float kNormalDotThreshold = 0.95f; // cos(~18°), 法向量相似性
constexpr float kMergeZRangeMultiplier = 2.0f; // Z差容忍倍数
std::vector<bool> merged(outPlanes.size(), false);
for (size_t i = 1; i < outPlanes.size(); i++) {
if (merged[i]) continue;
// 寻找最佳合并目标
int bestTarget = -1;
float bestDot = 0.0f;
for (size_t j = 0; j < i; j++) {
if (merged[j]) continue;
// 检查法向量相似性(绝对值,因为法向可能反向)
float dot = std::abs(
outPlanes[i].a * outPlanes[j].a +
outPlanes[i].b * outPlanes[j].b +
outPlanes[i].c * outPlanes[j].c
);
if (dot < kNormalDotThreshold) continue;
// 检查Z范围兼容性小平面的中位Z应在大平面Z范围附近
float largeRange = planeStats[j].maxZ - planeStats[j].minZ;
float tolerance = std::max(largeRange * 0.5f,
params.growthZThreshold * kMergeZRangeMultiplier);
float zDist = std::abs(planeStats[i].medianZ - planeStats[j].medianZ);
if (zDist > tolerance) continue;
if (dot > bestDot) {
bestDot = dot;
bestTarget = static_cast<int>(j);
}
}
if (bestTarget >= 0) {
std::cout << " Merging small plane (" << outPlanes[i].pointCount
<< " pts, medZ=" << planeStats[i].medianZ
<< ") into plane (" << outPlanes[bestTarget].pointCount
<< " pts, medZ=" << planeStats[bestTarget].medianZ
<< "), normalDot=" << bestDot << std::endl;
// 合并点索引
outPlanes[bestTarget].pointIndices.insert(
outPlanes[bestTarget].pointIndices.end(),
outPlanes[i].pointIndices.begin(),
outPlanes[i].pointIndices.end()
);
outPlanes[bestTarget].pointCount += outPlanes[i].pointCount;
// 更新合并后的Z统计
float newMinZ = std::min(planeStats[bestTarget].minZ, planeStats[i].minZ);
float newMaxZ = std::max(planeStats[bestTarget].maxZ, planeStats[i].maxZ);
planeStats[bestTarget].minZ = newMinZ;
planeStats[bestTarget].maxZ = newMaxZ;
merged[i] = true;
}
}
// 移除已合并的平面
std::vector<PlaneSegment> finalPlanes;
for (size_t i = 0; i < outPlanes.size(); i++) {
if (!merged[i]) {
finalPlanes.push_back(std::move(outPlanes[i]));
}
}
outPlanes = std::move(finalPlanes);
std::cout << "[RANSAC] After merge: " << outPlanes.size() << " planes" << std::endl;
}
// ===== 后处理:对每个平面做栅格连通域标注,只保留最大连通域 =====
for (size_t pi = 0; pi < outPlanes.size(); pi++) {
PlaneSegment& plane = outPlanes[pi];
// 建立该平面的栅格掩码
std::vector<bool> mask(pointCount, false);
for (int idx : plane.pointIndices) {
mask[idx] = true;
}
// BFS连通域标注 (4-连通)
std::vector<int> label(pointCount, -1);
std::vector<int> componentSizes;
int numComponents = 0;
for (int idx : plane.pointIndices) {
if (label[idx] >= 0) continue;
int compId = numComponents++;
int compSize = 0;
std::queue<int> q;
q.push(idx);
label[idx] = compId;
while (!q.empty()) {
int cur = q.front();
q.pop();
compSize++;
int cr = cur / cols;
int cc = cur % cols;
for (int d = 0; d < 4; d++) {
int nr = cr + dr[d];
int nc = cc + dc[d];
if (nr < 0 || nr >= rows || nc < 0 || nc >= cols) continue;
int nIdx = nr * cols + nc;
if (!mask[nIdx] || label[nIdx] >= 0) continue;
label[nIdx] = compId;
q.push(nIdx);
}
}
componentSizes.push_back(compSize);
}
if (numComponents <= 1) continue;
// 找到最大连通域
int maxCompId = 0;
for (int c = 1; c < numComponents; c++) {
if (componentSizes[c] > componentSizes[maxCompId]) {
maxCompId = c;
}
}
// 只保留最大连通域的点
int removedCount = plane.pointCount - componentSizes[maxCompId];
if (removedCount > 0) {
std::vector<int> filteredIndices;
filteredIndices.reserve(componentSizes[maxCompId]);
for (int idx : plane.pointIndices) {
if (label[idx] == maxCompId) {
filteredIndices.push_back(idx);
}
}
plane.pointIndices = std::move(filteredIndices);
plane.pointCount = static_cast<int>(plane.pointIndices.size());
std::cout << " Plane " << (pi + 1)
<< ": CCL removed " << removedCount
<< " pts from " << (numComponents - 1)
<< " small components, kept " << plane.pointCount << std::endl;
}
}
// ===== 按相对大小过滤:去除远小于最大平面的平面 =====
if (outPlanes.size() > 1) {
// 找最大平面的点数
int maxPoints = 0;
for (const auto& p : outPlanes) {
if (p.pointCount > maxPoints) maxPoints = p.pointCount;
}
int sizeThreshold = static_cast<int>(maxPoints * params.minPlaneRatio);
sizeThreshold = std::max(sizeThreshold, params.minPlanePoints);
size_t beforeCount = outPlanes.size();
outPlanes.erase(
std::remove_if(outPlanes.begin(), outPlanes.end(),
[sizeThreshold](const PlaneSegment& p) {
return p.pointCount < sizeThreshold;
}),
outPlanes.end()
);
if (outPlanes.size() < beforeCount) {
std::cout << "[RANSAC] Size filter (threshold=" << sizeThreshold
<< ", ratio=" << params.minPlaneRatio
<< " of max=" << maxPoints
<< "): removed " << (beforeCount - outPlanes.size())
<< " small planes" << std::endl;
}
}
std::cout << "[RANSAC] Final: " << outPlanes.size() << " planes" << std::endl;
*errCode = HD_SUCCESS;
return static_cast<int>(outPlanes.size());
}
int FilterPlanesByNormal(
std::vector<PlaneSegment>& planes,
const NormalFilterParams& params
) {
constexpr float kPi = 3.14159265358979323846f;
// 归一化参考法向量
float refLen = std::sqrt(
params.refNx * params.refNx +
params.refNy * params.refNy +
params.refNz * params.refNz
);
if (refLen < 1e-10f) return 0;
float rnx = params.refNx / refLen;
float rny = params.refNy / refLen;
float rnz = params.refNz / refLen;
// 计算每个平面的法向夹角
for (auto& plane : planes) {
// 使用绝对值处理法向正反两个方向
float dot = std::abs(plane.a * rnx + plane.b * rny + plane.c * rnz);
dot = std::min(1.0f, dot); // 防止数值误差导致 acos 越界
plane.normalAngleDeg = std::acos(dot) * 180.0f / kPi;
}
// 移除法向夹角超过阈值的平面
auto it = std::remove_if(planes.begin(), planes.end(),
[&params](const PlaneSegment& p) {
return p.normalAngleDeg > params.maxAngleDeg;
});
int removedCount = static_cast<int>(std::distance(it, planes.end()));
planes.erase(it, planes.end());
std::cout << "[Normal-Filter] Removed " << removedCount
<< " planes by normal direction, "
<< planes.size() << " remaining" << std::endl;
for (size_t i = 0; i < planes.size(); i++) {
std::cout << " Plane " << (i + 1)
<< ": normal angle=" << planes[i].normalAngleDeg << " deg"
<< ", points=" << planes[i].pointCount << std::endl;
}
return removedCount;
}
// ========== 行内Z差分波动统计 ==========
int ComputeRowZDiffFluctuation(
const SVzNLPointXYZ* points,
int rows,
int cols,
RowZDiffFluctuationStats* outStats,
int* errCode
) {
if (points == nullptr || rows <= 0 || cols <= 0 ||
outStats == nullptr || errCode == nullptr) {
if (errCode) *errCode = HD_ERR_INVALID_INPUT;
return HD_ERR_INVALID_INPUT;
}
std::vector<float> absDzValues;
absDzValues.reserve(rows * cols);
for (int r = 0; r < rows; ++r) {
bool hasPrev = false;
float prevZ = 0.0f;
for (int c = 0; c < cols; ++c) {
const int idx = r * cols + c;
const SVzNLPointXYZ& pt = points[idx];
if (!IsValidPoint(pt)) {
continue;
}
if (hasPrev) {
const float absDz = std::abs(pt.z - prevZ);
if (std::isfinite(absDz)) {
absDzValues.push_back(absDz);
}
}
prevZ = pt.z;
hasPrev = true;
}
}
if (absDzValues.size() < 3) {
*errCode = HD_ERR_INVALID_INPUT;
return HD_ERR_INVALID_INPUT;
}
std::vector<float> sorted = absDzValues;
std::sort(sorted.begin(), sorted.end());
const size_t n = sorted.size();
const float q1 = sorted[n / 4];
const float q3 = sorted[(3 * n) / 4];
const float iqr = q3 - q1;
const float lowerBound = std::max(0.0f, q1 - 1.5f * iqr);
const float upperBound = q3 + 1.5f * iqr;
std::vector<float> inliers;
inliers.reserve(absDzValues.size());
int outlierCount = 0;
for (float v : absDzValues) {
if (v >= lowerBound && v <= upperBound) {
inliers.push_back(v);
} else {
++outlierCount;
}
}
if (inliers.size() < 3) {
inliers = absDzValues;
outlierCount = 0;
}
std::sort(inliers.begin(), inliers.end());
const size_t m = inliers.size();
double sum = 0.0;
for (float v : inliers) {
sum += v;
}
const float mean = static_cast<float>(sum / m);
double sumSqDiff = 0.0;
for (float v : inliers) {
const double d = static_cast<double>(v) - mean;
sumSqDiff += d * d;
}
const float stdDev = static_cast<float>(std::sqrt(sumSqDiff / m));
float median = 0.0f;
if (m % 2 == 0) {
median = (inliers[m / 2 - 1] + inliers[m / 2]) * 0.5f;
} else {
median = inliers[m / 2];
}
outStats->meanAbsDz = mean;
outStats->medianAbsDz = median;
outStats->stdDevAbsDz = stdDev;
outStats->minAbsDz = inliers.front();
outStats->maxAbsDz = inliers.back();
outStats->rangeAbsDz = outStats->maxAbsDz - outStats->minAbsDz;
outStats->sampleCount = static_cast<int>(m);
outStats->outlierCount = outlierCount;
outStats->outlierRatio = static_cast<float>(outlierCount) /
static_cast<float>(absDzValues.size());
*errCode = HD_SUCCESS;
return HD_SUCCESS;
}