HoleDetect 小孔检测算法更新

This commit is contained in:
yiyi 2026-03-25 11:07:14 +08:00
parent 2b86078d0c
commit b1ecdd6ab6
14 changed files with 422 additions and 51 deletions

View File

@ -5,8 +5,8 @@
#define HOLEDETECTION_APP_NAME "孔洞检测"
// 版本字符串
#define HOLEDETECTION_VERSION_STRING "1.0.1"
#define HOLEDETECTION_BUILD_STRING "0"
#define HOLEDETECTION_VERSION_STRING "1.1.0"
#define HOLEDETECTION_BUILD_STRING "1"
#define HOLEDETECTION_FULL_VERSION_STRING "V" HOLEDETECTION_VERSION_STRING "_" HOLEDETECTION_BUILD_STRING
// 构建日期

View File

@ -1,3 +1,9 @@
# 1.1.0 2026-03-125
## build_1
1. 算法优化
2. 配置结构修改
3. 手眼标定,网络配置公用
# 1.0.0 2026-03-11
## build_4
1. 修复矩阵配置

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>780</width>
<height>656</height>
<height>541</height>
</rect>
</property>
<property name="windowTitle">
@ -323,6 +323,12 @@ QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 3px 0 3px;
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_calibMatrix">
<attribute name="title">
<string>手眼标定</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_handEyeCalibHost"/>
</widget>
<widget class="QWidget" name="tab_network">
<attribute name="title">
<string>网络配置</string>
@ -390,12 +396,6 @@ QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 3px 0 3px;
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_calibMatrix">
<attribute name="title">
<string>手眼标定</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_handEyeCalibHost"/>
</widget>
</widget>
</item>
<item>
@ -435,6 +435,19 @@ QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 3px 0 3px;
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btn_camer_cancel">
<property name="minimumSize">

View File

@ -4,6 +4,11 @@ TEMPLATE = lib
CONFIG += staticlib
CONFIG += c++11
# 编码设置
win32-msvc {
QMAKE_CXXFLAGS += /utf-8
}
# Output directory configuration
TARGET = HoleDetectionConfig

View File

