vim3上部署Fairmot总结(程序部分)

vim3上部署工程总结(程序部分)

2023.2.9 18:04

模型运行的程序总结

对输入图像前后处理部分


  • 前处理
  1. 设置uint8
/* set runtime options */
struct options opt;
opt.num_thread = num_thread;
opt.cluster = TENGINE_CLUSTER_ALL;
opt.precision = TENGINE_MODE_UINT8;
opt.affinity = 0;
  1. 设置后端为timvx
/* create VeriSilicon TIM-VX backend */
context_t timvx_context = create_context("timvx", 1);
int rtt = set_context_device(timvx_context, "TIMVX", nullptr, 0);
if (0 > rtt)
{
    fprintf(stderr, " add_context_device VSI DEVICE failed.\n");
    return -1;
}
  1. 设置输入buffer大小时不需要像float那样4倍空间
int img_size = letterbox_rows * letterbox_cols * img_c;
std::vector<uint8_t> input_data(img_size);
if (set_tensor_buffer(input_tensor, input_data.data(), img_size) < 0)
{
    fprintf(stderr, "Set input tensor buffer failed\n");
    return -1;
    }
  1. 输入数据要做量化处理
/* prepare process input data, set the data mem to input tensor */
float input_scale = 0.f;
int input_zero_point = 0;
get_tensor_quant_param(input_tensor, &input_scale, &input_zero_point, 1);
get_input_data_yolov4_uint8(image_file, input_data.data(), img_h, img_w, mean, scale, input_scale, input_zero_point);
  • 后处理
  1. 获取各个通道的输出
/* get output tensor */
tensor_t size_output = get_graph_output_tensor(graph, 0, 0);
tensor_t offset_output = get_graph_output_tensor(graph, 1, 0);
tensor_t feat_output = get_graph_output_tensor(graph, 2, 0);
tensor_t heatmap_output = get_graph_output_tensor(graph, 3, 0);
tensor_t pool_output = get_graph_output_tensor(graph, 4, 0);

+++

  1. 将输出进行量化
get_tensor_quant_param(feat_output, &feat_scale, &feat_zero_point, 1);
get_tensor_quant_param(offset_output, &offset_scale, &offset_zero_point, 1);
get_tensor_quant_param(heatmap_output, &heatmap_scale, &heatmap_zero_point, 1);
get_tensor_quant_param(size_output, &size_scale, &size_zero_point, 1);
get_tensor_quant_param(pool_output, &pool_scale, &pool_zero_point, 1); 

/* dequant output data */
uint8_t* offset_data_u8 = (uint8_t*)get_tensor_buffer(offset_output);
uint8_t* feat_data_u8 = (uint8_t*)get_tensor_buffer(feat_output);
uint8_t* heatmap_data_u8=(uint8_t*)get_tensor_buffer(heatmap_output);
uint8_t* size_data_u8 = (uint8_t*)get_tensor_buffer(size_output);
uint8_t* pool_data_u8 = (uint8_t*)get_tensor_buffer(pool_output);
  1. 后处理关键步骤分析

    根据预设的概率将输出数据进行初步筛选

std::vector<float> heatmap_data(heatmap_count);            
std::vector<Object> objects;   
std::vector<Object> objects_pick;
for (size_t i = 0; i < heatmap_count; i++)
{
float heatmap_data = ((float)pool_data_u8[i] - (float)pool_zero_point) * pool_scale;
float pool_data = ((float)pool_data_u8[i] - (float)pool_zero_point) * pool_scale;
if(heatmap_data == pool_data && heatmap_data >= prob_threshold)
   {
       Object obj;
       obj.idx = i;
       obj.prob = heatmap_data;
       objects.push_back(std::move(obj));
   }
}

将所有输出对象进行排序

qsort_descent_inplace(objects);

将所有输出对象进行分类

/*将object输入函数,计算交并比 若大于0.4则pick到object_pick*/
nms_sorted_bboxes(objects,objects_pick, picked, 0.4);

对输出图像进行逐帧分析通过计算欧氏距离与马氏距离,逐帧分析目标之间的关系,将目标分为四种状态

  1. unconfirmed_stracks(activated = F, track_state=tracked ) 只出现一次的目标(检测器误检的目标)

  2. activated_stracks(activate=T, track_state=tracked) 跟踪状态良好的tracker

  3. **lost_stracks(activate=T, track_state=lost)**激活,但是跟踪状态丢失

  4. **refind_stracks(activated=T, track_state=lost->tracked)**跟丢之后重新找回的目标
    最后获取的目标存放在tracks结构体中的id变量中 根据id的u不同可以实现reid功能

JDETracker::instance()->update(objects_pick, &tracks);

对视频文件及rtsp视频流的处理

