恢复到我自己的生长算法

This commit is contained in:
cool609 2026-04-09 23:39:51 +08:00
parent 9d304b19ac
commit 3aae9159ed
7 changed files with 559 additions and 993 deletions

View File

@ -335,136 +335,26 @@ void BarIntersectionVisualizer::VisualizeResult(
}
void BarIntersectionVisualizer::VisualizeBestCluster(
const SVzNLPointXYZ* points, int count,
const SBarIntersectionResult& result,
const std::string& title
) {
if (!points || count <= 0 || result.validClusterIndices.empty()) {
std::cout << "No valid bar-like cluster to visualize." << std::endl;
if (result.bestClusterIndex < 0 || result.bestClusterPoints.empty()) {
std::cout << "No valid best cluster to visualize." << std::endl;
return;
}
auto renderer = vtkSmartPointer<vtkRenderer>::New();
vtkActor* allPointsActor = static_cast<vtkActor*>(
// Best cluster filtered points (aligned coordinate system)
vtkActor* bestActor = static_cast<vtkActor*>(
CreatePointCloudActor(
points,
count,
0.75, 0.75, 0.75, 2.0
result.bestClusterPoints.data(),
(int)result.bestClusterPoints.size(),
0.0, 1.0, 0.3, 3.0
)
);
renderer->AddActor(allPointsActor);
allPointsActor->Delete();
double clusterColors[][3] = {
{1.0, 0.2, 0.1}, {0.1, 0.8, 0.2}, {0.1, 0.6, 1.0},
{1.0, 0.8, 0.1}, {1.0, 0.2, 0.8}, {0.0, 0.9, 0.9}
};
const int numColors = 6;
for (size_t i = 0; i < result.validClusterIndices.size(); ++i) {
const int clusterIdx = result.validClusterIndices[i];
if (clusterIdx < 0 || clusterIdx >= result.clusterCount) {
continue;
}
const SGrowthCluster& cluster = result.clusters[clusterIdx];
std::vector<SVzNLPointXYZ> clusterPts;
clusterPts.reserve(cluster.pointCount);
for (size_t j = 0; j < cluster.pointIndices.size(); ++j) {
const int linearIdx = cluster.pointIndices[j];
if (linearIdx >= 0 && linearIdx < count) {
clusterPts.push_back(points[linearIdx]);
}
}
if (clusterPts.empty()) {
continue;
}
const int ci = static_cast<int>(i % numColors);
vtkActor* clusterActor = static_cast<vtkActor*>(
CreatePointCloudActor(
clusterPts.data(),
(int)clusterPts.size(),
clusterColors[ci][0], clusterColors[ci][1], clusterColors[ci][2],
4.5
)
);
renderer->AddActor(clusterActor);
clusterActor->Delete();
}
renderer->AddActor(bestActor);
bestActor->Delete();
ShowWindow(renderer, title, m_interactive, this, "06_best_cluster.png");
}
void BarIntersectionVisualizer::VisualizeStep5Selection(
const SBarIntersectionResult& result,
const std::string& title
) {
if (result.step5BaseClusterIndex < 0 ||
result.step5BaseClusterAlignedPoints.empty() ||
result.step5FilteredAlignedPoints.empty()) {
std::cout << "No valid Step 5 selection to visualize." << std::endl;
return;
}
auto renderer = vtkSmartPointer<vtkRenderer>::New();
vtkActor* allPointsActor = static_cast<vtkActor*>(
CreatePointCloudActor(
result.step5FilteredAlignedPoints.data(),
static_cast<int>(result.step5FilteredAlignedPoints.size()),
0.75, 0.75, 0.75, 2.0
)
);
renderer->AddActor(allPointsActor);
allPointsActor->Delete();
vtkActor* baseActor = static_cast<vtkActor*>(
CreatePointCloudActor(
result.step5BaseClusterAlignedPoints.data(),
static_cast<int>(result.step5BaseClusterAlignedPoints.size()),
1.0, 0.2, 0.1, 5.0
)
);
renderer->AddActor(baseActor);
baseActor->Delete();
const size_t perpendicularClusterCount = std::min(
result.step5PerpendicularClusterAlignedPoints.size(),
result.step5PerpendicularClusterAlignedCentroids.size()
);
for (size_t i = 0; i < perpendicularClusterCount; ++i) {
const std::vector<SVzNLPointXYZ>& clusterPoints = result.step5PerpendicularClusterAlignedPoints[i];
if (!clusterPoints.empty()) {
vtkActor* clusterActor = static_cast<vtkActor*>(
CreatePointCloudActor(
clusterPoints.data(),
static_cast<int>(clusterPoints.size()),
0.1, 0.6, 1.0, 4.5
)
);
renderer->AddActor(clusterActor);
clusterActor->Delete();
}
const SVzNLPointXYZ& clusterCentroid = result.step5PerpendicularClusterAlignedCentroids[i];
vtkActor* lineActor = static_cast<vtkActor*>(
CreateLineActor(
result.step5BaseClusterAlignedCentroid.x,
result.step5BaseClusterAlignedCentroid.y,
result.step5BaseClusterAlignedCentroid.z,
clusterCentroid.x,
clusterCentroid.y,
clusterCentroid.z,
1.0, 1.0, 0.1, 3.0
)
);
renderer->AddActor(lineActor);
lineActor->Delete();
}
ShowWindow(renderer, title, m_interactive, this, "07_step5_selection.png");
}

View File

@ -54,18 +54,11 @@ public:
const std::string& title = "Detection Result"
);
/** @brief Visualize best cluster highlighted on the full point cloud */
/** @brief Visualize best cluster highlighted (filtered aligned points) */
void VisualizeBestCluster(
const SVzNLPointXYZ* points, int count,
const SBarIntersectionResult& result,
const std::string& title = "Best Cluster"
);
/** @brief Visualize Step 5 base cluster and perpendicular matches */
void VisualizeStep5Selection(
const SBarIntersectionResult& result,
const std::string& title = "Step 5 Selection (Aligned)"
);
void SetInteractive(bool interactive);
void SetOutputDirectory(const std::string& dir);

View File

@ -251,17 +251,8 @@ int ProcessSingleFile(
// Final visualization
visualizer.VisualizeResult(points, rows, cols, result, "Final Result");
// Visualize all valid bar-like clusters on the full point cloud
visualizer.VisualizeBestCluster(
points, rows * cols,
result,
"Valid Bar-Like Clusters on Full Point Cloud"
);
visualizer.VisualizeStep5Selection(
result,
"Step 5: Min-Z Base Cluster and XY-Perpendicular Matches (Aligned)"
);
// Best cluster visualization (filtered aligned points)
visualizer.VisualizeBestCluster(result, "Best Cluster (min Z, high pts)");
// Cleanup
FreeBarIntersectionResult(&result);

View File

@ -1,18 +1,18 @@
#ifndef BAR_INTERSECTION_PARAMS_H
#ifndef BAR_INTERSECTION_PARAMS_H
#define BAR_INTERSECTION_PARAMS_H
#include "VZNL_Types.h"
#include <vector>
/**
* @brief RANSAC plane segmentation parameters
* @brief RANSAC
*/
struct SPlaneSegmentationParams {
float distanceThreshold; // Point-to-plane distance threshold (mm)
int maxIterations; // Maximum RANSAC iterations
int minPlanePoints; // Minimum plane point count
float minPlaneRatio; // Minimum plane point ratio
float heightThreshold; // Minimum height above the plane to keep (mm)
float distanceThreshold; // 点到平面距离阈值 (mm)
int maxIterations; // RANSAC 最大迭代次数
int minPlanePoints; // 最小平面点数
float minPlaneRatio; // 平面最小点数占比
float heightThreshold; // 高于平面的最小高度,用于过滤平面点 (mm)
SPlaneSegmentationParams()
: distanceThreshold(1.0f)
@ -24,13 +24,13 @@ struct SPlaneSegmentationParams {
};
/**
* @brief Region growing parameters
* @brief
*/
struct SGrowthParams {
float thresholdX; // Max centroid-x difference when merging adjacent row segments (mm)
float thresholdY; // Max y difference for adjacent points in the same row (mm)
float thresholdZ; // Max z difference for adjacent points in the same row (mm)
int minClusterSize; // Minimum cluster size
float thresholdX; // 跨行合并阈值:相邻行 segment 质心 x 的最大允许差 (mm)
float thresholdY; // 行内分段阈值:同行相邻点 y 的最大允许差 (mm)
float thresholdZ; // 行内分段阈值:同行相邻点 z 的最大允许差 (mm)
int minClusterSize; // 最小簇点数
float maxAxisDeviationFromXYDeg; // Max allowed axis deviation from the XY plane (deg)
float maxPerpendicularDeviationDeg; // Max deviation from 90 deg for centroid-connection test
int minContinuousValidPointCount; // Min consecutive valid points kept on each laser line after plane filtering; <=1 disables
@ -49,13 +49,13 @@ struct SGrowthParams {
};
/**
* @brief Region growing output: one connected cluster
* @brief
*/
struct SGrowthCluster {
std::vector<int> pointIndices; // Linear indices in the original rows*cols grid
SVzNL3DPointF centroid;
SVzNL3DPointF minBound;
SVzNL3DPointF maxBound;
std::vector<int> pointIndices; // 簇内点在原始 rows*cols 网格中的线性索引
SVzNL3DPointF centroid; // 簇质心
SVzNL3DPointF minBound; // 包围盒最小角
SVzNL3DPointF maxBound; // 包围盒最大角
int pointCount;
SGrowthCluster()
@ -64,64 +64,37 @@ struct SGrowthCluster {
};
/**
* @brief Detection result
* @brief
*/
struct SBarIntersectionResult {
// Plane information
SVzNL3DPointF planeNormal;
float planeD; // Plane equation ax + by + cz + d = 0
// 平面信息
SVzNL3DPointF planeNormal; // 平面法向量
float planeD; // 平面方程 ax+by+cz+d=0 中的 d
// Clusters in the original coordinate system
// 簇(原始坐标系)
int clusterCount;
SGrowthCluster* clusters;
// Best cluster index, -1 means no valid cluster
// 最优簇索引(-1表示无有效簇
int bestClusterIndex;
// All cluster indices that satisfy the bar geometry constraint
std::vector<int> validClusterIndices;
// Step 5: the valid cluster with minimum z centroid
int step5BaseClusterIndex;
// Step 5: valid clusters whose centroid-connection line is perpendicular to the base cluster axis
std::vector<int> step5PerpendicularClusterIndices;
// Step 5 visualization data kept in aligned coordinates
std::vector<SVzNLPointXYZ> step5FilteredAlignedPoints;
std::vector<SVzNLPointXYZ> step5BaseClusterAlignedPoints;
SVzNLPointXYZ step5BaseClusterAlignedCentroid;
std::vector<std::vector<SVzNLPointXYZ> > step5PerpendicularClusterAlignedPoints;
std::vector<SVzNLPointXYZ> step5PerpendicularClusterAlignedCentroids;
// Best cluster filtered points in the original coordinate system
// 最优簇的过滤后点云(对齐坐标系,用于可视化)
std::vector<SVzNLPointXYZ> bestClusterPoints;
SBarIntersectionResult()
: planeNormal(), planeD(0.0f)
, clusterCount(0), clusters(nullptr)
, bestClusterIndex(-1)
, step5BaseClusterIndex(-1)
{}
};
/**
* @brief Release memory in a detection result
* @brief
*/
inline void FreeBarIntersectionResult(SBarIntersectionResult* result) {
if (result) {
if (result->clusters) { delete[] result->clusters; result->clusters = nullptr; }
result->clusterCount = 0;
result->bestClusterIndex = -1;
result->validClusterIndices.clear();
result->step5BaseClusterIndex = -1;
result->step5PerpendicularClusterIndices.clear();
result->step5FilteredAlignedPoints.clear();
result->step5BaseClusterAlignedPoints.clear();
result->step5BaseClusterAlignedCentroid = SVzNLPointXYZ();
result->step5PerpendicularClusterAlignedPoints.clear();
result->step5PerpendicularClusterAlignedCentroids.clear();
result->bestClusterPoints.clear();
}
}

View File

@ -610,8 +610,6 @@ BAR_INTERSECTION_API int DetectBarIntersections(
// 在该范围内选点数最多的簇
// ============================================
int bestIdx = -1;
std::vector<SClusterLineMetrics> clusterLineMetrics(clusters.size());
std::vector<int> validClusterIndices;
if (!clusters.empty()) {
// 找所有簇中最小的 centroid.z对齐空间
float minZ = std::numeric_limits<float>::max();
@ -626,31 +624,28 @@ BAR_INTERSECTION_API int DetectBarIntersections(
// 在范围内找点数最多的簇
int maxPoints = 0;
float bestDistanceStdDev = std::numeric_limits<float>::max();
float bestInlierRatio = 0.0f;
for (int i = 0; i < (int)clusters.size(); i++) {
if (clusters[i].centroid.z > zUpperBound) continue;
const SClusterLineMetrics lineMetrics = EvaluateClusterLinearity(
clusters[i], alignedPoints, totalPoints, growthParams
);
clusterLineMetrics[i] = lineMetrics;
if (!lineMetrics.isValid) continue;
validClusterIndices.push_back(i);
if (clusters[i].pointCount > maxPoints ||
(clusters[i].pointCount == maxPoints && lineMetrics.distanceStdDev < bestDistanceStdDev) ||
(clusters[i].pointCount == maxPoints &&
std::abs(lineMetrics.distanceStdDev - bestDistanceStdDev) < 1e-6f &&
lineMetrics.inlierRatio > bestInlierRatio)) {
if (clusters[i].pointCount > maxPoints) {
maxPoints = clusters[i].pointCount;
bestDistanceStdDev = lineMetrics.distanceStdDev;
bestInlierRatio = lineMetrics.inlierRatio;
bestIdx = i;
}
}
}
// 提取最优簇的过滤后点云(对齐坐标系)
std::vector<SVzNLPointXYZ> bestClusterFilteredPts;
if (bestIdx >= 0) {
const SGrowthCluster& bestCluster = clusters[bestIdx];
bestClusterFilteredPts.reserve(bestCluster.pointCount);
for (int j = 0; j < (int)bestCluster.pointIndices.size(); j++) {
int linearIdx = bestCluster.pointIndices[j];
if (linearIdx >= 0 && linearIdx < totalPoints) {
bestClusterFilteredPts.push_back(alignedPoints[linearIdx]);
}
}
}
// ============================================
// Transform cluster centroids back to original coordinate system
// ============================================
@ -661,113 +656,6 @@ BAR_INTERSECTION_API int DetectBarIntersections(
}
}
// ============================================
// Step 5: from valid clusters, find the one with minimum z and then
// find clusters whose centroid-connection line is perpendicular to its axis
// ============================================
int step5BaseClusterIndex = -1;
std::vector<int> step5PerpendicularClusterIndices;
if (!validClusterIndices.empty()) {
float minValidClusterZ = std::numeric_limits<float>::max();
for (size_t i = 0; i < validClusterIndices.size(); ++i) {
const int clusterIdx = validClusterIndices[i];
if (clusters[clusterIdx].centroid.z < minValidClusterZ) {
minValidClusterZ = clusters[clusterIdx].centroid.z;
step5BaseClusterIndex = clusterIdx;
}
}
if (step5BaseClusterIndex >= 0) {
const SClusterLineMetrics& baseMetrics = clusterLineMetrics[step5BaseClusterIndex];
SVzNLPointXYZ baseAxisDirection =
ComputeDirectionVector(baseMetrics.axisPointA, baseMetrics.axisPointB);
baseAxisDirection.z = 0.0f;
const float perpendicularTolerance =
std::sin(std::max(0.0f, std::min(growthParams.maxPerpendicularDeviationDeg, 89.0f)) *
3.1415926f / 180.0f);
for (size_t i = 0; i < validClusterIndices.size(); ++i) {
const int clusterIdx = validClusterIndices[i];
if (clusterIdx == step5BaseClusterIndex) {
continue;
}
SVzNLPointXYZ centroidConnection = {};
centroidConnection.x = clusters[clusterIdx].centroid.x - clusters[step5BaseClusterIndex].centroid.x;
centroidConnection.y = clusters[clusterIdx].centroid.y - clusters[step5BaseClusterIndex].centroid.y;
centroidConnection.z = 0.0f;
const float absDot = ComputeNormalizedAbsDot(baseAxisDirection, centroidConnection);
if (absDot <= perpendicularTolerance) {
step5PerpendicularClusterIndices.push_back(clusterIdx);
}
}
}
}
std::vector<SVzNLPointXYZ> step5FilteredAlignedPoints;
std::vector<SVzNLPointXYZ> step5BaseClusterAlignedPoints;
SVzNLPointXYZ step5BaseClusterAlignedCentroid = {};
std::vector<std::vector<SVzNLPointXYZ> > step5PerpendicularClusterAlignedPoints;
std::vector<SVzNLPointXYZ> step5PerpendicularClusterAlignedCentroids;
step5FilteredAlignedPoints.reserve(validCount);
for (int i = 0; i < totalPoints; ++i) {
if (IsValidPoint(alignedPoints[i])) {
step5FilteredAlignedPoints.push_back(alignedPoints[i]);
}
}
if (step5BaseClusterIndex >= 0) {
const SGrowthCluster& baseCluster = clusters[step5BaseClusterIndex];
step5BaseClusterAlignedCentroid.x = baseCluster.centroid.x;
step5BaseClusterAlignedCentroid.y = baseCluster.centroid.y;
step5BaseClusterAlignedCentroid.z = baseCluster.centroid.z;
step5BaseClusterAlignedPoints.reserve(baseCluster.pointCount);
for (size_t j = 0; j < baseCluster.pointIndices.size(); ++j) {
const int linearIdx = baseCluster.pointIndices[j];
if (linearIdx >= 0 && linearIdx < totalPoints && IsValidPoint(alignedPoints[linearIdx])) {
step5BaseClusterAlignedPoints.push_back(alignedPoints[linearIdx]);
}
}
step5PerpendicularClusterAlignedPoints.reserve(step5PerpendicularClusterIndices.size());
step5PerpendicularClusterAlignedCentroids.reserve(step5PerpendicularClusterIndices.size());
for (size_t i = 0; i < step5PerpendicularClusterIndices.size(); ++i) {
const int clusterIdx = step5PerpendicularClusterIndices[i];
const SGrowthCluster& cluster = clusters[clusterIdx];
std::vector<SVzNLPointXYZ> clusterAlignedPoints;
clusterAlignedPoints.reserve(cluster.pointCount);
for (size_t j = 0; j < cluster.pointIndices.size(); ++j) {
const int linearIdx = cluster.pointIndices[j];
if (linearIdx >= 0 && linearIdx < totalPoints && IsValidPoint(alignedPoints[linearIdx])) {
clusterAlignedPoints.push_back(alignedPoints[linearIdx]);
}
}
step5PerpendicularClusterAlignedPoints.push_back(std::move(clusterAlignedPoints));
SVzNLPointXYZ clusterAlignedCentroid;
clusterAlignedCentroid.x = cluster.centroid.x;
clusterAlignedCentroid.y = cluster.centroid.y;
clusterAlignedCentroid.z = cluster.centroid.z;
step5PerpendicularClusterAlignedCentroids.push_back(clusterAlignedCentroid);
}
}
// 提取最优簇的过滤后点云,并逆变换回原始坐标系,便于在整云上高亮显示
std::vector<SVzNLPointXYZ> bestClusterFilteredPts;
if (bestIdx >= 0) {
const SGrowthCluster& bestCluster = clusters[bestIdx];
bestClusterFilteredPts.reserve(bestCluster.pointCount);
for (int j = 0; j < (int)bestCluster.pointIndices.size(); j++) {
int linearIdx = bestCluster.pointIndices[j];
if (linearIdx >= 0 && linearIdx < totalPoints && IsValidPoint(alignedPoints[linearIdx])) {
bestClusterFilteredPts.push_back(
InverseTransformPoint(alignedPoints[linearIdx], invRotation, planeCentroid)
);
}
}
}
for (auto& cluster : clusters) {
// Transform centroid
float x = cluster.centroid.x;
@ -803,14 +691,6 @@ BAR_INTERSECTION_API int DetectBarIntersections(
result->planeNormal = planeNormal;
result->planeD = planeD;
result->bestClusterIndex = bestIdx;
result->validClusterIndices = std::move(validClusterIndices);
result->step5BaseClusterIndex = step5BaseClusterIndex;
result->step5PerpendicularClusterIndices = std::move(step5PerpendicularClusterIndices);
result->step5FilteredAlignedPoints = std::move(step5FilteredAlignedPoints);
result->step5BaseClusterAlignedPoints = std::move(step5BaseClusterAlignedPoints);
result->step5BaseClusterAlignedCentroid = step5BaseClusterAlignedCentroid;
result->step5PerpendicularClusterAlignedPoints = std::move(step5PerpendicularClusterAlignedPoints);
result->step5PerpendicularClusterAlignedCentroids = std::move(step5PerpendicularClusterAlignedCentroids);
result->bestClusterPoints = std::move(bestClusterFilteredPts);
result->clusterCount = (int)clusters.size();
@ -841,7 +721,6 @@ BAR_INTERSECTION_API void PrintDetectionResults(
std::cout << "Plane Normal: (" << result.planeNormal.x << ", "
<< result.planeNormal.y << ", " << result.planeNormal.z << ")" << std::endl;
std::cout << "Clusters detected: " << result.clusterCount << std::endl;
std::cout << "Valid bar-like clusters: " << result.validClusterIndices.size() << std::endl;
if (verbose) {
std::cout << "\n--- Clusters ---" << std::endl;
@ -865,33 +744,6 @@ BAR_INTERSECTION_API void PrintDetectionResults(
} else {
std::cout << " No valid best cluster found." << std::endl;
}
if (!result.validClusterIndices.empty()) {
std::cout << "\n--- Valid Cluster Indices ---" << std::endl;
for (size_t i = 0; i < result.validClusterIndices.size(); ++i) {
std::cout << " #" << result.validClusterIndices[i] << std::endl;
}
}
std::cout << "\n--- Step 5 ---" << std::endl;
if (result.step5BaseClusterIndex >= 0 && result.step5BaseClusterIndex < result.clusterCount) {
const SGrowthCluster& base = result.clusters[result.step5BaseClusterIndex];
std::cout << " Base: Cluster #" << result.step5BaseClusterIndex
<< " pts=" << base.pointCount
<< " centroid=(" << base.centroid.x << "," << base.centroid.y << "," << base.centroid.z << ")"
<< std::endl;
} else {
std::cout << " No Step 5 base cluster found." << std::endl;
}
if (!result.step5PerpendicularClusterIndices.empty()) {
std::cout << " Perpendicular clusters:" << std::endl;
for (size_t i = 0; i < result.step5PerpendicularClusterIndices.size(); ++i) {
std::cout << " #" << result.step5PerpendicularClusterIndices[i] << std::endl;
}
} else {
std::cout << " No perpendicular cluster found." << std::endl;
}
}
std::cout << "===========================================" << std::endl;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,40 +1,37 @@
#ifndef REGION_GROWING_H
#define REGION_GROWING_H
#include "VZNL_Types.h"
#include "BarIntersectionParams.h"
#include <vector>
namespace bar_intersection {
/**
* @brief Region growing based on scan-line feature trees.
*
* Stage 1: extract contiguous valid point runs from each row as line features.
* Stage 2: grow features onto alive trees using sg_lineGapsGrowing-style
* tail matching on centroid x/y/z differences. Unmatched features
* start new trees.
* Stage 3: finalize stale trees and flatten valid trees into SGrowthCluster.
*
* The input point cloud must be a regular grid. Invalid points are (0,0,0).
*
* @param points Filtered aligned point grid with rows * cols elements.
* @param rows Grid row count.
* @param cols Grid column count.
* @param params thresholdY/Z are used for row feature extraction and matching;
* thresholdX constrains cross-row displacement.
* @param outClusters Output clusters. pointIndices are linear indices in the
* original rows * cols grid.
* @return Number of valid clusters with pointCount >= minClusterSize.
*/
int RegionGrowClusters(
const SVzNLPointXYZ* points,
int rows,
int cols,
const SGrowthParams& params,
std::vector<SGrowthCluster>& outClusters
);
} // namespace bar_intersection
#endif // REGION_GROWING_H
#ifndef REGION_GROWING_H
#define REGION_GROWING_H
#include "VZNL_Types.h"
#include "BarIntersectionParams.h"
#include <vector>
namespace bar_intersection {
/**
* @brief 线
*
* 1 col y/z Segment
* 2 Segment Segment x/z col
* 使 Segment
* 3 SGrowthCluster
*
* (0,0,0)
*
* @param points (rows * cols) (0,0,0)
* @param rows
* @param cols
* @param params thresholdY/Z=, thresholdX/Z=
* @param outClusters pointIndices 线
* @return (>= minClusterSize)
*/
int RegionGrowClusters(
const SVzNLPointXYZ* points,
int rows,
int cols,
const SGrowthParams& params,
std::vector<SGrowthCluster>& outClusters
);
} // namespace bar_intersection
#endif // REGION_GROWING_H