#include "AapgsModelClassifier.h" #include #include #include #include #include #include #include #include #include #include "yolo_runtime.h" namespace { const char kModelConfigRelativePath[] = "models/primary/model.yaml"; const char kModelConfigFileName[] = "model.yaml"; void AppendUniquePath(QStringList& paths, const QString& path) { const QString cleanPath = QDir::cleanPath(path.trimmed()); if (!cleanPath.isEmpty() && !paths.contains(cleanPath)) { paths.push_back(cleanPath); } } QString EnvironmentPath(const char* name) { return QString::fromLocal8Bit(qgetenv(name)).trimmed(); } QString SourcePackageRoot() { QDir sourceDir(QFileInfo(QString::fromUtf8(__FILE__)).absolutePath()); #ifdef Q_OS_WIN const QString packageDirectory = QStringLiteral("Windows"); #else const QString packageDirectory = QStringLiteral("Arm"); #endif return QDir::cleanPath(sourceDir.absoluteFilePath( QStringLiteral("../../../../../AppAlgo/AAPGS_model/%1") .arg(packageDirectory))); } QString PackageRootForConfig(const QString& configPath) { QDir directory(QFileInfo(configPath).absolutePath()); for (int level = 0; level < 5; ++level) { const bool hasRuntime = QFileInfo::exists(directory.filePath( QStringLiteral("lib/libyolo_runtime.so.5"))) || QFileInfo::exists(directory.filePath( QStringLiteral("lib/yolo_runtime.dll"))); if (hasRuntime) { return directory.absolutePath(); } if (!directory.cdUp()) { break; } } return QFileInfo(configPath).absolutePath(); } bool FindModelConfig(QString& configPath, QString& packageRoot, QString& errorMessage) { const QString configuredPath = EnvironmentPath("AAPGS_MODEL_CONFIG"); if (!configuredPath.isEmpty()) { QFileInfo configuredFile(configuredPath); if (configuredFile.isDir()) { const QDir configuredDir(configuredFile.absoluteFilePath()); const QFileInfo directConfig(configuredDir.filePath( QString::fromLatin1(kModelConfigFileName))); configuredFile.setFile(directConfig.isFile() ? directConfig.absoluteFilePath() : configuredDir.filePath(QString::fromLatin1( kModelConfigRelativePath))); } if (!configuredFile.isFile()) { errorMessage = QStringLiteral("AAPGS模型配置不存在:%1") .arg(configuredPath); return false; } configPath = configuredFile.absoluteFilePath(); packageRoot = PackageRootForConfig(configPath); return true; } QStringList roots; const QString configuredRoot = EnvironmentPath("AAPGS_MODEL_ROOT"); if (!configuredRoot.isEmpty()) { AppendUniquePath(roots, configuredRoot); } const QString applicationDir = QCoreApplication::applicationDirPath(); AppendUniquePath(roots, QDir(applicationDir).filePath(QStringLiteral("AAPGS_model"))); AppendUniquePath(roots, QDir(applicationDir).filePath(QStringLiteral("aapgs"))); AppendUniquePath(roots, QDir(applicationDir).filePath(QStringLiteral("../AAPGS_model"))); AppendUniquePath(roots, QDir::current().filePath(QStringLiteral("AAPGS_model"))); AppendUniquePath(roots, QDir::current().filePath(QStringLiteral("AppAlgo/AAPGS_model"))); AppendUniquePath(roots, SourcePackageRoot()); AppendUniquePath(roots, QStringLiteral("/opt/rk3588-ai/AAPGS_model")); AppendUniquePath(roots, QStringLiteral("/usr/lib/AAPGS_model")); AppendUniquePath(roots, QStringLiteral("/usr/local/lib/AAPGS_model")); for (const QString& root : roots) { const QFileInfo candidate(QDir(root).filePath( QString::fromLatin1(kModelConfigRelativePath))); if (candidate.isFile()) { configPath = candidate.absoluteFilePath(); packageRoot = QDir(root).absolutePath(); return true; } } errorMessage = QStringLiteral( "未找到AAPGS机型分类配置,请部署到" "/opt/rk3588-ai/AAPGS_model/" "models/primary/model.yaml," "或设置AAPGS_MODEL_CONFIG"); return false; } QString ModelName(const yolo_result_common_v1_t& item, QString& errorMessage) { int length = 0; while (length < YOLO_LABEL_LENGTH && item.label[length] != '\0') { ++length; } const QString label = QString::fromUtf8(item.label, length).trimmed(); if (label.compare(QStringLiteral("a320"), Qt::CaseInsensitive) == 0 || (label.isEmpty() && item.class_id == 0)) { return QStringLiteral("A320"); } if (label.compare(QStringLiteral("b737"), Qt::CaseInsensitive) == 0 || (label.isEmpty() && item.class_id == 1)) { return QStringLiteral("B737"); } if (label.compare(QStringLiteral("other"), Qt::CaseInsensitive) == 0 || label == QStringLiteral("其他") || (label.isEmpty() && item.class_id == 2)) { return QStringLiteral("其他"); } errorMessage = label.isEmpty() ? QStringLiteral("AAPGS返回未知机型类别:%1").arg(item.class_id) : QStringLiteral("AAPGS返回未知机型类别:%1/%2") .arg(label) .arg(item.class_id); return QString(); } } // namespace class AapgsModelClassifier::Impl { public: using GetAbiVersionFunction = uint32_t (*)(); using CreateFunction = yolo_status_t (*)(const char*, yolo_runtime_t**, yolo_error_info_v1_t*); using GetInfoFunction = yolo_status_t (*)(const yolo_runtime_t*, yolo_runtime_info_v1_t*); using InferImageFunction = yolo_status_t (*)( yolo_runtime_t*, const yolo_image_view_v1_t*, const yolo_infer_options_v1_t*, yolo_result_set_t**, yolo_error_info_v1_t*); using ResultGetCountFunction = yolo_status_t (*)( const yolo_result_set_t*, uint32_t*); using ResultGetCommonFunction = yolo_status_t (*)( const yolo_result_set_t*, uint32_t, yolo_result_common_v1_t*); using ResultReleaseFunction = void (*)(yolo_result_set_t*); using DestroyFunction = void (*)(yolo_runtime_t*); ~Impl() { UnloadRuntimeLibrary(); } bool Classify(const QImage& frame, Classification& result, QString& errorMessage) { std::lock_guard lock(m_mutex); result = Classification(); errorMessage.clear(); if (frame.isNull() || frame.width() <= 0 || frame.height() <= 0) { errorMessage = QStringLiteral("AAPGS机型识别图像无效"); return false; } if (!EnsureInitialized(errorMessage)) { return false; } const QImage rgbFrame = frame.format() == QImage::Format_RGB888 ? frame : frame.convertToFormat(QImage::Format_RGB888); if (rgbFrame.isNull()) { errorMessage = QStringLiteral("AAPGS机型识别图像转换失败"); return false; } yolo_image_view_v1_t image{}; image.struct_size = sizeof(image); image.version = YOLO_IMAGE_VIEW_ABI_VERSION; image.data = rgbFrame.constBits(); image.width = rgbFrame.width(); image.height = rgbFrame.height(); image.row_stride_bytes = rgbFrame.bytesPerLine(); image.pixel_format = YOLO_PIXEL_FORMAT_RGB888; yolo_infer_options_v1_t options{}; options.struct_size = sizeof(options); options.version = YOLO_INFER_OPTIONS_VERSION; options.seg_geometry_mode = YOLO_SEG_GEOMETRY_NONE; yolo_error_info_v1_t error = MakeErrorInfo(); yolo_result_set_t* rawResults = nullptr; const yolo_status_t status = m_inferImage( m_runtime, &image, &options, &rawResults, &error); if (status != YOLO_STATUS_OK || !rawResults) { const QString runtimeError = RuntimeError(error); if (status == YOLO_STATUS_INITIALIZATION_FAILED || status == YOLO_STATUS_BACKEND_FAILED) { UnloadRuntimeLibrary(); } errorMessage = QStringLiteral("AAPGS模型推理失败(%1):%2") .arg(status) .arg(runtimeError); return false; } const auto releaseResults = [this](yolo_result_set_t* results) { if (results && m_resultRelease) { m_resultRelease(results); } }; std::unique_ptr results( rawResults, releaseResults); uint32_t resultCount = 0; if (m_resultGetCount(results.get(), &resultCount) != YOLO_STATUS_OK || resultCount == 0 || resultCount > 4096) { errorMessage = QStringLiteral("AAPGS分类结果数量异常:%1") .arg(resultCount); return false; } yolo_result_common_v1_t bestResult{}; bool hasBestResult = false; for (uint32_t index = 0; index < resultCount; ++index) { yolo_result_common_v1_t item{}; item.struct_size = sizeof(item); item.version = YOLO_RESULT_COMMON_VERSION; if (m_resultGetCommon(results.get(), index, &item) != YOLO_STATUS_OK) { continue; } if (item.task != YOLO_TASK_CLS) { continue; } if (!std::isfinite(item.score) || item.score < 0.0f || item.score > 1.0f) { continue; } if (!hasBestResult || item.score > bestResult.score) { bestResult = item; hasBestResult = true; } } if (!hasBestResult) { errorMessage = QStringLiteral("AAPGS未返回有效分类结果"); return false; } result.modelType = ModelName(bestResult, errorMessage); result.confidence = static_cast(bestResult.score); if (result.modelType.isEmpty()) { result = Classification(); return false; } return true; } private: bool EnsureInitialized(QString& errorMessage) { if (m_runtime) { return true; } QString configPath; QString packageRoot; if (!FindModelConfig(configPath, packageRoot, errorMessage) || !LoadRuntimeLibrary(packageRoot, errorMessage)) { return false; } const uint32_t runtimeAbi = m_getAbiVersion(); if (runtimeAbi != YOLO_RUNTIME_ABI_VERSION) { errorMessage = QStringLiteral("AAPGS运行库ABI不匹配:期望%1,实际%2") .arg(YOLO_RUNTIME_ABI_VERSION) .arg(runtimeAbi); UnloadRuntimeLibrary(); return false; } const QByteArray encodedConfigPath = QFile::encodeName(configPath); yolo_error_info_v1_t error = MakeErrorInfo(); const yolo_status_t status = m_create( encodedConfigPath.constData(), &m_runtime, &error); if (status != YOLO_STATUS_OK || !m_runtime) { const QString runtimeError = RuntimeError(error); UnloadRuntimeLibrary(); errorMessage = QStringLiteral("AAPGS模型初始化失败(%1):%2;配置:%3") .arg(status) .arg(runtimeError) .arg(configPath); return false; } yolo_runtime_info_v1_t runtimeInfo{}; runtimeInfo.struct_size = sizeof(runtimeInfo); runtimeInfo.version = YOLO_RUNTIME_INFO_VERSION; if (m_getInfo(m_runtime, &runtimeInfo) != YOLO_STATUS_OK) { UnloadRuntimeLibrary(); errorMessage = QStringLiteral("读取AAPGS运行库信息失败"); return false; } if (runtimeInfo.task != YOLO_TASK_CLS) { const int task = static_cast(runtimeInfo.task); UnloadRuntimeLibrary(); errorMessage = QStringLiteral("AAPGS模型任务不是分类任务:%1") .arg(task); return false; } if ((runtimeInfo.capabilities & YOLO_CAPABILITY_INFER_IMAGE) == 0) { UnloadRuntimeLibrary(); errorMessage = QStringLiteral("AAPGS运行库不支持内存图像推理"); return false; } return true; } bool LoadRuntimeLibrary(const QString& packageRoot, QString& errorMessage) { if (m_library.isLoaded()) { if (ResolveFunctions()) { return true; } UnloadRuntimeLibrary(); } QStringList candidates; const QString configuredLibrary = EnvironmentPath("AAPGS_RUNTIME_LIBRARY"); if (!configuredLibrary.isEmpty()) { AppendUniquePath(candidates, configuredLibrary); } #ifdef Q_OS_WIN AppendUniquePath(candidates, QDir(packageRoot).filePath( QStringLiteral("lib/yolo_runtime.dll"))); AppendUniquePath(candidates, QStringLiteral("yolo_runtime")); #else AppendUniquePath(candidates, QDir(packageRoot).filePath( QStringLiteral("lib/libyolo_runtime.so.5"))); AppendUniquePath(candidates, QDir(packageRoot).filePath( QStringLiteral("lib/libyolo_runtime.so"))); AppendUniquePath(candidates, QStringLiteral("libyolo_runtime.so.5")); AppendUniquePath(candidates, QStringLiteral("yolo_runtime")); #endif QString lastLoadError; for (const QString& candidate : candidates) { m_library.setFileName(candidate); if (!m_library.load()) { lastLoadError = m_library.errorString(); continue; } if (ResolveFunctions()) { return true; } lastLoadError = QStringLiteral("AAPGS运行库缺少必需的C ABI符号"); UnloadRuntimeLibrary(); } errorMessage = QStringLiteral("加载AAPGS运行库失败:%1") .arg(lastLoadError); return false; } bool ResolveFunctions() { m_getAbiVersion = reinterpret_cast( m_library.resolve("yolo_runtime_get_abi_version")); m_create = reinterpret_cast( m_library.resolve("yolo_runtime_create")); m_getInfo = reinterpret_cast( m_library.resolve("yolo_runtime_get_info")); m_inferImage = reinterpret_cast( m_library.resolve("yolo_runtime_infer_image")); m_resultGetCount = reinterpret_cast( m_library.resolve("yolo_result_set_get_count")); m_resultGetCommon = reinterpret_cast( m_library.resolve("yolo_result_set_get_common")); m_resultRelease = reinterpret_cast( m_library.resolve("yolo_result_set_release")); m_destroy = reinterpret_cast( m_library.resolve("yolo_runtime_destroy")); return m_getAbiVersion && m_create && m_getInfo && m_inferImage && m_resultGetCount && m_resultGetCommon && m_resultRelease && m_destroy; } static yolo_error_info_v1_t MakeErrorInfo() { yolo_error_info_v1_t error{}; error.struct_size = sizeof(error); error.version = YOLO_ERROR_INFO_VERSION; return error; } static QString RuntimeError(const yolo_error_info_v1_t& error) { int length = 0; while (length < static_cast(sizeof(error.message)) && error.message[length] != '\0') { ++length; } const QString text = QString::fromUtf8(error.message, length).trimmed(); return text.isEmpty() ? QStringLiteral("未知错误") : text; } void UnloadRuntimeLibrary() { if (m_runtime && m_destroy) { m_destroy(m_runtime); } m_runtime = nullptr; if (m_library.isLoaded()) { m_library.unload(); } m_getAbiVersion = nullptr; m_create = nullptr; m_getInfo = nullptr; m_inferImage = nullptr; m_resultGetCount = nullptr; m_resultGetCommon = nullptr; m_resultRelease = nullptr; m_destroy = nullptr; } private: std::mutex m_mutex; QLibrary m_library; yolo_runtime_t* m_runtime = nullptr; GetAbiVersionFunction m_getAbiVersion = nullptr; CreateFunction m_create = nullptr; GetInfoFunction m_getInfo = nullptr; InferImageFunction m_inferImage = nullptr; ResultGetCountFunction m_resultGetCount = nullptr; ResultGetCommonFunction m_resultGetCommon = nullptr; ResultReleaseFunction m_resultRelease = nullptr; DestroyFunction m_destroy = nullptr; }; AapgsModelClassifier::AapgsModelClassifier() : m_impl(std::make_unique()) { } AapgsModelClassifier::~AapgsModelClassifier() = default; bool AapgsModelClassifier::Classify(const QImage& frame, Classification& result, QString& errorMessage) { return m_impl && m_impl->Classify(frame, result, errorMessage); }