​ 代码中采用ffmpeg库对视频或rtsp视频流源文件进行解复用,分离出对应格式的packet,然后调用vim3板卡上的硬件解码器进行解码操作。由于vim3对h265格式的支持不够好,因此采用h264格式的视频文件进行测试

使用ffmpeg进行解复用

  • ffmpeg解复用部分的实例代码可以在ffmpeg源码中的**/doc/examples/demuxing_decoding.c**中看到。

    对视频进行解复用的过程主要通过avformat_find_stream_info函数获取文件信息,如视频,音频编码格式,视频的长宽以及帧率等基本信息。这些信息可以通过av_dump_format函数打印出来。同时,通过av_find_best_stream函数可以获取音频流与视频流的下标,该下标是区分视频与音频的唯一标识。

// 打开输入流
if (avformat_open_input(&(demuxer->fmt_ctx), url, NULL, NULL) < 0) {
    printf("failed to open input url\n");
    return-1;
}
// 读取媒体文件信息
if (avformat_find_stream_info(demuxer->fmt_ctx, NULL) < 0) {
    printf("failed to find stream\n");
    if (demuxer->fmt_ctx) avformat_close_input(&(demuxer->fmt_ctx));
    return -1;

}
av_dump_format(demuxer->fmt_ctx, 0, url, 0);

// 寻找音频流和视频流下标
demuxer->video_index = av_find_best_stream(demuxer->fmt_ctx, AVMEDIA_TYPE_VIDEO,-1,-1, NULL, 0);
printf("video i ndex: %d\n" ,demuxer->video_index);
if (demuxer->video_index   < 0) {
    printf("failed to find stream index\n");
    if (demuxer->fmt_ctx) avformat_close_input(&(demuxer->fmt_ctx));
    return -1;
}
  • 在获取视频信息后,还需要将视频信息读取出来进行下一步处理。主要通过av_read_frame读取视频数据,函数每读取一次会将指针后移动读取下一帧数据。同时会返回错误码,当读取出错时可以根据错误码确定错误信息。读取数据时读取到的不一定是视频数据,也会有音频数据,因此当获取到数据时,需要通过判断数据流的下标来确定数据的类型。对于视频数据,每次获取到一个packet便是一帧数据。当数据获取完毕后,需要通过av_packet_unref释放packet结构体否则会造成内存泄漏,无论是否获取成功都需要进行释放
if((errnum = av_read_frame(demuxer->fmt_ctx, demuxer->packet) )== 0){
    if (demuxer->packet->stream_index == demuxer->video_index) {
            if (av_bsf_send_packet(demuxer->bsf_ctx, demuxer->packet) == 0) {
                if (av_bsf_receive_packet(demuxer->bsf_ctx, demuxer->packet) == 0) {
               printf("num: %d the packet size: %d \n",packet_num++,demuxer->packet->size);
                }
            }
        }
    else
    {
        av_packet_unref(demuxer->packet);
        // return -1;
    }
}
else
{
    av_packet_unref(demuxer->packet);
    av_strerror(errnum,errnum_buffer,sizeof(errnum_buffer));
    printf(" error: %s\n",errnum_buffer);
    return -1;
    }
  • 以H264格式的数据为例,获取到的数据可能是无法被正常识别的,因为ffmpeg解复用得到的数据没有头部信息,需要通过H264过滤器添加头部信息。最终获取到的packet便是每一帧的视频数据,注意,由于每一帧的视频信息不同,每个packet的大小也不一样。
/*h264过滤器*/
const AVBitStreamFilter *bsf;
AVBSFContext *bsf_ctx;
//指定过滤器类型   
demuxer->bsf = av_bsf_get_by_name("h264_mp4toannexb");
if (demuxer->bsf == NULL) {
	printf("failed to find stream filter\n");
	return -1;
    }
av_bsf_alloc(demuxer->bsf, &(demuxer->bsf_ctx));
//初始化过滤器 
avcodec_parameters_copy(demuxer->bsf_ctx->par_in, demuxer->fmt_ctx->streams[demuxer->video_index]->codecpar);
av_bsf_init(demuxer->bsf_ctx);

//使用过滤器
if (av_bsf_send_packet(demuxer->bsf_ctx, demuxer->packet) == 0) {
	if (av_bsf_receive_packet(demuxer->bsf_ctx, demuxer->packet) == 0) {
        printf("num: %d the packet size: %d \n",packet_num++,demuxer->packet->size);
        }
	}

使用vim3板卡进行硬件解码

