如何使用ffmpeg简单地从视频中删除重复帧

8
首先,我要说明的是,我不是视频处理方面的专家,虽然我已经使用ffmpeg进行了几年(在一个相当有限的范围内)。因此,我对人们常用的“语言”以及它如何影响我所做的操作并不是很熟悉,但我仍会尝试解决这个问题...
我查看了一些链接,例如: ffmpeg - remove sequentially duplicate frames ...但内容并没有真正帮助到我。
我有数百个视频剪辑,它们是在Windows和Linux下使用ffmpeg和其他类似应用程序创建的。但是,它们在视频的某些时间段出现了“静止不动”的显示问题。
例如,假设我们有一个网站,在其中将实时视频流传输到Web浏览器中的Flash视频播放器/插件中。在这种情况下,我们正在谈论一个交通摄像头视频流。
有一个运行的ffmpeg实例正在捕获(Windows)桌面的某个区域,并将其保存为视频文件:
ffmpeg -hide_banner -y -f dshow ^
      -i video="screen-capture-recorder" ^
      -vf "setpts=1.00*PTS,crop=448:336:620:360" ^
      -an -r 25 -vcodec libx264 -crf 0 -qp 0 ^
      -preset ultrafast SAMPLE.flv

假设要捕获的实际“显示”看起来像这样:

123456789 XXXXX 1234567 XXXXXXXXXXX 123456789 XXXXXXX
^---a---^ ^-P-^ ^--b--^ ^----Q----^ ^---c---^ ^--R--^ 

每个字符位置表示一个(序列)帧,由于网络连接不佳,“单帧”可以显示很长时间('X'字符几乎是前一帧的完全复制)。这意味着我们捕获的视频中有一些段落图像根本没有变化(对于肉眼而言)。

我们如何处理重复帧?如果“重复”的内容对ffmpeg来说并不相同但在观众眼里却看起来更或多或少一样,我们的方法会如何改变?

如果我们只是删除重复的帧,则视频的“节奏感”会丢失,原本可能需要5秒钟才能显示的内容,现在只需要一小部分时间就能完成,这会导致非常生硬、不自然的动作,尽管视频中没有重复的图像。 使用ffmpeg的“mp_decimate”选项似乎可以实现这一点,即:

     ffmpeg -i SAMPLE.flv ^                      ... (i)
        -r 25 ^
        -vf mpdecimate,setpts=N/FRAME_RATE/TB DEC_SAMPLE.mp4

我引用的参考资料使用了一个命令,它会显示“mp_decimate”在认为某些帧“相同”时将要删除哪些帧,即:

     ffmpeg -i SAMPLE.flv ^                      ... (ii)
        -vf mpdecimate ^
        -loglevel debug -f null -

但是,如果我们知道了(格式复杂的)信息,如何重新组织视频而不必执行多次运行ffmpeg来提取视频“切片”以供重新组合呢?
在这种情况下,我猜我们需要运行类似于以下内容的东西:
• 用户指定重复时间段的“阈值持续时间”(可能仅运行1秒钟)
• 确定并保存主视频信息(fps等 - 假定帧速率恒定)
• 映射(开始重复的帧/时间)-> 重复帧数/持续时间
• 如果重复时间小于用户阈值,则不将此段时间视为“一系列重复帧”,并继续
• 提取“非重复”视频片段(上图中的a、b和c)
• 创建具有原始视频规格的“新视频”(空白)
• 对于每个视频片段
• 提取片段的最后一帧
• 创建一个短视频剪辑,其中包含重复的刚提取的帧(持续时间=用户规格= 1秒)
• 将(当前视频片段+短剪辑)附加到“新视频”并重复
但在我的情况下,许多捕获的视频可能长达30分钟,并且有数百个持续10秒钟的暂停,因此使用此方法“重建”视频将需要很长时间。
这就是为什么我希望有一种“可靠”和“更智能”的方法使用ffmpeg(带/不带'mp_decimate'过滤器)在几次传递或更少的时间内执行“减速”功能...也许有一种方式可以指定所需的段(例如在文本文件中),当ffmpeg运行时,它将停止/重新启动在指定的时间/帧数转码?
除此之外,是否有另一个应用程序(用于Windows或Linux)可以实现我想要的功能,而无需手动设置起始/结束点,手动提取/组合视频片段...?
我一直在尝试在Win7-SP1和Puppy Linux Slacko 5.6.4下使用ffmpeg N-79824-gcaee88d来完成所有这些工作。
非常感谢您提供任何线索。
3个回答

13
我理解您想要做的是保留有动作的帧和最多一秒钟的重复帧,但丢弃其余的帧。
ffmpeg -i in.mp4 -vf
"select='if(gt(scene,0.01),st(1,t),lte(t-ld(1),1))',setpts=N/FRAME_RATE/TB"
trimmed.mp4

