diff --git a/App/HoleDetection/HoleDetectionApp/Presenter/Src/HoleDetectionPresenter.cpp b/App/HoleDetection/HoleDetectionApp/Presenter/Src/HoleDetectionPresenter.cpp
index c55beccf..95a0ab82 100644
--- a/App/HoleDetection/HoleDetectionApp/Presenter/Src/HoleDetectionPresenter.cpp
+++ b/App/HoleDetection/HoleDetectionApp/Presenter/Src/HoleDetectionPresenter.cpp
@@ -211,19 +211,21 @@ int HoleDetectionPresenter::InitAlgoParams()
xmlParams.detectionParam.maxRadius,
xmlParams.detectionParam.expansionSize1,
xmlParams.detectionParam.expansionSize2);
- LOG_INFO("Loaded XML params - Detection: minVTransitionPoints=%d\n",
- xmlParams.detectionParam.minVTransitionPoints);
+ LOG_INFO("Loaded XML params - Detection: minVTransitionPoints=%d, edgeBoundaryFilterDist=%.2f\n",
+ xmlParams.detectionParam.minVTransitionPoints,
+ xmlParams.detectionParam.edgeBoundaryFilterDist);
// 打印过滤参数
LOG_INFO("Loaded XML params - Filter: maxEccentricity=%.5f, minAngularCoverage=%.1f, maxRadiusFitRatio=%.2f\n",
xmlParams.filterParam.maxEccentricity,
xmlParams.filterParam.minAngularCoverage,
xmlParams.filterParam.maxRadiusFitRatio);
- LOG_INFO("Loaded XML params - Filter: minQualityScore=%.2f, maxPlaneResidual=%.1f, maxAngularGap=%.1f, minInlierRatio=%.2f\n",
+ LOG_INFO("Loaded XML params - Filter: minQualityScore=%.2f, maxPlaneResidual=%.1f, maxAngularGap=%.1f, minInlierRatio=%.2f, minHoleDepth=%.2f\n",
xmlParams.filterParam.minQualityScore,
xmlParams.filterParam.maxPlaneResidual,
xmlParams.filterParam.maxAngularGap,
- xmlParams.filterParam.minInlierRatio);
+ xmlParams.filterParam.minInlierRatio,
+ xmlParams.filterParam.minHoleDepth);
LOG_INFO("Loaded XML params - SortMode: %d\n", xmlParams.sortMode);
diff --git a/App/HoleDetection/HoleDetectionApp/Version.h b/App/HoleDetection/HoleDetectionApp/Version.h
index a768be77..803b9df3 100644
--- a/App/HoleDetection/HoleDetectionApp/Version.h
+++ b/App/HoleDetection/HoleDetectionApp/Version.h
@@ -5,7 +5,7 @@
#define HOLEDETECTION_APP_NAME "孔洞检测"
// 版本字符串
-#define HOLEDETECTION_VERSION_STRING "1.1.0"
+#define HOLEDETECTION_VERSION_STRING "1.1.1"
#define HOLEDETECTION_BUILD_STRING "1"
#define HOLEDETECTION_FULL_VERSION_STRING "V" HOLEDETECTION_VERSION_STRING "_" HOLEDETECTION_BUILD_STRING
diff --git a/App/HoleDetection/HoleDetectionApp/Version.md b/App/HoleDetection/HoleDetectionApp/Version.md
index e0dca117..e41c9e12 100644
--- a/App/HoleDetection/HoleDetectionApp/Version.md
+++ b/App/HoleDetection/HoleDetectionApp/Version.md
@@ -1,3 +1,7 @@
+# 1.1.1 2026-04-02
+## build_1
+1. 更新算法
+
# 1.1.0 2026-03-25
## build_1
1. 算法优化
diff --git a/App/HoleDetection/HoleDetectionApp/dialogalgoarg.cpp b/App/HoleDetection/HoleDetectionApp/dialogalgoarg.cpp
index 4154990a..5941dd93 100644
--- a/App/HoleDetection/HoleDetectionApp/dialogalgoarg.cpp
+++ b/App/HoleDetection/HoleDetectionApp/dialogalgoarg.cpp
@@ -319,6 +319,7 @@ void DialogAlgoarg::LoadDetectionParamToUI(const VrHoleDetectionParam& param)
ui->lineEdit_expansionSize1->setText(QString::number(param.expansionSize1));
ui->lineEdit_expansionSize2->setText(QString::number(param.expansionSize2));
ui->lineEdit_minVTransitionPoints->setText(QString::number(param.minVTransitionPoints));
+ ui->lineEdit_edgeBoundaryFilterDist->setText(QString::number(param.edgeBoundaryFilterDist));
}
void DialogAlgoarg::LoadFilterParamToUI(const VrHoleFilterParam& param)
@@ -332,6 +333,7 @@ void DialogAlgoarg::LoadFilterParamToUI(const VrHoleFilterParam& param)
ui->lineEdit_maxPlaneResidual->setText(QString::number(param.maxPlaneResidual));
ui->lineEdit_maxAngularGap->setText(QString::number(param.maxAngularGap));
ui->lineEdit_minInlierRatio->setText(QString::number(param.minInlierRatio));
+ ui->lineEdit_minHoleDepth->setText(QString::number(param.minHoleDepth));
}
void DialogAlgoarg::LoadSortModeToUI(int sortMode)
@@ -377,6 +379,9 @@ bool DialogAlgoarg::SaveDetectionParamFromUI(VrHoleDetectionParam& param)
param.minVTransitionPoints = ui->lineEdit_minVTransitionPoints->text().toInt(&ok);
if (!ok) return false;
+ param.edgeBoundaryFilterDist = ui->lineEdit_edgeBoundaryFilterDist->text().toDouble(&ok);
+ if (!ok) return false;
+
return true;
}
@@ -405,6 +410,9 @@ bool DialogAlgoarg::SaveFilterParamFromUI(VrHoleFilterParam& param)
param.minInlierRatio = ui->lineEdit_minInlierRatio->text().toDouble(&ok);
if (!ok) return false;
+ param.minHoleDepth = ui->lineEdit_minHoleDepth->text().toDouble(&ok);
+ if (!ok) return false;
+
return true;
}
diff --git a/App/HoleDetection/HoleDetectionApp/dialogalgoarg.ui b/App/HoleDetection/HoleDetectionApp/dialogalgoarg.ui
index 8b7d9161..8ab436b4 100644
--- a/App/HoleDetection/HoleDetectionApp/dialogalgoarg.ui
+++ b/App/HoleDetection/HoleDetectionApp/dialogalgoarg.ui
@@ -182,6 +182,16 @@ QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 3px 0 3px;
-
+ -
+
+
+ 边缘过滤距离 (mm):
+
+
+
+ -
+
+
@@ -296,13 +306,23 @@ QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 3px 0 3px;
-
+
+
+ 最小孔深 (mm):
+
+
+
+ -
+
+
+ -
排序模式:
- -
+
-
diff --git a/App/HoleDetection/HoleDetectionConfig/Inc/IVrConfig.h b/App/HoleDetection/HoleDetectionConfig/Inc/IVrConfig.h
index c25ae4f0..3f860dbc 100644
--- a/App/HoleDetection/HoleDetectionConfig/Inc/IVrConfig.h
+++ b/App/HoleDetection/HoleDetectionConfig/Inc/IVrConfig.h
@@ -20,12 +20,13 @@ struct VrHoleDetectionParam
int neighborCount = 3; // 相邻点连接数
double angleThresholdPos = 30.0; // 正角度阈值(度)
double angleThresholdNeg = -30.0; // 负角度阈值(度)
- double minPitDepth = 1.0; // 最小凹坑深度(mm)
- double minRadius = 2.0; // 最小孔半径(mm)
- double maxRadius = 50.0; // 最大孔半径(mm)
- int expansionSize1 = 5; // 第一环扩展大小
- int expansionSize2 = 10; // 第二环扩展大小
- int minVTransitionPoints = 1; // V形端点间最小有效过渡点数
+ double minPitDepth = 1.0; // 最小凹坑深度(mm)
+ double minRadius = 2.0; // 最小孔半径(mm)
+ double maxRadius = 50.0; // 最大孔半径(mm)
+ int expansionSize1 = 5; // 第一环扩展大小
+ int expansionSize2 = 10; // 第二环扩展大小
+ int minVTransitionPoints = 1; // V形端点间最小有效过渡点数
+ double edgeBoundaryFilterDist = 0.0; // 边缘过滤距离(mm)
};
/**
@@ -40,6 +41,7 @@ struct VrHoleFilterParam
double maxPlaneResidual = 10.0; // 最大平面残差(mm)
double maxAngularGap = 90.0; // 最大角度间隙(度)
double minInlierRatio = 0.0; // 最小内点比率
+ double minHoleDepth = 2.5; // 最小孔深(mm)
};
/**
diff --git a/App/HoleDetection/HoleDetectionConfig/Src/AlgoParamConverter.cpp b/App/HoleDetection/HoleDetectionConfig/Src/AlgoParamConverter.cpp
index 7eec5ef8..968b12a9 100644
--- a/App/HoleDetection/HoleDetectionConfig/Src/AlgoParamConverter.cpp
+++ b/App/HoleDetection/HoleDetectionConfig/Src/AlgoParamConverter.cpp
@@ -15,6 +15,7 @@ SHoleDetectionParams ToAlgoParam(const VrHoleDetectionParam& param)
algo.expansionSize1 = param.expansionSize1;
algo.expansionSize2 = param.expansionSize2;
algo.minVTransitionPoints = param.minVTransitionPoints;
+ algo.edgeBoundaryFilterDist = static_cast(param.edgeBoundaryFilterDist);
return algo;
}
@@ -28,6 +29,7 @@ SHoleFilterParams ToAlgoParam(const VrHoleFilterParam& param)
algo.maxPlaneResidual = static_cast(param.maxPlaneResidual);
algo.maxAngularGap = static_cast(param.maxAngularGap);
algo.minInlierRatio = static_cast(param.minInlierRatio);
+ algo.minHoleDepth = static_cast(param.minHoleDepth);
return algo;
}
@@ -46,15 +48,16 @@ void LogAlgoParams(const std::string& logTag,
LOG_INFO("%s DetectionParams: angleThresholdPos=%.1f, angleThresholdNeg=%.1f, minPitDepth=%.1f\n",
logTag.c_str(), detectionParams.angleThresholdPos,
detectionParams.angleThresholdNeg, detectionParams.minPitDepth);
- LOG_INFO("%s DetectionParams: minRadius=%.1f, maxRadius=%.1f, expansionSize1=%d, expansionSize2=%d, minVTransitionPoints=%d\n",
+ LOG_INFO("%s DetectionParams: minRadius=%.1f, maxRadius=%.1f, expansionSize1=%d, expansionSize2=%d, minVTransitionPoints=%d, edgeBoundaryFilterDist=%.2f\n",
logTag.c_str(), detectionParams.minRadius, detectionParams.maxRadius,
- detectionParams.expansionSize1, detectionParams.expansionSize2, detectionParams.minVTransitionPoints);
+ detectionParams.expansionSize1, detectionParams.expansionSize2,
+ detectionParams.minVTransitionPoints, detectionParams.edgeBoundaryFilterDist);
LOG_INFO("%s FilterParams: maxEccentricity=%.5f, minAngularCoverage=%.1f, maxRadiusFitRatio=%.2f\n",
logTag.c_str(), filterParams.maxEccentricity, filterParams.minAngularCoverage, filterParams.maxRadiusFitRatio);
- LOG_INFO("%s FilterParams: minQualityScore=%.2f, maxPlaneResidual=%.1f, maxAngularGap=%.1f, minInlierRatio=%.2f\n",
+ LOG_INFO("%s FilterParams: minQualityScore=%.2f, maxPlaneResidual=%.1f, maxAngularGap=%.1f, minInlierRatio=%.2f, minHoleDepth=%.2f\n",
logTag.c_str(), filterParams.minQualityScore, filterParams.maxPlaneResidual,
- filterParams.maxAngularGap, filterParams.minInlierRatio);
+ filterParams.maxAngularGap, filterParams.minInlierRatio, filterParams.minHoleDepth);
}
} // namespace AlgoParamConverter
diff --git a/App/HoleDetection/HoleDetectionConfig/Src/VrConfig.cpp b/App/HoleDetection/HoleDetectionConfig/Src/VrConfig.cpp
index 05a0484b..7d28b67f 100644
--- a/App/HoleDetection/HoleDetectionConfig/Src/VrConfig.cpp
+++ b/App/HoleDetection/HoleDetectionConfig/Src/VrConfig.cpp
@@ -84,6 +84,8 @@ int CVrConfig::LoadConfig(const std::string& filePath, ConfigResult& configResul
configResult.algorithmParams.detectionParam.expansionSize2 = detectionParamElement->IntAttribute("expansionSize2");
if (detectionParamElement->Attribute("minVTransitionPoints"))
configResult.algorithmParams.detectionParam.minVTransitionPoints = detectionParamElement->IntAttribute("minVTransitionPoints");
+ if (detectionParamElement->Attribute("edgeBoundaryFilterDist"))
+ configResult.algorithmParams.detectionParam.edgeBoundaryFilterDist = detectionParamElement->DoubleAttribute("edgeBoundaryFilterDist");
}
// 解析孔洞过滤参数
@@ -104,6 +106,8 @@ int CVrConfig::LoadConfig(const std::string& filePath, ConfigResult& configResul
configResult.algorithmParams.filterParam.maxAngularGap = filterParamElement->DoubleAttribute("maxAngularGap");
if (filterParamElement->Attribute("minInlierRatio"))
configResult.algorithmParams.filterParam.minInlierRatio = filterParamElement->DoubleAttribute("minInlierRatio");
+ if (filterParamElement->Attribute("minHoleDepth"))
+ configResult.algorithmParams.filterParam.minHoleDepth = filterParamElement->DoubleAttribute("minHoleDepth");
}
// 解析排序模式
@@ -196,6 +200,7 @@ bool CVrConfig::SaveConfig(const std::string& filePath, ConfigResult& configResu
detectionParamElement->SetAttribute("expansionSize1", configResult.algorithmParams.detectionParam.expansionSize1);
detectionParamElement->SetAttribute("expansionSize2", configResult.algorithmParams.detectionParam.expansionSize2);
detectionParamElement->SetAttribute("minVTransitionPoints", configResult.algorithmParams.detectionParam.minVTransitionPoints);
+ detectionParamElement->SetAttribute("edgeBoundaryFilterDist", configResult.algorithmParams.detectionParam.edgeBoundaryFilterDist);
algoParamsElement->InsertEndChild(detectionParamElement);
// 添加孔洞过滤参数
@@ -207,6 +212,7 @@ bool CVrConfig::SaveConfig(const std::string& filePath, ConfigResult& configResu
filterParamElement->SetAttribute("maxPlaneResidual", configResult.algorithmParams.filterParam.maxPlaneResidual);
filterParamElement->SetAttribute("maxAngularGap", configResult.algorithmParams.filterParam.maxAngularGap);
filterParamElement->SetAttribute("minInlierRatio", configResult.algorithmParams.filterParam.minInlierRatio);
+ filterParamElement->SetAttribute("minHoleDepth", configResult.algorithmParams.filterParam.minHoleDepth);
algoParamsElement->InsertEndChild(filterParamElement);
// 添加排序模式
diff --git a/App/HoleDetection/HoleDetectionConfig/config/config.xml b/App/HoleDetection/HoleDetectionConfig/config/config.xml
index 4cbd567d..2c42ec8c 100644
--- a/App/HoleDetection/HoleDetectionConfig/config/config.xml
+++ b/App/HoleDetection/HoleDetectionConfig/config/config.xml
@@ -12,11 +12,13 @@
minPitDepth="1.0" angleStep="1.0" maxScanRadius="100.0" clusterEps="5.0"
clusterMinPoints="5" minRadius="5.0" maxRadius="50.0" expansionSize1="5"
expansionSize2="10" validZThreshold="0.0001" minVTransitionPoints="1"
+ edgeBoundaryFilterDist="0.0"
cornerScale="2.0" cornerAngleThreshold="45.0" jumpCornerTh_1="10.0"
jumpCornerTh_2="30.0" minEndingGap="5.0" minEndingGap_z="1.0"/>
+ minQualityScore="0.3" maxPlaneResidual="10.0" maxAngularGap="90.0" minInlierRatio="0.3"
+ minHoleDepth="2.5"/>
#include
#include
#include
#include
+#include
+
+#ifdef Q_OS_UNIX
+#include
+#include
+#endif
+
+// 无桌面模式下的状态监听器,将状态信息输出到日志
+class HeadlessStatusListener : public IYHolePitPositionStatus
+{
+public:
+ void OnStatusUpdate(const std::string& msg) override {
+ LOG_INFO("[Headless] Status: %s\n", msg.c_str());
+ }
+ void OnDetectionResult(const HolePitPositionResult& result) override {
+ LOG_INFO("[Headless] DetectionResult received, errorCode=%d\n", result.errorCode);
+ }
+ void OnCamera1StatusChanged(bool connected) override {
+ LOG_INFO("[Headless] Camera1: %s\n", connected ? "connected" : "disconnected");
+ }
+ void OnCamera2StatusChanged(bool connected) override {
+ LOG_INFO("[Headless] Camera2: %s\n", connected ? "connected" : "disconnected");
+ }
+ void OnRobotConnectionChanged(bool connected) override {
+ LOG_INFO("[Headless] Robot: %s\n", connected ? "connected" : "disconnected");
+ }
+ void OnSerialConnectionChanged(bool connected) override {
+ LOG_INFO("[Headless] Serial: %s\n", connected ? "connected" : "disconnected");
+ }
+ void OnCameraCountChanged(int count) override {
+ LOG_INFO("[Headless] CameraCount: %d\n", count);
+ }
+ void OnWorkStatusChanged(WorkStatus status) override {
+ LOG_INFO("[Headless] WorkStatus changed: %d\n", static_cast(status));
+ }
+};
+
+#ifdef Q_OS_UNIX
+// 检测设备文件是否存在
+static bool deviceExists(const char* path)
+{
+ struct stat st;
+ return stat(path, &st) == 0;
+}
+
+// 检测是否有可用的显示设备(X11、Wayland、framebuffer、DRM 任意一种即可)
+static bool detectDisplay()
+{
+ // X11 / Wayland 环境变量
+ const char* display = std::getenv("DISPLAY");
+ const char* waylandDisp = std::getenv("WAYLAND_DISPLAY");
+ if ((display && strlen(display) > 0) ||
+ (waylandDisp && strlen(waylandDisp) > 0)) {
+ return true;
+ }
+
+ // Framebuffer(嵌入式 ARM 常见)
+ if (deviceExists("/dev/fb0")) {
+ return true;
+ }
+
+ return false;
+}
+#endif
int main(int argc, char *argv[])
{
+ bool hasDisplay = true;
+#ifdef Q_OS_UNIX
+ hasDisplay = detectDisplay();
+ if (!hasDisplay) {
+ qputenv("QT_QPA_PLATFORM", "offscreen");
+ fprintf(stderr, "[Main] No display detected, running in headless mode.\n");
+ fflush(stderr);
+ }
+#endif
+
QApplication a(argc, argv);
- // 设置应用程序图标
- a.setWindowIcon(QIcon(":/common/resource/logo.png"));
-
- // 使用 SingleInstanceManager 检查单例
SingleInstanceManager instanceManager("HolePitPositionApp_SingleInstance_Key");
- if (instanceManager.isAnotherInstanceRunning()) {
- // 已有实例在运行,显示提示信息并退出
- QMessageBox::information(nullptr,
- QObject::tr("应用程序已运行"),
- QObject::tr("坑孔定位检测应用程序已经在运行中,请勿重复启动!"),
- QMessageBox::Ok);
- return 0;
+ if (hasDisplay) {
+ // ---- 有桌面:完整 UI 模式 ----
+ a.setWindowIcon(QIcon(":/common/resource/logo.png"));
+
+ if (instanceManager.isAnotherInstanceRunning()) {
+ QMessageBox::information(nullptr,
+ QObject::tr("应用程序已运行"),
+ QObject::tr("坑孔定位检测应用程序已经在运行中,请勿重复启动!"),
+ QMessageBox::Ok);
+ return 0;
+ }
+
+ if (!AuthView::CheckAndShow(nullptr)) {
+ return 0;
+ }
+
+ MainWindow w;
+ w.show();
+ return a.exec();
+ } else {
+ // ---- 无桌面:只启动业务逻辑 ----
+ if (instanceManager.isAnotherInstanceRunning()) {
+ return 0;
+ }
+
+ // 无UI模式下直接检查授权有效性
+ if (!AuthManager::CheckLicenseValid()) {
+ fprintf(stderr, "[Main] Authorization check failed, exiting.\n");
+ fflush(stderr);
+ return 0;
+ }
+
+ static HeadlessStatusListener listener;
+ static HolePitPositionPresenter presenter;
+ presenter.SetStatusCallback(&listener);
+ presenter.Init();
+ return a.exec();
}
-
- // 检查授权,无授权则显示授权对话框
- if (!AuthView::CheckAndShow(nullptr)) {
- // 用户取消授权或授权失败,退出程序
- return 0;
- }
-
- // 没有其他实例运行,正常启动应用程序
- MainWindow w;
- w.show();
-
- return a.exec();
}
diff --git a/AppAlgo/holeDetection/arm/debug/libHoleDetectionLib.so.1.0.0 b/AppAlgo/holeDetection/arm/debug/libHoleDetectionLib.so.1.0.0
index f0de25cb..a839aa60 100644
Binary files a/AppAlgo/holeDetection/arm/debug/libHoleDetectionLib.so.1.0.0 and b/AppAlgo/holeDetection/arm/debug/libHoleDetectionLib.so.1.0.0 differ
diff --git a/AppAlgo/holeDetection/arm/release/libHoleDetectionLib.so.1.0.0 b/AppAlgo/holeDetection/arm/release/libHoleDetectionLib.so.1.0.0
index 3ea66756..5987c84c 100644
Binary files a/AppAlgo/holeDetection/arm/release/libHoleDetectionLib.so.1.0.0 and b/AppAlgo/holeDetection/arm/release/libHoleDetectionLib.so.1.0.0 differ
diff --git a/AppAlgo/holeDetection/include/HoleDetection.h b/AppAlgo/holeDetection/include/HoleDetection.h
index a354f44c..59e2356a 100644
--- a/AppAlgo/holeDetection/include/HoleDetection.h
+++ b/AppAlgo/holeDetection/include/HoleDetection.h
@@ -5,17 +5,19 @@
#include "ErrorCodes.h"
#include "../include/VZNL_Types.h"
-// Export macro for Windows DLL
+// Export macro for shared library
#ifdef _WIN32
#ifdef HOLE_DETECTION_EXPORTS
#define HOLE_DETECTION_API __declspec(dllexport)
#else
#define HOLE_DETECTION_API __declspec(dllimport)
#endif
-#elif defined(__GNUC__) || defined(__clang__)
- #define HOLE_DETECTION_API __attribute__((visibility("default")))
#else
- #define HOLE_DETECTION_API
+ #ifdef HOLE_DETECTION_EXPORTS
+ #define HOLE_DETECTION_API __attribute__((visibility("default")))
+ #else
+ #define HOLE_DETECTION_API
+ #endif
#endif
/**
@@ -82,6 +84,16 @@ struct SClusterInfo {
int count; // Number of points in this cluster
};
+/**
+ * @brief Plane segmentation information for debug callbacks
+ */
+struct SPlaneSegmentInfo {
+ float normalX, normalY, normalZ; // 平面法向量 (归一化)
+ int pointCount; // 该平面的点数
+ const int* pointIndices; // 点索引数组 (在原始grid中的索引)
+ float normalAngleDeg; // 法向量与参考方向的夹角 (度)
+};
+
/**
* @brief Debug callbacks for visualization and debugging
*
@@ -149,10 +161,47 @@ struct SHoleDetectionDebugCallbacks {
void* userData
);
+ /**
+ * @brief Called after RANSAC plane segmentation and normal filtering
+ * @param planes Array of plane segment information
+ * @param planeCount Number of planes
+ * @param totalPointCount Total number of points in the grid (rows * cols)
+ * @param userData User-provided context pointer
+ */
+ void (*onPlanesSegmented)(
+ const SPlaneSegmentInfo* planes,
+ int planeCount,
+ int totalPointCount,
+ void* userData
+ );
+
+ /**
+ * @brief Called after masked points grid is built for a plane
+ * @param points Masked point array (grid format, rows * cols)
+ * @param rows Row count
+ * @param cols Column count
+ * @param planeIndex Index of the current plane (0-based)
+ * @param userData User-provided context pointer
+ */
+ void (*onMaskedPointsReady)(
+ const SVzNLPointXYZ* points,
+ int rows,
+ int cols,
+ int planeIndex,
+ void* userData
+ );
+
/**
* @brief User-provided context pointer, passed to all callbacks
*/
void* userData;
+
+ /**
+ * @brief If non-null, each cluster's boundary points are saved as a CSV file
+ * in this directory before fitting. Files are named cluster_0.csv, cluster_1.csv, ...
+ * Fields: row,col,x,y,z
+ */
+ const char* clusterOutputDir;
};
/**
@@ -200,12 +249,4 @@ HOLE_DETECTION_API int DetectMultipleHoles(
*/
HOLE_DETECTION_API void PrintDetectionResults(const SMultiHoleResult& result, bool verbose = true);
-/**
- * @brief Print a single hole result to console
- *
- * @param [in] hole Single hole result
- * @param [in] index Hole index (for display purposes)
- */
-HOLE_DETECTION_API void PrintHoleResult(const SHoleResult& hole, int index);
-
#endif // HOLE_DETECTION_H
diff --git a/AppAlgo/holeDetection/include/HoleDetectionParams.h b/AppAlgo/holeDetection/include/HoleDetectionParams.h
index 25911426..66d674a3 100644
--- a/AppAlgo/holeDetection/include/HoleDetectionParams.h
+++ b/AppAlgo/holeDetection/include/HoleDetectionParams.h
@@ -2,7 +2,7 @@
#define HOLE_DETECTION_PARAMS_H
#include
-#include "VZNL_Types.h" // 使用 VZNL_Types.h 中定义的类型
+#include "../include/VZNL_Types.h" // 使用 VZNL_Types.h 中定义的类型
/**
* @brief 孔洞检测算法的检测参数
@@ -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)
{}
};
diff --git a/AppAlgo/holeDetection/include/PlaneSegmentation.h b/AppAlgo/holeDetection/include/PlaneSegmentation.h
index 56aa8e05..0c07e425 100644
--- a/AppAlgo/holeDetection/include/PlaneSegmentation.h
+++ b/AppAlgo/holeDetection/include/PlaneSegmentation.h
@@ -282,4 +282,109 @@ int ComputeRowZDiffFluctuation(
int* errCode
);
+// ========== RANSAC 平面分割 ==========
+
+/**
+ * @brief RANSAC平面分割参数
+ */
+struct RansacPlaneSegmentationParams {
+ float distanceThreshold; // 点到平面距离阈值 (mm), 建议 0.5-2.0
+ int maxIterations; // RANSAC最大迭代次数, 建议 100-1000
+ int minPlanePoints; // 最小平面点数, 建议 50-200
+ int maxPlanes; // 最大提取平面数量, 建议 3-10
+ float growthZThreshold; // 区域生长时相邻点Z差阈值 (mm), 建议 0.5-2.0
+ float minPlaneRatio; // 平面最小点数占比 (相对最大平面), 建议 0.05-0.2
+
+ RansacPlaneSegmentationParams()
+ : distanceThreshold(1.0f)
+ , maxIterations(500)
+ , minPlanePoints(100)
+ , maxPlanes(5)
+ , growthZThreshold(1.0f)
+ , minPlaneRatio(0.1f)
+ {}
+};
+
+/**
+ * @brief 法向量过滤参数
+ */
+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)
+ {}
+};
+
+/**
+ * @brief 分割出的平面信息
+ */
+struct PlaneSegment {
+ float a, b, c, d; // 平面方程 ax + by + cz + d = 0 (a,b,c 已归一化)
+ std::vector pointIndices; // 属于该平面的点索引 (在原始grid中的索引)
+ int pointCount; // 点数
+ float normalAngleDeg; // 与参考法向的夹角 (度)
+
+ PlaneSegment()
+ : a(0.0f), b(0.0f), c(1.0f), d(0.0f)
+ , pointCount(0)
+ , normalAngleDeg(0.0f)
+ {}
+};
+
+/**
+ * @brief 使用RANSAC方法迭代分割多个平面
+ *
+ * 算法流程:
+ * 1. 收集所有有效点
+ * 2. 迭代执行RANSAC:
+ * a. 随机采样3个点拟合平面
+ * b. 统计内点数
+ * c. 保留最优平面模型
+ * d. 提取内点,从剩余点中移除
+ * 3. 重复直到剩余点不足或达到最大平面数
+ *
+ * @param points 输入点云 (grid格式, rows*cols)
+ * @param rows 行数
+ * @param cols 列数
+ * @param params RANSAC参数
+ * @param outPlanes 输出平面列表
+ * @param errCode 错误码
+ * @return 检测到的平面数量
+ */
+int SegmentPlanesByRansac(
+ const SVzNLPointXYZ* points,
+ int rows,
+ int cols,
+ const RansacPlaneSegmentationParams& params,
+ std::vector& outPlanes,
+ int* errCode
+);
+
+/**
+ * @brief 根据法向量方向过滤平面,并去除各平面中距离平面过远的点
+ *
+ * 1. 计算每个平面法向量与参考方向的夹角,移除夹角超过阈值的平面。
+ * 2. 对保留的平面,将 pointIndices 中签名距离绝对值超过 maxDistFromPlane 的点移除
+ * (maxDistFromPlane <= 0 时跳过此步骤)。
+ *
+ * @param planes 平面列表 (会被原地修改)
+ * @param params 法向过滤参数
+ * @param points 原始点云数组
+ * @param pointCount 原始点云数量
+ * @return 被移除的平面数量
+ */
+int FilterPlanesByNormal(
+ std::vector& planes,
+ const NormalFilterParams& params,
+ const SVzNLPointXYZ* points,
+ int pointCount
+);
+
#endif // PLANE_SEGMENTATION_H
diff --git a/AppAlgo/holeDetection/windows/x64/Debug/HoleDetectionLib.dll b/AppAlgo/holeDetection/windows/x64/Debug/HoleDetectionLib.dll
index 217e114a..61b2b3ad 100644
Binary files a/AppAlgo/holeDetection/windows/x64/Debug/HoleDetectionLib.dll and b/AppAlgo/holeDetection/windows/x64/Debug/HoleDetectionLib.dll differ
diff --git a/AppAlgo/holeDetection/windows/x64/Debug/HoleDetectionLib.lib b/AppAlgo/holeDetection/windows/x64/Debug/HoleDetectionLib.lib
index d9697b13..03bfd786 100644
Binary files a/AppAlgo/holeDetection/windows/x64/Debug/HoleDetectionLib.lib and b/AppAlgo/holeDetection/windows/x64/Debug/HoleDetectionLib.lib differ
diff --git a/AppAlgo/holeDetection/windows/x64/Debug/HoleDetectionLib.pdb b/AppAlgo/holeDetection/windows/x64/Debug/HoleDetectionLib.pdb
index 3aab682a..6fee57f9 100644
Binary files a/AppAlgo/holeDetection/windows/x64/Debug/HoleDetectionLib.pdb and b/AppAlgo/holeDetection/windows/x64/Debug/HoleDetectionLib.pdb differ
diff --git a/AppAlgo/holeDetection/windows/x64/Release/HoleDetectionLib.dll b/AppAlgo/holeDetection/windows/x64/Release/HoleDetectionLib.dll
index 3d512ab9..b087f5a5 100644
Binary files a/AppAlgo/holeDetection/windows/x64/Release/HoleDetectionLib.dll and b/AppAlgo/holeDetection/windows/x64/Release/HoleDetectionLib.dll differ
diff --git a/AppAlgo/holeDetection/windows/x64/Release/HoleDetectionLib.lib b/AppAlgo/holeDetection/windows/x64/Release/HoleDetectionLib.lib
index d9697b13..03bfd786 100644
Binary files a/AppAlgo/holeDetection/windows/x64/Release/HoleDetectionLib.lib and b/AppAlgo/holeDetection/windows/x64/Release/HoleDetectionLib.lib differ
diff --git a/Utils b/Utils
index 7631f8aa..5e978eae 160000
--- a/Utils
+++ b/Utils
@@ -1 +1 @@
-Subproject commit 7631f8aadce030e71321af4a4d6c9c2bf8be5b87
+Subproject commit 5e978eaea4f7891cc2f4a9ac871598cbb3178f50