RK3588部署YOLOv8(2):OpenCV和RGA实现模型前处理对比

目录

前言

[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](https://github.com/airockchip/librga "https://github.com/airockchip/librga") \[2\] [rga_resize_demo.cpp](https://github.com/airockchip/librga/blob/main/samples/resize_demo/src/rga_resize_demo.cpp "rga_resize_demo.cpp") \[3\] [rga_padding_demo.cpp](https://github.com/airockchip/librga/blob/main/samples/padding_demo/src/rga_padding_demo.cpp "rga_padding_demo.cpp") ### 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 #include #include #include #include #include "opencv2/opencv.hpp" #include "common.h" // 增加RGA库实现pad resize #include #include #include #include #include // 打印时间 #include #include #include // 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 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(target_size) / std::max(input_width_, input_height_); // ------------------【Step2:计算缩放后尺寸】---------------------------- new_width_ = static_cast(input_width_ * scale_); new_height_ = static_cast(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(target_width_) / input_width_; double height_scale = static_cast(target_height_) / input_height_; // 取较小的缩放比例 scale_ = std::min(width_scale, height_scale); // ------------------【Step2:计算缩放后尺寸】---------------------------- new_width_ = static_cast(input_width_ * scale_); new_height_ = static_cast(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 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 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 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 ```

相关推荐
元亓亓亓几秒前
LeetCode热题100--53.最大子数组和--中等
数据结构·算法·leetcode
白熊1883 分钟前
【计算机视觉】目标检测:深度解析YOLOv9:下一代实时目标检测架构的创新与实战
目标检测·计算机视觉·架构
大数据在线4 分钟前
当向量数据库与云计算相遇:AI应用全面提速
人工智能·云计算·向量数据库·亚马逊云科技·zilliz
stevenzqzq6 分钟前
编程中如何与AI交互-结构化输入和理解确认机制
人工智能·交互
图像僧36 分钟前
Opencv中图像深度(Depth)和通道数(Channels)区别
opencv
高峰君主40 分钟前
生成式AI全栈入侵:当GPT-4开始自动编写你的Next.js路由时,人类开发者该如何重新定义存在价值?
人工智能
J先生x1 小时前
【开源项目】基于sherpa-onnx的实时语音识别系统 - LiveASR
人工智能·语音识别
火星资讯1 小时前
“兴火·燎原”总冠军诞生,云宏信息《金融高算力轻量云平台》登顶
人工智能·科技
ttk2191 小时前
【算法练习】归并排序和归并分治
数据结构·c++·算法·排序算法
情报员0071 小时前
Java练习6
java·算法·排序算法