zoukankan      html  css  js  c++  java
  • OpenCV绘制检测结果

    OpenCV绘制检测结果

    一、介绍

    由于在验证阶段,使用FPGA时我们的算法检测速度很慢,没法直接在主流上进行绘图,否则的话,主流就要等待算法很久才能出图。所以,我们的解决方案是把框推到客户端上,在客户端上进行绘图。

    这时,客户端不仅收到图像帧,音频帧,还会收到一个框信息,需要把三者进行同步显示,不能图像、音频、框不匹配。而图像、音频都是通过ffmpeg写入的,不会有问题,而检测算法这边是独立于前面的出图进程,没有通过ffmpeg打包,所以需要使用ntp时间进行同步。

    问题:

    1. 如果做同步的话,最好能将rtcp_ntp_timestamp设为av_buffer的时间戳,这样同步会准一些,否则两者时间稍有差别。
    2. 如何从ffmpeg取得ntp_time,修改ffmpeg在AVPacket中增加first_rtcp_ntp_time字段(实际上在AVFormatContext中有start_real_time字段,但有些要注意的)
    3. 20fps时,AVPackate->pts中两帧之间的差是:50223us,也就是多了223us。

    二、FFMPEG

    ffmpeg的时间戳介绍:
    https://www.cnblogs.com/gr-nick/p/10993363.html

    由于ffmpeg把一切都封起来了,刚开始的时候发现只能得到pts,而pts的结果是通过计算得到的,它是相对第1帧的相对时间的,而框是绝对时间,无法进行两者的匹配。

    打算在AVPacket中添加一个first_rtcp_ntp_time,这样便可以通过与AVPacket中的pts合并计算得到真实的ntp时间。

    分析代码,发现pts是在libavofrmat/rtpdec.c finalize_packet函数中计算赋值的,同理,我们也在这个函数计算first_rtcp_ntp_time

        pkt->first_rtcp_ntp_time = av_rescale(s->first_rtcp_ntp_time,
                s->st->time_base.den,
                (uint64_t) s->st->time_base.num << 32);
    

    写个程序测试,打印该值,并没有写入到AVPacket中,发现,实际上ffmpeg中内部处理时会新建临时packet对象,而在赋值的时候,之前的代码并没有进行这个字段的拷贝,所以导致值没有传出来,需要修改libavformat/utils.cparse_packet函数:

            out_pkt.stream_index = st->index;
            out_pkt.pts          = st->parser->pts;
            out_pkt.dts          = st->parser->dts;
            out_pkt.pos          = st->parser->pos;
            /* 新增如下行 */
            out_pkt.first_rtcp_ntp_time = pkt->first_rtcp_ntp_time; 
    

    实际上,后来突然发现在AVFormatContext中存在一个start_time_realtime字段,里面存储了ntp时间,所以可以直接利用这个值,不需要修改AVPacket。但存在一个问题,这边在计算时,对这个时间减去了一个NTP_OFFSET(1900~1970的时间差),而我们在编码时并没有加上该偏移,这边相当于多减了NTP_OFFSET,所以使用的时候需要把这个值再加回来。

    s->start_time_realtime = av_rescale (rtpctx->first_rtcp_ntp_time - (NTP_OFFSET << 32), 1000000, 1LL << 32);
    

    三、OpenCV

    OpenCV中包含了一个cap.get(CV_CAP_PROP_POS_MSEC)函数用于获取当前帧时间,其实现方式如下,主要是通过内部维护的frame_number与fps进行计算,但好像对我们没什么用:

    case CV_FFMPEG_CAP_PROP_POS_MSEC:
        return 1000.0*(double)frame_number/get_fps();
    

    OpenCV调用ffmpeg进行rtsp解析的主要流程,最终还是调用av_read_frame()函数:

    OpenCV
    OpenCV

    在VideoCapture中提供一个成员函数cap.getRealTimestamp()用于返回时间戳,
    CvCapture_FFMPEG中提供一个timestamp成员用来保存时间戳,提供一个接口函数,如下:

    @@ -423,6 +423,7 @@ struct CvCapture_FFMPEG
         double  get_duration_sec() const;
         double  get_fps() const;
         int     get_bitrate() const;
    +    int64_t getRealTimestamp() const { return timestamp; }
         AVRational get_sample_aspect_ratio(AVStream *stream) const;
         
    @@ -436,6 +437,7 @@ struct CvCapture_FFMPEG
         AVFrame         * picture;
         AVFrame           rgb_picture;
         int64_t           picture_pts;
    +    int64_t           timestamp;
    

    最后在CvCapture_FFMPEG grabFrame函数中计算时间戳:

    int64_t time = packet.first_rtcp_ntp_time + packet.pts;
    AVRational in_base = {1, 90000};
    AVRational out_base = {1, 1000000};
    timestamp = av_rescale_q(time, in_base, out_base);
    

    如果是使用start_time_realtime则有:

    #define NTP_OFFSET 2208988800ULL            
    int64_t time = av_rescale (ic->start_time_realtime, 1LL << 32, 1000000) + (NTP_OFFSET << 32);
    timestamp = av_rescale (time, 1000000, 1LL << 32) + av_rescale (packet.pts, 1000000, 90000);
    

    四、绘制

    新建一个线程使用libcurl获取websocket传过来的框信息,放入到一个std::list容器中进行管理。

    分别获取帧时间与框时间进行比较,满足条件则绘图:

    list<struct box_result>::iterator it;
    
    /* frame time */
    int64_t frame_time = cap.getRealTimestamp();
    
    /* compare and draw */
    pthread_mutex_lock(&mutex);
    for (it = l.begin(); it != l.end();) {
        int64_t diff = it->timestamp - frame_time;
        if (diff < DROP_TIME * 1.3) {
            it = l.erase(it);
        } else if (diff < -WAIT_TIME*0.1) {
            for (int i = 0; i < it->box_num; ++i)
                rectangle(frame, it->box[i].start, it->box[i].end, it->box[i].color, 2);
            it++;
        } else
            break;
    
    }
    pthread_mutex_unlock(&mutex);
    
    

    五、长时间之后pts不对

    通过打印发现,ffmpeg中计算的地方结果是正确的,但opencv取到的结果确是错误的。

    查看代码,发现有进行wrap操作:

        pkt->dts = wrap_timestamp(st, pkt->dts);
        pkt->pts = wrap_timestamp(st, pkt->pts);
    

    经查文档发现,可以在打开流时,将correct_ts_overflow关掉就可以解决这个问题了。

  • 相关阅读:
    Redis基础
    Windows 10 中 安装 RabbitMQ
    Nginx
    第二章-矩阵
    第一章-行列式
    第六章-微分方程
    第五章-多元函数
    第四章-定积分
    第三章-不定积分
    第二章-导数
  • 原文地址:https://www.cnblogs.com/gr-nick/p/10993823.html
Copyright © 2011-2022 走看看