Compare commits

...

10 Commits

Author SHA1 Message Date
7969a242ba 修改算法,获取了单根钢筋 2026-04-11 23:24:16 +08:00
1d649f9c01 增加了单行特征的检测函数 2026-04-10 00:18:05 +08:00
916f2c7662 删除无用代码 2026-04-09 23:43:28 +08:00
3aae9159ed 恢复到我自己的生长算法 2026-04-09 23:39:51 +08:00
9d304b19ac 修改了编译选项 2026-04-09 22:52:30 +08:00
937e9c0d8a 增加注释 2026-04-09 00:22:23 +08:00
MaJunwei
976f87b6ba 增加了过滤 2026-04-08 19:45:15 +08:00
2e92ddfab0 修改了生长规则 2026-04-08 00:19:01 +08:00
0d88858738 1.0.3增加了点到平面的参数调整 2026-04-07 22:56:18 +08:00
MaJunwei
cced2a9646 改为欧式距离 2026-04-07 19:34:48 +08:00
14 changed files with 1182 additions and 546 deletions

5
.gitignore vendored
View File

@ -1,4 +1,7 @@
.claude
nul
build
Algo/DetectHole/Export/
Algo/DetectHole/Export
Algo/DetectBarIntersection/build
Algo/DetectBarIntersection/data
Algo/DetectBarIntersection/Export

View File

@ -1,23 +1,32 @@
cmake_minimum_required(VERSION 3.10)
project(DetectBarIntersection VERSION 1.0.0 LANGUAGES CXX)
# Set C++ standard
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(WIN32)
if(MSVC)
add_compile_options(
/wd4267
/wd4996
)
if(CMAKE_BUILD_TYPE STREQUAL "Release")
add_compile_options(/O2)
endif()
endif()
else()
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
message(STATUS "Debug mode: Adding debug flags")
cmake_minimum_required(VERSION 3.10)
project(DetectBarIntersection VERSION 1.0.0 LANGUAGES CXX)
# Set C++ standard
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(CMAKE_CONFIGURATION_TYPES)
string(REPLACE ";" ", " DETECTBARINTERSECTION_CONFIGURATION_LIST "${CMAKE_CONFIGURATION_TYPES}")
set(DETECTBARINTERSECTION_BUILD_DESCRIPTION "Multi-config (${DETECTBARINTERSECTION_CONFIGURATION_LIST})")
else()
set(DETECTBARINTERSECTION_BUILD_DESCRIPTION "${CMAKE_BUILD_TYPE}")
endif()
if(NOT DETECTBARINTERSECTION_BUILD_DESCRIPTION)
set(DETECTBARINTERSECTION_BUILD_DESCRIPTION "Not specified")
endif()
if(WIN32)
if(MSVC)
add_compile_options(
/wd4267
/wd4996
$<$<CONFIG:Release>:/O2>
)
endif()
else()
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
message(STATUS "Debug mode: Adding debug flags")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -O0 -Wall -Wextra")
add_definitions(-DDEBUG)
elseif(CMAKE_BUILD_TYPE STREQUAL "Release")
@ -232,11 +241,11 @@ if(BUILD_EXAMPLES)
endif()
# Print configuration summary
message(STATUS "")
message(STATUS "DetectBarIntersection Configuration Summary:")
message(STATUS " C++ Standard: ${CMAKE_CXX_STANDARD}")
message(STATUS " Build Type: ${CMAKE_BUILD_TYPE}")
message(STATUS " Eigen3 Include: ${EIGEN3_INCLUDE_DIR}")
message(STATUS " OpenMP Enabled: ${ENABLE_OPENMP}")
message(STATUS " Build Examples: ${BUILD_EXAMPLES}")
message(STATUS "")
message(STATUS "")
message(STATUS "DetectBarIntersection Configuration Summary:")
message(STATUS " C++ Standard: ${CMAKE_CXX_STANDARD}")
message(STATUS " Build Type: ${DETECTBARINTERSECTION_BUILD_DESCRIPTION}")
message(STATUS " Eigen3 Include: ${EIGEN3_INCLUDE_DIR}")
message(STATUS " OpenMP Enabled: ${ENABLE_OPENMP}")
message(STATUS " Build Examples: ${BUILD_EXAMPLES}")
message(STATUS "")

View File

