xx
This commit is contained in:
parent
fb53e932dc
commit
d6e11828a9
@ -477,10 +477,10 @@ int ProcessSingleFile(const std::string& inputFile,
|
||||
callbackData.showLines = showLines;
|
||||
|
||||
SHoleDetectionDebugCallbacks debugCallbacks;
|
||||
debugCallbacks.onBoundaryDetected = nullptr;//OnBoundaryDetected;
|
||||
debugCallbacks.onClustersFound = nullptr;//OnClustersFound;
|
||||
debugCallbacks.onExpandedRegion = nullptr;//OnExpandedRegion;
|
||||
debugCallbacks.onHoleFitted = nullptr;// OnHoleFitted;
|
||||
debugCallbacks.onBoundaryDetected = OnBoundaryDetected;
|
||||
debugCallbacks.onClustersFound = OnClustersFound;
|
||||
debugCallbacks.onExpandedRegion = OnExpandedRegion;
|
||||
debugCallbacks.onHoleFitted = OnHoleFitted;
|
||||
debugCallbacks.onSegmentPairsDetected = OnSegmentPairsDetected;
|
||||
debugCallbacks.onLineAngleProfileDetected = nullptr;// OnLineAngleProfileDetected;
|
||||
debugCallbacks.onPlanesSegmented = OnPlanesSegmented;
|
||||
|
||||
@ -174,7 +174,7 @@ int DetectMultipleHoles(
|
||||
|
||||
// ============ Step 2: 法向量过滤 ============
|
||||
NormalFilterParams normalParams;
|
||||
FilterPlanesByNormal(planes, normalParams);
|
||||
FilterPlanesByNormal(planes, normalParams, points, totalPointCount);
|
||||
|
||||
// 如果没有找到有效平面,回退到处理整个点云
|
||||
if (planes.empty()) {
|
||||
@ -296,7 +296,7 @@ int DetectMultipleHoles(
|
||||
|
||||
SHoleResult hole;
|
||||
ret = hole_detection::internal::FitHoleFromExtremePoints(
|
||||
maskedPoints.data(),
|
||||
points,
|
||||
rows,
|
||||
cols,
|
||||
clusterPoints,
|
||||
|
||||
@ -31,6 +31,7 @@ void EvaluateLine(
|
||||
int count,
|
||||
const SHoleDetectionParams& params,
|
||||
std::vector<SSegmentPair>& segmentPairs,
|
||||
bool& bIsInvalidLine,
|
||||
EPanelType panelType
|
||||
) {
|
||||
segmentPairs.clear();
|
||||
@ -75,6 +76,10 @@ void EvaluateLine(
|
||||
pts[i].signedAngleDeg = 0.0f;
|
||||
pts[i].hasAngle = false;
|
||||
pts[i].trend = keLineAngleTrend_Invalid;
|
||||
|
||||
if (pts[i].valid) {
|
||||
bIsInvalidLine = false;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
@ -444,23 +449,48 @@ int DetectPitBoundaries(
|
||||
#if 1
|
||||
std::vector<std::vector<SSegmentPair>> allRowSegmentPairs(rows);
|
||||
|
||||
int startIdx = 0;
|
||||
int startIdx = 0; int nFirstValidLine = -1; int nLastValidLine = -1;
|
||||
for (int row = 0; row < rows; row++) {
|
||||
std::vector<SSegmentPair> curSegmentPairs;
|
||||
|
||||
EvaluateLine(points, rows, cols, startIdx, 1, cols, params, curSegmentPairs, EPanelType::YZ_PANEL);
|
||||
|
||||
std::vector<SSegmentPair> curSegmentPairs; bool bIsInvalidLine = true;
|
||||
EvaluateLine(points, rows, cols, startIdx, 1, cols, params, curSegmentPairs, bIsInvalidLine, EPanelType::YZ_PANEL);
|
||||
if (false == bIsInvalidLine)
|
||||
{
|
||||
if (nFirstValidLine < 0) {
|
||||
nFirstValidLine = row;
|
||||
}
|
||||
nLastValidLine = row;
|
||||
}
|
||||
allRowSegmentPairs[row] = curSegmentPairs;
|
||||
startIdx += cols;
|
||||
}
|
||||
if (nFirstValidLine >= 0) {
|
||||
allRowSegmentPairs[nFirstValidLine].clear();
|
||||
}
|
||||
if (nLastValidLine >= 0) {
|
||||
allRowSegmentPairs[nLastValidLine].clear();
|
||||
}
|
||||
|
||||
// 列扫描:逐列调用 EvaluateLine
|
||||
nFirstValidLine = -1; nLastValidLine = -1;
|
||||
std::vector<std::vector<SSegmentPair>> allColSegmentPairs(cols);
|
||||
for (int col = 0; col < cols; col++) {
|
||||
std::vector<SSegmentPair> curSegmentPairs;
|
||||
EvaluateLine(points, rows, cols, col, cols, rows, params, curSegmentPairs, EPanelType::XZ_PANEL);
|
||||
std::vector<SSegmentPair> curSegmentPairs; bool bIsInvalidLine = true;
|
||||
EvaluateLine(points, rows, cols, col, cols, rows, params, curSegmentPairs, bIsInvalidLine, EPanelType::XZ_PANEL);
|
||||
if (false == bIsInvalidLine)
|
||||
{
|
||||
if (nFirstValidLine < 0) {
|
||||
nFirstValidLine = col;
|
||||
}
|
||||
nLastValidLine = col;
|
||||
}
|
||||
allColSegmentPairs[col] = curSegmentPairs;
|
||||
}
|
||||
if (nFirstValidLine >= 0) {
|
||||
allColSegmentPairs[nFirstValidLine].clear();
|
||||
}
|
||||
if (nLastValidLine >= 0) {
|
||||
allColSegmentPairs[nLastValidLine].clear();
|
||||
}
|
||||
|
||||
if (debugCallbacks && debugCallbacks->onSegmentPairsDetected) {
|
||||
std::vector<SSegmentPair> allSegmentPairs;
|
||||
|
||||
@ -177,108 +177,5 @@ int ExtractClusterExtremePoints(
|
||||
return HD_SUCCESS;
|
||||
}
|
||||
|
||||
int ExpandClusterBoundingBox(
|
||||
const SVzNLPointXYZ* points,
|
||||
int rows,
|
||||
int cols,
|
||||
const SHoleDetectionParams& params,
|
||||
std::vector<unsigned char>& isBoundary,
|
||||
std::vector<SHoleBoundaryPoint>& boundaryPoints,
|
||||
std::vector<SHoleBoundaryPoint>& clusterPoints,
|
||||
int expandSize,
|
||||
int* errCode
|
||||
) {
|
||||
if (points == nullptr || rows <= 0 || cols <= 0 || errCode == nullptr) {
|
||||
if (errCode) *errCode = HD_ERR_INVALID_INPUT;
|
||||
return HD_ERR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
if (clusterPoints.empty()) {
|
||||
*errCode = HD_SUCCESS;
|
||||
return HD_SUCCESS;
|
||||
}
|
||||
|
||||
if (expandSize < 0) {
|
||||
*errCode = HD_ERR_INVALID_INPUT;
|
||||
return HD_ERR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
int minRow = INT_MAX;
|
||||
int maxRow = INT_MIN;
|
||||
int minCol = INT_MAX;
|
||||
int maxCol = INT_MIN;
|
||||
|
||||
for (const auto& cp : clusterPoints) {
|
||||
minRow = std::min(minRow, cp.row);
|
||||
maxRow = std::max(maxRow, cp.row);
|
||||
minCol = std::min(minCol, cp.col);
|
||||
maxCol = std::max(maxCol, cp.col);
|
||||
}
|
||||
|
||||
int expandedMinCol = std::max(0, minCol);
|
||||
int expandedMaxCol = std::min(cols - 1, maxCol);
|
||||
int expandedMinRow = std::max(0, minRow - expandSize);
|
||||
int expandedMaxRow = std::min(rows - 1, maxRow + expandSize);
|
||||
|
||||
size_t originalSize = clusterPoints.size();
|
||||
size_t originalBoundarySize = boundaryPoints.size();
|
||||
|
||||
std::cout << " [EXPAND] Original BBox=[" << minRow << "," << maxRow << "]x["
|
||||
<< minCol << "," << maxCol << "]" << std::endl;
|
||||
std::cout << " [EXPAND] Expanded BBox=[" << expandedMinRow << "," << expandedMaxRow << "]x["
|
||||
<< expandedMinCol << "," << expandedMaxCol << "]" << std::endl;
|
||||
|
||||
std::set<std::pair<int, int>> existingPoints;
|
||||
for (const auto& cp : clusterPoints) {
|
||||
existingPoints.insert(std::make_pair(cp.row, cp.col));
|
||||
}
|
||||
|
||||
int scannedColumns = 0;
|
||||
for (int col = expandedMinCol; col <= expandedMaxCol; col++) {
|
||||
int startIdx = expandedMinRow * cols + col;
|
||||
int step = cols;
|
||||
int count = expandedMaxRow - expandedMinRow + 1;
|
||||
|
||||
std::vector<SSegmentPair> segmentPairs;
|
||||
EvaluateLine(points, rows, cols, startIdx, step, count, params, segmentPairs, EPanelType::XZ_PANEL);
|
||||
|
||||
for (const auto& bp : segmentPairs) {
|
||||
if (existingPoints.find(std::make_pair(bp.startRow, bp.startCol)) == existingPoints.end()) {
|
||||
SHoleBoundaryPoint pt;
|
||||
pt.point = bp.startPoint;
|
||||
pt.row = bp.startRow;
|
||||
pt.col = bp.startCol;
|
||||
clusterPoints.push_back(pt);
|
||||
existingPoints.insert(std::make_pair(pt.row, pt.col));
|
||||
}
|
||||
if (existingPoints.find(std::make_pair(bp.endRow, bp.endCol)) == existingPoints.end()) {
|
||||
SHoleBoundaryPoint pt;
|
||||
pt.point = bp.endPoint;
|
||||
pt.row = bp.endRow;
|
||||
pt.col = bp.endCol;
|
||||
clusterPoints.push_back(pt);
|
||||
existingPoints.insert(std::make_pair(pt.row, pt.col));
|
||||
}
|
||||
}
|
||||
|
||||
if (!segmentPairs.empty()) {
|
||||
scannedColumns++;
|
||||
}
|
||||
}
|
||||
|
||||
size_t addedCount = clusterPoints.size() - originalSize;
|
||||
size_t newBoundaryCount = boundaryPoints.size() - originalBoundarySize;
|
||||
|
||||
std::cout << " [EXPAND] Scanned " << (expandedMaxCol - expandedMinCol + 1)
|
||||
<< " columns, " << scannedColumns << " columns found boundary points" << std::endl;
|
||||
std::cout << " [EXPAND] Cluster points: " << originalSize << " -> "
|
||||
<< clusterPoints.size() << " (added " << addedCount << ")" << std::endl;
|
||||
std::cout << " [EXPAND] Global boundary points: " << originalBoundarySize << " -> "
|
||||
<< boundaryPoints.size() << " (added " << newBoundaryCount << ")" << std::endl;
|
||||
|
||||
*errCode = HD_SUCCESS;
|
||||
return HD_SUCCESS;
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace hole_detection
|
||||
|
||||
@ -273,26 +273,28 @@ int FitHoleFromExtremePoints(
|
||||
|
||||
std::vector<SHoleBoundaryPoint> filteredExtremePoints = extremePoints;
|
||||
|
||||
int minRow = INT_MAX;
|
||||
int maxRow = INT_MIN;
|
||||
int minCol = INT_MAX;
|
||||
int maxCol = INT_MIN;
|
||||
for (const auto& ep : extremePoints) {
|
||||
minRow = std::min(minRow, ep.row);
|
||||
maxRow = std::max(maxRow, ep.row);
|
||||
minCol = std::min(minCol, ep.col);
|
||||
maxCol = std::max(maxCol, ep.col);
|
||||
int innerMinRow = INT_MAX, innerMaxRow = INT_MIN;
|
||||
int innerMinCol = INT_MAX, innerMaxCol = INT_MIN;
|
||||
for (const auto& ep : filteredExtremePoints) {
|
||||
innerMinRow = std::min(innerMinRow, ep.row);
|
||||
innerMaxRow = std::max(innerMaxRow, ep.row);
|
||||
innerMinCol = std::min(innerMinCol, ep.col);
|
||||
innerMaxCol = std::max(innerMaxCol, ep.col);
|
||||
}
|
||||
innerMinRow = std::max(0, innerMinRow);
|
||||
innerMaxRow = std::min(rows - 1, innerMaxRow);
|
||||
innerMinCol = std::max(0, innerMinCol);
|
||||
innerMaxCol = std::min(cols - 1, innerMaxCol);
|
||||
|
||||
int minExpandRow = std::max(0, minRow - detectionParams.expansionSize2);
|
||||
int maxExpandRow = std::min(rows - 1, maxRow + detectionParams.expansionSize2);
|
||||
int minExpandCol = std::max(0, minCol - detectionParams.expansionSize2);
|
||||
int maxExpandCol = std::min(cols - 1, maxCol + detectionParams.expansionSize2);
|
||||
int minExpandRow = std::max(0, innerMinRow - detectionParams.expansionSize2);
|
||||
int maxExpandRow = std::min(rows - 1, innerMaxRow + detectionParams.expansionSize2);
|
||||
int minExpandCol = std::max(0, innerMinCol - detectionParams.expansionSize2);
|
||||
int maxExpandCol = std::min(cols - 1, innerMaxCol + detectionParams.expansionSize2);
|
||||
|
||||
minRow -= detectionParams.expansionSize1;
|
||||
maxRow += detectionParams.expansionSize1;
|
||||
minCol -= detectionParams.expansionSize1;
|
||||
maxCol += detectionParams.expansionSize1;
|
||||
int minRow = innerMinRow - detectionParams.expansionSize1;
|
||||
int maxRow = innerMaxRow + detectionParams.expansionSize1;
|
||||
int minCol = innerMinCol - detectionParams.expansionSize1;
|
||||
int maxCol = innerMaxCol + detectionParams.expansionSize1;
|
||||
|
||||
std::vector<SVzNLPointXYZ> expandedPoints;
|
||||
for (int r = minExpandRow; r <= maxExpandRow; r++) {
|
||||
@ -647,6 +649,40 @@ int FitHoleFromExtremePoints(
|
||||
return HD_ERR_NO_VALID_HOLES;
|
||||
}
|
||||
|
||||
// 搜索圆孔内部点(距圆心 < radius),计算各点到参考平面的垂直距离(沿法向),
|
||||
// 取最大值作为孔洞实际深度,支持斜面场景。
|
||||
if (filterParams.minHoleDepth > 0.0f && hasReferencePlane) {
|
||||
float maxInteriorDist = 0.0f;
|
||||
float r2 = radius * radius;
|
||||
float invNorm = static_cast<float>(1.0 / planeResidualNorm);
|
||||
for (int r = innerMinRow; r <= innerMaxRow; r++) {
|
||||
for (int c = innerMinCol; c <= innerMaxCol; c++) {
|
||||
const SVzNLPointXYZ& pt = points[r * cols + c];
|
||||
if (!IsValidPoint(pt)) continue;
|
||||
float dx = pt.x - center.x;
|
||||
float dy = pt.y - center.y;
|
||||
if (dx * dx + dy * dy < r2) {
|
||||
// 点到参考平面的有符号垂直距离:(pz - (A*px + B*py + C)) / ||n||
|
||||
// 正值:点在平面下方(孔洞内部方向);负值:点在平面上方,跳过
|
||||
float planeZAtPt = static_cast<float>(planeA * pt.x + planeB * pt.y + planeC);
|
||||
float perpDist = (pt.z - planeZAtPt) * invNorm;
|
||||
if (perpDist <= 0.0f) continue;
|
||||
if (perpDist > maxInteriorDist) maxInteriorDist = perpDist;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float holeActualDepth = maxInteriorDist;
|
||||
std::cout << " [DIAG] holeActualDepth=" << holeActualDepth
|
||||
<< " (minHoleDepth=" << filterParams.minHoleDepth << ")" << std::endl;
|
||||
if (holeActualDepth < filterParams.minHoleDepth) {
|
||||
std::cout << " -> FAILED: holeActualDepth " << holeActualDepth
|
||||
<< " < minHoleDepth " << filterParams.minHoleDepth << std::endl;
|
||||
*errCode = HD_ERR_NO_VALID_HOLES;
|
||||
return HD_ERR_NO_VALID_HOLES;
|
||||
}
|
||||
}
|
||||
|
||||
hole.center.x = center.x;
|
||||
hole.center.y = center.y;
|
||||
hole.center.z = center.z;
|
||||
|
||||
@ -37,6 +37,7 @@ void EvaluateLine(
|
||||
int count,
|
||||
const SHoleDetectionParams& params,
|
||||
std::vector<SSegmentPair>& segmentPairs,
|
||||
bool& bIsInvalidLine,
|
||||
EPanelType panelType
|
||||
);
|
||||
|
||||
|
||||
@ -33,6 +33,9 @@ struct SHoleDetectionParams {
|
||||
// 当曲率角未超过 angleThreshold 时,若前后参考点间的
|
||||
// 净 Z 坡度角超过此阈值,则将该点判定为下降或上升趋势。
|
||||
// 设置为 0 可禁用坡度补充判断。
|
||||
float edgeBoundaryFilterDist; // 边缘过滤距离,单位:mm(默认值:0.0,即不过滤)
|
||||
// pair 中任意端点到首/尾有效线(行扫描取 y,列扫描取 x)
|
||||
// 的距离小于此值时,该 pair 将被剔除。
|
||||
|
||||
// 构造函数,设置默认值
|
||||
SHoleDetectionParams()
|
||||
@ -51,6 +54,7 @@ struct SHoleDetectionParams {
|
||||
, minFeatureSpan(2.0f)
|
||||
, residualSmoothWindow(5)
|
||||
, slopeAngleThreshold(3.0f)
|
||||
, edgeBoundaryFilterDist(0.0f)
|
||||
{}
|
||||
};
|
||||
|
||||
@ -78,6 +82,10 @@ struct SHoleFilterParams {
|
||||
float minInlierRatio; // 圆拟合的最小内点比率(默认值:0.0)
|
||||
// 表示落在拟合圆容差范围内的点所占比例。
|
||||
|
||||
// 深度验证
|
||||
float minHoleDepth; // 圆孔内部最大 Z 与参考平面 Z 的最小差值,单位:mm(默认值:2.5)
|
||||
// 若差值小于此阈值则认为是假孔,返回失败。<=0 表示不过滤。
|
||||
|
||||
// 构造函数,设置默认值
|
||||
SHoleFilterParams()
|
||||
: maxEccentricity(0.99995f)
|
||||
@ -87,6 +95,7 @@ struct SHoleFilterParams {
|
||||
, maxPlaneResidual(10.0f)
|
||||
, maxAngularGap(90.0f)
|
||||
, minInlierRatio(0.f)
|
||||
, minHoleDepth(2.5f)
|
||||
{}
|
||||
};
|
||||
|
||||
|
||||
@ -1375,7 +1375,9 @@ int SegmentPlanesByRansac(
|
||||
|
||||
int FilterPlanesByNormal(
|
||||
std::vector<PlaneSegment>& planes,
|
||||
const NormalFilterParams& params
|
||||
const NormalFilterParams& params,
|
||||
const SVzNLPointXYZ* points,
|
||||
int pointCount
|
||||
) {
|
||||
constexpr float kPi = 3.14159265358979323846f;
|
||||
|
||||
@ -1412,6 +1414,22 @@ int FilterPlanesByNormal(
|
||||
<< " planes by normal direction, "
|
||||
<< planes.size() << " remaining" << std::endl;
|
||||
|
||||
// 去除各平面 pointIndices 中距离平面过远的点(上方/下方均过滤)
|
||||
if (params.maxDistFromPlane > 0.0f && points != nullptr && pointCount > 0) {
|
||||
for (auto& plane : planes) {
|
||||
auto& indices = plane.pointIndices;
|
||||
auto removeIt = std::remove_if(indices.begin(), indices.end(),
|
||||
[&](int idx) {
|
||||
if (idx < 0 || idx >= pointCount) return true;
|
||||
const SVzNLPointXYZ& pt = points[idx];
|
||||
float signedDist = plane.a * pt.x + plane.b * pt.y + plane.c * pt.z + plane.d;
|
||||
return std::abs(signedDist) > params.maxDistFromPlane;
|
||||
});
|
||||
indices.erase(removeIt, indices.end());
|
||||
plane.pointCount = static_cast<int>(indices.size());
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < planes.size(); i++) {
|
||||
std::cout << " Plane " << (i + 1)
|
||||
<< ": normal angle=" << planes[i].normalAngleDeg << " deg"
|
||||
|
||||
@ -311,12 +311,14 @@ struct RansacPlaneSegmentationParams {
|
||||
struct NormalFilterParams {
|
||||
float maxAngleDeg; // 法向量与参考方向的最大夹角 (度), 建议 30-60
|
||||
float refNx, refNy, refNz; // 参考法向量方向 (默认Z轴)
|
||||
float maxDistFromPlane; // 点到平面的最大允许距离;超出则从 pointIndices 中移除;<=0 表示不过滤
|
||||
|
||||
NormalFilterParams()
|
||||
: maxAngleDeg(45.0f)
|
||||
, refNx(0.0f)
|
||||
, refNy(0.0f)
|
||||
, refNz(1.0f)
|
||||
, maxDistFromPlane(2.0f)
|
||||
{}
|
||||
};
|
||||
|
||||
@ -366,18 +368,23 @@ int SegmentPlanesByRansac(
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief 根据法向量方向过滤平面
|
||||
* @brief 根据法向量方向过滤平面,并去除各平面中距离平面过远的点
|
||||
*
|
||||
* 计算每个平面法向量与参考方向的夹角,
|
||||
* 移除夹角超过阈值的平面(使用绝对值处理法向正反两个方向)。
|
||||
* 1. 计算每个平面法向量与参考方向的夹角,移除夹角超过阈值的平面。
|
||||
* 2. 对保留的平面,将 pointIndices 中签名距离绝对值超过 maxDistFromPlane 的点移除
|
||||
* (maxDistFromPlane <= 0 时跳过此步骤)。
|
||||
*
|
||||
* @param planes 平面列表 (会被原地修改)
|
||||
* @param params 法向过滤参数
|
||||
* @param planes 平面列表 (会被原地修改)
|
||||
* @param params 法向过滤参数
|
||||
* @param points 原始点云数组
|
||||
* @param pointCount 原始点云数量
|
||||
* @return 被移除的平面数量
|
||||
*/
|
||||
int FilterPlanesByNormal(
|
||||
std::vector<PlaneSegment>& planes,
|
||||
const NormalFilterParams& params
|
||||
const NormalFilterParams& params,
|
||||
const SVzNLPointXYZ* points,
|
||||
int pointCount
|
||||
);
|
||||
|
||||
#endif // PLANE_SEGMENTATION_H
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user