@ -18,8 +18,8 @@
struct VrHoleDetectionParam
{
int neighborCount = 3; // 相邻点连接数
double angleThresholdPos = 10.0; // 正角度阈值(度)
double angleThresholdNeg = -10.0; // 负角度阈值(度)
double angleThresholdPos = 30.0; // 正角度阈值(度)
double angleThresholdNeg = -30.0; // 负角度阈值(度)
double minPitDepth = 1.0; // 最小凹坑深度mm
double minRadius = 2.0; // 最小孔半径mm
double maxRadius = 50.0; // 最大孔半径mm

View File

@ -7,7 +7,6 @@ namespace AlgoParamConverter
SHoleDetectionParams ToAlgoParam(const VrHoleDetectionParam& param)
{
SHoleDetectionParams algo;
algo.neighborCount = param.neighborCount;
algo.angleThresholdPos = static_cast<float>(param.angleThresholdPos);
algo.angleThresholdNeg = static_cast<float>(param.angleThresholdNeg);
algo.minPitDepth = static_cast<float>(param.minPitDepth);
@ -44,8 +43,8 @@ void LogAlgoParams(const std::string& logTag,
clibMatrix[8], clibMatrix[9], clibMatrix[10], clibMatrix[11],
clibMatrix[12], clibMatrix[13], clibMatrix[14], clibMatrix[15]);
LOG_INFO("%s DetectionParams: neighborCount=%d, angleThresholdPos=%.1f, angleThresholdNeg=%.1f, minPitDepth=%.1f\n",
logTag.c_str(), detectionParams.neighborCount, detectionParams.angleThresholdPos,
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",
logTag.c_str(), detectionParams.minRadius, detectionParams.maxRadius,

View File

@ -12,6 +12,8 @@
#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
#endif
@ -31,6 +33,47 @@ struct SHoleBoundaryPoint {
: point(p), row(r), col(c) {}
};
/**
* @brief Per-point signed angle state along one scanned line
*/
enum ELineAngleTrend {
keLineAngleTrend_Invalid = 0,
keLineAngleTrend_Flat = 1,
keLineAngleTrend_PositiveJump = 2,
keLineAngleTrend_NegativeJump = 3
};
/**
* @brief Angle profile sample for one valid point on a scanned line
*/
struct SLineAngleSample {
SVzNLPointXYZ point; // Current point
int row; // Grid row
int col; // Grid column
int linePos; // Position inside the scanned line
float signedAngleDeg; // Signed turning angle in degrees
float beforeMeanZ; // Mean Z of points found within A before current point
float afterMeanZ; // Mean Z of points found within A after current point
float beforeCoord; // Projected coord of the furthest point before current point
float afterCoord; // Projected coord of the furthest point after current point
ELineAngleTrend trend; // Flat / positive jump / negative jump
unsigned char hasAngle; // 1 if both sides have enough points to form an angle
SLineAngleSample()
: point()
, row(-1)
, col(-1)
, linePos(-1)
, signedAngleDeg(0.0f)
, beforeMeanZ(0.0f)
, afterMeanZ(0.0f)
, beforeCoord(0.0f)
, afterCoord(0.0f)
, trend(keLineAngleTrend_Invalid)
, hasAngle(0U)
{}
};
/**
* @brief Cluster information for debug callbacks
*/
@ -90,6 +133,22 @@ struct SHoleDetectionDebugCallbacks {
*/
void (*onSegmentPairsDetected)(const SSegmentPair* segmentPairs, int count, void* userData);
/**
* @brief Called when one scanned line's signed angle profile is available
* @param samples Array of valid-point angle samples in line order
* @param count Number of samples
* @param lineType "Row" or "Column"
* @param lineIndex Row/column index
* @param userData User-provided context pointer
*/
void (*onLineAngleProfileDetected)(
const SLineAngleSample* samples,
int count,
const char* lineType,
int lineIndex,
void* userData
);
/**
* @brief User-provided context pointer, passed to all callbacks
*/

View File

@ -2,26 +2,16 @@
#define HOLE_DETECTION_PARAMS_H
#include <cmath>
#include "../include/VZNL_Types.h" // 使用 VZNL_Types.h 中的类型
/**
* @brief
*/
enum ESortMode {
keSortMode_None = 0, // 不排序
keSortMode_ByRadius = 1, // 按半径排序(最大的在前)
keSortMode_ByDepth = 2, // 按深度排序(最深的在前)
keSortMode_ByQuality = 3 // 按质量分数排序(最高的在前)
};
#include "VZNL_Types.h" // 使用 VZNL_Types.h 中定义的类型
/**
* @brief
*/
struct SHoleDetectionParams {
// 凹坑检测参数
int neighborCount; // 线连接的相邻点数默认值3
float angleThresholdPos; // 正角度阈值单位默认值10.0
float angleThresholdNeg; // 负角度阈值,单位:度(默认值:-10.0
float angleThresholdPos; // 正角度阈值单位默认值30.0
float angleThresholdNeg; // 负角度阈值,单位:度(默认值:-30.0
float angleSearchDistance; // Method1 前后搜索距离 A单位mm
float minPitDepth; // 最小凹坑深度单位mm默认值1.0
// 椭圆拟合参数
@ -29,23 +19,38 @@ struct SHoleDetectionParams {
float maxRadius; // 最大孔洞半径单位mm默认值50.0
// 平面拟合参数
int expansionSize1; // 第一环扩展大小单位mm默认值5
int expansionSize2; // 第二环扩展大小单位mm默认值10
int expansionSize1; // 第一圈扩展大小默认值5
int expansionSize2; // 第二圈扩展大小默认值10
// V型检测参数
int minVTransitionPoints; // V型端点之间的最小有效过渡点数默认值1
// V 型检测参数
int minVTransitionPoints; // V 型端点之间的最小有效过渡点数默认值1
float jumpThresholdResidual; // 相邻有效点的残差跳变阈值,<=0 表示自适应
float gapJumpThresholdResidual; // 跨无效点间隙的残差跳变阈值,<=0 表示自适应
int maxGapPointsInLine; // 允许跨越的最大无效点数
float minFeatureSpan; // 特征点对最小弧长跨度mm
int residualSmoothWindow; // 残差平滑窗口(奇数,建议 3~7
float slopeAngleThreshold; // 坡度补充判断阈值单位默认值3.0
// 当曲率角未超过 angleThreshold 时,若前后参考点间的
// 净 Z 坡度角超过此阈值,则将该点判定为下降或上升趋势。
// 设置为 0 可禁用坡度补充判断。
// 构造函数,设置默认值
SHoleDetectionParams()
: neighborCount(3)
, angleThresholdPos(10.0f)
, angleThresholdNeg(-10.0f)
: angleThresholdPos(30.0f)
, angleThresholdNeg(-30.0f)
, angleSearchDistance(2.0f)
, minPitDepth(1.0f)
, minRadius(2.0f)
, maxRadius(50.0f)
, expansionSize1(5)
, expansionSize2(10)
, minVTransitionPoints(1)
, jumpThresholdResidual(0.0f)
, gapJumpThresholdResidual(0.0f)
, maxGapPointsInLine(12)
, minFeatureSpan(2.0f)
, residualSmoothWindow(5)
, slopeAngleThreshold(3.0f)
{}
};
@ -60,18 +65,18 @@ struct SHoleFilterParams {
// 形状过滤(拟合前)
float minAngularCoverage; // 最小角度覆盖范围单位默认值10.0
// 用于过滤非闭合边界。设置为 0 可禁用。
float maxRadiusFitRatio; // 最大半径拟合比率 radiusVariance/radius默认值1.0
// 衡量点与圆的拟合程度。设置为 1.0 可禁用。
float maxRadiusFitRatio; // 最大半径拟合比率 radiusVariance / radius默认值1.0
// 衡量边界点与圆的拟合程度。设置为 1.0 可禁用。
float minQualityScore; // 最小整体质量分数默认值0.0
// 形状指标的加权组合。设置为 0 可禁用。
// 平面性过滤(投影前)
// 平面一致性过滤(投影前)
float maxPlaneResidual; // 最大点到平面残差单位mm默认值10.0
// 拒绝非平面簇(例如悬崖、台阶边缘)
// 用于拒绝非平面簇,例如悬崖、台阶边缘
float maxAngularGap; // 最大角度间隙单位默认值90.0
// 拒绝 L 型或非闭合边界。
float minInlierRatio; // 圆拟合的最小内点比率默认值0.0
// 在拟合椭圆容差范围内的点的比例。
// 用于拒绝 L 型或非闭合边界。
float minInlierRatio; // 圆拟合的最小内点比率默认值0.0
// 表示落在拟合圆容差范围内的点所占比例。
// 构造函数,设置默认值
SHoleFilterParams()
@ -88,17 +93,17 @@ struct SHoleFilterParams {
/**
* @brief
*
* SVzNL3DPointF SVzNL2DPointF VZNL_Types.h
* SVzNL3DPointF SVzNL2DPointF VZNL_Types.h
*/
struct SHoleResult {
SVzNL3DPointF center; // 孔洞中心点 (x, y, z)
SVzNL3DPointF normal; // 平面法向量(单位长度)
SVzNL3DPointF normal; // 拟合平面法向量(单位长度)
float radius; // 孔洞半径单位mm
float depth; // 凹坑深度单位mm
float eccentricity; // 离心率0 = 完美圆形)
float radiusVariance; // 半径方差单位mm
float eccentricity; // 离心率0 表示完美圆形)
float radiusVariance; // 半径离散度单位mm
float angularSpan; // 角度覆盖范围,单位:度
float qualityScore; // 质量分数0-1越高越好
float qualityScore; // 质量分数0~1越高越好
SHoleResult()
: center()
@ -146,11 +151,10 @@ inline void FreeMultiHoleResult(SMultiHoleResult* result) {
}
/**
* @brief 线
* @brief 线
*
* 线线
* "PeakValley"线
* /
* 线
*
*/
struct SSegmentPair {
SVzNLPointXYZ startPoint; // 线段起点

View File

@ -0,0 +1,285 @@
#ifndef PLANE_SEGMENTATION_H
#define PLANE_SEGMENTATION_H
#include "../include/VZNL_Types.h"
#include "ErrorCodes.h"
#include <vector>
/**
* @brief Z值直方图峰值信息
*/
struct ZHistogramPeak {
float zValue; // 峰值对应的Z坐标
int pointCount; // 该峰值区域的点数
float zMin; // 峰值区域的Z最小值
float zMax; // 峰值区域的Z最大值
std::vector<int> pointIndices; // 属于该平面的点索引
};
/**
* @brief Z值直方图平面分割参数
*/
struct ZHistogramSegmentationParams {
float binSize; // 直方图bin大小 (mm), 建议 1.0-5.0
int minPeakPoints; // 最小峰值点数, 建议 50-100
float minPeakRatio; // 最小峰值点数占比, 建议 0.03 (3%)
float peakMergeThreshold; // 峰值合并阈值 (mm), 建议 5.0-10.0
int smoothingWindow; // 平滑窗口大小, 建议 3-5
};
/**
* @brief 使Z值直方图分割平面
*
* :
* 1. Z值范围
* 2. bin的点数
* 3.
* 4.
* 5.
* 6.
*
*
* - O(n)
* -
* -
*
*
* - Z值会分散
* - Z方向分层明显的平面
*
* @param points
* @param pointCount
* @param params
* @param outPeaks Z值排序
* @param errCode
* @return
*/
int SegmentPlanesByZHistogram(
const SVzNLPointXYZ* points,
int pointCount,
const ZHistogramSegmentationParams& params,
std::vector<ZHistogramPeak>& outPeaks,
int* errCode
);
/**
* @brief
*
*
* 1. Z直方图粗分割
* 2. Z层XY平面上再做一次分层
* 3.
*
* @param points
* @param pointCount
* @param params
* @param outPeaks
* @param errCode
* @return
*/
int SegmentTiltedPlanesByHistogram(
const SVzNLPointXYZ* points,
int pointCount,
const ZHistogramSegmentationParams& params,
std::vector<ZHistogramPeak>& outPeaks,
int* errCode
);
/**
* @brief Z值波动统计信息
*/
struct ZFluctuationStats {
float meanZ; // Z值均值
float stdDevZ; // Z值标准差去除离群点后
float minZ; // Z值最小值去除离群点后
float maxZ; // Z值最大值去除离群点后
float rangeZ; // Z值范围maxZ - minZ
int validPointCount; // 有效点数(去除离群点后)
int outlierCount; // 离群点数量
float outlierRatio; // 离群点占比
float medianZ; // Z值中位数
float iqrZ; // 四分位距IQR
};
/**
* @brief Z值波动分析参数
*/
struct ZFluctuationParams {
enum OutlierMethod {
SIGMA_3, // 3-sigma规则适合正态分布
IQR, // 四分位距方法(更鲁棒,推荐)
PERCENTILE // 百分位数方法
};
OutlierMethod method; // 离群点检测方法
float sigmaMultiplier; // sigma倍数默认3.0
float iqrMultiplier; // IQR倍数默认1.5
float percentileLow; // 下百分位数默认5.0 (5%)
float percentileHigh; // 上百分位数默认95.0 (95%)
// 默认构造函数
ZFluctuationParams()
: method(IQR)
, sigmaMultiplier(3.0f)
, iqrMultiplier(1.5f)
, percentileLow(5.0f)
, percentileHigh(95.0f)
{}
};
/**
* @brief Z值波动统计
*
*
* 1. Z值
* 2.
* - 3-sigma: 3
* - IQR: [Q1-1.5*IQR, Q3+1.5*IQR]
* - Percentile:
* 3.
*
* 使
* -
* -
* -
*
*
* ```cpp
* ZFluctuationParams params;
* params.method = ZFluctuationParams::IQR; // 使用IQR方法推荐
*
* ZFluctuationStats stats;
* int errCode = 0;
* ComputeZFluctuation(points, pointCount, params, &stats, &errCode);
*
* std::cout << "Z波动: " << stats.stdDevZ << " mm" << std::endl;
* std::cout << "Z范围: " << stats.rangeZ << " mm" << std::endl;
* std::cout << "离群点: " << stats.outlierCount << " ("
* << stats.outlierRatio * 100 << "%)" << std::endl;
* ```
*
* @param points
* @param pointCount
* @param params
* @param outStats
* @param errCode
* @return HD_SUCCESS
*/
int ComputeZFluctuation(
const SVzNLPointXYZ* points,
int pointCount,
const ZFluctuationParams& params,
ZFluctuationStats* outStats,
int* errCode
);
/**
* @brief 使Z值波动
*
* 使IQR方法自动去除离群点
*
* @param points
* @param pointCount
* @param outStats
* @param errCode
* @return HD_SUCCESS
*/
int ComputeZFluctuation(
const SVzNLPointXYZ* points,
int pointCount,
ZFluctuationStats* outStats,
int* errCode
);
/**
* @brief
*/
struct PointSpacingStats {
// 主要结果:取两个方向的最大值
float typicalSpacing; // 典型间距(行内和列内中位数的最大值,推荐使用)
// 分方向统计
float rowSpacing; // 行内平均间距同一激光线上相邻点YZ平面距离
float colSpacing; // 列内平均间距相邻激光线间XZ平面距离
float rowSpacingMedian; // 行内间距中位数(更鲁棒)
float colSpacingMedian; // 列内间距中位数(更鲁棒)
float rowSpacingStdDev; // 行内间距标准差
float colSpacingStdDev; // 列内间距标准差
int rowSampleCount; // 行内采样数量
int colSampleCount; // 列内采样数量
float rowSpacingMin; // 行内最小间距
float rowSpacingMax; // 行内最大间距
float colSpacingMin; // 列内最小间距
float colSpacingMax; // 列内最大间距
};
/**
* @brief
*
*
* 1.
* - 线使YZ平面距离 sqrt((y2-y1)² + (z2-z1)²)
* - 线使XZ平面距离 sqrt((x2-x1)² + (z2-z1)²)
* 2. (0,0,0)
* 3.
* 4. 使IQR方法去除噪点
* 5.
*
* 使
* - /
* -
* -
* -
*
*
* - 使YZ平面线沿Y方向扫描Z是深度
* - 使XZ平面线X方向分布Z是深度
* - (0,0,0)
* - 使
*
*
* ```cpp
* PointSpacingStats stats;
* int errCode = 0;
* ComputePointSpacing(points, rows, cols, &stats, &errCode);
*
* std::cout << "行内间距: " << stats.rowSpacingMedian << " mm" << std::endl;
* std::cout << "列内间距: " << stats.colSpacingMedian << " mm" << std::endl;
* ```
*
* @param points
* @param rows 线
* @param cols 线
* @param outStats
* @param errCode
* @return HD_SUCCESS
*/
int ComputePointSpacing(
const SVzNLPointXYZ* points,
int rows,
int cols,
PointSpacingStats* outStats,
int* errCode
);
struct RowZDiffFluctuationStats {
float meanAbsDz; // |dz| 均值(去除异常值后)
float medianAbsDz; // |dz| 中位数(去除异常值后)
float stdDevAbsDz; // |dz| 标准差作为Z波动值
float minAbsDz; // |dz| 最小值(去除异常值后)
float maxAbsDz; // |dz| 最大值(去除异常值后)
float rangeAbsDz; // |dz| 波动范围maxAbsDz - minAbsDz
int sampleCount; // 有效样本数(去除异常值后)
int outlierCount; // 被剔除的异常样本数
float outlierRatio; // 异常样本占比outlierCount / 原始样本数)
};
int ComputeRowZDiffFluctuation(
const SVzNLPointXYZ* points,
int rows,
int cols,
RowZDiffFluctuationStats* outStats,
int* errCode
);
#endif // PLANE_SEGMENTATION_H