基于 RK3588 的 YOLO 多线程推理多级硬件加速引擎框架设计(代码框架和实现细节)

news2025/4/25 5:02:51

一、前言

        接续上一篇文章,这个部分主要分析代码框架的实现细节和设计理念。

基于RK3588的YOLO多线程推理多级硬件加速引擎框架设计(项目总览和加速效果)-CSDN博客https://blog.csdn.net/plmm__/article/details/146542002?spm=1001.2014.3001.5501

二、框架分析

        在原作者的基础上,我增加了命令行的参数解析、多态视频读取引擎、硬件视频解码、RGA 硬件图像缩放,色彩空间转换,以及部分代码优化和内存管理调整。

1、命令行参数解析

        使用 ConfigParser 类封装,便于移植:

头文件 parse_config.hpp:

#ifndef _PARSE_CONFIG_HPP_
#define _PARSE_CONFIG_HPP_

#include <iostream>
#include <string>
#include <SharedTypes.hpp>

/* 定义配置解析类 */
class ConfigParser {
    
public:
    // 输入格式
    int input_format;  
    // 显示帮助信息
    void print_help(const std::string &program_name) const;
    // 打印配置信息
    void printConfig(const AppConfig &config) const;
    // 解析命令行参数
    AppConfig parse_arguments(int argc, char *argv[]) const;

private:
    // 私有成员(如果有需要可以添加)

};

#endif

        这里的 AppConfig 是参数列表结构体,定义在全项目的共享头文件 Shared_Types.hpp 中:

/* 定义命令行参数结构体 */ 
struct AppConfig {
    // 在屏幕显示 FPS
    bool screen_fps = false;
    // 在终端打印 FPS
    bool print_fps = false;
    // 是否使用opencl
    bool opencl = true;
    // 是否打印命令行参数
    bool verbose = false;
    // 视频加载引擎,默认为 ffmpeg
    int read_engine = READ_ENGINE::EN_FFMPEG;
    // 输入格式,默认为视频
    int input_format = INPUT_FORMAT::IN_VIDEO;
    // 硬件加速,默认为 RGA
    int accels_2d = ACCELS_2D::ACC_RGA;
    // 线程数,默认为1
    int threads = 1;
    // rknn 模型路径
    string model_path = "";
    // 输入源    
    string input = "";
    // 解码器,默认为 h264_rkmpp
    string decodec = "h264_rkmpp";
};

源文件较大,这里仅放一个长短命令解析的部分截图:

        各位可根据自己喜好,修改参数列表,我比较喜欢设置默认值,直接执行可执行文件时,只需要传递必要的参数。

2、多态视频读取引擎

        原作者使用 OpenCV 进行视频读取和取帧操作,为了保留 OpenCV 的读取,我使用多态的方式可以灵活选择 OpenCV 和 FFmpeg 两种方式进行读取。本节均只介绍头文件中的接口,具体实现较长,还请读者移步 Github 。整体框架为:

(1)​Reader(基类)​

定义了视频读取操作的通用接口(如 open、close、readFrame 等)。

作为所有具体读取器(如 FFmpegReader、OpencvReader 等)的基类,利用多态性实现运行时动态选择具体的实现类。

#ifndef READER_H
#define READER_H

#include <string>
#include "opencv2/core.hpp"

/**
 * @Description: 基类引擎
 * @return {*}
 */
class Reader {
public:
    // 析构虚函数
    virtual ~Reader() = default;
    /* 纯虚函数接口 */
    virtual void openVideo(const std::string& filePath) = 0;
    virtual bool readFrame(cv::Mat& frame) = 0;
    virtual void closeVideo() = 0;
};

#endif // READER_H

(2)​FFmpegReader 或 OpencvReader(Reader 的子类)​

继承自 Reader 基类。

实现了基类中定义的虚函数,具体使用 FFmpeg 或 OpenCV 库提供的函数来处理视频操作。

在初始化时,可能配置和加载与读取器相关的资源或参数。

#ifndef FFMPEGREADER_H
#define FFMPEGREADER_H

#include <iostream>
#include "Reader.hpp"
#include "preprocess.h"
#include "SharedTypes.hpp"

#include <opencv2/opencv.hpp>
extern "C" {
#include <libavutil/frame.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
}

/**
 * @Description: FFmpeg 引擎
 * @return {*}
 */
