0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

OpenCV如何计算帧率?为什么OpenCV得到的帧率是错的

LiveVideoStack 来源:LiveVideoStack 作者:王伟 2022-07-10 14:11 次阅读

引言

我们有一个平台来周期性地对线上的直播流数据进行某些检测,例如黑/白屏检测、静态画面检测……在检测中,我们会根据提取到的直播流的帧率来预估要计算的帧数量,例如,如果要检测5s的直播流,而该直播流的帧率为20fps,需要计算的帧数量则为100。忽然有一天,我们发现,平台开始大面积的超时,之前只需要2s就能完成的计算,现在却需要30+分钟。查了之后,我们发现,之所以计算超时是因为OpenCV计算的帧率为2000,从而导致需要计算的帧数量从之前的100变为了10000,进而引起了计算超时。

1、OpenCV 如何计算帧率

这个问题的具体描述可以参见 OpenCVIssues 21006。该问题的模拟直播流片段test.ts可以下载

如果用如下的代码获取test.ts的fps,

poYBAGLKbeSAaNopAAAuOVfxEns488.jpg

可以得到:


poYBAGLKbfuAbFs4AAAQYV7WGYo987.jpg

用ffprobe对视频进行分析,可以得到:

poYBAGLKbhaAKVX2AAAti0_kEmE794.jpg

从 opencv/modules/videoio/src/cap_ffmpeg_impl.hpp[2]中,我们发现fps由CvCapture_FFMPEG::get计算而来,其计算逻辑如下:

poYBAGLKbiuAI6X5AABXELvlcvk539.jpg

2、为什么OpenCV得到的帧率是错的

利用test_time_base.cpp,我们可以得到:

pYYBAGLKbkuAULEkAABCUgODDTE510.jpg

所以OpenCV采用了:

poYBAGLKbmGAGkZsAAAllRH5Zl8997.jpg

来计算该视频的fps。而此处的time_base = 1/2000,因此,最终得到的fps是2000。

也就是说,AVStream->codec->time_base的值导致了OpenCV得到一个看起来是错误的fps。那么,AVStream->codec->time_base为什么是这个值呢?FFmpeg是怎么计算这个字段的呢?

3、FFmpeg 如何计算

AVCodecContext.time_base

AVStream->codec->time_baseAVCodecContext中定义的 time_base字段,根据libavcodec/avcodec.h[4]中的定义可知,对于解码而言,time_base已经被废弃,需要使用framerate来替换 time_base。并且,对于固定帧率而言,time_base = 1/framerate,但并非总是如此。

利用H264Naked对test.ts对应的H.264码流进行分析,我们得到SPS.Vui信息

pYYBAGLKbnmACCeeAAA9aIKdKvE771.jpg

从中可以看到,test.ts是非固定帧率视频。从test_time_base.cpp的结果看,test.ts视频中,framerate = 0/0,而time_base = 1/2000

难道,对于非固定帧率视频而言,time_baseframerate之间没有关联?如果存在关联,那又是怎样的运算才能产生这种结果?这个 time_base究竟是怎么计算的呢?究竟和framerate有没有关系呢?一连串的问题随之而来……

源码面前,了无秘密。接下来,带着这个问题,我们来一起分析一下FFmpeg究竟是如何处理time_base的。

3.1 avformat_find_stream_info

在 FFmpeg中,avformat_find_stream_info()对ic->streams[video_stream]->codec进行初始化,因此我们可以从avformat_find_stream_info()开始分析。

从libavformat/avformat.h[6]中,可以得知avformat_open_input()会打开视频流,从中读取相关的信息,然后存储在AVFormatContext中,但是有时候,此处获取的信息并不完整,因此需要调用avformat_find_stream_info()来获取更多的信息。

需要注意的是:

avformat_find_stream_info()会尝试通过解码部分视频帧来获取需要的信息。

pYYBAGLKbpuACU-OAAFvaXlDwMM491.jpg

avformat_find_stream_info()的整体逻辑大致如下图所示,其中特别需要关注图中所示的 7 个步骤:

18323f4a-f395-11ec-ba43-dac502259ad0.png

3.2 avformat_find_stream_info()的重要步骤说明

