孔检测HoleDetection 做了算法更新
This commit is contained in:
parent
512c72b895
commit
de7df9398d
@ -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);
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
# 1.1.1 2026-04-02
|
||||
## build_1
|
||||
1. 更新算法
|
||||
|
||||
# 1.1.0 2026-03-25
|
||||
## build_1
|
||||
1. 算法优化
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -182,6 +182,16 @@ QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 3px 0 3px;
|
||||
<item row="8" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_minVTransitionPoints"/>
|
||||
</item>
|
||||
<item row="9" column="0">
|
||||
<widget class="QLabel" name="label_edgeBoundaryFilterDist">
|
||||
<property name="text">
|
||||
<string>边缘过滤距离 (mm):</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_edgeBoundaryFilterDist"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@ -296,13 +306,23 @@ QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 3px 0 3px;
|
||||
<widget class="QLineEdit" name="lineEdit_minInlierRatio"/>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="label_minHoleDepth">
|
||||
<property name="text">
|
||||
<string>最小孔深 (mm):</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_minHoleDepth"/>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<widget class="QLabel" name="label_sortMode">
|
||||
<property name="text">
|
||||
<string>排序模式:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<item row="8" column="1">
|
||||
<widget class="QComboBox" name="comboBox_sortMode"/>
|
||||
</item>
|
||||
</layout>
|
||||
|
||||
@ -26,6 +26,7 @@ struct VrHoleDetectionParam
|
||||
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)
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -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<float>(param.edgeBoundaryFilterDist);
|
||||
return algo;
|
||||
}
|
||||
|
||||
@ -28,6 +29,7 @@ SHoleFilterParams ToAlgoParam(const VrHoleFilterParam& param)
|
||||
algo.maxPlaneResidual = static_cast<float>(param.maxPlaneResidual);
|
||||
algo.maxAngularGap = static_cast<float>(param.maxAngularGap);
|
||||
algo.minInlierRatio = static_cast<float>(param.minInlierRatio);
|
||||
algo.minHoleDepth = static_cast<float>(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
|
||||
|
||||
@ -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);
|
||||
|
||||
// 添加排序模式
|
||||
|
||||
@ -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"/>
|
||||
<FilterParam minHoleRadius="1.0" maxHoleRadius="10.0" maxEccentricity="0.99995"
|
||||
maxCornerRatio="0.15" minAngularCoverage="10.0" maxRadiusFitRatio="0.3"
|
||||
minQualityScore="0.3" maxPlaneResidual="10.0" maxAngularGap="90.0" minInlierRatio="0.3"/>
|
||||
minQualityScore="0.3" maxPlaneResidual="10.0" maxAngularGap="90.0" minInlierRatio="0.3"
|
||||
minHoleDepth="2.5"/>
|
||||
<SortMode value="0"/>
|
||||
<PlaneCalibParams>
|
||||
<CameraCalibParam cameraIndex="1" cameraName="Camera1" isCalibrated="false" planeHeight="-1.0"
|
||||
|
||||
@ -1,25 +1,102 @@
|
||||
#include "mainwindow.h"
|
||||
#include "SingleInstanceManager.h"
|
||||
#include "AuthView.h"
|
||||
#include "AuthManager.h"
|
||||
#include "HolePitPositionPresenter.h"
|
||||
#include "IYHolePitPositionStatus.h"
|
||||
#include "VrLog.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QIcon>
|
||||
#include <QMessageBox>
|
||||
#include <QObject>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#ifdef Q_OS_UNIX
|
||||
#include <cstdlib>
|
||||
#include <sys/stat.h>
|
||||
#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<int>(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 (hasDisplay) {
|
||||
// ---- 有桌面:完整 UI 模式 ----
|
||||
a.setWindowIcon(QIcon(":/common/resource/logo.png"));
|
||||
|
||||
if (instanceManager.isAnotherInstanceRunning()) {
|
||||
// 已有实例在运行,显示提示信息并退出
|
||||
QMessageBox::information(nullptr,
|
||||
QObject::tr("应用程序已运行"),
|
||||
QObject::tr("坑孔定位检测应用程序已经在运行中,请勿重复启动!"),
|
||||
@ -27,15 +104,30 @@ int main(int argc, char *argv[])
|
||||
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<IYHolePitPositionStatus>(&listener);
|
||||
presenter.Init();
|
||||
return a.exec();
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@ -5,18 +5,20 @@
|
||||
#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__)
|
||||
#else
|
||||
#ifdef HOLE_DETECTION_EXPORTS
|
||||
#define HOLE_DETECTION_API __attribute__((visibility("default")))
|
||||
#else
|
||||
#define HOLE_DETECTION_API
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Boundary point with grid location (for debug callbacks)
|
||||
@ -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
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
#define HOLE_DETECTION_PARAMS_H
|
||||
|
||||
#include <cmath>
|
||||
#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)
|
||||
{}
|
||||
};
|
||||
|
||||
|
||||
@ -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<int> 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<PlaneSegment>& outPlanes,
|
||||
int* errCode
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief 根据法向量方向过滤平面,并去除各平面中距离平面过远的点
|
||||
*
|
||||
* 1. 计算每个平面法向量与参考方向的夹角,移除夹角超过阈值的平面。
|
||||
* 2. 对保留的平面,将 pointIndices 中签名距离绝对值超过 maxDistFromPlane 的点移除
|
||||
* (maxDistFromPlane <= 0 时跳过此步骤)。
|
||||
*
|
||||
* @param planes 平面列表 (会被原地修改)
|
||||
* @param params 法向过滤参数
|
||||
* @param points 原始点云数组
|
||||
* @param pointCount 原始点云数量
|
||||
* @return 被移除的平面数量
|
||||
*/
|
||||
int FilterPlanesByNormal(
|
||||
std::vector<PlaneSegment>& planes,
|
||||
const NormalFilterParams& params,
|
||||
const SVzNLPointXYZ* points,
|
||||
int pointCount
|
||||
);
|
||||
|
||||
#endif // PLANE_SEGMENTATION_H
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
2
Utils
2
Utils
@ -1 +1 @@
|
||||
Subproject commit 7631f8aadce030e71321af4a4d6c9c2bf8be5b87
|
||||
Subproject commit 5e978eaea4f7891cc2f4a9ac871598cbb3178f50
|
||||
Loading…
x
Reference in New Issue
Block a user