ffmpeg 跑步数据图层 • 手艺

前面介绍了 Garmin VIRB,一款可以将运动数据图层叠加到视频上的小工具。

工具本身也算良心,简单、免费。

可惜,存在太多瑕疵:

无法导入 1080p (MAC OSX 11.2.3)的原始视频

导入视频如果重名,会覆盖原来的

视频和 gpx 必须从一开始对齐,没有找到设置 offset 的地方,这就导致必须全程拍摄,不能分段叠加运动图层再剪辑

每次 UI 操作比较费时间,似乎有模板功能,但没有尝试成功

Gauge 的大小不知道怎么调整

只能全程用一套 gauge 组合,不能做类似旋转木马这样的效果

渲染比较慢,几乎是影片长度的 1/2,并且作为桌面软件,不方便做并发

目前尝试了几条片,感觉整个流程的时间成本过高。跑步一个小时左右的片,大概需要另外一个小时,才能完成后期编辑。

于是决定自己动手。

探索

先准备好基底视频素材。

去 Strava 上下载 GPX。因为没有开 Garmin 的 Track 模式,这个漂移有点厉害。作为测试倒也是够了。

先试用 ffmpeg 的命令行,随手截一组图,来试试叠加图片。

这部分命令,和两年前 《火神山延迟摄影》 一文中,叠加水印的方法,是类似的。命令如下。

ffmpeg -i base.mp4 -i overlay-sample/overlay01.png -filter_complex "[0:v][1:v] overlay=10:10:enable='between(t,1,2)'" output.mp4

检查一下叠加效果。

进一步叠加多条。

ffmpeg -i base.mp4 -i overlay-sample/overlay01.png -i overlay-sample/overlay02.png -filter_complex "[0:v][1:v] overlay=10:10:enable='between(t,5,10)' [tmp]; [tmp][2:v] overlay=500:20:enable='between(t,2,15)'" output.mp4

检查叠加效果。

接下来尝试用 Python 的 PIL 库自动生成图片。

from PIL import Image, ImageDraw

for seq in range(10):
    img = Image.new('RGBA', (100, 30))
    d = ImageDraw.Draw(img)
    d.text((10,10), f"Time: {seq}", fill=(255,0,0))
    img.save(f'overlay/overlay{seq:03d}.png')

先假装放一段时间文字在上面。

接着用 Python 来拼装 ffmpeg 的命令。

这个拼装过程需要格外注意参数和格式。经过几个步骤,最后输出了一条巨长无比的 ffmepg 命令。把命令直接贴进 Terminal 运行即可。

第一个系统性的试验成功。图片是加上去了,但由于视频很大,overlay 只占了一小块。

接着调整字号。

调整字号后的效果如图。

最后一步,就是把 gpx 文件内部的信息读取出来,然后写点逻辑排版 overlay。使用 gpxpy 这个库。

import gpxpy 
import gpxpy.gpx 

gpx_file = open(gpx_file_name, 'r') 
gpx = gpxpy.parse(gpx_file) 

points = []
for track in gpx.tracks: 
    for segment in track.segments: 
        for point in segment.points: 
            points.append(point)

第一版的结果如图:

到目前为止,已经可以看到 gpx 轨迹以及当前的座标了。

接下来就是各种细化:

Overlay 的位置拜访

gpx 到视频座标系的投影

gpx 和视频串流对齐时间

一些 PIL 的样式调整

得到的最终结果如图。

完整影片,可以到 Youtube 查看。

[no cut][1080p] Warm-up at A before speed test, 2 laps, 4 min[1]

进阶需求

目前算是完成原型。想到的进一步需求有:

增加常用指标的 overlay component:距离、时间、爬升、心率、配速。

响应式(responsive)的 overlay 设计

根据视频文件元信息对齐 gpx 文件;也支持手动设置 offset

基于上一条,支持一个 gpx 对应一组 footage,自动全加 overlay

将 gpx 的 tracking points 插值(解决当前位置的跳跃问题;大约 2s 一个点)

基于上一条,提高 overlay 层的 FPS (目前是 1 FPS)

使用云技术将 overlay 的过程并发 (e.g. Apache beam/ GCP DataFlow)

使用 Youtube resumable API[2] 上传大文件

可以指定 Strava activity ID,直接拉取丰富数据

可以指定 activity 使用的起止时间

从 open street map 上,拉取简单的地图层做 background

REF

[1] [no cut][1080p] Warm-up at A before speed test, 2 laps, 4 min: https://www.youtube.com/watch?v=MVwVuyAwFFY

[2] Youtube resumable API: https://developers.google.com/youtube/v3/guides/using_resumable_upload_protocol

[3] https://www.titanwolf.org/Network/q/8006bfa5-a0a1-44e8-85b1-3ae3aac63936/x: https://www.titanwolf.org/Network/q/8006bfa5-a0a1-44e8-85b1-3ae3aac63936/x

[4] https://www.geeksforgeeks.org/create-transparent-png-image-with-python-pillow/: https://www.geeksforgeeks.org/create-transparent-png-image-with-python-pillow/

[5] https://stackoverflow.com/questions/4902198/pil-how-to-scale-text-size-in-relation-to-the-size-of-the-image: https://stackoverflow.com/questions/4902198/pil-how-to-scale-text-size-in-relation-to-the-size-of-the-image

博闻 | 明察 | 躬行
心法 | 手艺 | 随想