提交一些线段的bug修复
This commit is contained in:
parent
d6e11828a9
commit
39837df7d1
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user