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