class FFmpegReader : public Reader {
public:
    FFmpegReader(const string& decodec, const int& accels_2d);
    ~FFmpegReader() override;

    void openVideo(const std::string& filePath) override;
    bool readFrame(cv::Mat& frame) override;
    void closeVideo() override;

    // 获取视频信息
    void print_video_info(const string& filePath);
    int getWidth() const;
    int getHeight() const;
    AVRational getTimeBase() const;
    double getFrameRate() const;
    
private:
    
    string decodec;                             // 解码器
    int accels_2d;                              // 2D 硬件加速类型
    AVFormatContext *formatContext = nullptr;   // 输入文件的上下文
    AVCodecContext *codecContext = nullptr;     // 解码器上下文
    const AVCodec* codec = nullptr;             // 解码器
    int videoStreamIndex = -1;                  // 视频流的索引
    AVStream *video_stream;                     // 视频流
    AVFrame *tempFrame = nullptr;               // 临时帧(用于解码)
    AVPacket *packet = nullptr;                 // 数据包

    int NV12_to_BGR(cv::Mat& bgr_frame);
    int FFmpeg_yuv420sp_to_bgr(cv::Mat& bgr_frame);
    void AV_Frame_To_CVMat(cv::Mat& nv12_mat);
};

#endif // FFMPEGREADER_H
#ifndef OPENCVREADER_H
#define OPENCVREADER_H

#include "Reader.hpp"
#include <iostream>
#include <opencv2/opencv.hpp>

/**
 * @Description: Opencv 引擎
 * @return {*}
 */
class OpencvReader : public Reader {
public:
    OpencvReader();
    ~OpencvReader() override;

    void openVideo(const std::string& filePath) override;
    bool readFrame(cv::Mat& frame) override;
    void closeVideo() override;

private:
    cv::VideoCapture videoCapture; // OpenCV 视频捕获对象
};

#endif // OPENCVREADER_H

(3)VideoReader(中间件)​

提供给 main 函数或其他上层模块使用的接口。

负责根据配置或输入动态选择并实例化合适的 Reader 子类(如 FFmpegReader 或 OpencvReader)。

封装了对具体 Reader 实例的管理,简化了上层模块对视频读取操作的调用。

#ifndef VIDEOREADER_H
#define VIDEOREADER_H

#include <memory>
#include <string>

#include "SharedTypes.hpp"
#include "Reader.hpp"

/**
 * @Description: 视频读取器
 * @return {*}
 */
class VideoReader {
public:
    VideoReader(const AppConfig& config);
    ~VideoReader();

    /* 以下禁止拷贝和允许移动两部分实现:
    1、提高性能;
    2、管理独占资源;
    3、现代C++鼓励使用移动语义和智能指针等工具来管理资源。 
    */
    // 禁止拷贝构造和拷贝赋值
    VideoReader(const VideoReader&) = delete;
    VideoReader& operator=(const VideoReader&) = delete;
    // 允许移动构造和移动赋值
    VideoReader(VideoReader&&) = default;
    VideoReader& operator=(VideoReader&&) = default;

    /* 函数接口 */
    bool readFrame(cv::Mat &frame);  // 读取一帧
    void Close_Video();              // 关闭视频


private:
    // 使用智能指针管理资源,这里只是声明, ​没有申请内存
    std::unique_ptr<Reader> reader_ptr; 
    // 加载引擎
    void Init_Load_Engine(const int& engine, const string& decodec, const int& accels_2d);
};

#endif // VIDEOREADER_H

(4)​main 函数

使用 VideoReader 提供的统一接口来操作视频,无需关心底层使用了哪种具体的读取器实现。

创建 VideoReader:

读取帧:

3、硬件视频解码

        这部分主要由 FFmpeg 实现,通过 FFmpeg 来调用 Rkmpp 解码器。这里需要注意,FFmpeg 不是官方源码,而是 rockchip 版本的 ffmpeg-rockchip,来自 nyanmisaka 大佬的项目:

nyanmisaka/ffmpeg-rockchip: FFmpeg with async and zero-copy Rockchip MPP & RGA supporthttps://github.com/nyanmisaka/ffmpeg-rockchip        专门针对瑞芯微的 Rockchip MPP & RGA 进行适配和优化,可以在编译时开启 rkmpp 解码支持和 RGA 过滤器支持。编译方法移步:

