目录
[1. 结果对比](#1. 结果对比)
[1.1 时间对比](#1.1 时间对比)
[1.2 CPU和NPU占用对比](#1.2 CPU和NPU占用对比)
[2. RGA实现YOLO前处理](#2. RGA实现YOLO前处理)
[2.1 实现思路](#2.1 实现思路)
[2.2 处理类的声明](#2.2 处理类的声明)
[2.3 处理类的实现](#2.3 处理类的实现)
前言
RK平台上有RGA (Raster Graphic Acceleration Unit) 加速,使用RGA可以减少资源占用、加速图片处理速度。因此,在部署YOLOv8是针对RGA和OpenCV的分别进行了实现,并对性能、速度和资源占用进行对比。
1. 结果对比
1.1 时间对比
总共跑100次计算平均时间。
纯OpenCV实现:
TypeScript
OPencv实现resize+pad
[Convert] Step1: Check input pointer => 0 us
[Convert] Step2: define intermediate Mat => 37 us
[Convert] Step3: cv::resize => 9564 us
[Convert] Step4: create pad_img => 1629 us
[Convert] Step5: compute position => 61 us
[Convert] Step6: copyTo => 340 us
[Convert] Step7: return => 54 us
INFO: image resize time 12.15 ms
INFO: total infer time 22.71 ms: model time is 22.45 ms and postprocess time is 0.26ms
Iteration 100 - time: 35.034000 ms
Total execution time: 3770.930000 ms
Average execution time: 37.709300 ms
纯RGA实现:
TypeScript
RGA实现
[Convert] Step1: Check input pointer => 1 us
[Convert] Step2: Set format/bpp => 92 us
[Convert] Step3: Calculate buffer sizes => 13 us
[Convert] Step4: Define variables => 12 us
[Convert] Step5: Compute border => 13 us
[Convert] Step6: Alloc & memcpy src => 10048 us
[Convert] Step7: Alloc resized buffer => 477 us
[Convert] Step8: Alloc dst buffer => 269 us
[Convert] Step9: importbuffer_fd => 3494 us
[Convert] Step10: wrapbuffer_handle => 80 us
[Convert] Step11: imresize => 2714 us
[Convert] Step12: immakeBorder => 1154 us
[Convert] Step13: copy result => 428 us
[Convert] Step14: cleanup => 2607 us
INFO: image resize time 24.26 ms
INFO: total infer time 22.10 ms: model time is 21.84 ms and postprocess time is 0.26ms
Iteration 100 - time: 46.496000 ms
Total execution time: 4398.143000 ms
Average execution time: 43.981430 ms
总结:
(1)从上可以看到OpenCV最占时间的是resize步骤,需要9~10ms,而RGA只要2~3ms。
(2)但使用RGA,如果图片数据不在DMA缓冲区,则需要进行拷贝,导致耗时太久。
(3)最终,导致RGA实现resize要比OpenCV慢了6~7ms。
1.2 CPU和NPU占用对比
跑单个模型持续推理。
纯OpenCV实现:
CPU占用率:120%~140%
NPU占用率:43%~48%

纯RGA实现:
CPU占用率:50%~60%
NPU占用率:35%~40%

总结:
(1)OPenCV使用CPU多线程计算差值,导致CPU占用率较高。
(2)RGA在DMA缓冲区使用硬件计算,减少对CPU依赖。
(3)RGA比OpenCV减少了60%的CPU占用和10%的NPU占用。
2. RGA实现YOLO前处理
参考代码:
[1] https://github.com/airockchip/librga
2.1 实现思路
在DMA缓冲区,每有一个形状,便需要一个CPU指针、句柄和缓冲区,导致分配内存、处理起来极其麻烦,且宽度需要16倍对齐(高度没有要求)、同时还存在着4G范围寻址问题。所以,实际应用要起来还是很麻烦的(尤其是自定义的任意尺寸输入)。
所以,这里仅使用RGA对合规的图片做resize,再使用OpenCV做pad填充。
由于工资预算有限,这里仅实现摄像头输入的原图尺寸(宽度满足16倍长,主要以1920×1080为主)的图片的前处理。
补充(实现任意尺寸处理的思路):
TypeScript
(1) 设传入的宽和高为:[H,W],目标宽高(模型输入大小)为:[H_T, W_T],INT_UP表示向上取整函数。
(2) 原16倍宽度 W_16 为: INT_UP(W / 16) * 16,则现将原图先填充(仅右边填充)到尺寸[H, W_16]。
(3) 计算放缩比 R = min(H / H_T, W_16 / W_t)。
(4) 继续计算得到目标放缩尺寸宽度的16倍 W_T_16 为:INT_UP(W_T_16 / 16) * 16。
(5) 然后更新放缩比 R = min(H / H_T, W_T_16 / R / W_T),此过程可能需要迭代。
(6) 得到放缩比 R 后,计算原图真正要填充到的尺寸为:[H_T / R, W_T_16 / R]。
(7) 然后对原图仅做右边和下面的填充,这样就把原图对齐到16倍长(放缩前后均是)。
2.2 处理类的声明
cpp
#include <iostream>
#include <memory>
#include <numeric>
#include <vector>
#include <algorithm>
#include "opencv2/opencv.hpp"
#include "common.h"
// 增加RGA库实现pad resize
#include <rga/RgaApi.h>
#include <rga/im2d.hpp>
#include <rga/rga.h>
#include <rga/RgaUtils.h>
#include <dma/dma_alloc.h>
// 打印时间
#include <sys/time.h>
#include <stdint.h>
#include <stdio.h>
// RGA 版本的 Pad Resize 处理类
class ImagePreProcessRGA {
public:
// 构造函数:输入图像为 width x height,目标尺寸为正方形 target_size x target_size
ImagePreProcessRGA(int width, int height, int target_size);
// 构造函数:输入图像为 width x height,目标尺寸为 target_width x target_height
ImagePreProcessRGA(int width, int height, int target_width, int target_height);
// 对输入图像数据进行pad resize处理,返回处理后的图像数据(unique_ptr管理)
std::unique_ptr<uint8_t[]> Convert(const det_model_input& input);
// 获取letterbox信息
const letterbox_t &get_letter_box() { return letterbox_; }
private:
double scale_; // 缩放比例
int input_width_, input_height_; // 输入图像尺寸
int real_input_width_, real_input_height_; // 输入的实际尺寸
int target_width_, target_height_; // 目标图像尺寸
int new_width_, new_height_; // 缩放后图像的尺寸(填输入的尺寸缩放后,经过填充才能变成目标尺寸)
int padding_x_, padding_y_; // pad的总尺寸(左右、上下)
letterbox_t letterbox_; // letterbox信息记录缩放比例及左右/上下填充(一般为居中填充)
};
2.3 处理类的实现
实现RGB的3通道或者4通道的图片。
cpp
// 构造函数1:只传一个target_size,默认目标是正方形
ImagePreProcessRGA::ImagePreProcessRGA(int width, int height, int target_size)
: input_width_(width), input_height_(height), target_width_(target_size), target_height_(target_size){
// ------------------【Step1:根据最大边计算放缩比例】---------------------
// 如果原图是 (width x height),目标是 (target_size x target_size),则 scale = target_size / max(width, height)
scale_ = static_cast<double>(target_size) / std::max(input_width_, input_height_);
// ------------------【Step2:计算缩放后尺寸】----------------------------
new_width_ = static_cast<int>(input_width_ * scale_);
new_height_ = static_cast<int>(input_height_ * scale_);
// ------------------【Step3:计算在目标图像中的剩余填充】------------------
padding_x_ = target_size - new_width_;
padding_y_ = target_size - new_height_;
// ------------------【Step4:更新 letterbox】--------------------------
letterbox_.scale = scale_;
letterbox_.x_pad = padding_x_ / 2;
letterbox_.y_pad = padding_y_ / 2;
// ------------------【可选:打印结果】-----------------------------------
// printf(">>> After => new_width_=%d, new_height_=%d, scale=%.3f\n", new_width_, new_height_, scale_);
// printf(">>> padding_x_=%d, padding_y_=%d\n", padding_x_, padding_y_);
}
// 构造函数2:传入独立的target_width和target_height,可能目标不是正方形
ImagePreProcessRGA::ImagePreProcessRGA(int width, int height, int target_width, int target_height)
: input_width_(width), input_height_(height), target_width_(target_width), target_height_(target_height){
// ------------------【Step1:分别计算宽高缩放比例】-----------------------
double width_scale = static_cast<double>(target_width_) / input_width_;
double height_scale = static_cast<double>(target_height_) / input_height_;
// 取较小的缩放比例
scale_ = std::min(width_scale, height_scale);
// ------------------【Step2:计算缩放后尺寸】----------------------------
new_width_ = static_cast<int>(input_width_ * scale_);
new_height_ = static_cast<int>(input_height_ * scale_);
// ------------------【Step3:计算填充大小】------------------------------
padding_x_ = target_width_ - new_width_;
padding_y_ = target_height_ - new_height_;
// ------------------【Step4:更新 letterbox】---------------------------
letterbox_.scale = scale_;
letterbox_.x_pad = padding_x_ / 2;
letterbox_.y_pad = padding_y_ / 2;
// ------------------【可选:打印结果】-----------------------------------
// printf(">>> After => new_width_=%d, new_height_=%d, scale=%.3f\n", new_width_, new_height_, scale_);
// printf(">>> padding_x_=%d, padding_y_=%d\n", padding_x_, padding_y_);
}
// 核心函数:基于RGA对输入图像进行pad resize,并返回处理后的图像数据
std::unique_ptr<uint8_t[]> ImagePreProcessRGA::Convert(const det_model_input& input){
// -------------------【Step0:在函数开头声明所有变量】-------------------
// 中间处理函数返回值
int ret = 0;
// DMA fd
int src_dma_fd = -1;
int resized_dma_fd = -1;
// CPU指针
uint8_t *src_buf = nullptr;
uint8_t *resized_buf = nullptr;
// RGA handle
rga_buffer_handle_t src_handle = 0;
rga_buffer_handle_t resized_handle = 0;
// RGA buffer
rga_buffer_t rga_src;
rga_buffer_t rga_resized;
memset(&rga_src, 0, sizeof(rga_src));
memset(&rga_resized, 0, sizeof(rga_resized));
// 最终的返回结果
std::unique_ptr<uint8_t[]> final_data;
// 其他局部变量
int bpp_src = 0;
int bpp_dst = 3; // 目标一定3通道
int src_format = 0;
int dst_format = RK_FORMAT_RGB_888;
// 源图大小和resize后大小
int src_size = 0;
int resized_size = 0;
// 用于 pad 的边界
int left=0, right=0, top=0, bottom=0;
// 常用114作为灰度
cv::Scalar pad_color(114,114,114);
// -------------------【Step1:基础检查】------------------------------------
if (!input.data || input.width <= 0 || input.height <= 0 || (input.channel != 3 && input.channel != 4)){
fprintf(stderr, "ERROR: invalid input data or channel.\n");
return nullptr;
}
// 根据通道数决定 bpp & format
bpp_src = (input.channel == 4) ? 4 : 3;
src_format = (input.channel == 4) ? RK_FORMAT_RGBA_8888 : RK_FORMAT_RGB_888;
// 源图大小
src_size = input.width * input.height * bpp_src;
// resize后大小
int out_size_w = new_width_; // 由构造函数算好
int out_size_h = new_height_; // 由构造函数算好
resized_size = out_size_w * out_size_h * bpp_dst;
std::unique_ptr<uint8_t[]> resized_cpu(new uint8_t[resized_size]);
// 用于后面 OpenCV pad
left = padding_x_ / 2;
right = padding_x_ - left;
top = padding_y_ / 2;
bottom = padding_y_ - top;
// -------------------【Step2:分配 src_buf, resized_buf】-------------------
ret = dma_buf_alloc(DMA_HEAP_DMA32_PATH, src_size, &src_dma_fd, (void**)&src_buf);
if (ret < 0 || !src_buf) {
fprintf(stderr, "ERROR: alloc src_buf failed.\n");
return nullptr;
}
ret = dma_buf_alloc(DMA_HEAP_DMA32_PATH, resized_size, &resized_dma_fd, (void**)&resized_buf);
if (ret < 0 || !resized_buf) {
fprintf(stderr, "ERROR: alloc resized_buf failed.\n");
goto cleanup;
}
// 不考虑16对齐等,只做最原始的 YOLO思路。只需将 input.data 拷贝到 src_buf
memcpy(src_buf, input.data, src_size);
// -------------------【Step3:import & wrap】-------------------------------
src_handle = importbuffer_fd(src_dma_fd, src_size);
resized_handle = importbuffer_fd(resized_dma_fd, resized_size);
if (!src_handle || !resized_handle) {
fprintf(stderr, "ERROR: importbuffer_fd failed.\n");
ret = -1;
goto cleanup;
}
rga_src = wrapbuffer_handle(src_handle, input.width, input.height, src_format);
rga_resized = wrapbuffer_handle(resized_handle, out_size_w, out_size_h, dst_format);
// -------------------【Step4:RGA仅做 resize or color convert+resize】-------------------
if (input.channel == 4) {
// RGBA => color convert => resized
IM_STATUS st_cvt = imcvtcolor(rga_src, rga_resized, RK_FORMAT_RGBA_8888, RK_FORMAT_RGB_888);
if (st_cvt != IM_STATUS_SUCCESS) {
fprintf(stderr, "ERROR: imcvtcolor failed: %s.\n", imStrError(st_cvt));
ret = -1;
goto cleanup;
}
}
else {
// channel=3 => 直接 resize
IM_STATUS st_resize = imresize(rga_src, rga_resized, 0, 0, INTER_LINEAR);
if (st_resize != IM_STATUS_SUCCESS) {
fprintf(stderr, "ERROR: imresize failed: %s.\n", imStrError(st_resize));
ret = -1;
goto cleanup;
}
}
// -------------------【Step5:将 resized_buf 拷回 CPU】-------------------
// 拿到 resize 后的 RGB 图像数据
memcpy(resized_cpu.get(), resized_buf, resized_size);
// -------------------【Step6:用OpenCV进行 pad】-------------------
{
// 1) 构造一个 cv::Mat 指向 resized_cpu
cv::Mat resized_mat(new_height_, new_width_, CV_8UC3, resized_cpu.get());
// 2) 构造一个 pad_mat (target_height_ x target_width_),初始颜色(114,114,114)
cv::Mat pad_mat(target_height_, target_width_, CV_8UC3, pad_color);
// 3) 计算在 pad_mat 中的放置位置
// left=padding_x_/2, top=padding_y_/2
cv::Rect roi(left, top, resized_mat.cols, resized_mat.rows);
// 4) 拷贝 resized_mat 到 pad_mat 对应区域
resized_mat.copyTo(pad_mat(roi));
// 5) 将 pad_mat 拷到 final_data
int final_size = target_width_ * target_height_ * 3; // 3通道
final_data.reset(new uint8_t[final_size]);
memcpy(final_data.get(), pad_mat.data, final_size);
}
cleanup:
// -------------------【Step7:释放资源】-------------------
if (src_handle) releasebuffer_handle(src_handle);
if (resized_handle) releasebuffer_handle(resized_handle);
if (src_buf) dma_buf_free(src_size, &src_dma_fd, src_buf);
if (resized_buf) dma_buf_free(resized_size,&resized_dma_fd, resized_buf);
// 若 ret!=0, 返回 nullptr
if (ret != 0) {
return nullptr;
}
// 否则返回 final_data,即 "pad后" 的图像
return final_data;
}
总结
这样实现资源占用还是约等于纯RGA实现,在推理单张图的时候,速度还可能更快一些:
TypeScript
RGA-resize+OpenCV-pad:
>>> After => new_width_=640, new_height_=360, scale=0.333
>>> padding_x_=0, padding_y_=24
[Convert] Step1: check => 9 us
Original Input => width=1920, height=1080, channel=3
Resize => new_width_=640, new_height_=360
Pad => target_width_=640, target_height_=384
[Convert] Step2: alloc => 187 us
[Convert] StepFILL => copy input => 6220800 bytes
[Convert] StepFILL => 1709 us
rga_api version 1.10.1_[0]
[Convert] Step3: wrap => src=(1920x1080), resized=(640x360)
[Convert] Step3 => 892 us
[Convert] Step4: imresize => done
[Convert] Step4 => 2294 us
[Convert] Step5: copy resized => 691200 bytes
[Convert] Step5 => 543 us
[Convert] Step6: OpenCV pad => final_size=737280
[Convert] Step6 => 1314 us
[Convert] Step7: cleanup => 648 us
INFO: image resize time 7.64 ms
INFO: total infer time 28.11 ms: model time is 27.93 ms and postprocess time is 0.18ms
Iteration 1 - time: 35.846000 ms
Total execution time: 35.846000 ms
Average execution time: 35.846000 ms
OpenCV:
INFO: image resize time 9.49 ms
INFO: total infer time 23.12 ms: model time is 23.09 ms and postprocess time is 0.03ms
Iteration 1 - time: 32.688000 ms