提交一些线段的bug修复

This commit is contained in:
MaJunwei 2026-04-01 14:00:29 +08:00
parent d6e11828a9
commit 39837df7d1
2 changed files with 140 additions and 183 deletions

View File

@ -477,6 +477,7 @@ int ProcessSingleFile(const std::string& inputFile,
callbackData.showLines = showLines;
SHoleDetectionDebugCallbacks debugCallbacks;
#if 1
debugCallbacks.onBoundaryDetected = OnBoundaryDetected;
debugCallbacks.onClustersFound = OnClustersFound;
debugCallbacks.onExpandedRegion = OnExpandedRegion;
@ -485,6 +486,16 @@ int ProcessSingleFile(const std::string& inputFile,
debugCallbacks.onLineAngleProfileDetected = nullptr;// OnLineAngleProfileDetected;
debugCallbacks.onPlanesSegmented = OnPlanesSegmented;
debugCallbacks.onMaskedPointsReady = nullptr;// OnMaskedPointsReady;
#else
debugCallbacks.onBoundaryDetected = nullptr;
debugCallbacks.onClustersFound = nullptr;
debugCallbacks.onExpandedRegion = nullptr;
debugCallbacks.onHoleFitted = nullptr;
debugCallbacks.onSegmentPairsDetected = nullptr;
debugCallbacks.onLineAngleProfileDetected = nullptr;
debugCallbacks.onPlanesSegmented = nullptr;
debugCallbacks.onMaskedPointsReady = nullptr;
#endif
debugCallbacks.userData = &callbackData;
SMultiHoleResult result;

View File

@ -343,6 +343,13 @@ void EvaluateLine(
if (segs[ei].type == ESegType::Descending) {
// 若已见过内部 Flat 且尚未找到上升沿,说明这是第二个独立特征的下降,不应桥接
if (seenInnerFlat && !seenAsc) break;
// 已完成一个完整的 Desc→Asc 凹坑,再遇到 Desc 表示新的孔洞开始,
// 应在此停止,避免将两个相邻孔洞桥接成一个 pair。
// bestExit 指向第二个 Desc后续 isPit 分支会回溯找到 Asc 的末端作为真正右边界。
if (seenDesc && seenAsc && descFirst) {
bestExit = ei;
break;
}
if (!seenAsc) descFirst = true;
seenDesc = true;
continue;
@ -392,27 +399,47 @@ void EvaluateLine(
}
}
// depth: 表面与坑底的 z 极差
float refZ = std::min(pts[startPos].point.z, pts[endPos].point.z);
float minZ = refZ, maxZ = refZ;
for (int k = startPos; k <= endPos; k++) {
if (pts[k].valid) {
minZ = std::min(minZ, pts[k].point.z);
maxZ = std::max(maxZ, pts[k].point.z);
// depth: 使用线性插值补偿表面斜度
// 旧方案min/max Z 相对 refZ会把表面倾斜误判为坑深
// 新方案以 startPos→endPos 的 Z 直线为基准,取最大偏差,
// 仅在真正存在弯折/凹陷处才得到显著 depth。
float depth = 0.0f;
{
float startZ = pts[startPos].point.z;
float endZ = pts[endPos].point.z;
float posSpanF = static_cast<float>(endPos - startPos);
if (posSpanF < 1.0f) posSpanF = 1.0f;
for (int k = startPos; k <= endPos; k++) {
if (!pts[k].valid) continue;
float t = static_cast<float>(k - startPos) / posSpanF;
float expectedZ = startZ + (endZ - startZ) * t;
float dev = std::abs(pts[k].point.z - expectedZ);
if (dev > depth) depth = dev;
}
}
float depth = std::max(refZ - minZ, maxZ - refZ);
if (isPit && depth < params.minPitDepth) continue;
float span = planeDist(pts[startPos].point, pts[endPos].point);
if (span < params.minFeatureSpan) continue;
// 纯空洞特征:检查物理跨度不超过最大孔洞直径
// 原先用点数maxGapPointsInLine过滤但点数与传感器密度相关
// 导致真实孔洞在点密度较低或孔洞稍大时被误杀(如 33 点 gap ≈ 12mm 被 12 点上限拒绝)。
// 改用物理跨度与 maxRadius 比较,保证检测上限与孔洞尺寸参数一致。
if (isGapFeature && !isPit && span > params.maxRadius * 2.0f) continue;
// 所有类型的 pair 跨度不应超过最大孔洞直径。
// 超过 maxRadius * 2 的 pair 通常是平坦区域内散落无效点和角度噪声
// 组合成 Desc+Gap+Asc 模式后形成的误检,应当过滤。
if (span > params.maxRadius * 2.0f) continue;
// 纯空洞特征(无 Desc+Asc 边缘):要求内部无效点占比达到一定比例。
// 真正的穿透孔洞内部绝大多数点无效(传感器无法采集数据),
// 而平坦区域散落的少量无效点不应构成空洞特征。
if (isGapFeature && !isPit) {
int invalidCount = 0;
int totalCount = endPos - startPos + 1;
for (int k = startPos; k <= endPos; k++) {
if (!pts[k].valid) invalidCount++;
}
float invalidRatio = static_cast<float>(invalidCount) / static_cast<float>(totalCount);
if (invalidRatio < 0.3f) continue;
}
SSegmentPair pair;
pair.startPoint = pts[startPos].point;
@ -515,63 +542,9 @@ int DetectPitBoundaries(
}
// ================================================================
// 聚类:行-行重叠、列-列重叠、行-列交叉
// 聚类:行-列交叉聚类 + 空间点云子聚类
// ================================================================
// 行 pair 列重叠判定
auto hasColOverlap = [](const SSegmentPair& pair1, const SSegmentPair& pair2, int tolerance = 0, float minOverlapRatio = 0.6f) -> bool {
int min1 = std::min(pair1.startCol, pair1.endCol);
int max1 = std::max(pair1.startCol, pair1.endCol);
int min2 = std::min(pair2.startCol, pair2.endCol);
int max2 = std::max(pair2.startCol, pair2.endCol);
int length1 = max1 - min1 + 1;
int length2 = max2 - min2 + 1;
min1 -= tolerance;
max1 += tolerance;
min2 -= tolerance;
max2 += tolerance;
if (max1 < min2 || max2 < min1) return false;
int overlapStart = std::max(min1, min2);
int overlapEnd = std::min(max1, max2);
int overlapSize = overlapEnd - overlapStart + 1;
int minLength = std::min(length1, length2);
float overlapRatio = static_cast<float>(overlapSize) / static_cast<float>(minLength);
return overlapRatio >= minOverlapRatio;
};
// 列 pair 行重叠判定
auto hasRowOverlap = [](const SSegmentPair& pair1, const SSegmentPair& pair2, int tolerance = 0, float minOverlapRatio = 0.6f) -> bool {
int min1 = std::min(pair1.startRow, pair1.endRow);
int max1 = std::max(pair1.startRow, pair1.endRow);
int min2 = std::min(pair2.startRow, pair2.endRow);
int max2 = std::max(pair2.startRow, pair2.endRow);
int length1 = max1 - min1 + 1;
int length2 = max2 - min2 + 1;
min1 -= tolerance;
max1 += tolerance;
min2 -= tolerance;
max2 += tolerance;
if (max1 < min2 || max2 < min1) return false;
int overlapStart = std::max(min1, min2);
int overlapEnd = std::min(max1, max2);
int overlapSize = overlapEnd - overlapStart + 1;
int minLength = std::min(length1, length2);
float overlapRatio = static_cast<float>(overlapSize) / static_cast<float>(minLength);
return overlapRatio >= minOverlapRatio;
};
// 行-列交叉判定行pair的行号在列pair的行范围内且列pair的列号在行pair的列范围内
auto hasCrossOverlap = [](const SSegmentPair& rowPair, const SSegmentPair& colPair, int tolerance = 1) -> bool {
int colMinRow = std::min(colPair.startRow, colPair.endRow) - tolerance;
@ -584,7 +557,7 @@ int DetectPitBoundaries(
return (colCol >= rowMinCol && colCol <= rowMaxCol);
};
// shouldKeep 标记
// shouldKeep 标记:仅参与了行-列交叉匹配的 pair 才保留
std::vector<std::vector<bool>> shouldKeepRow(rows);
for (int row = 0; row < rows; row++) {
shouldKeepRow[row].resize(allRowSegmentPairs[row].size(), false);
@ -649,53 +622,7 @@ int DetectPitBoundaries(
return id + pairIndex;
};
int lookAheadWindow = 3;
// 行-行聚类(现有逻辑)
for (int row = 0; row < rows; row++) {
const auto& currentRowPairs = allRowSegmentPairs[row];
for (int nextRow = row + 1; nextRow < std::min(row + lookAheadWindow + 1, rows); nextRow++) {
const auto& nextRowPairs = allRowSegmentPairs[nextRow];
for (size_t i = 0; i < currentRowPairs.size(); i++) {
for (size_t j = 0; j < nextRowPairs.size(); j++) {
if (hasColOverlap(currentRowPairs[i], nextRowPairs[j])) {
shouldKeepRow[row][i] = true;
shouldKeepRow[nextRow][j] = true;
int pairId1 = getRowPairId(row, static_cast<int>(i));
int pairId2 = getRowPairId(nextRow, static_cast<int>(j));
unite(pairId1, pairId2);
}
}
}
}
}
// 列-列聚类
for (int col = 0; col < cols; col++) {
const auto& currentColPairs = allColSegmentPairs[col];
for (int nextCol = col + 1; nextCol < std::min(col + lookAheadWindow + 1, cols); nextCol++) {
const auto& nextColPairs = allColSegmentPairs[nextCol];
for (size_t i = 0; i < currentColPairs.size(); i++) {
for (size_t j = 0; j < nextColPairs.size(); j++) {
if (hasRowOverlap(currentColPairs[i], nextColPairs[j])) {
shouldKeepCol[col][i] = true;
shouldKeepCol[nextCol][j] = true;
int pairId1 = getColPairId(col, static_cast<int>(i));
int pairId2 = getColPairId(nextCol, static_cast<int>(j));
unite(pairId1, pairId2);
}
}
}
}
}
// 行-列交叉聚类
// 行-列交叉聚类:行 pair 与列 pair 在空间上交叉则合并
for (int row = 0; row < rows; row++) {
const auto& rowPairs = allRowSegmentPairs[row];
for (size_t i = 0; i < rowPairs.size(); i++) {
@ -715,33 +642,6 @@ int DetectPitBoundaries(
}
}
// 收集有效 pair → cluster
std::map<int, std::vector<int>> rootToPairIds;
for (int row = 0; row < rows; row++) {
const auto& rowPairs = allRowSegmentPairs[row];
for (size_t i = 0; i < rowPairs.size(); i++) {
int pairId = getRowPairId(row, static_cast<int>(i));
int root = find(pairId);
rootToPairIds[root].push_back(pairId);
}
}
for (int col = 0; col < cols; col++) {
const auto& colPairs = allColSegmentPairs[col];
for (size_t i = 0; i < colPairs.size(); i++) {
int pairId = getColPairId(col, static_cast<int>(i));
int root = find(pairId);
rootToPairIds[root].push_back(pairId);
}
}
std::set<int> validRoots;
for (const auto& entry : rootToPairIds) {
int root = entry.first;
validRoots.insert(root);
}
// 从 pairId 反查 SSegmentPair
auto getPairById = [&](int pairId) -> const SSegmentPair& {
if (pairId < totalRowPairs) {
@ -769,49 +669,95 @@ int DetectPitBoundaries(
return dummy;
};
std::map<int, std::vector<int>> pairIdToBoundaryIndices;
for (const auto& entry : rootToPairIds) {
int root = entry.first;
const auto& pairIds = entry.second;
if (validRoots.find(root) == validRoots.end()) continue;
for (int pairId : pairIds) {
const auto& pair = getPairById(pairId);
SHoleBoundaryPoint peakBp;
peakBp.point = pair.startPoint;
peakBp.row = pair.startRow;
peakBp.col = pair.startCol;
int peakIndex = static_cast<int>(boundaryPoints.size());
boundaryPoints.push_back(peakBp);
pairIdToBoundaryIndices[pairId].push_back(peakIndex);
SHoleBoundaryPoint valleyBp;
valleyBp.point = pair.endPoint;
valleyBp.row = pair.endRow;
valleyBp.col = pair.endCol;
int valleyIndex = static_cast<int>(boundaryPoints.size());
boundaryPoints.push_back(valleyBp);
pairIdToBoundaryIndices[pairId].push_back(valleyIndex);
// ================================================================
// 空间点云子聚类:对每个 union-find 组按空间距离做连通分量分析
// 防止错误 pair横跨两个孔洞把两个独立孔洞桥接成同一 cluster。
// 以每个 pair 的中点为特征,连通半径 = maxRadius * 3
// 使同一孔洞内的 pair 聚在一起,而分属不同孔洞的 pair 自然断开。
// ================================================================
{
// 1. 仅收集参与了行-列交叉匹配的 pair按 union-find root 分组
std::map<int, std::vector<int>> rootToPairIds;
for (int row = 0; row < rows; row++) {
for (size_t i = 0; i < allRowSegmentPairs[row].size(); i++) {
if (!shouldKeepRow[row][i]) continue;
int pairId = getRowPairId(row, static_cast<int>(i));
rootToPairIds[find(pairId)].push_back(pairId);
}
}
}
std::map<int, std::vector<int>> rootToCluster;
for (const auto& entry : pairIdToBoundaryIndices) {
int pairId = entry.first;
const auto& boundaryIndices = entry.second;
int root = find(pairId);
for (int idx : boundaryIndices) {
rootToCluster[root].push_back(idx);
for (int col = 0; col < cols; col++) {
for (size_t j = 0; j < allColSegmentPairs[col].size(); j++) {
if (!shouldKeepCol[col][j]) continue;
int pairId = getColPairId(col, static_cast<int>(j));
rootToPairIds[find(pairId)].push_back(pairId);
}
}
}
for (const auto& entry : rootToCluster) {
clusters.push_back(entry.second);
// pair 中点XY 平面),用于空间聚类
auto getPairCenter2D = [&](int pairId) -> std::pair<float, float> {
const auto& p = getPairById(pairId);
return {(p.startPoint.x + p.endPoint.x) * 0.5f,
(p.startPoint.y + p.endPoint.y) * 0.5f};
};
// 连通半径:孔洞直径的 1.5 倍,可分离间距大于一个孔洞直径的相邻孔洞
const float splitRadius = params.maxRadius * 3.0f;
// 2. 对每个 union-find 组做空间连通分量,各子组输出为独立 cluster
for (const auto& entry : rootToPairIds) {
const auto& pairIds = entry.second;
int n = static_cast<int>(pairIds.size());
// 子组内空间 union-find
std::vector<int> sp(n);
for (int i = 0; i < n; i++) sp[i] = i;
std::function<int(int)> sf = [&sp, &sf](int x) -> int {
if (sp[x] != x) sp[x] = sf(sp[x]);
return sp[x];
};
std::vector<std::pair<float, float>> centers(n);
for (int a = 0; a < n; a++) {
centers[a] = getPairCenter2D(pairIds[a]);
}
for (int a = 0; a < n; a++) {
for (int b = a + 1; b < n; b++) {
float dx = centers[a].first - centers[b].first;
float dy = centers[a].second - centers[b].second;
if (std::sqrt(dx * dx + dy * dy) <= splitRadius) {
int ra = sf(a), rb = sf(b);
if (ra != rb) sp[ra] = rb;
}
}
}
// 按子组收集 boundary points每个子组形成一个独立 cluster
std::map<int, std::vector<int>> subGroupBoundaries;
for (int a = 0; a < n; a++) {
const auto& pair = getPairById(pairIds[a]);
int sg = sf(a);
SHoleBoundaryPoint bp1;
bp1.point = pair.startPoint;
bp1.row = pair.startRow;
bp1.col = pair.startCol;
subGroupBoundaries[sg].push_back(static_cast<int>(boundaryPoints.size()));
boundaryPoints.push_back(bp1);
SHoleBoundaryPoint bp2;
bp2.point = pair.endPoint;
bp2.row = pair.endRow;
bp2.col = pair.endCol;
subGroupBoundaries[sg].push_back(static_cast<int>(boundaryPoints.size()));
boundaryPoints.push_back(bp2);
}
for (auto& sg_entry : subGroupBoundaries) {
clusters.push_back(sg_entry.second);
}
}
}
#endif