STEP 1 设置线程数,避免H.264多线程解码时没有把SPS/PPS信息提取到extradata

STEP 2 设置AVStream *st,st会在后续的函数调用中一直透传到try_decode_frame()。

STEP 3比较简单,这里不再赘述。

STEP 4 设置AVCodecContext *avctx为透传的st->internal->avctx,在后续的解码函数调用中,一直透传的就是这个avctx,因此,从这里开始的执行流程,FFmpeg使用的全部都是st->internal->avctx,而不是st->codec,这里要特别的注意。此处同时会设置解码的线程数,其目的和STEP 1是一致的。

STEP 5 因为之前设置了解码线程数为1,所以此处会调用

poYBAGLKbrmAEnYJAAAmOUueAUc640.jpg

来解码并计算avctx->framerate。注意,此处的avctx实际上是透传而来的st->internal->avctx。计算 framerate的逻辑会在如何计算framerate部分介绍。

STEP 6 根据解码器得到的framerate信息来计算 avctx->time_base,注意此处实际上是st->internal->avctx->time_base。根据如何计算framerate可知,此处framerate ={1000, 1}。根据 AVCodecContext.ticks_per_frame的介绍可知,ticks_per_frame = 2。因此,此处avctx->time_base ={1, 2000}

poYBAGLKbtiAEgh1AAAsnPPBFX4256.jpg

STEP 7 这一步可谓是“瞒天过海,明修栈道暗度陈仓”。这一步为了解决API的前向兼容,做了一个替换,把st->internal->avctx->time_base 赋值给了st->codec->time_base,而把st->avg_frame_rate 赋值给了 st->codec->framerate。因此:

poYBAGLKbvGAcVdEAAAujpRKHpU924.jpg

st->codec->time_base 的计算和 st->codec->framerate 之间没有任何关系,而是和 st->internal->avctx->framerate 有关。究其本质,是和sps.time_scale,sps.num_units_in_tick有关。

poYBAGLKbwiAcIT3AACYM6YWuDM639.jpg

3.3 internal->avctx->time_base &internal->framerate

所以实际上,internal->avctx->time_base为:

poYBAGLKbx2AKVh1AAAnCR3AyEQ492.jpg

internal->avctx->framerate则是:

pYYBAGLKbzWALc5gAAAz1oBC9e8359.jpg

因此,对于 H.264 码流而言,time_base = 1 / (2 * framerate),而不是1 /framerate

这也就是为什么

libavcodec/avcodec.h中说:

poYBAGLKb0qALyHaAAAwixex044253.jpg

从如上的分析可以知道:

poYBAGLKb1-ABPUAAAAqzKCjdzc393.jpg

因此,当st->avg_frame_rate = 0时,OpenCV计算fps的逻辑是错误的。

在H.265中,ticks_per_frame = 1,因此对于H.265的编码,OpenCV是没有这个问题的。可以使用Zond 265 工具来分析一个 H.265的视频码流,然后对照OpenCV以及FFmpeg的结果来验证。

同时,正是如上所示的STEP 7中的移花接木导致了 test_time_base.cpp[3]的结果:

poYBAGLKb3GAT1ZUAAAk58dn75E010.jpg

3.4 ff_h264_decoder

libavcodec/decode.c[8]中的

decode_simple_internal()会调用对应的解码器来进行解码(STEP5)。而正如前所示,test.ts为H.264 编码的视频流,因此此处会调用 H.264 解码器来进行解码。在FFmpeg中,H.264解码器位于 libavcodec/h264dec.c[9]中定义的

const AVCodec ff_h264_decoder。

pYYBAGLKb4eAY5HlAACFcLzfijE382.jpg

在上文图中的STEP 5中,

pYYBAGLKb6CATzx4AAAkaD_IIu0097.jpg

实际调用的就是:

poYBAGLKb6uAIwnKAAAowwXYUW8449.jpg

而此处的avctx也就是

try_decode_frame()中透传下来的st->internal->avctx,即上文图中的STEP4。

3.5 h264_decode_frame

h264_decode_frame()的整体逻辑如下图所示:

1843489e-f395-11ec-ba43-dac502259ad0.png

