756 lines
31 KiB
C++
Raw Normal View History

2026-07-11 16:02:57 +08:00
#include "DetectPresenter.h"
#include <algorithm>
2026-07-14 17:14:55 +08:00
#include <cmath>
2026-07-20 11:58:49 +08:00
#include <utility>
#include <vector>
2026-07-14 17:14:55 +08:00
#include "AapgsModelClassifier.h"
2026-07-30 12:06:02 +08:00
#include "PersionModelDetector.h"
2026-07-20 11:58:49 +08:00
#include "SG_errCode.h"
#include "planeLocalization_Export.h"
namespace
{
struct PlaneLocalizationResult
{
SSX_planeInfo info{};
int errorCode = 0;
QString errorMessage;
};
bool IsFinitePoint(const SVzNL3DPoint& point)
{
return std::isfinite(point.x) &&
std::isfinite(point.y) &&
std::isfinite(point.z);
}
2026-07-30 12:06:02 +08:00
bool IsPersonDetection(const PersionModelDetector::Detection& detection)
{
const QString label = detection.label.trimmed();
return label.compare(QStringLiteral("person"), Qt::CaseInsensitive) == 0 ||
(label.isEmpty() && detection.classId == 0);
}
2026-07-20 11:58:49 +08:00
bool ConvertCloudToScanLines(
const RsCloudData& cloud,
std::vector<std::vector<SVzNL3DPosition>>& scanLines,
QString& errorMessage)
{
scanLines.clear();
errorMessage.clear();
scanLines.reserve(cloud.size());
size_t validPointCount = 0;
for (const auto& item : cloud) {
const EVzResultDataType dataType = item.first;
const SVzLaserLineData& sourceLine = item.second;
if (sourceLine.nPointCount < 0 ||
(sourceLine.nPointCount > 0 && !sourceLine.p3DPoint)) {
errorMessage = QStringLiteral("点云包含无效扫描线");
return false;
}
if (dataType != keResultDataType_PointXYZI &&
dataType != keResultDataType_Position) {
errorMessage = QStringLiteral("停机算法不支持点云类型:%1")
.arg(static_cast<int>(dataType));
return false;
}
std::vector<SVzNL3DPosition> targetLine;
targetLine.resize(static_cast<size_t>(sourceLine.nPointCount));
if (dataType == keResultDataType_PointXYZI) {
const auto* points =
static_cast<const SVzNLPointXYZI*>(sourceLine.p3DPoint);
for (int pointIndex = 0;
pointIndex < sourceLine.nPointCount;
++pointIndex) {
SVzNL3DPosition& target =
targetLine[static_cast<size_t>(pointIndex)];
target.nPointIdx = pointIndex;
target.pt3D.x = static_cast<double>(points[pointIndex].x);
target.pt3D.y = static_cast<double>(points[pointIndex].y);
target.pt3D.z = static_cast<double>(points[pointIndex].z);
if (!IsFinitePoint(target.pt3D)) {
target.pt3D = { 0.0, 0.0, 0.0 };
} else if (std::fabs(target.pt3D.x) > 1e-6 ||
std::fabs(target.pt3D.y) > 1e-6 ||
std::fabs(target.pt3D.z) > 1e-6) {
++validPointCount;
}
}
} else {
const auto* points =
static_cast<const SVzNL3DPosition*>(sourceLine.p3DPoint);
for (int pointIndex = 0;
pointIndex < sourceLine.nPointCount;
++pointIndex) {
targetLine[static_cast<size_t>(pointIndex)] = points[pointIndex];
SVzNL3DPoint& point =
targetLine[static_cast<size_t>(pointIndex)].pt3D;
if (!IsFinitePoint(point)) {
point = { 0.0, 0.0, 0.0 };
} else if (std::fabs(point.x) > 1e-6 ||
std::fabs(point.y) > 1e-6 ||
std::fabs(point.z) > 1e-6) {
++validPointCount;
}
}
}
scanLines.push_back(std::move(targetLine));
}
if (scanLines.empty() || validPointCount == 0) {
errorMessage = QStringLiteral("点云不包含有效三维点");
return false;
}
return true;
}
SSX_planeParkingParam BuildParkingParam(const VrPlaneParkingParam& source)
{
SSX_planeParkingParam target{};
target.parkingPoint = { source.parkingPointX,
source.parkingPointY,
source.parkingPointZ };
target.guideLinePoint = { source.guideLinePointX,
source.guideLinePointY,
source.guideLinePointZ };
target.guidingRange = source.guidingRange;
target.parkingRange = source.parkingRange;
target.distFromNoseToWheel = source.distFromNoseToWheel;
return target;
}
SSG_planeCalibPara BuildGroundCalibrationParam(
const VrPlaneGroundCalibrationParam& source)
{
SSG_planeCalibPara target{};
for (int i = 0; i < 9; ++i) {
target.planeCalib[i] = source.planeCalib[i];
target.invRMatrix[i] = source.invRMatrix[i];
}
target.planeHeight = source.planeHeight;
return target;
}
SSG_treeGrowParam BuildTreeGrowParam(const VrPlaneTreeGrowParam& source)
{
SSG_treeGrowParam target{};
target.yDeviation_max = source.yDeviationMax;
target.zDeviation_max = source.zDeviationMax;
target.maxLineSkipNum = source.maxLineSkipNum;
target.maxSkipDistance = source.maxSkipDistance;
target.minLTypeTreeLen = source.minLTypeTreeLen;
target.minVTypeTreeLen = source.minVTypeTreeLen;
return target;
}
PlaneLocalizationResult LocalizePlane(const RsCloudData& cloud,
const VrAlgorithmParams& algorithmParams)
{
PlaneLocalizationResult result;
std::vector<std::vector<SVzNL3DPosition>> scanLines;
if (!ConvertCloudToScanLines(cloud, scanLines, result.errorMessage)) {
result.errorCode = cloud.empty() ? SG_ERR_3D_DATA_NULL
: SG_ERR_3D_DATA_INVLD;
return result;
}
std::vector<std::vector<SVzNL3DPosition>> debugData;
int algorithmError = 0;
result.info = wd_planeLocalization(
scanLines,
BuildGroundCalibrationParam(algorithmParams.groundCalibrationParam),
BuildParkingParam(algorithmParams.planeParkingParam),
BuildTreeGrowParam(algorithmParams.treeGrowParam),
debugData,
&algorithmError);
result.errorCode = algorithmError;
if (algorithmError == SX_ERR_NO_PLANE_FOUND) {
result.errorMessage = QStringLiteral("未找到有效飞机目标");
} else if (algorithmError == SX_ERR_NOSEPOINT_FAIL) {
result.errorMessage = QStringLiteral("飞机机鼻定位失败");
} else if (algorithmError == SX_ERR_ENDINE_FAIL) {
result.errorMessage = QStringLiteral("飞机主体特征提取失败");
} else if (algorithmError != 0) {
result.errorMessage = QStringLiteral("飞机定位算法错误:%1")
.arg(algorithmError);
} else if (!std::isfinite(result.info.distance) ||
!std::isfinite(result.info.deviation) ||
!std::isfinite(result.info.dirAngle_deg) ||
!IsFinitePoint(result.info.nosePoint) ||
!IsFinitePoint(result.info.axis)) {
result.errorCode = SG_ERR_3D_DATA_INVLD;
result.errorMessage = QStringLiteral("飞机定位算法返回非有限数值");
}
return result;
}
bool IsPlaneLostError(int errorCode)
{
return errorCode == SX_ERR_NO_PLANE_FOUND ||
errorCode == SX_ERR_NOSEPOINT_FAIL ||
errorCode == SX_ERR_ENDINE_FAIL ||
errorCode == SG_ERR_3D_DATA_NULL;
}
QString GuideText(ParkingGuideState state)
{
switch (state) {
case ParkingGuideState::DockingStarted: return QStringLiteral("START");
case ParkingGuideState::Capturing: return QStringLiteral("CAPTURE");
case ParkingGuideState::Tracking: return QStringLiteral("TRACKING");
case ParkingGuideState::ApproachRate: return QStringLiteral("APPROACH");
case ParkingGuideState::CenterLineAligned: return QStringLiteral("CENTER");
case ParkingGuideState::Slow: return QStringLiteral("SLOW");
case ParkingGuideState::AzimuthGuidance: return QStringLiteral("AZIMUTH");
case ParkingGuideState::StopPositionReached: return QStringLiteral("STOP");
case ParkingGuideState::DockingCompleted: return QStringLiteral("OK");
2026-07-30 12:06:02 +08:00
case ParkingGuideState::Overshot: return QStringLiteral("TOOFAR");
2026-07-20 11:58:49 +08:00
case ParkingGuideState::StoppedShort: return QStringLiteral("STOP SHORT");
case ParkingGuideState::SlowAircraftLost: return QStringLiteral("SLOW+LOST");
2026-07-30 12:06:02 +08:00
case ParkingGuideState::AircraftVerificationFailed:
return QStringLiteral("STOP+IDFAIL");
case ParkingGuideState::TooFast: return QStringLiteral("STOPTOOFAST");
case ParkingGuideState::EmergencyStop: return QStringLiteral("STOP");
case ParkingGuideState::ChocksOn: return QStringLiteral("CHOCK ON");
2026-07-20 11:58:49 +08:00
case ParkingGuideState::SystemError: return QStringLiteral("ERROR");
default: return QStringLiteral("WAIT");
}
}
void SetGuideState(ParkingSpaceGuideInfo& info, ParkingGuideState state)
{
info.guideStateCode = static_cast<int>(state);
info.guideText = GuideText(state);
}
ParkingSpaceGuideInfo MakeLastKnownInfo(
const ParkingGuideAlgorithmState& state)
{
ParkingSpaceGuideInfo info;
2026-07-30 12:06:02 +08:00
const QString selectedModelType = state.selectedModelType.trimmed();
const QString recognizedModelType = state.recognizedModelType.trimmed();
info.expectedModelType = selectedModelType;
info.modelType = recognizedModelType.isEmpty()
? QStringLiteral("未知")
: recognizedModelType;
info.modelMatched = state.modelVerified;
info.modelVerificationSupported = state.modelVerificationSupported;
info.personInChockRegion = state.personInChockRegion;
info.chocksConfirmed = state.chocksConfirmed;
info.personCount = state.personCount;
info.personConfidence = state.personConfidence;
2026-07-20 11:58:49 +08:00
info.distance = state.lastDistance;
info.lateralOffset = state.lastDeviation;
info.angle = state.lastAngle;
info.aircraftSpeed = state.filteredApproachSpeed / 1000.0;
2026-07-30 12:06:02 +08:00
info.confidence = std::isfinite(state.modelConfidence)
? state.modelConfidence
: 0.0;
2026-07-20 11:58:49 +08:00
return info;
}
}
DetectPresenter::DetectPresenter()
: m_modelClassifier(std::make_unique<AapgsModelClassifier>())
2026-07-30 12:06:02 +08:00
, m_personDetector(std::make_unique<PersionModelDetector>())
{
}
DetectPresenter::~DetectPresenter() = default;
2026-07-14 17:14:55 +08:00
bool ModelRecognitionResult::IsVerified(double minimumConfidence) const
{
if (!std::isfinite(minimumConfidence)) {
return false;
}
const QString normalizedModel = modelType.trimmed();
const bool isUnknown = normalizedModel == QStringLiteral("未知") ||
normalizedModel.compare(QStringLiteral("unknown"), Qt::CaseInsensitive) == 0;
const double threshold = (std::max)(0.0, minimumConfidence);
return verified &&
!normalizedModel.isEmpty() &&
!isUnknown &&
std::isfinite(confidence) &&
confidence >= threshold;
}
2026-07-11 16:02:57 +08:00
QString DetectPresenter::GetAlgoVersion()
{
2026-07-20 11:58:49 +08:00
const char* planeVersion = wd_PlaneLocalizationVersion();
2026-07-30 12:06:02 +08:00
return QStringLiteral(
"planeLocalization %1 / AAPGS_model 1.0.0 / Persion_model 1.0.0")
2026-07-20 11:58:49 +08:00
.arg(planeVersion ? QString::fromLocal8Bit(planeVersion)
: QStringLiteral("未知"));
2026-07-11 16:02:57 +08:00
}
2026-07-14 17:14:55 +08:00
int DetectPresenter::DetectAirplanePresence(const RsCloudData& cloud,
const VrAlgorithmParams& algorithmParams,
AirplanePresenceResult& result)
{
result = AirplanePresenceResult();
2026-07-20 11:58:49 +08:00
const PlaneLocalizationResult localization =
LocalizePlane(cloud, algorithmParams);
if (localization.errorCode == 0) {
result.state = AirplanePresenceState::Detected;
result.message = QStringLiteral("检测到飞机,距离停机点 %1 mm")
.arg(localization.info.distance, 0, 'f', 1);
return 0;
}
if (IsPlaneLostError(localization.errorCode)) {
result.state = AirplanePresenceState::NotDetected;
result.message = localization.errorMessage.trimmed().isEmpty()
? QStringLiteral("未检测到飞机")
: localization.errorMessage;
2026-07-14 17:14:55 +08:00
return 0;
}
2026-07-20 11:58:49 +08:00
result.state = AirplanePresenceState::ViewBlocked;
result.message = localization.errorMessage.trimmed().isEmpty()
? QStringLiteral("飞机检测视野或点云异常")
: localization.errorMessage;
2026-07-14 17:14:55 +08:00
return 0;
}
int DetectPresenter::DetectParkingSpaceGuide(const RsCloudData& cloud,
2026-07-11 16:02:57 +08:00
const VrAlgorithmParams& algorithmParams,
2026-07-20 11:58:49 +08:00
qint64 frameTimestampMs,
2026-07-14 17:14:55 +08:00
const ParkingGuideAlgorithmState& previousState,
DetectionResult& result,
ParkingGuideAlgorithmControl& control)
2026-07-11 16:02:57 +08:00
{
result = DetectionResult();
2026-07-14 17:14:55 +08:00
control = ParkingGuideAlgorithmControl();
control.nextState = previousState;
2026-07-11 16:02:57 +08:00
result.cameraIndex = 1;
result.errorCode = 0;
2026-07-20 11:58:49 +08:00
const VrGuideDecisionParam& guideParam = algorithmParams.guideDecisionParam;
const VrParkingProcessParam& processParam = algorithmParams.processParam;
const VrModelRecognitionParam& modelParam =
algorithmParams.modelRecognitionParam;
const PlaneLocalizationResult localization =
LocalizePlane(cloud, algorithmParams);
2026-07-30 12:06:02 +08:00
control.planeLocalizationErrorCode = localization.errorCode;
control.planeLocalizationMessage = localization.errorMessage;
2026-07-20 11:58:49 +08:00
if (localization.errorCode != 0) {
ParkingGuideAlgorithmState& next = control.nextState;
2026-07-30 12:06:02 +08:00
next.errorFrameCount =
(std::max)(0, previousState.errorFrameCount) + 1;
2026-07-20 11:58:49 +08:00
const bool planeLost = IsPlaneLostError(localization.errorCode);
2026-07-30 12:06:02 +08:00
next.lostFrameCount = planeLost
? (std::max)(0, previousState.lostFrameCount) + 1
: 0;
ParkingSpaceGuideInfo errorInfo = MakeLastKnownInfo(previousState);
const int errorThreshold =
(std::max)(1, processParam.errorFrameThreshold);
if (next.errorFrameCount < errorThreshold) {
const ParkingGuideState retainedState = previousState.hasMeasurement
? ParkingGuideStateFromCode(previousState.lastGuideStateCode)
: ParkingGuideState::Waiting;
SetGuideState(errorInfo,
retainedState == ParkingGuideState::Unknown
? ParkingGuideState::Waiting
: retainedState);
result.message = previousState.hasMeasurement
? QStringLiteral(
"距离 %1 mm横向偏差 %2 mm航向角 %3°接近速度 %4 m/s")
.arg(errorInfo.distance, 0, 'f', 1)
.arg(errorInfo.lateralOffset, 0, 'f', 1)
.arg(errorInfo.angle, 0, 'f', 2)
.arg(errorInfo.aircraftSpeed, 0, 'f', 1)
: QStringLiteral("等待飞机定位");
2026-07-20 11:58:49 +08:00
} else {
2026-07-30 12:06:02 +08:00
SetGuideState(errorInfo,
planeLost
? ParkingGuideState::SlowAircraftLost
: ParkingGuideState::SystemError);
errorInfo.hasException = true;
2026-07-20 11:58:49 +08:00
result.errorCode = localization.errorCode;
2026-07-30 12:06:02 +08:00
next.stoppedFrameCount = 0;
next.stoppedShortFrameCount = 0;
next.stopPositionReached = false;
next.dockingCompleted = false;
next.completedHoldFrameCount = 0;
result.message = localization.errorMessage.trimmed().isEmpty()
? QStringLiteral("停机引导定位失败")
: localization.errorMessage;
2026-07-20 11:58:49 +08:00
}
2026-07-30 12:06:02 +08:00
result.parkingSpaceInfoList.push_back(errorInfo);
2026-07-20 11:58:49 +08:00
return 0;
}
2026-07-30 12:06:02 +08:00
if (previousState.hasMeasurement) {
const double distanceChange = std::fabs(
localization.info.distance - previousState.lastDistance);
const double lateralOffsetChange = std::fabs(
localization.info.deviation - previousState.lastDeviation);
const double distanceThreshold = (std::max)(
0.0, processParam.distanceChangeThreshold);
const double lateralOffsetThreshold = (std::max)(
0.0, processParam.lateralOffsetChangeThreshold);
if (distanceChange > distanceThreshold ||
lateralOffsetChange > lateralOffsetThreshold) {
ParkingSpaceGuideInfo retainedInfo =
MakeLastKnownInfo(previousState);
const ParkingGuideState retainedState =
ParkingGuideStateFromCode(previousState.lastGuideStateCode);
SetGuideState(retainedInfo,
retainedState == ParkingGuideState::Unknown
? ParkingGuideState::Waiting
: retainedState);
result.parkingSpaceInfoList.push_back(retainedInfo);
result.message = QStringLiteral(
"检测数据突变已忽略:距离变化 %1 mm阈值 %2 mm%3横向偏移变化 %4 mm阈值 %5 mm%6")
.arg(distanceChange, 0, 'f', 1)
.arg(distanceThreshold, 0, 'f', 1)
.arg(distanceChange > distanceThreshold
? QStringLiteral("超阈值")
: QStringLiteral("未超阈值"))
.arg(lateralOffsetChange, 0, 'f', 1)
.arg(lateralOffsetThreshold, 0, 'f', 1)
.arg(lateralOffsetChange > lateralOffsetThreshold
? QStringLiteral("超阈值")
: QStringLiteral("未超阈值"));
return 0;
}
}
2026-07-20 11:58:49 +08:00
ParkingGuideAlgorithmState& next = control.nextState;
2026-07-30 12:06:02 +08:00
next.errorFrameCount = 0;
2026-07-20 11:58:49 +08:00
next.lostFrameCount = 0;
next.successfulFrameCount =
(std::max)(0, previousState.successfulFrameCount) + 1;
double approachSpeed = previousState.filteredApproachSpeed;
if (previousState.hasMeasurement &&
2026-07-30 12:06:02 +08:00
previousState.errorFrameCount == 0 &&
2026-07-20 11:58:49 +08:00
frameTimestampMs > previousState.lastTimestampMs) {
const double elapsedSeconds =
static_cast<double>(frameTimestampMs - previousState.lastTimestampMs) /
1000.0;
const double rawApproachSpeed =
(previousState.lastDistance - localization.info.distance) /
elapsedSeconds;
const double alpha = std::clamp(processParam.speedFilterAlpha,
0.0,
1.0);
approachSpeed = alpha * rawApproachSpeed +
(1.0 - alpha) * previousState.filteredApproachSpeed;
} else if (!previousState.hasMeasurement ||
2026-07-30 12:06:02 +08:00
previousState.errorFrameCount > 0) {
2026-07-20 11:58:49 +08:00
approachSpeed = 0.0;
}
if (!std::isfinite(approachSpeed)) {
approachSpeed = 0.0;
}
next.hasMeasurement = true;
next.lastTimestampMs = frameTimestampMs;
next.lastDistance = localization.info.distance;
next.lastDeviation = localization.info.deviation;
next.lastAngle = localization.info.dirAngle_deg;
next.lastNoseX = localization.info.nosePoint.x;
next.lastNoseY = localization.info.nosePoint.y;
next.lastNoseZ = localization.info.nosePoint.z;
next.filteredApproachSpeed = approachSpeed;
2026-07-11 16:02:57 +08:00
ParkingSpaceGuidePosition position;
2026-07-20 11:58:49 +08:00
position.x = localization.info.nosePoint.x;
position.y = localization.info.nosePoint.y;
position.z = localization.info.nosePoint.z;
2026-07-11 16:02:57 +08:00
position.roll = 0.0;
position.pitch = 0.0;
2026-07-20 11:58:49 +08:00
position.yaw = localization.info.dirAngle_deg;
2026-07-11 16:02:57 +08:00
result.positions.push_back(position);
2026-07-20 11:58:49 +08:00
ParkingSpaceGuideInfo info = MakeLastKnownInfo(next);
2026-07-11 16:02:57 +08:00
info.hasException = false;
2026-07-20 11:58:49 +08:00
const double stopTolerance =
(std::max)(0.0, processParam.stopDistanceTolerance);
2026-07-30 12:06:02 +08:00
const double overshootDistance =
(std::max)(stopTolerance, processParam.overshootDistance);
const double slowDistance =
(std::max)(stopTolerance, processParam.slowDistance);
const double approachStartDistance =
(std::max)(slowDistance, processParam.approachStartDistance);
const double captureStartDistance =
(std::max)(approachStartDistance,
processParam.captureStartDistance);
const double dockingStartDistance =
(std::max)(captureStartDistance,
processParam.dockingStartDistance);
2026-07-20 11:58:49 +08:00
const double stoppedSpeed =
(std::max)(0.0, processParam.stoppedSpeedThreshold);
const double maxApproachSpeed =
(std::max)(0.0, processParam.maxApproachSpeed);
const bool hasSpeedBaseline = previousState.hasMeasurement &&
2026-07-30 12:06:02 +08:00
previousState.errorFrameCount == 0;
2026-07-20 11:58:49 +08:00
const bool isStopped = hasSpeedBaseline &&
std::fabs(approachSpeed) <= stoppedSpeed;
const bool isAtStopPosition =
std::fabs(localization.info.distance) <= stopTolerance;
ParkingGuideState guideState = ParkingGuideState::Tracking;
2026-07-30 12:06:02 +08:00
// Overshoot must win over a previously reached stop position. The
// completion latch is only valid while the aircraft remains in the stop
// tolerance; otherwise the process must resume guidance.
if (localization.info.distance <= -overshootDistance) {
2026-07-20 11:58:49 +08:00
next.stopPositionReached = false;
next.dockingCompleted = false;
next.stoppedFrameCount = 0;
next.stoppedShortFrameCount = 0;
2026-07-30 12:06:02 +08:00
next.completedHoldFrameCount = 0;
2026-07-20 11:58:49 +08:00
next.centerLineAligned = false;
guideState = ParkingGuideState::Overshot;
2026-07-30 12:06:02 +08:00
} else if (previousState.dockingCompleted &&
isAtStopPosition &&
isStopped) {
next.dockingCompleted = true;
2026-07-20 11:58:49 +08:00
guideState = ParkingGuideState::DockingCompleted;
} else if (isAtStopPosition) {
2026-07-30 12:06:02 +08:00
next.dockingCompleted = false;
2026-07-20 11:58:49 +08:00
next.stoppedShortFrameCount = 0;
if (!previousState.stopPositionReached) {
next.stopPositionReached = true;
next.completedHoldFrameCount = 0;
}
if (isStopped) {
next.stoppedFrameCount =
(std::max)(0, previousState.stoppedFrameCount) + 1;
} else {
next.stoppedFrameCount = 0;
}
next.completedHoldFrameCount =
(std::max)(0, previousState.completedHoldFrameCount) + 1;
const int stableFrames = (std::max)(1, processParam.stopStableFrames);
const int completedFrames =
(std::max)(1, processParam.completedHoldFrames);
if (next.stoppedFrameCount >= stableFrames &&
next.completedHoldFrameCount >= completedFrames) {
next.dockingCompleted = true;
guideState = ParkingGuideState::DockingCompleted;
} else {
guideState = ParkingGuideState::StopPositionReached;
}
} else {
next.stopPositionReached = false;
2026-07-30 12:06:02 +08:00
next.dockingCompleted = false;
2026-07-20 11:58:49 +08:00
next.stoppedFrameCount = 0;
2026-07-30 12:06:02 +08:00
next.stoppedShortFrameCount = 0;
next.centerLineAligned = false;
2026-07-20 11:58:49 +08:00
next.completedHoldFrameCount = 0;
const double stoppedShortDistance =
(std::max)(stopTolerance, processParam.stoppedShortMinDistance);
if (previousState.hasMeasurement &&
next.successfulFrameCount > 4 &&
isStopped &&
localization.info.distance >= stoppedShortDistance) {
next.stoppedShortFrameCount =
(std::max)(0, previousState.stoppedShortFrameCount) + 1;
} else {
next.stoppedShortFrameCount = 0;
}
if (next.stoppedShortFrameCount >=
(std::max)(1, processParam.stoppedShortStableFrames)) {
guideState = ParkingGuideState::StoppedShort;
2026-07-30 12:06:02 +08:00
} else if (localization.info.distance >= 0.0 &&
localization.info.distance <= slowDistance &&
hasSpeedBaseline &&
maxApproachSpeed > 0.0 &&
approachSpeed >= maxApproachSpeed) {
guideState = ParkingGuideState::Slow;
} else if (localization.info.distance > slowDistance &&
hasSpeedBaseline &&
maxApproachSpeed > 0.0 &&
approachSpeed >= maxApproachSpeed) {
guideState = ParkingGuideState::TooFast;
} else if (localization.info.distance >= 0.0 &&
localization.info.distance <= approachStartDistance &&
(std::fabs(localization.info.deviation) >
2026-07-20 11:58:49 +08:00
(std::max)(0.0, guideParam.lateralTolerance) ||
std::fabs(localization.info.dirAngle_deg) >
2026-07-30 12:06:02 +08:00
(std::max)(0.0, guideParam.angleTolerance))) {
2026-07-20 11:58:49 +08:00
next.centerLineAligned = false;
guideState = ParkingGuideState::AzimuthGuidance;
2026-07-30 12:06:02 +08:00
} else if (localization.info.distance >= 0.0 &&
localization.info.distance <= approachStartDistance) {
2026-07-20 11:58:49 +08:00
next.centerLineAligned = true;
guideState = ParkingGuideState::CenterLineAligned;
2026-07-30 12:06:02 +08:00
} else if (localization.info.distance <= captureStartDistance &&
localization.info.distance > approachStartDistance) {
next.centerLineAligned = false;
guideState = ParkingGuideState::Capturing;
} else if (localization.info.distance <= dockingStartDistance &&
localization.info.distance > captureStartDistance) {
next.centerLineAligned = false;
guideState = ParkingGuideState::DockingStarted;
} else if (localization.info.distance > dockingStartDistance) {
next.centerLineAligned = false;
guideState = ParkingGuideState::Waiting;
2026-07-20 11:58:49 +08:00
} else {
2026-07-30 12:06:02 +08:00
next.centerLineAligned = false;
2026-07-20 11:58:49 +08:00
guideState = ParkingGuideState::Tracking;
}
}
SetGuideState(info, guideState);
next.lastGuideStateCode = info.guideStateCode;
2026-07-11 16:02:57 +08:00
result.parkingSpaceInfoList.push_back(info);
2026-07-20 11:58:49 +08:00
result.message = QStringLiteral(
"距离 %1 mm横向偏差 %2 mm航向角 %3°接近速度 %4 m/s")
.arg(info.distance, 0, 'f', 1)
.arg(info.lateralOffset, 0, 'f', 1)
.arg(info.angle, 0, 'f', 2)
.arg(info.aircraftSpeed, 0, 'f', 1);
2026-07-11 16:02:57 +08:00
2026-07-20 11:58:49 +08:00
const double modelVerifyDistance = modelParam.modelVerifyDistance;
const int maxRecognitionAttempts =
(std::max)(1, modelParam.maxRecognitionAttempts);
2026-07-14 17:14:55 +08:00
const bool isInModelVerifyRange = modelVerifyDistance >= 0.0 &&
info.distance >= 0.0 &&
info.distance <= modelVerifyDistance;
2026-07-30 12:06:02 +08:00
const ParkingGuideState measuredState =
ParkingGuideStateFromCode(info.guideStateCode);
const bool mustPreserveMeasuredState =
measuredState == ParkingGuideState::Overshot;
const bool hasSelectedModel =
!previousState.selectedModelType.trimmed().isEmpty();
const bool canAttemptRecognition =
!hasSelectedModel ||
previousState.modelRecognitionAttempts < maxRecognitionAttempts;
2026-07-14 17:14:55 +08:00
if (isInModelVerifyRange &&
2026-07-30 12:06:02 +08:00
!previousState.modelRecognitionComplete &&
!previousState.modelRecognitionInProgress &&
canAttemptRecognition) {
2026-07-14 17:14:55 +08:00
control.needModelRecognition = true;
2026-07-30 12:06:02 +08:00
control.recognitionContext =
previousState.selectedModelType.trimmed().toUtf8();
} else if (isInModelVerifyRange &&
!previousState.modelRecognitionComplete &&
!previousState.modelRecognitionInProgress &&
hasSelectedModel &&
!mustPreserveMeasuredState) {
2026-07-14 17:14:55 +08:00
ParkingSpaceGuideInfo& failedInfo = result.parkingSpaceInfoList.back();
failedInfo.hasException = true;
failedInfo.guideStateCode =
static_cast<int>(ParkingGuideState::AircraftVerificationFailed);
2026-07-30 12:06:02 +08:00
failedInfo.guideText =
GuideText(ParkingGuideState::AircraftVerificationFailed);
control.nextState.lastGuideStateCode = failedInfo.guideStateCode;
2026-07-14 17:14:55 +08:00
result.message = QStringLiteral("机型二次验证失败");
}
2026-07-11 16:02:57 +08:00
return 0;
}
2026-07-14 17:14:55 +08:00
int DetectPresenter::RecognizeModel2D(const QImage& frame,
const QByteArray& recognitionContext,
ModelRecognitionResult& result)
{
result = ModelRecognitionResult();
if (!m_modelClassifier) {
result.message = QStringLiteral("AAPGS机型识别器未初始化");
return -1;
}
AapgsModelClassifier::Classification classification;
QString errorMessage;
if (!m_modelClassifier->Classify(frame, classification, errorMessage)) {
result.message = errorMessage.trimmed().isEmpty()
? QStringLiteral("AAPGS机型识别失败")
: errorMessage.trimmed();
return -1;
}
2026-07-30 12:06:02 +08:00
result.recognitionComplete = true;
result.verificationSupported = true;
result.modelType = classification.modelType;
result.confidence = classification.confidence;
2026-07-30 12:06:02 +08:00
const QString expectedModelType =
QString::fromUtf8(recognitionContext).trimmed();
const QString recognizedModelType = result.modelType.trimmed();
if (expectedModelType.isEmpty()) {
result.message = QStringLiteral("自动识别机型:%1")
.arg(recognizedModelType);
return 0;
}
if (recognizedModelType.compare(expectedModelType,
Qt::CaseInsensitive) != 0) {
result.message = QStringLiteral("机型验证不一致:选择%1识别%2")
.arg(expectedModelType,
recognizedModelType.isEmpty()
? QStringLiteral("未知")
: recognizedModelType);
return 0;
}
result.verified = true;
return 0;
2026-07-14 17:14:55 +08:00
}
2026-07-30 12:06:02 +08:00
int DetectPresenter::DetectPersonInRegion2D(
const QImage& frame,
const VrPersonDetectionParam& personParam,
PersonDetectionResult& result)
{
result = PersonDetectionResult();
if (!m_personDetector || frame.isNull()) {
result.message = QStringLiteral("人员检测图像或检测器不可用");
return -1;
}
const double imageWidth = static_cast<double>(frame.width());
const double imageHeight = static_cast<double>(frame.height());
// The caller has already cropped the configured chock ROI. Detection and
// result coordinates therefore use the cropped image as their full area.
result.roi = QRectF(0.0, 0.0, imageWidth, imageHeight);
PersionModelDetector::Analysis analysis;
QString errorMessage;
if (!m_personDetector->Analyze(frame, analysis, errorMessage)) {
result.message = errorMessage.trimmed().isEmpty()
? QStringLiteral("Persion人员检测失败")
: errorMessage.trimmed();
return -1;
}
const double threshold = std::clamp(personParam.confidenceThreshold,
0.0,
1.0);
for (const PersionModelDetector::Detection& detection : analysis.detections) {
if (!IsPersonDetection(detection) ||
detection.confidence < threshold) {
continue;
}
const QRectF box = detection.boundingBox.intersected(
QRectF(0.0, 0.0, imageWidth, imageHeight));
if (box.isEmpty()) {
continue;
}
result.personBoxes.push_back(box);
++result.personCount;
result.confidence = (std::max)(result.confidence,
detection.confidence);
}
result.personInRegion = result.personCount > 0;
result.message = result.personInRegion
? QStringLiteral("轮挡区域检测到人员")
: QStringLiteral("等待人员进入轮挡区域");
return 0;
}