编译支持 RKmpp 和 RGA 的 ffmpeg 源码_ffmpeg支持mpp-CSDN博客https://blog.csdn.net/plmm__/article/details/146188927?spm=1001.2014.3001.5501        代码部分就是常规的 FFmpeg 进行视频解码,我这里分为了两部分:打开视频文件和读取视频帧。

打开视频文件

/**
 * @Description: 打开视频文件
 * @param {string} &filePath: 
 * @return {*}
 */
void FFmpegReader::openVideo(const std::string& filePath) {

    /* 分配一个 AVFormatContext */
    formatContext = avformat_alloc_context();
    if (!formatContext)
        throw std::runtime_error("Couldn't allocate format context");

    /* 打开视频文件 */
    // 并读取头部信息,此时编解码器尚未开启
    if (avformat_open_input(&formatContext, filePath.c_str(), nullptr, nullptr) != 0)
        throw std::runtime_error("Couldn't open video file");

    /* 读取媒体文件的数据包以获取流信息 */
    if (avformat_find_stream_info(formatContext, nullptr) < 0)
        throw std::runtime_error("Couldn't find stream information");

    /* 查找视频流 AVMEDIA_TYPE_VIDEO */
    // -1, -1,意味着没有额外的选择条件,返回值是流索引
    videoStreamIndex = av_find_best_stream(formatContext, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
    if (videoStreamIndex < 0)
        throw std::runtime_error("Couldn't find a video stream");

    /* 查找解码器 */
    codec = avcodec_find_decoder_by_name(this->decodec.c_str());
    if (!codec)
        throw std::runtime_error("Decoder not found");
    
    /* 初始化编解码器上下文 */ 
    codecContext = avcodec_alloc_context3(codec);
    if (!codecContext)
        throw std::runtime_error("Couldn't allocate decoder context");

    /* 获取视频流,它包含了视频流的元数据和参数 */
    video_stream = formatContext->streams[videoStreamIndex];
    
    /* 复制视频参数到解码器上下文 */ 
    if (avcodec_parameters_to_context(codecContext, video_stream->codecpar) < 0)
        throw std::runtime_error("Couldn't copy decoder context");

    /* 自动选择线程数 */
    codecContext->thread_count = 0;
    
    /* 打开编解码器 */ 
    if (avcodec_open2(codecContext, codec, nullptr) < 0)
        throw std::runtime_error("Couldn't open decoder");
    
    /* 分配 AVPacket 和 AVFrame */ 
    tempFrame = av_frame_alloc();
    packet = av_packet_alloc();
    if (!tempFrame || !packet)
        throw std::runtime_error("Couldn't allocate frame or packet"); 
}

        其中下面的代码需要注意:

/* 自动选择线程数 */
    codecContext->thread_count = 0;

这个变量主要用于设置 FFmpeg 工作线程数量,0 代表自动选择,具体的实验可以看这篇文章:

解决 FFmpeg 使用 C/C++ 接口时,解码没有 shell 快的问题(使用多线程)-CSDN博客https://blog.csdn.net/plmm__/article/details/146523965?spm=1001.2014.3001.5501

读取视频帧

/**
 * @Description: 读取一帧
 * @param {Mat&} frame: 取出的帧
 * @return {*}
 */
bool FFmpegReader::readFrame(cv::Mat& frame) {
    // 读取帧
    /*if (av_read_frame(formatContext, packet) < 0) {
        return false; // 没有更多帧
    }*/
    while (av_read_frame(formatContext, packet) >= 0) {
        if (packet->stream_index != videoStreamIndex) {
            av_packet_unref(packet);
            continue;
        }
        break;
    }

    // 如果是视频流
    if (packet->stream_index != videoStreamIndex) {
        cerr << "Not a video stream: " << packet->stream_index << " != " << videoStreamIndex << endl;
        av_packet_unref(packet);
        return false; // 不是视频流
    }

    // 发送数据包到解码器
    if (avcodec_send_packet(codecContext, packet) < 0) {
        std::cerr << "Failed to send packet to decoder" << std::endl;
        av_packet_unref(packet);
        return false; // 发送数据包失败
    }

    // 接收解码后的帧
    if (avcodec_receive_frame(codecContext, tempFrame) < 0) {
        std::cerr << "Failed to receive frame from decoder" << std::endl;
        av_packet_unref(packet);
        return false;
    }

    // 成功读取一帧,保存在 tempFrame 中
    // 将帧数据转换为 cv::Mat BGR 格式
    if (this->NV12_to_BGR(frame) != 0) {
        std::cerr << "Failed to convert YUV420SP to BGR" << std::endl;
        av_packet_unref(packet);
        return false;
    }

    // 释放数据包
    av_packet_unref(packet);
    
    return true; // 处理完成
}

        av_read_frame 函数在实测过程中发现开头几帧取出后不是视频流,因此直接使用 while 跳过。在成功取出帧后,会保存在 tempFrame 中,为 AVFrame 格式,色彩空间为 NV12,由解码器决定,我使用 h264_rkmpp 解码器,默认输出是 NV12。

4、RGA 硬件加速

        目前主要有三个地方使用到了图像的缩放和格式转换的操作,并且三个操作是前后关系,分别是上一节取出视频帧后要将 NV12 转为 BGR888,转为 YOLO 输入的 RGB888,以及输入尺寸的修改。

NV12 转为 BGR888

        由于需要保持接口的通用性,与 OpenCV 取帧保持一致(OpenCV 解码后为 BGR888 格式), 并且数据传输使用 OpenCV 的 cv::Mat 对象进行图像传输,所以在取出帧后进行了颜色空间的转换,并改用 cv::Mat 进行保存:

/**
 * @Description: 转换格式,NV12 转 BGR
 *               该函数内有三种转换方式:
 *                  1. FFmpeg SwsContext 软件转换  
 *                  2. OpenCV 软件转换,可启用 opencl(目前区别不大)
 *                  3. RGA 硬件加速转换
 * @param {Mat&} frame: 
 * @return {*}
 */
int FFmpegReader::NV12_to_BGR(cv::Mat& bgr_frame) {
    if (tempFrame->format != AV_PIX_FMT_NV12) {
        return -EXIT_FAILURE; // 格式错误
    }

    // 设置输出帧的尺寸和格式,防止地址无法访问
    bgr_frame.create(tempFrame->height, tempFrame->width, CV_8UC3);

#if 0 
    // 方式1:使用 FFmpeg SwsContext 软件转换
    return this->FFmpeg_yuv420sp_to_bgr(bgr_frame);
#endif

    // 创建一个完整的 NV12 数据块(Y + UV 交错)
    cv::Mat nv12_mat(tempFrame->height + tempFrame->height / 2, tempFrame->width, CV_8UC1);
    // 将 AVFrame 内的数据,转换为 OpenCV Mat 格式保存
    this->AV_Frame_To_CVMat(nv12_mat);

    // 硬件加速
    if (this->accels_2d == ACCELS_2D::ACC_OPENCV) {
        // 方式2:使用 OpenCV 软件转换
        cv::cvtColor(nv12_mat, bgr_frame, cv::COLOR_YUV2BGR_NV12);
        return EXIT_SUCCESS;
    } else if (this->accels_2d == ACCELS_2D::ACC_RGA) {
        // 方式3:使用 RGA 硬件加速转换
        return RGA_yuv420sp_to_bgr((uint8_t *)nv12_mat.data, tempFrame->width, tempFrame->height, bgr_frame);
    }
    else
        return -EXIT_FAILURE;
}

        这个函数可以使用三种方式进行转换,分别是:

1. FFmpeg SwsContext 软件转换

2. OpenCV 软件转换

3. RGA 硬件转换

        三种转换方式的源码较多,可在项目源码中查看。根据目前实测的结果(只针对当前转换函数),SwsContext 转换一次耗时约 20ms,RGA 约 2-5ms,OpenCV 约 2-4ms。RGA 转换接口可能和我的接口调用方式有关,还有优化的空间,平均值甚至不如 OpenCV。

转为 YOLO 输入的 RGB888

        这里的转换操作是放在了推理线程中,理论上是在多线程进行:

// YOLO 推理需要 RGB 格式,后处理需要 BGR 格式
    // 即使前处理时提前转换为 RGB,后处理部分任然需要转换为 BGR,需要在本函数中保留两种格式
    if (this->config.accels_2d == ACCELS_2D::ACC_OPENCV) {
        cv::cvtColor(orig_img, rgb_img, cv::COLOR_BGR2RGB);
    }
    else if (this->config.accels_2d == ACCELS_2D::ACC_RGA) {
        if (RGA_bgr_to_rgb(orig_img, rgb_img) != 0) {
            cout << "RGA_bgr_to_rgb error" << endl;
            return cv::Mat();
        }
    }
    else {
        cout << "Unsupported 2D acceleration" << endl;
        return cv::Mat();
    }

        在原作者转换逻辑的基础上,我增加了 OpenCV 和 RGA 的选择。注释中也说明了为什么需要 BGR 转 RGB 这一步,这也和 cv::Mat 对象的默认格式有关,cv::imshow 显示时也是需要数据为 BGR,与 YOLO 的输入格式相反。

输入尺寸的修改

        即输入图像的 resize:

// 图像缩放
    if (orig_img.cols != width || orig_img.rows != height)
    {
        // 如果需要缩放,再对 resized_img 申请大小,节约内存开销
        resized_img.create(height, width, CV_8UC3);
        if (this->config.accels_2d == ACCELS_2D::ACC_OPENCV)
        {
            // 打包模型输入尺寸
            cv::Size target_size(width, height);
            float min_scale = std::min(scale_w, scale_h);
			scale_w = min_scale;
			scale_h = min_scale;
			letterbox(rgb_img, resized_img, pads, min_scale, target_size, this->config.opencl);
        }
        else if (this->config.accels_2d == ACCELS_2D::ACC_RGA)
        {
            ret = RGA_resize(rgb_img, resized_img);
            if (ret != 0) {
                cout << "resize_rga error" << endl;
            }
        }
        else {
            cout << "Unsupported 2D acceleration" << endl;
            return cv::Mat();
        }
        inputs[0].buf = resized_img.data;
    }
    else
    {
        inputs[0].buf = rgb_img.data;
    }

        上面与瑞芯微官方的 YOLO demo 是一样的,我对 letterbox 函数内部做了 OpenCL 的一个修改:

void letterbox_with_opencl(const cv::Mat &image, cv::UMat &padded_image, BOX_RECT &pads, const float scale, const cv::Size &target_size, const cv::Scalar &pad_color) {
    // 将输入图像转换为 UMat
    cv::UMat uImage = image.getUMat(cv::ACCESS_READ);

    // 调整图像大小
    cv::UMat resized_image;
    cv::resize(uImage, resized_image, cv::Size(), scale, scale);

    if (uImage.empty()) {
        std::cerr << "Error: uImage is empty." << std::endl;
        return;
    }
    if (resized_image.empty()) {
        std::cerr << "Error: resized_image is empty." << std::endl;
        return;
    }

    // 计算填充大小
    int pad_width = target_size.width - resized_image.cols;
    int pad_height = target_size.height - resized_image.rows;

    pads.left = pad_width / 2;
    pads.right = pad_width - pads.left;
    pads.top = pad_height / 2;
    pads.bottom = pad_height - pads.top;

    // 在图像周围添加填充
    cv::copyMakeBorder(resized_image, padded_image, pads.top, pads.bottom, pads.left, pads.right, cv::BORDER_CONSTANT, pad_color);
}

/**
 * @Description: OpenCV 图像预处理
 * @return {*}
 */
void letterbox(const cv::Mat &image, cv::Mat &padded_image, BOX_RECT &pads, const float scale, const cv::Size &target_size, bool Use_opencl, const cv::Scalar &pad_color)
{
    // 图像数据检查
    if (image.empty()) {
        std::cerr << "Error: Input image is empty." << std::endl;
        return;
    }
    // 调整图像大小
    cv::Mat resized_image;

    if (Use_opencl)
    {
        // 预处理图像
        cv::UMat U_padded_image;
        letterbox_with_opencl(image, U_padded_image, pads, scale, target_size, pad_color);

        // 将处理后的图像从 GPU 内存复制回 CPU 内存(如果需要显示)
        // padded_image = U_padded_image.getMat(cv::ACCESS_READ);
        // padded_image = std::move(U_padded_image.getMat(cv::ACCESS_READ));
        padded_image = U_padded_image.getMat(cv::ACCESS_READ).clone(); // 深拷贝
        return ;
    }

    cv::resize(image, resized_image, cv::Size(), scale, scale);

    // 计算填充大小
    int pad_width = target_size.width - resized_image.cols;
    int pad_height = target_size.height - resized_image.rows;

    pads.left = pad_width / 2;
    pads.right = pad_width - pads.left;
    pads.top = pad_height / 2;
    pads.bottom = pad_height - pads.top;

    // 在图像周围添加填充
    cv::copyMakeBorder(resized_image, padded_image, pads.top, pads.bottom, pads.left, pads.right, cv::BORDER_CONSTANT, pad_color);
}

        使用 cv::UMat 对象来调用 OpenCL 进行 resize 的并行计算。

5、其他

还有一些 C 语言的接口,我封装为了类的形式,虽然牺牲了一些性能,不过为了项目的通用性和可维护性,很多都使用 C++ 的语法替换掉了,比如加载模型的函数:

原始的 C 函数:

static unsigned char *load_data(FILE *fp, size_t ofst, size_t sz)
{
    unsigned char *data;
    int ret;

    data = NULL;

    if (NULL == fp)
    {
        return NULL;
    }

    ret = fseek(fp, ofst, SEEK_SET);
    if (ret != 0)
    {
        printf("blob seek failure.\n");
        return NULL;
    }

    data = (unsigned char *)malloc(sz);
    if (data == NULL)
    {
        printf("buffer malloc failure.\n");
        return NULL;
    }
    ret = fread(data, 1, sz, fp);
    return data;
}

static unsigned char *load_model(const char *filename, int *model_size)
{
    FILE *fp;
    unsigned char *data;

    fp = fopen(filename, "rb");
    if (NULL == fp)
    {
        printf("Open file %s failed.\n", filename);
        return NULL;
    }

    fseek(fp, 0, SEEK_END);
    int size = ftell(fp);

    data = load_data(fp, 0, size);

    fclose(fp);

    *model_size = size;
    return data;
}

改用更便捷的方式,并且内存的申请放到了函数外,由调用者进行管理,提高内存维护的便捷性:

/**
 * @Description: 获取文件大小
 * @param {string&} filename: 
 * @return {size_t}: 返回字节数,失败返回0
 */
static size_t get_file_size(const std::string& filename) {
    // std::ios::ate:打开文件后立即将文件指针移动到文件末尾(at end)
    std::ifstream ifs(filename, std::ios::binary | std::ios::ate);
    if (!ifs.is_open())
        return 0;
    
    // 通过文件尾定位获取大小
    size_t size = ifs.tellg();
    ifs.close();
    return size;
}

/**
 * @Description: 加载文件数据
 * @param {ifstream&} ifs: 
 * @param {size_t} offset: 
 * @param {unsigned char*} buffer: 
 * @param {size_t} size: 
 * @return {*}
 */
static bool load_data(std::ifstream& ifs, size_t offset, unsigned char* buffer, size_t size) {
    if (!ifs.is_open()) {
        std::cerr << "File stream not open" << std::endl;
        return false;
    }
    // 定位到指定位置
    ifs.seekg(offset, std::ios::beg);
    if (ifs.fail()) {
        std::cerr << "Seek failed at offset " << offset << std::endl;
        return false;
    }

    ifs.read(reinterpret_cast<char*>(buffer), size);
    // ifs.gcount():返回实际读取的字节数
    if (ifs.gcount() != static_cast<std::streamsize>(size)) {
        std::cerr << "Read failed, expected " << size << " bytes, got " << ifs.gcount() << std::endl;
        return false;
    }
    return true;
}

/**
 * @Description: 加载模型
 * @param {string} &filename: 
 * @param {unsigned char} *buffer: 
 * @param {size_t&} buffer_size: 
 * @return {*}
 */
static bool load_model(const std::string &filename, unsigned char *buffer, const size_t& buffer_size) {
    // std::ios::binary:以二进制模式打开文件
    std::ifstream ifs(filename, std::ios::binary);
    if (!ifs)
    {
        std::cerr << "Failed to open: " << filename << std::endl;
        return false;
    }
    if (buffer_size == 0)
    {
        std::cerr << "Failed to open: " << filename << std::endl;
        return false;
    }

    return load_data(ifs, 0, buffer, buffer_size);
}

三、总结

        以上就是我做的一些修改的粗略描述,具体细节我也都在代码中做了注释。希望这个项目可以帮到有需要硬件解码,以及正在学习 RGA 接口的小伙伴。各位读者有任何修改意见,欢迎与我联系,代码会放至Gitee 和 Github,我有空也会持续完善优化:

Gitee:

YOLO_RKNN_Acceleration_Program: YOLO multi-threaded and hardware-accelerated inference framework based on RKNNhttps://gitee.com/lrf1125962926/yolo_rknn_acceleration_programGithub:

1125962926/YOLO_RKNN_Acceleration_Program: YOLO multi-threaded and hardware-accelerated inference framework based on RKNNhttps://github.com/1125962926/YOLO_RKNN_Acceleration_Program

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2325903.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Yolov8部署】 VS2019+opencv+onnxruntime 环境下部署目标检测模型

文章目录 前言一、导出yolov8模型为onnx文件二、VS2019中环境配置三、源码与实际运行 前言 本文主要研究场景为工业场景下&#xff0c;在工控机与工业相机环境中运行的视觉缺陷检测系统&#xff0c;因此本文主要目的为实现c环境下&#xff0c;将yolov8已训练好的检测模型使用o…

论文阅读:Dual Anchor Graph Fuzzy Clustering for Multiview Data

论文地址:Dual Anchor Graph Fuzzy Clustering for Multiview Data | IEEE Journals & Magazine | IEEE Xplore 代码地址&#xff1a;https://github.com/BBKing49/DAG_FC 摘要 多视角锚图聚类近年来成为一个重要的研究领域&#xff0c;催生了多个高效的方法。然而&#…

乐橙R10 AI智能锁:以「技术减法」终结智能家居「参数内卷」

1 行业迷思&#xff1a;当「技术内卷」背离用户真实需求 “三摄猫眼”、“0.3秒人脸解锁”、“DeepSeek大模型”……智能锁行业的营销话术日益浮夸&#xff0c;但用户体验却陷入“功能冗余”与“操作复杂”的泥潭。 一位用户在社交平台直言&#xff1a;“我的智能锁有六个摄像…

如何使用 FastAPI 构建 MCP 服务器

哎呀&#xff0c;各位算法界的小伙伴们&#xff01;今天咱们要聊聊一个超酷的话题——MCP 协议&#xff01;你可能已经听说了&#xff0c;Anthropic 推出了这个新玩意儿&#xff0c;目的是让 AI 代理和你的应用程序之间的对话变得更顺畅、更清晰。不过别担心&#xff0c;为你的…

当 EcuBus-Pro + UTA0401 遇上 NSUC1500

文章目录 1.前言2.EcuBus-Pro简介2.1 官方地址2.2 概览 3.纳芯微NSUC1500简介3.1 NSUC1500概述3.2 产品特性 4.测试环境5.基础功能5.1 数据发送5.2 数据监控 6.自动化功能6.1 脚本创建6.2 脚本编辑6.3 脚本编辑与测试 7.音乐律动7.1 导入例程7.2 效果展示 ECB工程 1.前言 最近…

【FreeRTOS】裸机开发与操作系统区别

&#x1f50e;【博主简介】&#x1f50e; &#x1f3c5;CSDN博客专家 &#x1f3c5;2021年博客之星物联网与嵌入式开发TOP5 &#x1f3c5;2022年博客之星物联网与嵌入式开发TOP4 &#x1f3c5;2021年2022年C站百大博主 &#x1f3c5;华为云开发…

Deepseek API+Python 测试用例一键生成与导出 V1.0.4 (接口文档生成接口测试用例保姆级教程)

接口文档生成接口测试用例保姆级教程 随着测试需求的复杂性增加,测试用例的设计和生成变得愈发重要。Deepseek API+Python 测试用例生成工具在 V1.0.4 中进行了全方位的优化和功能扩展,特别是对接口测试用例设计的支持和接口文档的智能解析处理。本文将详细介绍 V1.0.4 版本…

CET-4增量表

CET-4词表-增量表 注&#xff1a; 【1】所谓增量&#xff0c;是相对于高中高考之增量 即&#xff0c;如果你是在读大学生&#xff0c;高中英语单词过关了&#xff0c;准备考CET-4&#xff0c;那么侧重下面的增量词表的学习&#xff0c;也算是一条捷径吧 ^_^ 【2】本结果数据 官…

DeepSeek详解:探索下一代语言模型

文章目录 前言一、什么是DeepSeek二、DeepSeek核心技术2.1 Transformer架构2.1.1 自注意力机制 (Self-Attention Mechanism)(a) 核心思想(b) 计算过程(c) 代码实现 2.1.2 多头注意力 (Multi-Head Attention)(a) 核心思想(b) 工作原理(c) 数学描述(d) 代码实现 2.1.3 位置编码 (…

FOC 控制笔记【三】磁链观测器

一、磁链观测器基础 1.1 什么是磁链 磁链&#xff08;magnetic linkage&#xff09;是电磁学中的一个重要概念&#xff0c;指导电线圈或电流回路所链环的磁通量。单位为韦伯&#xff08;Wb&#xff09;&#xff0c;又称磁通匝。 公式为&#xff1a; 线圈匝数 穿过单匝数的…

SpringBoot项目读取自定义的配置文件

先说使用场景: 开发时在resource目录下新建一个 config 文件夹, 在里面存放 myconf.properties 文件, 打包后这个文件会放到与jar包同级的目录下, 如下图 关键点&#xff1a;自定义的文件名(当然后缀是.properties)&#xff0c;自定义的存放路径。 主要的要求是在打包后运行过…

在PyCharm 中免费集成Amazon CodeWhisperer

CodeWhisperer 是Amazon发布的一款免费的AI 编程辅助小工具&#xff0c;可在你的集成开发环境&#xff08;IDE&#xff09;中生成实时单行或全函数代码建议&#xff0c;帮助你快速构建软件。简单来说&#xff0c;Amazon CodeWhisperer就是你写一段注释&#xff08;支持中文&…

[7-02-02].第15节:生产经验 - 消费者相关操作

Kafka笔记大纲 五、生产经验——分区的分配以及再平衡: 4.1.生产经验——分区的分配以及再平衡 4.2.参数&#xff1a; 5.4.1 Range 以及再平衡

Matlab_Simulink中导入CSV数据与仿真实现方法

前言 在Simulink仿真中&#xff0c;常需将外部数据&#xff08;如CSV文件或MATLAB工作空间变量&#xff09;作为输入信号驱动模型。本文介绍如何高效导入CSV数据至MATLAB工作空间&#xff0c;并通过From Workspace模块实现数据到Simulink的精确传输&#xff0c;适用于运动控制…

文件操作与IO—File类

目录 1 属性 2 构造方法 3 常用方法 4 示例代码 1 属性 修饰符与类型 属性 含义 static String pathSeparator 依赖于系统的路径分隔符&#xff0c;String类型的表示 static char pathSeparator 依赖于系统的路径分隔符&#xff0c;char类型的表示 2 构造方法 构造…

音频进阶学习二十四——IIR滤波器设计方法

文章目录 前言一、滤波器设计要求1.选频滤波器种类2.通带、阻带、过度带3.滤波器设计指标 二、IIR滤波器的设计过程1.设计方法2.常见的模拟滤波器设计1&#xff09;巴特沃斯滤波器&#xff08;Butterworth Filter&#xff09;2&#xff09;切比雪夫滤波器&#xff08;Chebyshev…

MVC编程

MVC基本概述 例子——显示本地文件系统结构 先分别拖入ListView,TableView,TreeView 然后在进行布局 在widget.cpp 结果 mock测试 1&#xff0c;先加入json测试对象 2.创建后端目录 3&#xff0c;在src添加新文件 在models文件夹里 在mybucket.h,添加测试用例的三个字段 4.在…

Qt进阶开发:对象树与拥有权

文章目录 一、对象树的概念二、对象拥有权&#xff08;Ownership&#xff09;三、Qt Widgets 中的特殊情况四、对象树与拥有权的实例 一、对象树的概念 在 Qt 中&#xff0c;对象树&#xff08;Object Tree&#xff09;与对象的拥有权&#xff08;Ownership&#xff09;密切相…

Django:构建高性能Web应用

引言&#xff1a;为何选择Django&#xff1f; 在当今快速发展的互联网时代&#xff0c;Web应用的开发效率与可维护性成为开发者关注的核心。Django作为一款基于Python的高级Web框架&#xff0c;以其"开箱即用"的特性、强大的ORM系统、优雅的URL路由设计&#xff0c;…

C语言基础系列【32】指针进阶5:指针与常量

博主介绍&#xff1a;程序喵大人 35- 资深C/C/Rust/Android/iOS客户端开发10年大厂工作经验嵌入式/人工智能/自动驾驶/音视频/游戏开发入门级选手《C20高级编程》《C23高级编程》等多本书籍著译者更多原创精品文章&#xff0c;首发gzh&#xff0c;见文末&#x1f447;&#x1f…