选择筛选表达式的作用是利用if-then-else运算符: gt(scene,0.01)检查当前帧是否相对于前一帧检测到了运动。这个值必须根据手动观察进行校准,以确定哪个值能准确捕捉到实际活动与传感器/压缩噪声或帧中的视觉噪声之间的差异。请参见此处,了解如何获取所有场景更改值的列表。
如果帧被判断有运动,则then语句评估st(1,t)。函数st(val,expr)expr的值存储在编号为val的变量中,并将其返回为结果。因此,在遇到静态帧之前,保持帧的时间戳将不断更新该变量。 else子句检查当前帧时间戳与存储值的时间戳之间的差异。如果差异小于1秒,则保留该帧,否则丢弃。 setpts消除了所有选定帧的时间戳。 编辑:我测试了我的命令,并使用合成的视频输入成功了。

如果您已经生成了所有场景变化值的文本,请在其中一个视频的静态帧中查看文本文件以获取这些帧的值。 - Gyan
请看下面我的帖子。(为什么当我包含它时,'@Mulvya'没有显示出来!?真烦人...) - Skeeve
你说“最多1秒的重复帧”。是否可能丢弃所有重复帧? - schuelermine
1
@Gyan 你好,谢谢你的回答,我发现这个方法非常慢,而且自从你回答了这个问题之后,"freezedetect" 过滤器出现了,有没有办法使用它来剪切掉冻结帧?(我在我的个人资料中发布了一个关于此的问题,请查看) - Itzhak Eretz Kdosha
你提到了噪声对阈值的影响量... 我该如何在选择滤镜中过滤掉这些噪声,例如通过高斯模糊之类的方法? - Michael
显示剩余2条评论

1

我在这个问题上做了一些工作... 并发现以下方法非常有效...

似乎输入视频必须具有“恒定的帧速率”才能正常工作,因此第一个命令是:

ffmpeg -i test.mp4 ^
       -vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" ^
       -vsync cfr test01.mp4

我需要查看每一帧的“分数”。 以下命令可以生成这样的列表:-
ffmpeg -i test01.mp4 ^
       -vf select="'gte(scene,0)',metadata=print" -f null - 

我会查看所有分数...并计算平均值(平均数)- 有点不可靠,但似乎可以正常工作。在这个例子中,平均得分是“0.021187”。
然后我必须选择一个“持续时间”值 - 让“重复”的帧运行多长时间。如果你强制只保留一个帧,整个视频的播放速度将会过快...因此,我一开始使用0.2秒作为起点。
所以下一个命令变成了:
ffmpeg -i test01.mp4 ^
       -vf "select='if(gt(scene,0.021187),st(1,t),lte(t-ld(1),0.20))', 
                    setpts=N/FRAME_RATE/TB" output.mp4

在此之后,生成的“output.mp4”视频似乎运行良好。只需要稍微调整“persistence”值,以在平滑播放视频和场景突然变化之间进行折中。
我编写了一些Perl代码,效果还不错,如果有人感兴趣,我会想办法发布它...最终!
编辑:进行这种“减速”的另一个好处是,文件持续时间更短(显然),而且文件大小更小。例如,一个运行时间为00:07:14且大小为22MB的示例视频变成了00:05:35和11MB。

为什么第一个命令里要缩放比例?(除非你正在使用完整色度采样)另外,你可以在选择滤镜之前添加 fps 滤镜来跳过它。 - Gyan
@Mulvya 有一些视频的尺寸不是偶数,例如537x253。由于我不是编解码器、容器、格式等方面的专家,我不知道为什么会出现这种情况...但是ffmepg会报告类似以下的错误: [libx264 @ 03067340] height not divisible by 2 (537x253)使用“scale”选项可以解决这个问题,并强制输出文件具有接近、偶数的尺寸-看到(1/2)*2=1这样的类型。 - Skeeve
我明白,但如果你的源是一个MP4文件“test.mp4”,你不应该有这个问题。 - Gyan
好的..所以如果我理解正确,我根本不需要使用第一个命令...如果我在每个其他命令上使用类似于:-vf "fps=fps=film:round, select=..."(一个用于获取每个帧的“分数”,另一个用于实际进行“减少”)...? 顺便说一下,我在文档中找不到除“film”之外的任何选项? 我用什么来代替PAL或...除数字(如“25”)以外的其他内容? - Skeeve
似乎最重要的一点是,“fps=fps=pal”子句需要在检查“匹配”帧分数的任何子句之前,例如“if(gt(...”,否则将不会发生“抽样”。非常感谢这些提示...并为了节省所有设备的磨损。 - Skeeve
显示剩余2条评论

-1

可变帧率编码是完全可能的,但我认为它并不是你想象中的那样。我假设您希望删除这些重复帧以节省空间/带宽?如果是这样,那么它将无法起作用,因为编解码器已经在执行此操作。编解码器使用参考帧,并仅对与参考帧不同的内容进行编码。因此,重复帧几乎不占用任何空间。基本上,帧只是被编码为一组数据包,指示复制前一帧并进行更改。X帧没有任何更改,因此每个帧只需要几个字节来进行编码。


1
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Skeeve

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接