482 lines
17 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "AapgsModelClassifier.h"
#include <cmath>
#include <cstdint>
#include <mutex>
#include <QCoreApplication>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QLibrary>
#include <QStringList>
#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<std::mutex> 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<yolo_result_set_t, decltype(releaseResults)> 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<double>(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<int>(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<GetAbiVersionFunction>(
m_library.resolve("yolo_runtime_get_abi_version"));
m_create = reinterpret_cast<CreateFunction>(
m_library.resolve("yolo_runtime_create"));
m_getInfo = reinterpret_cast<GetInfoFunction>(
m_library.resolve("yolo_runtime_get_info"));
m_inferImage = reinterpret_cast<InferImageFunction>(
m_library.resolve("yolo_runtime_infer_image"));
m_resultGetCount = reinterpret_cast<ResultGetCountFunction>(
m_library.resolve("yolo_result_set_get_count"));
m_resultGetCommon = reinterpret_cast<ResultGetCommonFunction>(
m_library.resolve("yolo_result_set_get_common"));
m_resultRelease = reinterpret_cast<ResultReleaseFunction>(
m_library.resolve("yolo_result_set_release"));
m_destroy = reinterpret_cast<DestroyFunction>(
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<int>(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<Impl>())
{
}
AapgsModelClassifier::~AapgsModelClassifier() = default;
bool AapgsModelClassifier::Classify(const QImage& frame,
Classification& result,
QString& errorMessage)
{
return m_impl && m_impl->Classify(frame, result, errorMessage);
}