@ -1,26 +1,33 @@
@echo off
setlocal
echo === Building DetectBarIntersection ===
if not exist build (
mkdir build
)
cd build
cmake .. -G "Visual Studio 17 2022" -A x64
if %ERRORLEVEL% NEQ 0 (
echo CMake configuration failed!
exit /b %ERRORLEVEL%
)
cmake --build . --config Debug
if %ERRORLEVEL% NEQ 0 (
echo Build failed!
exit /b %ERRORLEVEL%
)
echo === Build completed successfully ===
@echo off
setlocal EnableExtensions
set "BUILD_CONFIG=Debug"
if not "%~1"=="" (
if /I "%~1"=="Debug" (
set "BUILD_CONFIG=Debug"
) else if /I "%~1"=="Release" (
set "BUILD_CONFIG=Release"
) else (
echo Usage: %~nx0 [Debug^|Release]
exit /b 1
)
)
echo === Building DetectBarIntersection [%BUILD_CONFIG%] ===
cmake -S . -B build -G "Visual Studio 17 2022" -A x64
if errorlevel 1 (
echo CMake configuration failed!
exit /b %ERRORLEVEL%
)
cmake --build build --config %BUILD_CONFIG%
if errorlevel 1 (
echo Build failed!
exit /b %ERRORLEVEL%
)
echo === Build completed successfully [%BUILD_CONFIG%] ===

View File

@ -8,8 +8,8 @@ if(WIN32)
add_subdirectory(visualization_demo)
endif()
message(STATUS "")
message(STATUS "Examples Configuration:")
message(STATUS " Build Type: ${CMAKE_BUILD_TYPE}")
message(STATUS " DetectBarIntersection Root: ${DETECTBARINTERSECTION_ROOT}")
message(STATUS "")
message(STATUS "")
message(STATUS "Examples Configuration:")
message(STATUS " Build Type: ${DETECTBARINTERSECTION_BUILD_DESCRIPTION}")
message(STATUS " DetectBarIntersection Root: ${DETECTBARINTERSECTION_ROOT}")
message(STATUS "")

View File

@ -54,11 +54,11 @@ public:
const std::string& title = "Detection Result"
);
/** @brief Visualize best cluster highlighted (filtered aligned points) */
void VisualizeBestCluster(
const SBarIntersectionResult& result,
const std::string& title = "Best Cluster"
);
/** @brief Visualize best cluster highlighted (filtered aligned points) */
void VisualizeBestCluster(
const SBarIntersectionResult& result,
const std::string& title = "Best Cluster"
);
void SetInteractive(bool interactive);
void SetOutputDirectory(const std::string& dir);

View File

