孔检测HoleDetection 做了算法更新
This commit is contained in:
parent
512c72b895
commit
de7df9398d
@ -211,19 +211,21 @@ int HoleDetectionPresenter::InitAlgoParams()
|
|||||||
xmlParams.detectionParam.maxRadius,
|
xmlParams.detectionParam.maxRadius,
|
||||||
xmlParams.detectionParam.expansionSize1,
|
xmlParams.detectionParam.expansionSize1,
|
||||||
xmlParams.detectionParam.expansionSize2);
|
xmlParams.detectionParam.expansionSize2);
|
||||||
LOG_INFO("Loaded XML params - Detection: minVTransitionPoints=%d\n",
|
LOG_INFO("Loaded XML params - Detection: minVTransitionPoints=%d, edgeBoundaryFilterDist=%.2f\n",
|
||||||
xmlParams.detectionParam.minVTransitionPoints);
|
xmlParams.detectionParam.minVTransitionPoints,
|
||||||
|
xmlParams.detectionParam.edgeBoundaryFilterDist);
|
||||||
|
|
||||||
// 打印过滤参数
|
// 打印过滤参数
|
||||||
LOG_INFO("Loaded XML params - Filter: maxEccentricity=%.5f, minAngularCoverage=%.1f, maxRadiusFitRatio=%.2f\n",
|
LOG_INFO("Loaded XML params - Filter: maxEccentricity=%.5f, minAngularCoverage=%.1f, maxRadiusFitRatio=%.2f\n",
|
||||||
xmlParams.filterParam.maxEccentricity,
|
xmlParams.filterParam.maxEccentricity,
|
||||||
xmlParams.filterParam.minAngularCoverage,
|
xmlParams.filterParam.minAngularCoverage,
|
||||||
xmlParams.filterParam.maxRadiusFitRatio);
|
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.minQualityScore,
|
||||||
xmlParams.filterParam.maxPlaneResidual,
|
xmlParams.filterParam.maxPlaneResidual,
|
||||||
xmlParams.filterParam.maxAngularGap,
|
xmlParams.filterParam.maxAngularGap,
|
||||||
xmlParams.filterParam.minInlierRatio);
|
xmlParams.filterParam.minInlierRatio,
|
||||||
|
xmlParams.filterParam.minHoleDepth);
|
||||||
|
|
||||||
LOG_INFO("Loaded XML params - SortMode: %d\n", xmlParams.sortMode);
|
LOG_INFO("Loaded XML params - SortMode: %d\n", xmlParams.sortMode);
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
#define HOLEDETECTION_APP_NAME "孔洞检测"
|
#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_BUILD_STRING "1"
|
||||||
#define HOLEDETECTION_FULL_VERSION_STRING "V" HOLEDETECTION_VERSION_STRING "_" HOLEDETECTION_BUILD_STRING
|
#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
|
# 1.1.0 2026-03-25
|
||||||
## build_1
|
## build_1
|
||||||
1. 算法优化
|
1. 算法优化
|
||||||
|
|||||||
@ -319,6 +319,7 @@ void DialogAlgoarg::LoadDetectionParamToUI(const VrHoleDetectionParam& param)
|
|||||||
ui->lineEdit_expansionSize1->setText(QString::number(param.expansionSize1));
|
ui->lineEdit_expansionSize1->setText(QString::number(param.expansionSize1));
|
||||||
ui->lineEdit_expansionSize2->setText(QString::number(param.expansionSize2));
|
ui->lineEdit_expansionSize2->setText(QString::number(param.expansionSize2));
|
||||||
ui->lineEdit_minVTransitionPoints->setText(QString::number(param.minVTransitionPoints));
|
ui->lineEdit_minVTransitionPoints->setText(QString::number(param.minVTransitionPoints));
|
||||||
|
ui->lineEdit_edgeBoundaryFilterDist->setText(QString::number(param.edgeBoundaryFilterDist));
|
||||||
}
|
}
|
||||||
|
|
||||||
void DialogAlgoarg::LoadFilterParamToUI(const VrHoleFilterParam& param)
|
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_maxPlaneResidual->setText(QString::number(param.maxPlaneResidual));
|
||||||
ui->lineEdit_maxAngularGap->setText(QString::number(param.maxAngularGap));
|
ui->lineEdit_maxAngularGap->setText(QString::number(param.maxAngularGap));
|
||||||
ui->lineEdit_minInlierRatio->setText(QString::number(param.minInlierRatio));
|
ui->lineEdit_minInlierRatio->setText(QString::number(param.minInlierRatio));
|
||||||
|
ui->lineEdit_minHoleDepth->setText(QString::number(param.minHoleDepth));
|
||||||
}
|
}
|
||||||
|
|
||||||
void DialogAlgoarg::LoadSortModeToUI(int sortMode)
|
void DialogAlgoarg::LoadSortModeToUI(int sortMode)
|
||||||
@ -377,6 +379,9 @@ bool DialogAlgoarg::SaveDetectionParamFromUI(VrHoleDetectionParam& param)
|
|||||||
param.minVTransitionPoints = ui->lineEdit_minVTransitionPoints->text().toInt(&ok);
|
param.minVTransitionPoints = ui->lineEdit_minVTransitionPoints->text().toInt(&ok);
|
||||||
if (!ok) return false;
|
if (!ok) return false;
|
||||||
|
|
||||||
|
param.edgeBoundaryFilterDist = ui->lineEdit_edgeBoundaryFilterDist->text().toDouble(&ok);
|
||||||
|
if (!ok) return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -405,6 +410,9 @@ bool DialogAlgoarg::SaveFilterParamFromUI(VrHoleFilterParam& param)
|
|||||||
param.minInlierRatio = ui->lineEdit_minInlierRatio->text().toDouble(&ok);
|
param.minInlierRatio = ui->lineEdit_minInlierRatio->text().toDouble(&ok);
|
||||||
if (!ok) return false;
|
if (!ok) return false;
|
||||||
|
|
||||||
|
param.minHoleDepth = ui->lineEdit_minHoleDepth->text().toDouble(&ok);
|
||||||
|
if (!ok) return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -182,6 +182,16 @@ QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 3px 0 3px;
|
|||||||
<item row="8" column="1">
|
<item row="8" column="1">
|
||||||
<widget class="QLineEdit" name="lineEdit_minVTransitionPoints"/>
|
<widget class="QLineEdit" name="lineEdit_minVTransitionPoints"/>
|
||||||
</item>
|
</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>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@ -296,13 +306,23 @@ QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 3px 0 3px;
|
|||||||
<widget class="QLineEdit" name="lineEdit_minInlierRatio"/>
|
<widget class="QLineEdit" name="lineEdit_minInlierRatio"/>
|
||||||
</item>
|
</item>
|
||||||
<item row="7" column="0">
|
<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">
|
<widget class="QLabel" name="label_sortMode">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>排序模式:</string>
|
<string>排序模式:</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="7" column="1">
|
<item row="8" column="1">
|
||||||
<widget class="QComboBox" name="comboBox_sortMode"/>
|
<widget class="QComboBox" name="comboBox_sortMode"/>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
|
|||||||
@ -20,12 +20,13 @@ struct VrHoleDetectionParam
|
|||||||
int neighborCount = 3; // 相邻点连接数
|
int neighborCount = 3; // 相邻点连接数
|
||||||
double angleThresholdPos = 30.0; // 正角度阈值(度)
|
double angleThresholdPos = 30.0; // 正角度阈值(度)
|
||||||
double angleThresholdNeg = -30.0; // 负角度阈值(度)
|
double angleThresholdNeg = -30.0; // 负角度阈值(度)
|
||||||
double minPitDepth = 1.0; // 最小凹坑深度(mm)
|
double minPitDepth = 1.0; // 最小凹坑深度(mm)
|
||||||
double minRadius = 2.0; // 最小孔半径(mm)
|
double minRadius = 2.0; // 最小孔半径(mm)
|
||||||
double maxRadius = 50.0; // 最大孔半径(mm)
|
double maxRadius = 50.0; // 最大孔半径(mm)
|
||||||
int expansionSize1 = 5; // 第一环扩展大小
|
int expansionSize1 = 5; // 第一环扩展大小
|
||||||
int expansionSize2 = 10; // 第二环扩展大小
|
int expansionSize2 = 10; // 第二环扩展大小
|
||||||
int minVTransitionPoints = 1; // V形端点间最小有效过渡点数
|
int minVTransitionPoints = 1; // V形端点间最小有效过渡点数
|
||||||
|
double edgeBoundaryFilterDist = 0.0; // 边缘过滤距离(mm)
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -40,6 +41,7 @@ struct VrHoleFilterParam
|
|||||||
double maxPlaneResidual = 10.0; // 最大平面残差(mm)
|
double maxPlaneResidual = 10.0; // 最大平面残差(mm)
|
||||||
double maxAngularGap = 90.0; // 最大角度间隙(度)
|
double maxAngularGap = 90.0; // 最大角度间隙(度)
|
||||||
double minInlierRatio = 0.0; // 最小内点比率
|
double minInlierRatio = 0.0; // 最小内点比率
|
||||||
|
double minHoleDepth = 2.5; // 最小孔深(mm)
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -15,6 +15,7 @@ SHoleDetectionParams ToAlgoParam(const VrHoleDetectionParam& param)
|
|||||||
algo.expansionSize1 = param.expansionSize1;
|
algo.expansionSize1 = param.expansionSize1;
|
||||||
algo.expansionSize2 = param.expansionSize2;
|
algo.expansionSize2 = param.expansionSize2;
|
||||||
algo.minVTransitionPoints = param.minVTransitionPoints;
|
algo.minVTransitionPoints = param.minVTransitionPoints;
|
||||||
|
algo.edgeBoundaryFilterDist = static_cast<float>(param.edgeBoundaryFilterDist);
|
||||||
return algo;
|
return algo;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,6 +29,7 @@ SHoleFilterParams ToAlgoParam(const VrHoleFilterParam& param)
|
|||||||
algo.maxPlaneResidual = static_cast<float>(param.maxPlaneResidual);
|
algo.maxPlaneResidual = static_cast<float>(param.maxPlaneResidual);
|
||||||
algo.maxAngularGap = static_cast<float>(param.maxAngularGap);
|
algo.maxAngularGap = static_cast<float>(param.maxAngularGap);
|
||||||
algo.minInlierRatio = static_cast<float>(param.minInlierRatio);
|
algo.minInlierRatio = static_cast<float>(param.minInlierRatio);
|
||||||
|
algo.minHoleDepth = static_cast<float>(param.minHoleDepth);
|
||||||
return algo;
|
return algo;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,15 +48,16 @@ void LogAlgoParams(const std::string& logTag,
|
|||||||
LOG_INFO("%s DetectionParams: angleThresholdPos=%.1f, angleThresholdNeg=%.1f, minPitDepth=%.1f\n",
|
LOG_INFO("%s DetectionParams: angleThresholdPos=%.1f, angleThresholdNeg=%.1f, minPitDepth=%.1f\n",
|
||||||
logTag.c_str(), detectionParams.angleThresholdPos,
|
logTag.c_str(), detectionParams.angleThresholdPos,
|
||||||
detectionParams.angleThresholdNeg, detectionParams.minPitDepth);
|
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,
|
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",
|
LOG_INFO("%s FilterParams: maxEccentricity=%.5f, minAngularCoverage=%.1f, maxRadiusFitRatio=%.2f\n",
|
||||||
logTag.c_str(), filterParams.maxEccentricity, filterParams.minAngularCoverage, filterParams.maxRadiusFitRatio);
|
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,
|
logTag.c_str(), filterParams.minQualityScore, filterParams.maxPlaneResidual,
|
||||||
filterParams.maxAngularGap, filterParams.minInlierRatio);
|
filterParams.maxAngularGap, filterParams.minInlierRatio, filterParams.minHoleDepth);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace AlgoParamConverter
|
} // namespace AlgoParamConverter
|
||||||
|
|||||||
@ -84,6 +84,8 @@ int CVrConfig::LoadConfig(const std::string& filePath, ConfigResult& configResul
|
|||||||
configResult.algorithmParams.detectionParam.expansionSize2 = detectionParamElement->IntAttribute("expansionSize2");
|
configResult.algorithmParams.detectionParam.expansionSize2 = detectionParamElement->IntAttribute("expansionSize2");
|
||||||
if (detectionParamElement->Attribute("minVTransitionPoints"))
|
if (detectionParamElement->Attribute("minVTransitionPoints"))
|
||||||
configResult.algorithmParams.detectionParam.minVTransitionPoints = detectionParamElement->IntAttribute("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");
|
configResult.algorithmParams.filterParam.maxAngularGap = filterParamElement->DoubleAttribute("maxAngularGap");
|
||||||
if (filterParamElement->Attribute("minInlierRatio"))
|
if (filterParamElement->Attribute("minInlierRatio"))
|
||||||
configResult.algorithmParams.filterParam.minInlierRatio = filterParamElement->DoubleAttribute("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("expansionSize1", configResult.algorithmParams.detectionParam.expansionSize1);
|
||||||
detectionParamElement->SetAttribute("expansionSize2", configResult.algorithmParams.detectionParam.expansionSize2);
|
detectionParamElement->SetAttribute("expansionSize2", configResult.algorithmParams.detectionParam.expansionSize2);
|
||||||
detectionParamElement->SetAttribute("minVTransitionPoints", configResult.algorithmParams.detectionParam.minVTransitionPoints);
|
detectionParamElement->SetAttribute("minVTransitionPoints", configResult.algorithmParams.detectionParam.minVTransitionPoints);
|
||||||
|
detectionParamElement->SetAttribute("edgeBoundaryFilterDist", configResult.algorithmParams.detectionParam.edgeBoundaryFilterDist);
|
||||||
algoParamsElement->InsertEndChild(detectionParamElement);
|
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("maxPlaneResidual", configResult.algorithmParams.filterParam.maxPlaneResidual);
|
||||||
filterParamElement->SetAttribute("maxAngularGap", configResult.algorithmParams.filterParam.maxAngularGap);
|
filterParamElement->SetAttribute("maxAngularGap", configResult.algorithmParams.filterParam.maxAngularGap);
|
||||||
filterParamElement->SetAttribute("minInlierRatio", configResult.algorithmParams.filterParam.minInlierRatio);
|
filterParamElement->SetAttribute("minInlierRatio", configResult.algorithmParams.filterParam.minInlierRatio);
|
||||||
|
filterParamElement->SetAttribute("minHoleDepth", configResult.algorithmParams.filterParam.minHoleDepth);
|
||||||
algoParamsElement->InsertEndChild(filterParamElement);
|
algoParamsElement->InsertEndChild(filterParamElement);
|
||||||
|
|
||||||
// 添加排序模式
|
// 添加排序模式
|
||||||
|
|||||||
@ -12,11 +12,13 @@
|
|||||||
minPitDepth="1.0" angleStep="1.0" maxScanRadius="100.0" clusterEps="5.0"
|
minPitDepth="1.0" angleStep="1.0" maxScanRadius="100.0" clusterEps="5.0"
|
||||||
clusterMinPoints="5" minRadius="5.0" maxRadius="50.0" expansionSize1="5"
|
clusterMinPoints="5" minRadius="5.0" maxRadius="50.0" expansionSize1="5"
|
||||||
expansionSize2="10" validZThreshold="0.0001" minVTransitionPoints="1"
|
expansionSize2="10" validZThreshold="0.0001" minVTransitionPoints="1"
|
||||||
|
edgeBoundaryFilterDist="0.0"
|
||||||
cornerScale="2.0" cornerAngleThreshold="45.0" jumpCornerTh_1="10.0"
|
cornerScale="2.0" cornerAngleThreshold="45.0" jumpCornerTh_1="10.0"
|
||||||
jumpCornerTh_2="30.0" minEndingGap="5.0" minEndingGap_z="1.0"/>
|
jumpCornerTh_2="30.0" minEndingGap="5.0" minEndingGap_z="1.0"/>
|
||||||
<FilterParam minHoleRadius="1.0" maxHoleRadius="10.0" maxEccentricity="0.99995"
|
<FilterParam minHoleRadius="1.0" maxHoleRadius="10.0" maxEccentricity="0.99995"
|
||||||
maxCornerRatio="0.15" minAngularCoverage="10.0" maxRadiusFitRatio="0.3"
|
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"/>
|
<SortMode value="0"/>
|
||||||
<PlaneCalibParams>
|
<PlaneCalibParams>
|
||||||
<CameraCalibParam cameraIndex="1" cameraName="Camera1" isCalibrated="false" planeHeight="-1.0"
|
<CameraCalibParam cameraIndex="1" cameraName="Camera1" isCalibrated="false" planeHeight="-1.0"
|
||||||
|
|||||||
@ -1,41 +1,133 @@
|
|||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "SingleInstanceManager.h"
|
#include "SingleInstanceManager.h"
|
||||||
#include "AuthView.h"
|
#include "AuthView.h"
|
||||||
|
#include "AuthManager.h"
|
||||||
|
#include "HolePitPositionPresenter.h"
|
||||||
|
#include "IYHolePitPositionStatus.h"
|
||||||
|
#include "VrLog.h"
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <cstdio>
|
#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[])
|
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);
|
QApplication a(argc, argv);
|
||||||
|
|
||||||
// 设置应用程序图标
|
|
||||||
a.setWindowIcon(QIcon(":/common/resource/logo.png"));
|
|
||||||
|
|
||||||
// 使用 SingleInstanceManager 检查单例
|
|
||||||
SingleInstanceManager instanceManager("HolePitPositionApp_SingleInstance_Key");
|
SingleInstanceManager instanceManager("HolePitPositionApp_SingleInstance_Key");
|
||||||
|
|
||||||
if (instanceManager.isAnotherInstanceRunning()) {
|
if (hasDisplay) {
|
||||||
// 已有实例在运行,显示提示信息并退出
|
// ---- 有桌面:完整 UI 模式 ----
|
||||||
QMessageBox::information(nullptr,
|
a.setWindowIcon(QIcon(":/common/resource/logo.png"));
|
||||||
QObject::tr("应用程序已运行"),
|
|
||||||
QObject::tr("坑孔定位检测应用程序已经在运行中,请勿重复启动!"),
|
if (instanceManager.isAnotherInstanceRunning()) {
|
||||||
QMessageBox::Ok);
|
QMessageBox::information(nullptr,
|
||||||
return 0;
|
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<IYHolePitPositionStatus>(&listener);
|
||||||
|
presenter.Init();
|
||||||
|
return a.exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查授权,无授权则显示授权对话框
|
|
||||||
if (!AuthView::CheckAndShow(nullptr)) {
|
|
||||||
// 用户取消授权或授权失败,退出程序
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 没有其他实例运行,正常启动应用程序
|
|
||||||
MainWindow w;
|
|
||||||
w.show();
|
|
||||||
|
|
||||||
return a.exec();
|
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@ -5,17 +5,19 @@
|
|||||||
#include "ErrorCodes.h"
|
#include "ErrorCodes.h"
|
||||||
#include "../include/VZNL_Types.h"
|
#include "../include/VZNL_Types.h"
|
||||||
|
|
||||||
// Export macro for Windows DLL
|
// Export macro for shared library
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#ifdef HOLE_DETECTION_EXPORTS
|
#ifdef HOLE_DETECTION_EXPORTS
|
||||||
#define HOLE_DETECTION_API __declspec(dllexport)
|
#define HOLE_DETECTION_API __declspec(dllexport)
|
||||||
#else
|
#else
|
||||||
#define HOLE_DETECTION_API __declspec(dllimport)
|
#define HOLE_DETECTION_API __declspec(dllimport)
|
||||||
#endif
|
#endif
|
||||||
#elif defined(__GNUC__) || defined(__clang__)
|
|
||||||
#define HOLE_DETECTION_API __attribute__((visibility("default")))
|
|
||||||
#else
|
#else
|
||||||
#define HOLE_DETECTION_API
|
#ifdef HOLE_DETECTION_EXPORTS
|
||||||
|
#define HOLE_DETECTION_API __attribute__((visibility("default")))
|
||||||
|
#else
|
||||||
|
#define HOLE_DETECTION_API
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -82,6 +84,16 @@ struct SClusterInfo {
|
|||||||
int count; // Number of points in this cluster
|
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
|
* @brief Debug callbacks for visualization and debugging
|
||||||
*
|
*
|
||||||
@ -149,10 +161,47 @@ struct SHoleDetectionDebugCallbacks {
|
|||||||
void* userData
|
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
|
* @brief User-provided context pointer, passed to all callbacks
|
||||||
*/
|
*/
|
||||||
void* userData;
|
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);
|
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
|
#endif // HOLE_DETECTION_H
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
#define HOLE_DETECTION_PARAMS_H
|
#define HOLE_DETECTION_PARAMS_H
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include "VZNL_Types.h" // 使用 VZNL_Types.h 中定义的类型
|
#include "../include/VZNL_Types.h" // 使用 VZNL_Types.h 中定义的类型
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 孔洞检测算法的检测参数
|
* @brief 孔洞检测算法的检测参数
|
||||||
@ -33,6 +33,9 @@ struct SHoleDetectionParams {
|
|||||||
// 当曲率角未超过 angleThreshold 时,若前后参考点间的
|
// 当曲率角未超过 angleThreshold 时,若前后参考点间的
|
||||||
// 净 Z 坡度角超过此阈值,则将该点判定为下降或上升趋势。
|
// 净 Z 坡度角超过此阈值,则将该点判定为下降或上升趋势。
|
||||||
// 设置为 0 可禁用坡度补充判断。
|
// 设置为 0 可禁用坡度补充判断。
|
||||||
|
float edgeBoundaryFilterDist; // 边缘过滤距离,单位:mm(默认值:0.0,即不过滤)
|
||||||
|
// pair 中任意端点到首/尾有效线(行扫描取 y,列扫描取 x)
|
||||||
|
// 的距离小于此值时,该 pair 将被剔除。
|
||||||
|
|
||||||
// 构造函数,设置默认值
|
// 构造函数,设置默认值
|
||||||
SHoleDetectionParams()
|
SHoleDetectionParams()
|
||||||
@ -51,6 +54,7 @@ struct SHoleDetectionParams {
|
|||||||
, minFeatureSpan(2.0f)
|
, minFeatureSpan(2.0f)
|
||||||
, residualSmoothWindow(5)
|
, residualSmoothWindow(5)
|
||||||
, slopeAngleThreshold(3.0f)
|
, slopeAngleThreshold(3.0f)
|
||||||
|
, edgeBoundaryFilterDist(0.0f)
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -78,6 +82,10 @@ struct SHoleFilterParams {
|
|||||||
float minInlierRatio; // 圆拟合的最小内点比率(默认值:0.0)
|
float minInlierRatio; // 圆拟合的最小内点比率(默认值:0.0)
|
||||||
// 表示落在拟合圆容差范围内的点所占比例。
|
// 表示落在拟合圆容差范围内的点所占比例。
|
||||||
|
|
||||||
|
// 深度验证
|
||||||
|
float minHoleDepth; // 圆孔内部最大 Z 与参考平面 Z 的最小差值,单位:mm(默认值:2.5)
|
||||||
|
// 若差值小于此阈值则认为是假孔,返回失败。<=0 表示不过滤。
|
||||||
|
|
||||||
// 构造函数,设置默认值
|
// 构造函数,设置默认值
|
||||||
SHoleFilterParams()
|
SHoleFilterParams()
|
||||||
: maxEccentricity(0.99995f)
|
: maxEccentricity(0.99995f)
|
||||||
@ -87,6 +95,7 @@ struct SHoleFilterParams {
|
|||||||
, maxPlaneResidual(10.0f)
|
, maxPlaneResidual(10.0f)
|
||||||
, maxAngularGap(90.0f)
|
, maxAngularGap(90.0f)
|
||||||
, minInlierRatio(0.f)
|
, minInlierRatio(0.f)
|
||||||
|
, minHoleDepth(2.5f)
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -282,4 +282,109 @@ int ComputeRowZDiffFluctuation(
|
|||||||
int* errCode
|
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
|
#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