3.6 AVCodecContext.ticks_per_frame

后面会用到ticks_per_frame来计算framerate。在STEP6中计算 time_base的时候也用到了该值。因此,有必要做一下特殊说明。在H.264解码器中,ticks_per_frame=2,其具体的取值可以从如下几处得知:

libavcodec/avcodec.h 中的字段说明:
pYYBAGLKcA2AWE2nAAAHKIAypyw750.jpg

pYYBAGLKb_2AaNQIAACWmNuOT3M189.jpg

libavcodec/h264dec.c 中的h264_decode_init():

pYYBAGLKcCyABU9TAAAWlJlnRwA260.jpg

4、如何计算framerate

STEP 1 根据整体的计算流程可知,此处的h实际上就是

avformat_find_stream_info()中的

st->internal->avctx->priv_datah会一直透传到之后的所有流程,这个务必要注意。

STEP 2此处会首先获取到sps的相关信息,以备后续的计算使用,我们可以再次看一下test.ts sps的相关信息。


poYBAGLKcD-AExUjAAA6wl5psTM781.jpg

STEP 3根据sps的相关信息计算framerate,在上文的STEP 6中计算 time_base用到的framerate就是在此处计算的。因为 timing_info_present_flag = 1,因此会执行计算framerate的逻辑:

poYBAGLKcHOASLy5AABTs8_Z5CA584.jpg

因此,


poYBAGLKcIaACzyLAAAiT55R4Bc385.jpg

但是,因为avctx->time_base={1,2000},所以OpenCV计算出来的帧率结果为2000。导致这种不一致的原因在于,OpenCV在使用codec->time_base计算帧率的时候没有考虑ticks_per_frame。因此,对于OpenCV而言,正确的计算帧率的方式应该为:

pYYBAGLKcLqAQ0NsAABuzSEFKxQ944.jpg


结论

通过上面的分析我们可以知道:

FFmpeg在计算 AVCodecContex 中的frameratetime_base的时候,会用到:

o sps.time_scale

o sps.num_units_in_tick

o AVCodecContex.ticks_per_frame

在 FFmpeg 中,frameratetime_base的关系为:

o framerate = 1 / (time_base *ticks_per_frame)

o time_base = 1 / (framerate *ticks_per_frame)

对于非 H.264/MPEG-2,

ticks_per_frame=1,因此frameratetime_base是互为倒数的关系。而对于H.264/MPEG-2 而言,ticks_per_frame=2,因此,此时二者并非是互为倒数的关系。因而,FFmpeg 中才说,frameratetime_base通常是互为倒数的关系,但并非总是如此。

在OpenCV中,对于H.264/MPEG-2视频而言,当

AVStream.avg_frame_rate=0时,其计算fps的逻辑存在BUG。

因为在解码时,

AVCodecContex.time_base已经废弃,同时 AVStream.avctx也已经废弃,而

avformat_find_stream_info()中为了兼容老的API,因此会利用 AVStream.internal.avctx和其他的信息来设置AVStream.avctx。而AVStream.avctx.time_base取自AVStream.internal.avctxAVStream.avctx.framerate 则取自 AVStream.framerate

审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • H.264解码器
    +关注

    关注

    0

    文章

    2

    浏览量

    6303
  • OpenCV
    +关注

    关注

    29

    文章

    611

    浏览量

    40786
  • ffmpeg
    +关注

    关注

    0

    文章

    45

    浏览量

    7290

原文标题:为什么OpenCV计算的帧率是错误的?