@ -48,27 +48,38 @@ if(VTK_FOUND)
${VTK_LIBRARIES}
)
if(WIN32)
add_custom_command(TARGET visualization_demo POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"$<TARGET_FILE:BarIntersectionLib>"
"$<TARGET_FILE_DIR:visualization_demo>"
if(WIN32)
add_custom_command(TARGET visualization_demo POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"$<TARGET_FILE:BarIntersectionLib>"
"$<TARGET_FILE_DIR:visualization_demo>"
COMMENT "Copying BarIntersectionLib DLL to visualization_demo output directory"
)
set(VTK_BIN_DIR "${DETECTBARINTERSECTION_ROOT}/../../3rdparty/VTK-8.2.0/bin")
# Copy VTK DLLs
file(GLOB VTK_DLLS "${VTK_BIN_DIR}/*.dll")
foreach(vtk_dll ${VTK_DLLS})
add_custom_command(TARGET visualization_demo POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${vtk_dll}"
"$<TARGET_FILE_DIR:visualization_demo>"
COMMENT "Copying VTK DLL: ${vtk_dll}"
)
endforeach()
endif()
else()
message(WARNING "VTK not found. visualization_demo will not be built.")
endif()
set(VTK_BIN_DIR "${DETECTBARINTERSECTION_ROOT}/../../3rdparty/VTK-8.2.0/bin")
# Copy VTK DLLs
file(GLOB VTK_DLLS "${VTK_BIN_DIR}/*.dll")
set(VTK_DEBUG_DLLS)
set(VTK_RELEASE_DLLS)
foreach(vtk_dll ${VTK_DLLS})
get_filename_component(vtk_dll_name "${vtk_dll}" NAME)
if(vtk_dll_name MATCHES "d\\.dll$")
list(APPEND VTK_DEBUG_DLLS "${vtk_dll}")
else()
list(APPEND VTK_RELEASE_DLLS "${vtk_dll}")
endif()
endforeach()
add_custom_command(TARGET visualization_demo POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"$<$<CONFIG:Debug>:${VTK_DEBUG_DLLS}>"
"$<$<NOT:$<CONFIG:Debug>>:${VTK_RELEASE_DLLS}>"
"$<TARGET_FILE_DIR:visualization_demo>"
COMMAND_EXPAND_LISTS
COMMENT "Copying configuration-matched VTK DLLs to visualization_demo output directory"
)
endif()
else()
message(WARNING "VTK not found. visualization_demo will not be built.")
endif()

View File

@ -224,7 +224,7 @@ int ProcessSingleFile(
debugCallbacks.onPlaneSegmented = nullptr;// OnPlaneSegmented;
debugCallbacks.onPointsAligned = nullptr;// OnPointsAligned;
debugCallbacks.onPlaneFiltered = nullptr;// OnPlaneFiltered;
debugCallbacks.onClustersDetected = OnClustersDetected;
debugCallbacks.onClustersDetected = nullptr;// OnClustersDetected;
debugCallbacks.userData = &callbackData;
// Set parameters

View File

@ -1,93 +1,109 @@
#ifndef BAR_INTERSECTION_PARAMS_H
#define BAR_INTERSECTION_PARAMS_H
#include "VZNL_Types.h"
#include <vector>
/**
* @brief RANSAC
*/
struct SPlaneSegmentationParams {
float distanceThreshold; // 点到平面距离阈值 (mm)
int maxIterations; // RANSAC 最大迭代次数
int minPlanePoints; // 最小平面点数
float minPlaneRatio; // 平面最小点数占比
float heightThreshold; // 高于平面的最小高度,用于过滤平面点 (mm)
SPlaneSegmentationParams()
: distanceThreshold(1.0f)
, maxIterations(500)
, minPlanePoints(1000)
, minPlaneRatio(0.05f)
, heightThreshold(10.0f)
{}
};
/**
* @brief
*/
struct SGrowthParams {
float thresholdX; // 跨行合并阈值:相邻行 segment 质心 x 的最大允许差 (mm)
float thresholdY; // 行内分段阈值:同行相邻点 y 的最大允许差 (mm)
float thresholdZ; // 行内分段阈值:同行相邻点 z 的最大允许差 (mm)
int minClusterSize; // 最小簇点数
SGrowthParams()
: thresholdX(5.0f)
, thresholdY(5.0f)
, thresholdZ(5.0f)
, minClusterSize(20)
{}
};
/**
* @brief
*/
struct SGrowthCluster {
std::vector<int> pointIndices; // 簇内点在原始 rows*cols 网格中的线性索引
SVzNL3DPointF centroid; // 簇质心
SVzNL3DPointF minBound; // 包围盒最小角
SVzNL3DPointF maxBound; // 包围盒最大角
int pointCount;
SGrowthCluster()
: centroid(), minBound(), maxBound(), pointCount(0)
{}
};
/**
* @brief
*/
struct SBarIntersectionResult {
// 平面信息
SVzNL3DPointF planeNormal; // 平面法向量
float planeD; // 平面方程 ax+by+cz+d=0 中的 d
// 簇(原始坐标系)
int clusterCount;
SGrowthCluster* clusters;
// 最优簇索引(-1表示无有效簇
int bestClusterIndex;
// 最优簇的过滤后点云(对齐坐标系,用于可视化)
std::vector<SVzNLPointXYZ> bestClusterPoints;
SBarIntersectionResult()
: planeNormal(), planeD(0.0f)
, clusterCount(0), clusters(nullptr)
, bestClusterIndex(-1)
{}
};
/**
* @brief
*/
inline void FreeBarIntersectionResult(SBarIntersectionResult* result) {
if (result) {
if (result->clusters) { delete[] result->clusters; result->clusters = nullptr; }
result->clusterCount = 0;
}
}
#endif // BAR_INTERSECTION_PARAMS_H
#ifndef BAR_INTERSECTION_PARAMS_H
#define BAR_INTERSECTION_PARAMS_H
#include "VZNL_Types.h"
#include <vector>
/**
* @brief RANSAC
*/
struct SPlaneSegmentationParams {
float distanceThreshold; // 点到平面距离阈值 (mm)
int maxIterations; // RANSAC 最大迭代次数
int minPlanePoints; // 最小平面点数
float minPlaneRatio; // 平面最小点数占比
float heightThreshold; // 高于平面的最小高度,用于过滤平面点 (mm)
SPlaneSegmentationParams()
: distanceThreshold(1.0f)
, maxIterations(500)
, minPlanePoints(1000)
, minPlaneRatio(0.05f)
, heightThreshold(10.0f)
{}
};
/**
* @brief
*/
struct SGrowthParams {
float thresholdX; // 跨行合并阈值:相邻行 segment 质心 x 的最大允许差 (mm)
float thresholdY; // 行内分段阈值:同行相邻点 y 的最大允许差 (mm)
float thresholdZ; // 行内分段阈值:同行相邻点 z 的最大允许差 (mm)
int minClusterSize; // 最小簇点数
float angleSearchDistance;
int residualSmoothWindow;
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
float maxContinuousPointZTolerance; // Max z difference between adjacent points inside one valid run (mm)
float minBarDiameter;
float maxBarDiameter;
SGrowthParams()
: thresholdX(5.0f)
, thresholdY(5.0f)
, thresholdZ(5.0f)
, minClusterSize(20)
, angleSearchDistance(2.f)
, residualSmoothWindow(5)
, maxAxisDeviationFromXYDeg(50.0f)
, maxPerpendicularDeviationDeg(50.0f)
, minContinuousValidPointCount(0)
, maxContinuousPointZTolerance(5.0f)
, minBarDiameter(10.0f)
, maxBarDiameter(30.0f)
{}
};
/**
* @brief
*/
struct SGrowthCluster {
std::vector<int> pointIndices; // 簇内点在原始 rows*cols 网格中的线性索引
SVzNL3DPointF centroid; // 簇质心
SVzNL3DPointF minBound; // 包围盒最小角
SVzNL3DPointF maxBound; // 包围盒最大角
int pointCount;
SGrowthCluster()
: centroid(), minBound(), maxBound(), pointCount(0)
{}
};
/**
* @brief
*/
struct SBarIntersectionResult {
// 平面信息
SVzNL3DPointF planeNormal; // 平面法向量
float planeD; // 平面方程 ax+by+cz+d=0 中的 d
// 簇(原始坐标系)
int clusterCount;
SGrowthCluster* clusters;
// 最优簇索引(-1表示无有效簇
int bestClusterIndex;
// 最优簇的过滤后点云(对齐坐标系,用于可视化)
std::vector<SVzNLPointXYZ> bestClusterPoints;
SBarIntersectionResult()
: planeNormal(), planeD(0.0f)
, clusterCount(0), clusters(nullptr)
, bestClusterIndex(-1)
{}
};
/**
* @brief
*/
inline void FreeBarIntersectionResult(SBarIntersectionResult* result) {
if (result) {
if (result->clusters) { delete[] result->clusters; result->clusters = nullptr; }
result->clusterCount = 0;
}
}
#endif // BAR_INTERSECTION_PARAMS_H

View File

@ -1,6 +1,7 @@
#include "BarIntersection.h"
#include "PlaneAlignment.h"
#include "RegionGrowing.h"
#include <Eigen/Dense>
#include <vector>
#include <cstring>
#include <iostream>
@ -9,7 +10,7 @@
#include <numeric>
#include <limits>
static const char* s_algorithmVersion = "2.0.0";
static const char* s_algorithmVersion = "1.0.0";
static const char* s_algorithmName = "BarIntersectionDetection";
static bool IsValidPoint(const SVzNLPointXYZ& pt) {
@ -17,7 +18,107 @@ static bool IsValidPoint(const SVzNLPointXYZ& pt) {
return !(std::abs(pt.x) < eps && std::abs(pt.y) < eps && std::abs(pt.z) < eps);
}
BAR_INTERSECTION_API int DetectBarIntersections(
static void ClearPoint(SVzNLPointXYZ* pt) {
if (!pt) {
return;
}
pt->x = 0.0f;
pt->y = 0.0f;
pt->z = 0.0f;
}
static void ClearShortContinuousSegment(
SVzNLPointXYZ* rowPoints,
int startCol,
int segmentLength,
int minContinuousValidPointCount
) {
if (!rowPoints || startCol < 0 || segmentLength <= 0) {
return;
}
if (segmentLength >= minContinuousValidPointCount) {
return;
}
for (int offset = 0; offset < segmentLength; ++offset) {
ClearPoint(&rowPoints[startCol + offset]);
}
}
static void FilterLaserLineNoiseInPlace(
SVzNLPointXYZ* alignedPoints,
int rows,
int cols,
int minContinuousValidPointCount,
float maxContinuousPointZTolerance
) {
if (!alignedPoints || rows <= 0 || cols <= 0) {
return;
}
if (minContinuousValidPointCount <= 1 || maxContinuousPointZTolerance < 0.0f) {
return;
}
for (int row = 0; row < rows; ++row) {
SVzNLPointXYZ* rowPoints = alignedPoints + row * cols;
int segmentStartCol = -1;
int segmentLength = 0;
float prevZ = 0.0f;
for (int col = 0; col < cols; ++col) {
const SVzNLPointXYZ& pt = rowPoints[col];
if (!IsValidPoint(pt)) {
ClearShortContinuousSegment(
rowPoints,
segmentStartCol,
segmentLength,
minContinuousValidPointCount
);
segmentStartCol = -1;
segmentLength = 0;
prevZ = 0.0f;
continue;
}
if (segmentLength <= 0) {
segmentStartCol = col;
segmentLength = 1;
prevZ = pt.z;
continue;
}
const float zDiff = std::abs(pt.z - prevZ);
if (zDiff <= maxContinuousPointZTolerance) {
++segmentLength;
prevZ = pt.z;
continue;
}
ClearShortContinuousSegment(
rowPoints,
segmentStartCol,
segmentLength,
minContinuousValidPointCount
);
segmentStartCol = col;
segmentLength = 1;
prevZ = pt.z;
}
ClearShortContinuousSegment(
rowPoints,
segmentStartCol,
segmentLength,
minContinuousValidPointCount
);
}
}
int DetectBarIntersections(
const SVzNLPointXYZ* points,
int rows,
int cols,
@ -107,6 +208,17 @@ BAR_INTERSECTION_API int DetectBarIntersections(
alignedPoints, totalPoints, planeZ, planeParams.heightThreshold
);
// ============================================
// Step 2b: 按激光线过滤短连续段噪点
// ============================================
FilterLaserLineNoiseInPlace(
alignedPoints,
rows,
cols,
growthParams.minContinuousValidPointCount,
growthParams.maxContinuousPointZTolerance
);
// Debug callback: plane filtered
if (debugCallbacks && debugCallbacks->onPlaneFiltered) {
// 收集非零点用于回调显示
@ -137,7 +249,10 @@ BAR_INTERSECTION_API int DetectBarIntersections(
// ============================================
std::vector<SGrowthCluster> clusters;
bar_intersection::RegionGrowClusters(
alignedPoints, rows, cols, growthParams, clusters
alignedPoints, rows, cols, true, growthParams, clusters
);
bar_intersection::RegionGrowClusters(
alignedPoints, rows, cols, false, growthParams, clusters
);
// ============================================

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
#ifndef REGION_GROWING_H
#ifndef REGION_GROWING_H
#define REGION_GROWING_H
#include "VZNL_Types.h"
@ -28,6 +28,7 @@ int RegionGrowClusters(
const SVzNLPointXYZ* points,
int rows,
int cols,
bool bHorizontalScan,
const SGrowthParams& params,
std::vector<SGrowthCluster>& outClusters
);

View File

@ -16,6 +16,8 @@ struct RansacPlaneSegmentationParams {
float minPlaneRatio; // 平面最小点数占比 (相对最大平面), 建议 0.05-0.2
float maxNormalAngleDeg; // 平面法向量与Z轴最大夹角 (度), 超过则直接丢弃; <=0 表示不过滤
float maxDistFromPlane; // 点到平面的最大允许距离;超出则从 pointIndices 中移除;<=0 表示不过滤
RansacPlaneSegmentationParams()
: distanceThreshold(0.5f)
, maxIterations(500)
@ -24,6 +26,7 @@ struct RansacPlaneSegmentationParams {
, growthZThreshold(1.0f)
, minPlaneRatio(0.1f)
, maxNormalAngleDeg(30.0f)
, maxDistFromPlane(1.f)
{
}
};

View File

@ -12,7 +12,7 @@
namespace {
const static char* g_algo_name = "DetectHole";
const static char* g_algo_ver = "1.0.2";
const static char* g_algo_ver = "1.0.3";
void PrintHoleResult(const SHoleResult& hole, int index) {
@ -196,6 +196,7 @@ int DetectMultipleHoles(
// ============ Step 2: 法向量过滤 ============
NormalFilterParams normalParams;
normalParams.maxDistFromPlane = ransacParams.maxDistFromPlane;
FilterPlanesByNormal(planes, normalParams, points, totalPointCount);
// 如果没有找到有效平面,回退到处理整个点云

View File

@ -298,7 +298,7 @@ struct NormalFilterParams {
, refNx(0.0f)
, refNy(0.0f)
, refNz(1.0f)
, maxDistFromPlane(2.0f)
, maxDistFromPlane(0.0f)
{}
};