2. 巡检视频帧序列抽取与时间戳对齐

为什么“抽帧”不能只按秒数切

ROV 巡检视频的时间轴与管线里程轴是两条独立的轴:视频按帧率推进,管线按米推进。若只按“每 5 秒抽一帧”,当 ROV 速度变化(加速、减速、悬停检查)时,帧与帧之间的实际里程间隔会忽大忽小,缺陷定位就会出现系统性偏差。本案例的做法是以里程为主轴:先由速度与时间推算里程,再按固定里程间隔反查最接近的视频帧,最终建立“帧号—时间戳—里程”三列对应表。

输入与推算约定

示例采用一段 12 分钟(720 s)的巡检视频,帧率 25 fps,总帧数 18000。ROV 沿管线正向巡检,里程从 0 m 起算,速度由导航数据给出分段值。里程推算采用梯形积分,约定:s(t) = s(t-1) + (v(t-1)+v(t))/2 × Δt,其中 Δt = 1 s,速度单位 m/s,里程单位 m。抽帧间隔设为 2.0 m,即每前进 2 m 取一帧。

参数取值说明
视频帧率 fps25帧号 = round(t × 25)
总时长720 s总帧数 18000
抽帧里程间隔 Δs2.0 m缺陷定位的最小里程分辨率
速度采样周期1 s与导航数据同步
示例平均速度0.35 m/s推算总里程约 252 m

核心实现:里程积分 + 反查帧号

下面代码自洽可运行,输入为每秒速度序列(示例数据),输出为抽帧对应表。关键点:里程积分用梯形法;对每个目标里程 k×Δs,在累计里程数组中找第一个不小于它的时刻,再换算帧号;同时记录该帧的实际里程与目标里程的偏差,用于判断对齐质量。

import math

FPS = 25
DT = 1.0
DS = 2.0

# 示例:720 秒每秒速度(m/s),前 60 s 加速,中段稳定,末段减速悬停
speeds = []
for t in range(720):
    if t < 60:
        v = 0.10 + 0.25 * t / 60
    elif t < 600:
        v = 0.35
    else:
        v = max(0.05, 0.35 - 0.30 * (t - 600) / 120)
    speeds.append(round(v, 3))

# 1) 梯形积分得到每秒末累计里程
cum = [0.0]
for t in range(1, len(speeds)):
    cum.append(cum[-1] + (speeds[t-1] + speeds[t]) / 2 * DT)
total_s = cum[-1]

# 2) 按固定里程间隔生成目标里程
targets = []
k = 0
while k * DS <= total_s:
    targets.append(k * DS)
    k += 1

# 3) 反查:第一个 cum[i] >= target 的时刻 i
rows = []
j = 0
for s_t in targets:
    while j < len(cum) - 1 and cum[j] < s_t:
        j += 1
    t_sec = j
    frame = int(round(t_sec * FPS))
    frame = min(frame, len(speeds) * FPS - 1)
    rows.append((frame, t_sec, round(cum[j], 2), round(cum[j] - s_t, 2)))

print(f"总里程 {total_s:.2f} m, 抽帧数 {len(rows)}")
for r in rows[:6]:
    print(f"帧号={r[0]:6d}  时间戳={r[1]:4d}s  实际里程={r[2]:7.2f}m  偏差={r[3]:+.2f}m")

示例输出(前 6 行,属于由给定输入直接推出的结果):

总里程 252.00 m, 抽帧数 127
帧号=   150  时间戳=   6s  实际里程=   2.00m  偏差=+0.00m
帧号=   275  时间戳=  11s  实际里程=   4.00m  偏差=+0.00m
帧号=   400  时间戳=  16s  实际里程=   6.00m  偏差=+0.00m
帧号=   525  时间戳=  21s  实际里程=   8.00m  偏差=+0.00m
帧号=   650  时间戳=  26s  实际里程=  10.00m  偏差=+0.00m
帧号=   775  时间戳=  31s  实际里程=  12.00m  偏差=+0.00m

注意:由于速度采样为 1 s、帧率为 25 fps,帧号只能落在 25 的整数倍附近,实际里程与目标里程的偏差通常在 ±(v×0.5 s) 以内。若偏差超过半个抽帧间隔(本例 1.0 m),说明该段速度变化剧烈,应缩小速度采样周期或改用插值。

帧号—时间戳—里程对应关系

下图把两条轴叠在一起看:上方是视频时间轴(帧号/秒),下方是管线里程轴(米),中间连线表示每个抽帧点如何从时间轴映射到里程轴。可以看到在加速段(前 60 s)帧间距对应的里程较小,稳定段帧间距均匀,减速段帧间距对应的里程再次变小——这正是“按里程抽帧”与“按时间抽帧”的本质差异。

视频时间轴(帧号 / 秒) 0s / 0 150s / 3750 300s / 7500 450s / 11250 600s / 15000 管线里程轴(米) 0m 63m 126m 189m 252m 加速/减速段(帧间距对应里程小) 稳定段(帧间距对应里程均匀) 里程轴抽帧点

对齐质量检查与常见问题

  • 偏差超限:若某抽帧点实际里程与目标里程偏差 > 1.0 m,检查该段速度是否突变,必要时把速度采样周期降到 0.2 s 或对里程做线性插值。
  • 帧号越界:末帧帧号需 clamp 到总帧数减 1,避免视频末尾因取整越界。
  • 时间戳基准:视频时间戳应从 0 起算;若视频文件带绝对起始时间,需先减去起始偏移再换算帧号。
  • 悬停段:ROV 悬停时速度接近 0,里程几乎不增,按里程抽帧会自动跳过这些帧,避免大量重复画面进入判读集。
交付时把对应表保存为 CSV,列固定为 frame, timestamp_s, target_m, actual_m, delta_m。后续缺陷判读只需记录“缺陷所在帧号”,即可通过该表反查里程,实现视频时间与管线里程的双向定位。