前言:最近在學(xué)習(xí)一些rk3588相關(guān)的東西,趁著這個(gè)項(xiàng)目,把學(xué)習(xí)的相關(guān)東西整合下,放到一個(gè)項(xiàng)目里面,鞏固學(xué)習(xí)的知識(shí)。
項(xiàng)目名稱:yolov5識(shí)別圖像、ffmpeg發(fā)送到rtmp服務(wù)器
功能:1、opencv讀取usb攝像頭,使用RK3588自帶的NPU推理yolov5s算法,識(shí)別圖像
? ? ? ? ? ?2、使用ffmpeg,將處理的圖像進(jìn)行壓縮成h264格式,發(fā)送到rtmp服務(wù)器上。?
2023.3.4補(bǔ)充:
? ? ? ? 這兩天搞了一下OpenCL相關(guān)的,順帶在rk3588上運(yùn)行了一下。對(duì)項(xiàng)目的圖像轉(zhuǎn)化部分做了一個(gè)調(diào)整。以前用的是OpenCV提供API,將BGR轉(zhuǎn)化為RBG格式,現(xiàn)在用OpenCL調(diào)用GPU轉(zhuǎn)化。自己寫的用CPU完成這個(gè)功能的代碼,運(yùn)行時(shí)間大概在11.09317ms,調(diào)用GPU運(yùn)行的時(shí)間平均在2.15199ms(均調(diào)用100次,取平均值),速度還是有比較大的提升,GPU在大規(guī)模數(shù)據(jù)運(yùn)算效率是高不少。這里放下GPU運(yùn)行的內(nèi)核函數(shù)代碼,寫的很簡單,供參考。
/*
功能:使用GPU,見BGR像素轉(zhuǎn)化為RGB格式
dst_img_buffer: 轉(zhuǎn)化好的圖像存放緩存區(qū),RGB格式
src_img_buffer: 原始圖像,BGR格式
img_w: 圖像寬
img_h: 圖像高,代碼未用到
無返回值
*/
__kernel void bgr2rgb(
__global unsigned char* dst_img_buffer,
__global const unsigned char* src_img_buffer,
const int img_w,
const int img_h)
{
int w = get_global_id(0);
int h = get_global_id(1);
dst_img_buffer[(h * img_w + w) * 3 + 0] = src_img_buffer[(h * img_w + w) * 3 + 2];
dst_img_buffer[(h * img_w + w) * 3 + 1] = src_img_buffer[(h * img_w + w) * 3 + 1];
dst_img_buffer[(h * img_w + w) * 3 + 2] = src_img_buffer[(h * img_w + w) * 3 + 0];
}
在makefile里面添加OpenCL相關(guān)的部分。
OPENCL_LDLIBS = -lmali
OPENCL_LDLIBS_PATH = -L/usr/lib/aarch64-linux-gnu
?以后有時(shí)間再更新OpenCL部分。
一、環(huán)境搭建
? ? ? ? 本次用到的組件有opencv、ffmpeg、npu相關(guān)的庫,因此,需要先安裝環(huán)境。
1、rk3588固件
? ? ? ? 筆者這里用的系統(tǒng)固件是RK官網(wǎng)的ubuntu固件,名字為:ROC-RK3588S-PC_Ubuntu20.04-Gnome-r2202_v1.0.4b_221118.7z。使用官方提供的下載工具?RKDevTool_Release_v2.84下載固件到板子里面。具體方式不說了,參考一下官方的資料下栽進(jìn)去即可。
2、opencv編譯
? ? ? ? opencv是圖像處理用到的比較多的一個(gè)開源庫。在官方的資料里面,可以通過交叉編譯,編譯出來opencv庫,筆者電腦實(shí)在拉跨,rk的sdk編譯不出來,因此,就直接在rk3588板子里面編譯opencv,不得不說,rk3588性能確實(shí)強(qiáng),編譯opencv這種庫,一會(huì)就好了,好像比我虛擬機(jī)的ubuntu系統(tǒng)編譯的還要快。筆者這里用的是opencv-4.5.4.zip這個(gè)版本的opencv。準(zhǔn)備好源碼之后,開始編譯。具體如下:
? ? ? ? 1)安裝必要的庫
sudo apt-get install build-essential? cmake ?cmake-gui g++ ?pkg-config libgtk2.0-dev?
? ? ? ? 2)解壓源碼、進(jìn)入對(duì)應(yīng)的目錄
mkdir build
cd build
cmake ?-D CMAKE_BUILD_TYPE=RELEASE ?-D WITH_TBB=ON ?-D WITH_V4L=ON -D WITH_GTK=ON -D WITH_OPENGL=ON? ..
make -j16 && make install
我這里安裝到默認(rèn)的目錄下,也可以使用-D CMAKE_INSTALL_PREFIX=../install安裝到指定的目錄下面。opencv參考網(wǎng)上的博客安裝下就行了,遇到的問題,網(wǎng)上基本都有解決方案。
3、ffmpeg安裝
? ? ? ? 本次使用的壓縮格式是h264,ffmpeg里面沒有帶相關(guān)的源碼,因此,在ffmpeg編譯之前,需要先編譯libx264庫。準(zhǔn)備好libx264源碼,筆者用到的是x264.tar.bz2。這里直接給出configure配置,同樣是直接安裝到默認(rèn)目錄,需要的話,可以使用--prefix=../install指定對(duì)應(yīng)的目錄。
./configure --enable-shared --enable-static --disable-cli --enable-pic
make -j16 &&? make install
編譯ffmpeg之前,還需要編譯openssl,本次用到的版本是openssl-3.1.0-alpha1.tar.gz。rk3588上,直接
?./configure?
?make -j16? &&? sudo make install
ffmpeg類似,本次用到的版本是ffmpeg-snapshot.tar.bz2。給出configure
./configure --target-os=linux --arch=arm64 ?--enable-shared --enable-ffmpeg --enable-pthreads --enable-libx264 --enable-libsrt --enable-gpl?
?make -j16? &&? sudo make install
????????編譯的時(shí)候會(huì)遇到一些問題,請(qǐng)百度,參考其他的博主的,寫這篇文章的時(shí)候,項(xiàng)目已經(jīng)做好了,具體有哪些問題也記不太清了,百度上都有解決方法。若是遇到有些庫找不到路徑,可以添加鏈接路徑,或者建立軟鏈接都可以。環(huán)境上的問題基本上都比較容易解決。若是rk3588里面缺少了其他的庫,請(qǐng)對(duì)照網(wǎng)上的教程安裝。
4、rtmp服務(wù)器安裝
? ? ? ? 網(wǎng)上很多rtmp服務(wù)器的安裝,這里給一個(gè)博客鏈接,供小伙伴參考。
nginx搭建rtmp服務(wù)器_普通網(wǎng)友的博客-CSDN博客
搭建好nginx(虛擬機(jī)ubuntu),編譯好ffmpeg(rk3588開發(fā)板)之后,可以使用如下指令測試在rk3588板子上是否能夠正常運(yùn)行。
ffmpeg -re -stream_loop -1 -i 1.mp4 -vcodec libx264 -acodec aac -f flv rtmp://192.168.1.102:1935/live/test
其中,1.mp4是測試視頻,使用h264編碼(用到了libx264),192.168.1.100是筆者局域網(wǎng)的服務(wù)器ip,1935是端口號(hào)。使用ffplay等帶拉流的軟件,輸入
ffplay rtmp://192.168.1.100:1935/live/test,正常情況下可以看到音視頻流。
ffmpeg指令推流界面
?ffplay播放?
二、代碼流程
? ? ? ?這里,基本知識(shí)如h264編碼等不談,若是需要相關(guān)背景知識(shí),請(qǐng)參考網(wǎng)上其他博主的文章。本篇博文主要從代碼角度,談?wù)勗趺磳?shí)現(xiàn)功能并給出參考代碼。
????????代碼流程如圖。????????
?????????首先,需要初始化ffmpeg、opencv、npu相關(guān)部分。ffmpeg編程的時(shí)候,需要配置一些參數(shù),輸入流使用的是opencv打開的攝像頭。需要初始化、打開編碼器、初始化上下文。
video->codec = avcodec_find_encoder(AV_CODEC_ID_H264);
if (!video->codec) {
printf("Codec '%s' not found\n", "h264");
return -1;
}
video->codec_ctx = avcodec_alloc_context3(video->codec);
if (!video->codec_ctx) {
printf("codec_ctx alloc fail\n");
return -1;
}
video->codec_ctx->width = width; // 設(shè)置編碼視頻寬度
video->codec_ctx->height = height; // 設(shè)置編碼視頻高度
video->codec_ctx->bit_rate = 50 * 1024 * 8; //50kb
video->codec_ctx->codec_id = video->codec->id;
video->codec_ctx->thread_count = 8;
video->codec_ctx->time_base.num = 1;
video->codec_ctx->time_base.den = fps; // 設(shè)置幀率,num為分子,den為分母,如果是1/25則表示25幀/s
video->codec_ctx->framerate.num = fps;
video->codec_ctx->framerate.den = 1;
video->codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P; // 設(shè)置輸出像素格式
//畫面組的大小,多少幀一個(gè)關(guān)鍵幀
video->codec_ctx->gop_size = 50;
video->codec_ctx->max_b_frames = 0;
ret = avcodec_open2(video->codec_ctx, video->codec, NULL);
if (ret < 0){
printf("open codec fail\n");
return -1;
}
????????攝像頭輸入的是YUV格式,openCV打開的攝像頭,輸入的則是BGR格式,用ffmpeg轉(zhuǎn)碼的時(shí)候需要將BGR轉(zhuǎn)化為YUV420P格式。使用ffmpeg,定義一個(gè)轉(zhuǎn)化算法。
// 創(chuàng)建視頻重采樣上下文:指定源和目標(biāo)圖像分辨率、格式
video->swsCtx = sws_getContext(width, height, AV_PIX_FMT_BGR24,
width, height, AV_PIX_FMT_YUV420P,
SWS_BICUBIC,NULL, NULL, NULL);
????????代碼中,video是筆者自定義的一個(gè)結(jié)構(gòu)體,用于管理代碼。上面width、height是輸入圖像的格式,輸入格式為BGR,下面width、height是輸出圖像的格式,輸入格式為YUV420P。同時(shí),需要?jiǎng)?chuàng)建兩個(gè)視頻幀,用于保存視頻幀數(shù)據(jù),一個(gè)是BGR,一個(gè)YUV420P。video結(jié)構(gòu)體:
//定義輸入流,一般使用攝像頭
struct input_video_stream{
//使用opencv打開輸入流
VideoCapture cap; // capture
Mat img;
int width, height; //寬高
int fps; //幀率
AVFormatContext *fmt_ctx;
};
//定義輸出流
struct output_video_stream{
const AVOutputFormat *fmt;
AVFormatContext *fmt_ctx;
AVStream *stream;
AVPacket *packet;
const AVCodec *codec; //編碼器
};
struct h_video
{
struct input_video_stream input_video_stream;
struct output_video_stream output_video_stream;
const AVCodec *codec; //編碼器
AVCodecContext *codec_ctx; // 給編碼器分配內(nèi)存,返回對(duì)應(yīng)編碼器上下文
SwsContext *swsCtx; //用于轉(zhuǎn)化視頻格式
AVFrame *rgbFrame; //存放RGB格式的數(shù)據(jù)幀
AVFrame *yuvFrame; //存放YUV格式的數(shù)據(jù)幀
AVPacket *pkt; //packet包,存放處理過的壓縮數(shù)據(jù)
};
????????struct input_video_stream、struct output_video_stream、struct h_video筆者是定義的用于管理相關(guān)資源。
? ? ? ? BGR、YUV420P幀均需要地方存放,需要初始化兩個(gè)視頻幀。
//創(chuàng)建BGR視頻幀
video->rgbFrame = av_frame_alloc();
video->rgbFrame->format = AV_PIX_FMT_BGR24;
video->rgbFrame->width = width;
video->rgbFrame->height = height;
ret = av_frame_get_buffer(video->rgbFrame, 32);
//創(chuàng)建YUV視頻幀并配置
video->yuvFrame = av_frame_alloc();
video->yuvFrame->format = AV_PIX_FMT_YUV420P;
video->yuvFrame->width = width;
video->yuvFrame->height = height;
ret = av_frame_get_buffer(video->yuvFrame, 32);
packet用于存放轉(zhuǎn)碼之后的視頻數(shù)據(jù)。
video->pkt = av_packet_alloc();
if (!video->pkt){
printf("pkt alloc fail\n");
return -1;
}
av_init_packet(video->pkt);
ffmpeg處理的代碼基本上就這些。
初始化、配置npu部分,參考了rk的例程。
這里直接放我封裝的調(diào)用rknn識(shí)別圖像的類代碼吧,
頭文件
#ifndef __DETECET_H
#define __DETECET_H
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <iostream>
#include <sstream>
#define _BASETSD_H
#include "RgaUtils.h"
#include "im2d.h"
#include <opencv2/opencv.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/videoio.hpp>
#include "postprocess.h"
#include "rga.h"
#include "rknn_api.h"
#define PERF_WITH_POST 1
using namespace cv; //OpenCV標(biāo)準(zhǔn)庫
class detect{
public:
rknn_context ctx;
rknn_sdk_version version;
rknn_input_output_num io_num;
struct timeval start_time, stop_time;
size_t actual_size = 0;
int img_width;
int img_height;
int img_channel;
const float nms_threshold = NMS_THRESH;
const float box_conf_threshold = BOX_THRESH;
rga_buffer_t src;
rga_buffer_t dst;
im_rect src_rect;
im_rect dst_rect;
char *model_path;
detect(char* model_name);
~detect();
int rknn_envs_init(const char* model_path);
int rknn_envs_free();
int detect_image(Mat &orig_img, detect_result_group_t *detect_result_group);
int draw_results(Mat &orig_img, detect_result_group_t *detect_result_group);
private:
int model_data_size;
unsigned char *model_data;
unsigned char* load_data(FILE* fp, size_t ofst, size_t sz);
unsigned char* load_model(const char* filename, int* model_size);
};
#endif
cpp文件
#include "./include/detect.h"
using namespace cv; //OpenCV標(biāo)準(zhǔn)庫
using namespace std; //C++標(biāo)準(zhǔn)程序庫中的所有標(biāo)識(shí)符都被定義于一個(gè)名為std的namespace中
detect::detect(char* model_path){
int ret = 0;
memset(&src_rect, 0, sizeof(src_rect));
memset(&dst_rect, 0, sizeof(dst_rect));
memset(&src, 0, sizeof(src));
memset(&dst, 0, sizeof(dst));
this->model_path = model_path;
}
detect::~detect(){
}
/**************************************************************************
功能: 內(nèi)部使用,讀取rknn格式的模型文件數(shù)據(jù)
參數(shù)說明
fp:文件句柄
ofst: 偏移量
sz: 模型大小
返回值: 0表示成功
***************************************************************************/
unsigned char* detect::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;
}
/**************************************************************************
功能: 內(nèi)部使用,加載rknn格式的模型文件
參數(shù)說明
filename:模型路徑
model_size: 模型大小
返回值: 0表示成功
***************************************************************************/
unsigned char* detect::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;
}
/**************************************************************************
功能: 初始化rknn模型的運(yùn)行環(huán)境
參數(shù)說明
model_path:rknn格式的模型路徑
返回值: 0表示成功
***************************************************************************/
int detect::rknn_envs_init(const char* model_path)
{
int ret = 0;
/* 加載rknn文件,創(chuàng)建網(wǎng)絡(luò) */
model_data_size = 0;
model_data = load_model(model_path, &model_data_size);
ret = rknn_init(&ctx, model_data, model_data_size, 0, NULL);
if (ret < 0) {
printf("rknn_init error ret=%d\n", ret);
return -1;
}
ret = rknn_query(ctx, RKNN_QUERY_SDK_VERSION, &version, sizeof(rknn_sdk_version));
if (ret < 0) {
printf("rknn_query RKNN_QUERY_SDK_VERSION error ret=%d\n", ret);
return -1;
}
// printf("sdk version: %s driver version: %s\n", version.api_version, version.drv_version);
ret = rknn_query(ctx, RKNN_QUERY_IN_OUT_NUM, &io_num, sizeof(io_num));
if (ret < 0) {
printf("rknn_init RKNN_QUERY_IN_OUT_NUM error ret=%d\n", ret);
return -1;
}
return 0;
}
/**************************************************************************
功能: 識(shí)別之后的圖像,在原始圖像上繪制結(jié)果方框
參數(shù)說明
orig_img:待繪制圖像,原始圖像
detect_result_group: 存放模型推理輸出的結(jié)果
返回值: 0表示成功
***************************************************************************/
int detect::draw_results(Mat &orig_img, detect_result_group_t *detect_result_group){
char text[256];
//printf("count: %d\n", detect_result_group.count);
for (int i = 0; i < detect_result_group->count; i++) { //處理推理結(jié)果
detect_result_t* det_result = &(detect_result_group->results[i]);
sprintf(text, "%s %.1f%%", det_result->name, det_result->prop * 100);
printf("name: %s @ size:(%d %d %d %d) %f\n", det_result->name, det_result->box.left, det_result->box.top,
det_result->box.right, det_result->box.bottom, det_result->prop);
int x1 = det_result->box.left;
int y1 = det_result->box.top;
int x2 = det_result->box.right;
int y2 = det_result->box.bottom;
rectangle(orig_img, cv::Point(x1, y1), cv::Point(x2, y2), cv::Scalar(255, 0, 0, 255), 3);
putText(orig_img, text, cv::Point(x1, y1 + 12), cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 0, 0));
}
imshow("窗口", orig_img);
cv::waitKey(1);
return 0;
}
/**************************************************************************
功能: 將傳入的圖像進(jìn)行識(shí)別,結(jié)果保存到detect_result_group
參數(shù)說明
orig_img:待識(shí)別圖像,分辨率任意,最好大于 [640 × 640]
detect_result_group: 存放模型推理輸出的結(jié)果
返回值: 0表示成功
***************************************************************************/
int detect::detect_image(Mat &orig_img, detect_result_group_t *detect_result_group)
{
int ret = 0;
void* resize_buf = nullptr;
rknn_tensor_attr input_attrs[io_num.n_input]; //存放輸入?yún)?shù)
//先對(duì)傳來的圖像進(jìn)行處理
Mat img; //用于NPU推理的圖像
Mat tImg; //用于圖像的轉(zhuǎn)化
cvtColor(orig_img, tImg, cv::COLOR_BGR2RGB);
resize(tImg, img, Size(640, 640), 0, 0, cv::INTER_NEAREST); //模型的輸入圖像分辨率為[640 × 640], 原始圖像需要縮放一次
img_width = img.cols;
img_height = img.rows;
memset(input_attrs, 0, sizeof(input_attrs));
for (int i = 0; i < io_num.n_input; i++) {
input_attrs[i].index = i;
ret = rknn_query(ctx, RKNN_QUERY_INPUT_ATTR, &(input_attrs[i]), sizeof(rknn_tensor_attr));
if (ret < 0) {
printf("rknn_init error ret=%d\n", ret);
return -1;
}
}
rknn_tensor_attr output_attrs[io_num.n_output]; //存放輸出參數(shù)
memset(output_attrs, 0, sizeof(output_attrs));
for (int i = 0; i < io_num.n_output; i++) {
output_attrs[i].index = i;
ret = rknn_query(ctx, RKNN_QUERY_OUTPUT_ATTR, &(output_attrs[i]), sizeof(rknn_tensor_attr));
}
//模型的輸入通道數(shù)、寬、高
int channel = 3;
int width = 0;
int height = 0;
if (input_attrs[0].fmt == RKNN_TENSOR_NCHW) { //輸入的通道數(shù)、寬、高
channel = input_attrs[0].dims[1];
width = input_attrs[0].dims[2];
height = input_attrs[0].dims[3];
} else {
width = input_attrs[0].dims[1];
height = input_attrs[0].dims[2];
channel = input_attrs[0].dims[3];
}
rknn_input inputs[1]; //存放輸入圖像相關(guān)參數(shù)
memset(inputs, 0, sizeof(inputs));
inputs[0].index = 0;
inputs[0].type = RKNN_TENSOR_UINT8;
inputs[0].size = width * height * channel;
inputs[0].fmt = RKNN_TENSOR_NHWC;
inputs[0].pass_through = 0;
if (img_width != width || img_height != height) { //長寬和模型輸入不一致,resize一次
resize_buf = malloc(height * width * channel);
memset(resize_buf, 0x00, height * width * channel);
src = wrapbuffer_virtualaddr((void*)img.data, img_width, img_height, RK_FORMAT_RGB_888);
dst = wrapbuffer_virtualaddr((void*)resize_buf, width, height, RK_FORMAT_RGB_888);
ret = imcheck(src, dst, src_rect, dst_rect);
if (IM_STATUS_NOERROR != ret) {
printf("%d, check error! %s", __LINE__, imStrError((IM_STATUS)ret));
return -1;
}
inputs[0].buf = resize_buf; //
} else {
inputs[0].buf = (void*)img.data; //存放圖像數(shù)據(jù)
}
gettimeofday(&start_time, NULL); //開始時(shí)間
rknn_inputs_set(ctx, io_num.n_input, inputs); //設(shè)置NPU的輸入,
rknn_output outputs[io_num.n_output]; //存放輸出結(jié)果
memset(outputs, 0, sizeof(outputs));
for (int i = 0; i < io_num.n_output; i++) {
outputs[i].want_float = 0;
}
ret = rknn_run(ctx, NULL); //使用模型推理
ret = rknn_outputs_get(ctx, io_num.n_output, outputs, NULL); //獲得模型結(jié)果
gettimeofday(&stop_time, NULL); //結(jié)束時(shí)間
// 后處理
float scale_w = (float)width / img_width;
float scale_h = (float)height / img_height;
scale_w = (float)width / orig_img.cols;
scale_h = (float)height / orig_img.rows;
std::vector<float> out_scales;
std::vector<int32_t> out_zps;
for (int i = 0; i < io_num.n_output; ++i) {
out_scales.push_back(output_attrs[i].scale);
out_zps.push_back(output_attrs[i].zp);
}
//將模型推理的結(jié)果存放到detect_result_group
post_process((int8_t*)outputs[0].buf, (int8_t*)outputs[1].buf, (int8_t*)outputs[2].buf, height, width,
box_conf_threshold, nms_threshold, scale_w, scale_h, out_zps, out_scales, detect_result_group);
ret = rknn_outputs_release(ctx, io_num.n_output, outputs);
return 0;
}
/**************************************************************************
功能: 釋放資源
參數(shù)說明 無
返回值: 0表示成功
***************************************************************************/
int detect::rknn_envs_free(){
//釋放資源
int ret = 0;
// release
ret = rknn_destroy(ctx);
if (model_data) {
free(model_data);
}
}
代碼都比較簡單,注釋也比較詳細(xì)。不懂的可以私聊博主。
detect類使用的時(shí)候,初始化一下環(huán)境,?
detect.rknn_envs_init(detect.model_path);
然后就可以使用NPU推理圖像了,傳入?yún)?shù)為openCV的Mat格式圖像,結(jié)果保存在detect_result_group。
detect_result_group_t detect_result_group;
detect.detect_image(orig_img, &detect_result_group);
最后,就是將圖像進(jìn)行編碼成h264格式、發(fā)送給rtmp服務(wù)器了,將關(guān)鍵代碼貼出。
uint8_t *src_data[4];
int src_linesize[4];
//BGR24--->YUV420
av_image_fill_arrays(src_data, src_linesize, orig_img.data, AV_PIX_FMT_BGR24, orig_img.cols, orig_img.rows, 1);
cv::Size frameSize = orig_img.size();
int cvLinesizes[1];
cvLinesizes[0] = orig_img.step1();
av_image_copy(h_video.rgbFrame->data, h_video.rgbFrame->linesize, (const uint8_t **)src_data, src_linesize, AV_PIX_FMT_BGR24, orig_img.cols, orig_img.rows);
sws_scale(h_video.swsCtx, &orig_img.data, cvLinesizes, 0, height, h_video.yuvFrame->data, h_video.yuvFrame->linesize);
h_video.yuvFrame->pts = i++;
video_encode(&h_video.output_video_stream,h_video.codec_ctx, h_video.yuvFrame,h_video.pkt);
video_encode代碼在下面。
int video_encode(struct output_video_stream *out_stream,AVCodecContext *enc_ctx, AVFrame *frame, AVPacket *pkt)
{
int ret;
/* send the frame to the encoder */
if (frame)
printf("Send frame %lld\n", frame->pts);
ret = avcodec_send_frame(enc_ctx, frame);
if (ret < 0) {
printf("Error sending a frame for encoding\n");
return -1;
}
while (ret >= 0) {
ret = avcodec_receive_packet(enc_ctx, pkt);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF){
return -1;
} else if (ret < 0) {
printf("Error during encoding\n");
return -1;
}
//推流
pkt->pts = av_rescale_q(pkt->pts,out_stream->stream->time_base,out_stream->stream->time_base);
pkt->dts = av_rescale_q(pkt->dts,out_stream->stream->time_base,out_stream->stream->time_base);
// 往輸出流寫入數(shù)據(jù)
av_interleaved_write_frame(out_stream->fmt_ctx, pkt);
av_packet_unref(pkt);
}
}
????????整個(gè)項(xiàng)目做的都比較簡陋,供大家學(xué)習(xí)參考吧,路過的大佬請(qǐng)輕噴,筆者只是個(gè)初學(xué)者。
????????代碼部分參考了rk給的npu例子和網(wǎng)上各路大神的代碼,在此一并感謝。
????????筆者編譯用的是makefile進(jìn)行編譯程序,這里給出筆者的makefile部分,供小伙伴參考。
TARGET_NAME=app
CPPFLAGS = -g -fpermissive -std=c++11 -Wall -static
CPP = g++
CPPFILES = main.cpp video.cpp detect.cpp postprocess.cpp
LDLIBS :=
LDLIBS_PATH:=
INCS_PATH:=
RKNN_LDLIBS = -ldl -lmpimmz -lrga -lrknn_api -lrknnrt
RKNN_LDLIBS_PATH = -L./lib
RKNN_INCS_PATH = -I./include
OPENCV_LDLIBS = -lopencv_calib3d -lopencv_core -lopencv_dnn -lopencv_features2d -lopencv_flann -lopencv_gapi -lopencv_highgui -lopencv_imgcodecs -lopencv_imgproc -lopencv_ml -lopencv_objdetect -lopencv_photo -lopencv_stitching -lopencv_video -lopencv_videoio
OPENCV_INCS_PATH = -I/usr/local/include/opencv4
FFMPEG_LDLIBS = -lavformat -lavdevice -lavcodec -lavutil -lswresample -lavfilter -lpostproc -lswscale -lSDL2
LDLIBS += $(FFMPEG_LDLIBS) $(OPENCV_LDLIBS) $(RKNN_LDLIBS)
LDLIBS_PATH += -L/usr/local/lib $(RKNN_LDLIBS_PATH)
INCS_PATH += $(OPENCV_INCS_PATH) -I/usr/local/include $(RKNN_INCS_PATH)
all:$(TARGET_NAME)
$(TARGET_NAME):
$(CPP) -o ${TARGET_NAME} ${CPPFILES} ${INCS_PATH} ${LDLIBS_PATH} ${LDLIBS}
@echo "end"
clean:
rm -rf *.o $(TARGET_NAME)
三、結(jié)果
????????在rk3588上運(yùn)行程序,輸入./app "rtmp://192.168.1.100:1935/live/test"? 。在Ubuntu上輸入?ffplay rtmp://192.168.1.100:1935/live/test,測試效果。ffplay拉流界面
?這里是遠(yuǎn)程顯示的圖像。藍(lán)色的框框,是將識(shí)別到的人圈出來,用到的模型是之前訓(xùn)練出來的。
模型訓(xùn)練可以參考筆者之前的博文。
Yolo v5訓(xùn)練并移植到RK3588S平臺(tái)_rk3588 yolov5_紫川寧520的博客-CSDN博客
????????總結(jié):整個(gè)項(xiàng)目,從難度角度來看,其實(shí)都比較簡單,沒有用到特別復(fù)雜的東西,但牽扯到的東西比較多,有些很零碎,有些折騰起來很繁瑣。程序運(yùn)行的效果,只能說還行吧,有點(diǎn)卡頓,后面有時(shí)間再考慮優(yōu)化的問題了。
? ? ??文章來源:http://www.zghlxwxcb.cn/news/detail-779721.html
??文章來源地址http://www.zghlxwxcb.cn/news/detail-779721.html
到了這里,關(guān)于RK3588實(shí)戰(zhàn):調(diào)用npu加速,yolov5識(shí)別圖像、ffmpeg發(fā)送到rtmp服務(wù)器的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!