文章出处:【微信号:livevideostack,微信公众号:LiveVideoStack】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    【NanoPC-T4试用体验】GPU测试+OpenCV配置

    ` 本帖最后由 猎国倾城 于 2018-11-12 13:03 编辑 1.测试OpenGL ES性能这个主要测一下GPU的性能。主要有纹理、渲染,抗锯齿等方面的测试,每一项测完,都显示该项的帧率
    发表于 11-12 13:03

    ov7725如何降低帧率

    最近在使用ov7725来拍摄图片通过串口传送到pc端显示,但是图片出现了断层错位的情况。感觉可能是帧率出了问题,请问一下要怎么设置来降低ov7725的帧率呢?
    发表于 04-28 16:28

    RK3399开发板上linux下qt+opencv通过videocapture调用uvc免驱相机,帧率低且无法更改

    1.通过终端:v4l2-ctl --list-devices显示:/dev/video0/dev/video1显示两个设备,其实只接一个相机,这里猜测可能是两个视频格式。2.通过qt配置opencv
    发表于 05-22 10:47

    怎样去解决RK3588 HDMIin输入显示帧率很低的问题呢

    opencv感觉),目测帧率10帧以下  3. 用gstreamer, v4l2src 到 autovideosink,目测帧率也是10帧以下  感觉以rk3588的性能不该如此,目前是最新的固件。或许是我
    发表于 07-20 16:25

    opencv备忘单

    opencv备忘单,opencv_cheatsheet,opencv_tutorials,opencv_user,opencv2refman
    发表于 08-25 15:52 0次下载

    opencv基础的学习手册

    OpenCV (Open Source Computer Vision Library: http://opencv.org) is an open-source BSD-licensed
    发表于 08-25 15:52 0次下载

    The OpenCV User Guide

    The OpenCV User Guide Release 2.4.8.0,英文版OpenCV的用户指南。
    发表于 08-26 14:12 0次下载

    计算机视觉应用之OpenCV基础教程

    Library,OpenCV)。OpenCV优化了许多功能函数,并在实时的计算机视觉程序中得到应用。但是,由于嵌入式优化策略得天独厚的优势,仍然值得大家尝试利用逻辑硬件来加速
    发表于 11-18 04:45 8989次阅读
    <b class='flag-5'>计算</b>机视觉应用之<b class='flag-5'>OpenCV</b>基础教程

    OpenCV的起源和应用领域

    因此,OpenCV的目的是开发一个普遍可用的计算机视觉库。在Intel的性能库团队的帮助下,OpenCV实现了一些核心代码以及算法,并发给Intel俄罗斯的库团队。
    的头像 发表于 08-28 10:53 7219次阅读

    基于OpenCV如何提取中心线

    问题 前几天有个人问了我一个问题,问题是这样的,他有如下的一张二值图像: 怎么得到白色Blob中心线,他希望的效果如下: 显然OpenCV中常见的轮廓分析无法获得上面的中心红色线段,本质上这个问题是
    的头像 发表于 04-26 13:51 3387次阅读

    计算机视觉中如何调用OpenCV

    学习计算机视觉最重要的能力应该就是编程了,为了帮助小伙伴尽快入门计算机视觉,小白准备了【走进OpenCV】系列,主要帮助小伙伴了解如何调用OpenCV库,涉及到的知识点会做简单讲解。
    的头像 发表于 09-08 10:26 4610次阅读

    LVGL 优化帧率技巧

    目录标题前文LVGL帧率限制代码优化等级前文LVGL——PC模拟器仿真模拟+VS2017f429 discovery开发版 LVGL移植(带操作系统)首先就个人用过的几个芯片而言,可能还是要F4系列
    发表于 12-07 13:21 14次下载
    LVGL 优化<b class='flag-5'>帧率</b>技巧

    OpenCV配置CUDA以支持GPU加速

    最近在做OpenCV相关的项目时发现,在跑dnn模型时如果单纯只使用cpu帧率会非常低,有时甚至一两秒才刷一帧的图像出来,需要使用硬件加速,所以在各大论坛等翻阅使用GPU加速的教程,可惜非常杂,而且
    的头像 发表于 01-09 10:16 3133次阅读

    OpenCV学习总结之C++版

    OpenCV简介 OpenCV是“开源计算机视觉库”,「Open Sourec Computer Vision Library」,是一个基于开源发行的跨平台计算机视觉库,包含了许多图
    的头像 发表于 02-07 16:03 515次阅读

    OpenCV基础知识入门

    OpenCV计算机视觉中最受欢迎的库,最初由intel使用C和C ++进行开发的,现在也可以在python中使用。该库是一个跨平台的开源库,是免费使用的。OpenCV库是一个高度优化的库,主要关注实时应用程序。
    的头像 发表于 10-29 11:29 399次阅读
    <b class='flag-5'>OpenCV</b>基础知识入门