​ 调用板卡上的解码器进行解码操作,在khadas官网上hardware-decoding 中有相应介绍。具体的解码demo在aml_hardware_decode_demo 中,ionvideo 版本解码并保存为yuv格式文件。对于vim3上的解码器,解码出的yuv数据格式为NV21

  • 对解码器的初始化过程除了指定视频格式等信息,主要函数为ionvideo_initcodec_init
    vpcodec = &v_codec_para;
    memset(vpcodec, 0, sizeof(codec_para_t));

    vpcodec->has_video = 1;
    vpcodec->video_type = VFORMAT_H264;
    vpcodec->am_sysinfo.format = VIDEO_DEC_FORMAT_H264;
    vpcodec->am_sysinfo.param = (void *)(EXTERNAL_PTS | SYNC_OUTSIDE);
    vpcodec->stream_type = STREAM_TYPE_ES_VIDEO;
    vpcodec->am_sysinfo.rate = 96000 / decoder->fps;
    vpcodec->am_sysinfo.height = decoder->height;
    vpcodec->am_sysinfo.width = decoder->width;
    vpcodec->has_audio = 0;
    vpcodec->noblock = 0;

    ionvideo_init(vpcodec->am_sysinfo.width, vpcodec->am_sysinfo.height);
    decoder->ret = codec_init(vpcodec);
    if (decoder->ret != CODEC_ERROR_NONE) {
        printf("codec init failed, ret=-0x%x", -decoder->ret);
        return -1;
    }
    printf("video codec ok!\n");
    set_tsync_enable(0);
  • 将数据写入编码器中,主要过程如下。主要函数为codec_write,其中返回值ret是写入解码器的数据大小,采用如下的结构是为了避免数据未完全写入的情况。
    do{
        decoder->`ret` = codec_write(pcodec,buffer+isize,decoder->Readlen);
                    // printf("write size : %d isize :%d\n", decoder->ret,isize);

        if(decoder->ret <0){
            if (errno != EAGAIN) {
                printf("write data failed, errno %d\n", errno);
                return -1;
            } else {
                continue;
            }
        }else{
            isize += decoder->ret;

        }
    }while(isize < decoder->Readlen);
  • 从解码器中读取解码后的视频信息,由于视频分为I帧P帧与B帧,因此并不是每输入一帧便能从解码器获取到一帧的数据,当读取到关键帧才会有输出。主要过程为先获取解码器的状态,当有输出时进行读取操作。同时在读取时相当于是一种队列操作,将数据出队入队,按照NV21的格式获取数据的编码,从vbuffer中读取数据。但数据是如何存放在vbuffer中的,这一点我还是没看懂,可能在底层封装了?最后,根据返回的数据长度判断是否需要进行下一次读取一般是判断是否大于0x100;也就是256字节。还有一点说明,在读取数据时最好使用do while结构而不是while结构,后者会导致最后一次的数据被遗漏。同时数据的大小亦有说法,应该是**(W*H *3/2)**;
    decoder->ret = codec_get_vbuf_state(pcodec, &vbuf);
    if (decoder->ret != 0) {
        printf("codec_get_vbuf_state error: %x\n", -decoder->ret);
        return -1;
    }
    // printf("* vbuf.data_len %d\n",vbuf.data_len);
    decoder->ret = amlv4l_dequeuebuf(amvideo, &vf);
    if (decoder->ret >= 0) {
        printf("vf idx%d pts 0x%x pyt %p size %d\n", vf.index, vf.pts,vbuffer[vf.index].ptr, vbuffer[vf.index].size);
        // fwrite(vbuffer[vf.index].ptr, vbuffer[vf.index].size, 1, yuv);
        decoder->out_ptr = vbuffer[vf.index].ptr;
        decoder->size = vbuffer[vf.index].size;
        decoder->ret = amlv4l_queuebuf(amvideo, &vf);
        if (decoder->ret < 0) {
            // printf("amlv4l_queuebuf %d\n", decoder->ret);
        }
    } else {
        // printf("amlv4l_dequeuebuf %d vbuf.data_len %d\n", decoder->ret,vbuf.data_len);
        decoder->size = 0;
    }
    // printf(" vbuf.data_len %d\n",vbuf.data_len);
    return vbuf.data_len;
  • 最后别忘了释放资源
int ffmpeg_demuxer_release(struct ffmpeg_demuxer *demuxer)
{
    if (demuxer->fmt_ctx) avformat_close_input(&(demuxer->fmt_ctx));
    if (demuxer->packet)  av_packet_free(&(demuxer->packet));
    if (demuxer->bsf_ctx) av_bsf_free(&(demuxer->bsf_ctx));
    return 0;
}
int vim3_decoder_release()
{

    codec_close(vpcodec);
    // codec_close(decoder->pcodec);
    ionvideo_close();
    FreeBuffers();
	if (vpcodec->video_type == VFORMAT_HEVC)
		reset_double_write_mode("/sys/module/amvdec_h265/parameters/double_write_mode");
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值