400 lines
14 KiB
C++
Raw Normal View History

2026-07-20 11:58:49 +08:00
#include "AapgsModelClassifier.h"
#include <cmath>
#include <mutex>
#include <QCoreApplication>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QLibrary>
#include <QStringList>
#include "yolo_runtime.h"
namespace {
2026-07-30 12:06:02 +08:00
const char kModelConfigRelativePath[] =
2026-07-20 11:58:49 +08:00
"models/model_20260712T020258_0000_6339d04b/"
"model_20260712T020258_0000_6339d04b.yaml";
2026-07-30 12:06:02 +08:00
const char kModelConfigFileName[] =
"model_20260712T020258_0000_6339d04b.yaml";
2026-07-20 11:58:49 +08:00
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());
return QDir::cleanPath(sourceDir.absoluteFilePath(
QStringLiteral("../../../../../AppAlgo/AAPGS_model")));
}
QString PackageRootForConfig(const QString& configPath)
{
QDir directory(QFileInfo(configPath).absolutePath());
2026-07-30 12:06:02 +08:00
for (int level = 0; level < 5; ++level) {
2026-07-20 11:58:49 +08:00
if (QFileInfo::exists(directory.filePath(
2026-07-30 12:06:02 +08:00
QStringLiteral("lib/libyolo_runtime.so.3")))) {
2026-07-20 11:58:49 +08:00
return directory.absolutePath();
}
if (!directory.cdUp()) {
break;
}
}
return QFileInfo(configPath).absolutePath();
}
2026-07-30 12:06:02 +08:00
bool FindModelConfig(QString& configPath,
QString& packageRoot,
QString& errorMessage)
2026-07-20 11:58:49 +08:00
{
const QString configuredPath = EnvironmentPath("AAPGS_MODEL_CONFIG");
if (!configuredPath.isEmpty()) {
QFileInfo configuredFile(configuredPath);
if (configuredFile.isDir()) {
2026-07-30 12:06:02 +08:00
const QDir configuredDir(configuredFile.absoluteFilePath());
const QFileInfo directConfig(configuredDir.filePath(
QString::fromLatin1(kModelConfigFileName)));
configuredFile.setFile(directConfig.isFile()
? directConfig.absoluteFilePath()
: configuredDir.filePath(QString::fromLatin1(
kModelConfigRelativePath)));
2026-07-20 11:58:49 +08:00
}
if (!configuredFile.isFile()) {
2026-07-30 12:06:02 +08:00
errorMessage = QStringLiteral("AAPGS模型配置不存在%1")
.arg(configuredPath);
2026-07-20 11:58:49 +08:00
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());
2026-07-30 12:06:02 +08:00
AppendUniquePath(roots, QStringLiteral("/opt/rk3588-ai/AAPGS_model"));
2026-07-20 11:58:49 +08:00
AppendUniquePath(roots, QStringLiteral("/usr/lib/AAPGS_model"));
AppendUniquePath(roots, QStringLiteral("/usr/local/lib/AAPGS_model"));
for (const QString& root : roots) {
2026-07-30 12:06:02 +08:00
const QFileInfo candidate(QDir(root).filePath(
QString::fromLatin1(kModelConfigRelativePath)));
if (candidate.isFile()) {
configPath = candidate.absoluteFilePath();
packageRoot = QDir(root).absolutePath();
return true;
2026-07-20 11:58:49 +08:00
}
}
errorMessage = QStringLiteral(
2026-07-30 12:06:02 +08:00
"未找到AAPGS机型分类配置请部署到"
"/opt/rk3588-ai/AAPGS_model/"
"models/model_20260712T020258_0000_6339d04b/"
"model_20260712T020258_0000_6339d04b.yaml"
"或设置AAPGS_MODEL_CONFIG");
2026-07-20 11:58:49 +08:00
return false;
}
QString ModelName(const yolo_result_t& item, QString& errorMessage)
{
2026-07-30 12:06:02 +08:00
int length = 0;
while (length < YOLO_LABEL_LENGTH && item.label[length] != '\0') {
++length;
2026-07-20 11:58:49 +08:00
}
2026-07-30 12:06:02 +08:00
const QString label = QString::fromUtf8(item.label, length).trimmed();
if (label.compare(QStringLiteral("a320"), Qt::CaseInsensitive) == 0 ||
(label.isEmpty() && item.class_id == 0)) {
2026-07-20 11:58:49 +08:00
return QStringLiteral("A320");
}
2026-07-30 12:06:02 +08:00
if (label.compare(QStringLiteral("b737"), Qt::CaseInsensitive) == 0 ||
(label.isEmpty() && item.class_id == 1)) {
2026-07-20 11:58:49 +08:00
return QStringLiteral("B737");
}
errorMessage = label.isEmpty()
2026-07-30 12:06:02 +08:00
? QStringLiteral("AAPGS返回未知机型类别%1").arg(item.class_id)
: QStringLiteral("AAPGS返回未知机型类别%1/%2")
2026-07-20 11:58:49 +08:00
.arg(label)
.arg(item.class_id);
return QString();
}
} // namespace
class AapgsModelClassifier::Impl
{
public:
using GetAbiVersionFunction = int (*)();
using CreateFunction = int (*)(const char*, yolo_runtime_t**);
using GetCapabilitiesFunction = unsigned int (*)(const yolo_runtime_t*);
using InferImageFunction = int (*)(yolo_runtime_t*, const yolo_image_view_t*,
yolo_result_list_t*);
using LastErrorFunction = const char* (*)(const yolo_runtime_t*);
using DestroyFunction = void (*)(yolo_runtime_t*);
~Impl()
{
UnloadRuntimeLibrary();
}
bool Classify(const QImage& frame,
Classification& result,
QString& errorMessage)
{
std::lock_guard<std::mutex> lock(m_mutex);
result = Classification();
errorMessage.clear();
if (frame.isNull() || frame.width() <= 0 || frame.height() <= 0) {
2026-07-30 12:06:02 +08:00
errorMessage = QStringLiteral("AAPGS机型识别图像无效");
2026-07-20 11:58:49 +08:00
return false;
}
if (!EnsureInitialized(errorMessage)) {
return false;
}
const QImage rgbFrame = frame.format() == QImage::Format_RGB888
? frame
: frame.convertToFormat(QImage::Format_RGB888);
if (rgbFrame.isNull()) {
2026-07-30 12:06:02 +08:00
errorMessage = QStringLiteral("AAPGS机型识别图像转换失败");
2026-07-20 11:58:49 +08:00
return false;
}
yolo_image_view_t image{};
image.struct_size = sizeof(image);
image.abi_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_result_list_t output{};
const int status = m_inferImage(m_runtime, &image, &output);
if (status != YOLO_STATUS_OK) {
const QString runtimeError = RuntimeError();
if (status == YOLO_STATUS_INITIALIZATION_FAILED ||
status == YOLO_STATUS_BACKEND_FAILED) {
UnloadRuntimeLibrary();
}
2026-07-30 12:06:02 +08:00
errorMessage = QStringLiteral("AAPGS模型推理失败(%1)%2")
.arg(status)
.arg(runtimeError);
2026-07-20 11:58:49 +08:00
return false;
}
if (output.task != YOLO_TASK_CLS) {
2026-07-30 12:06:02 +08:00
errorMessage = QStringLiteral("AAPGS模型任务不是分类任务%1")
.arg(static_cast<int>(output.task));
2026-07-20 11:58:49 +08:00
return false;
}
if (output.count <= 0 || output.count > YOLO_MAX_RESULTS) {
errorMessage = QStringLiteral("AAPGS分类结果数量异常%1")
2026-07-30 12:06:02 +08:00
.arg(output.count);
2026-07-20 11:58:49 +08:00
return false;
}
const yolo_result_t* bestResult = nullptr;
for (int index = 0; index < output.count; ++index) {
2026-07-30 12:06:02 +08:00
const yolo_result_t& item = output.results[index];
if (!std::isfinite(item.score) || item.score < 0.0f ||
item.score > 1.0f) {
2026-07-20 11:58:49 +08:00
continue;
}
2026-07-30 12:06:02 +08:00
if (!bestResult || item.score > bestResult->score) {
bestResult = &item;
2026-07-20 11:58:49 +08:00
}
}
if (!bestResult) {
errorMessage = QStringLiteral("AAPGS未返回有效分类结果");
return false;
}
2026-07-30 12:06:02 +08:00
result.modelType = ModelName(*bestResult, errorMessage);
2026-07-20 11:58:49 +08:00
result.confidence = static_cast<double>(bestResult->score);
if (result.modelType.isEmpty()) {
result = Classification();
return false;
}
return true;
}
private:
bool EnsureInitialized(QString& errorMessage)
{
if (m_runtime) {
return true;
}
#if !defined(Q_OS_LINUX) || !defined(Q_PROCESSOR_ARM_64)
errorMessage = QStringLiteral(
2026-07-30 12:06:02 +08:00
"AAPGS机型分类仅支持RK3588 Linux AArch64");
2026-07-20 11:58:49 +08:00
return false;
#else
QString configPath;
QString packageRoot;
2026-07-30 12:06:02 +08:00
if (!FindModelConfig(configPath, packageRoot, errorMessage) ||
!LoadRuntimeLibrary(packageRoot, errorMessage)) {
2026-07-20 11:58:49 +08:00
return false;
}
const int runtimeAbi = m_getAbiVersion();
if (runtimeAbi != YOLO_RUNTIME_ABI_VERSION) {
errorMessage = QStringLiteral("AAPGS运行库ABI不匹配期望%1实际%2")
2026-07-30 12:06:02 +08:00
.arg(YOLO_RUNTIME_ABI_VERSION)
.arg(runtimeAbi);
2026-07-20 11:58:49 +08:00
UnloadRuntimeLibrary();
return false;
}
const QByteArray encodedConfigPath = QFile::encodeName(configPath);
const int status = m_create(encodedConfigPath.constData(), &m_runtime);
if (status != YOLO_STATUS_OK || !m_runtime) {
const QString runtimeError = RuntimeError();
UnloadRuntimeLibrary();
errorMessage = QStringLiteral("AAPGS模型初始化失败(%1)%2配置%3")
2026-07-30 12:06:02 +08:00
.arg(status)
.arg(runtimeError)
.arg(configPath);
2026-07-20 11:58:49 +08:00
return false;
}
if ((m_getCapabilities(m_runtime) & YOLO_CAPABILITY_INFER_IMAGE) == 0) {
UnloadRuntimeLibrary();
errorMessage = QStringLiteral("AAPGS运行库不支持内存图像推理");
return false;
}
return true;
#endif
}
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);
}
AppendUniquePath(candidates, QDir(packageRoot).filePath(
2026-07-30 12:06:02 +08:00
QStringLiteral("lib/libyolo_runtime.so.3")));
2026-07-20 11:58:49 +08:00
AppendUniquePath(candidates, QDir(packageRoot).filePath(
QStringLiteral("lib/libyolo_runtime.so")));
2026-07-30 12:06:02 +08:00
AppendUniquePath(candidates, QStringLiteral("libyolo_runtime.so.3"));
2026-07-20 11:58:49 +08:00
AppendUniquePath(candidates, QStringLiteral("yolo_runtime"));
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();
}
2026-07-30 12:06:02 +08:00
errorMessage = QStringLiteral("加载AAPGS运行库失败%1")
.arg(lastLoadError);
2026-07-20 11:58:49 +08:00
return false;
}
bool ResolveFunctions()
{
m_getAbiVersion = reinterpret_cast<GetAbiVersionFunction>(
m_library.resolve("yolo_runtime_get_abi_version"));
m_create = reinterpret_cast<CreateFunction>(
m_library.resolve("yolo_runtime_create"));
m_getCapabilities = reinterpret_cast<GetCapabilitiesFunction>(
m_library.resolve("yolo_runtime_get_capabilities"));
m_inferImage = reinterpret_cast<InferImageFunction>(
m_library.resolve("yolo_runtime_infer_image"));
m_lastError = reinterpret_cast<LastErrorFunction>(
m_library.resolve("yolo_runtime_last_error"));
m_destroy = reinterpret_cast<DestroyFunction>(
m_library.resolve("yolo_runtime_destroy"));
2026-07-30 12:06:02 +08:00
return m_getAbiVersion && m_create && m_getCapabilities &&
m_inferImage && m_lastError && m_destroy;
2026-07-20 11:58:49 +08:00
}
QString RuntimeError() const
{
if (!m_runtime || !m_lastError) {
return QStringLiteral("未知错误");
}
const char* error = m_lastError(m_runtime);
const QString text = error ? QString::fromUtf8(error).trimmed() : QString();
return text.isEmpty() ? QStringLiteral("未知错误") : text;
}
2026-07-30 12:06:02 +08:00
void UnloadRuntimeLibrary()
2026-07-20 11:58:49 +08:00
{
if (m_runtime && m_destroy) {
m_destroy(m_runtime);
}
m_runtime = nullptr;
2026-07-30 12:06:02 +08:00
if (m_library.isLoaded()) {
m_library.unload();
}
2026-07-20 11:58:49 +08:00
m_getAbiVersion = nullptr;
m_create = nullptr;
m_getCapabilities = nullptr;
m_inferImage = nullptr;
m_lastError = 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;
GetCapabilitiesFunction m_getCapabilities = nullptr;
InferImageFunction m_inferImage = nullptr;
LastErrorFunction m_lastError = nullptr;
DestroyFunction m_destroy = nullptr;
};
AapgsModelClassifier::AapgsModelClassifier()
: m_impl(std::make_unique<Impl>())
{
}
AapgsModelClassifier::~AapgsModelClassifier() = default;
bool AapgsModelClassifier::Classify(const QImage& frame,
Classification& result,
QString& errorMessage)
{
return m_impl && m_impl->Classify(frame, result, errorMessage);
}