1. 基于陀螺仪数据的简单平移补偿
任务说明
本案例实现运动相机电子防抖中最基础的环节:读取陀螺仪角速度数据,积分得到帧间旋转角度,进而计算视频帧的全局平移量,对帧进行平移补偿,输出稳定后的视频。案例聚焦于纯平移补偿模型,即假设抖动主要体现为小角度旋转引起的画面平移,忽略透视与缩放变化。
实现方案
整体流程为:解析陀螺仪数据文件(CSV格式,包含时间戳与三轴角速度)→ 对相邻视频帧间的时间段积分得到旋转角 → 将旋转角映射为像素平移量 → 使用OpenCV的仿射变换对每帧进行平移补偿 → 写出稳定视频。为简化,本案例仅处理绕Y轴(偏航)与X轴(俯仰)的旋转,对应画面的水平与垂直平移。
代码实现
import cv2
import numpy as np
import pandas as pd
from scipy.integrate import cumulative_trapezoid
def load_gyro_data(csv_path):
"""读取陀螺仪CSV: 列名为 timestamp, gx, gy, gz (rad/s)"""
df = pd.read_csv(csv_path)
return df['timestamp'].values, df[['gx', 'gy', 'gz']].values
def compute_frame_rotation(timestamps, gyro, fps=30):
"""
计算每帧对应的累积旋转角(rad)。
帧i的旋转角 = 从帧i-1到帧i时间段内的角速度积分。
返回形状为(N,2)的数组,列对应 [绕Y轴(偏航), 绕X轴(俯仰)]。
"""
dt = np.diff(timestamps)
# 累积积分得到每个采样时刻的总旋转角
cum_angle = cumulative_trapezoid(gyro, timestamps, initial=0)
frame_count = int((timestamps[-1] - timestamps[0]) * fps) + 1
frame_times = np.linspace(timestamps[0], timestamps[-1], frame_count)
# 在帧时间点插值得到每帧的累积旋转角
interp_angles = np.column_stack([
np.interp(frame_times, timestamps, cum_angle[:, 1]), # 偏航
np.interp(frame_times, timestamps, cum_angle[:, 0]) # 俯仰
])
# 帧间旋转角 = 当前帧累积角 - 前一帧累积角
frame_rot = np.diff(interp_angles, axis=0, prepend=interp_angles[0:1])
return frame_rot
def rotation_to_pixel_shift(angles, focal_length_px):
"""将旋转角(rad)转换为像素平移量。小角度近似: shift = f * angle"""
return angles * focal_length_px
def stabilize_video(input_video, gyro_csv, output_video, focal_length_px=500):
"""主函数: 读取视频与陀螺仪数据, 输出平移补偿后的稳定视频"""
cap = cv2.VideoCapture(input_video)
fps = cap.get(cv2.CAP_PROP_FPS)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(output_video, fourcc, fps, (width, height))
timestamps, gyro = load_gyro_data(gyro_csv)
frame_rot = compute_frame_rotation(timestamps, gyro, fps)
shifts = rotation_to_pixel_shift(frame_rot, focal_length_px)
frame_idx = 0
while True:
ret, frame = cap.read()
if not ret:
break
if frame_idx < len(shifts):
dx, dy = shifts[frame_idx]
# 平移变换矩阵
M = np.float32([[1, 0, -dx], [0, 1, -dy]])
stabilized = cv2.warpAffine(frame, M, (width, height),
borderMode=cv2.BORDER_REPLICATE)
else:
stabilized = frame
out.write(stabilized)
frame_idx += 1
cap.release()
out.release()
if __name__ == '__main__':
# 示例调用: 请替换为实际文件路径
stabilize_video('input.mp4', 'gyro_data.csv', 'output_stabilized.mp4')
输入数据格式
陀螺仪CSV文件需包含三列:timestamp(秒)、gx(绕X轴角速度,rad/s)、gy(绕Y轴角速度,rad/s)。gz列可省略或忽略。示例数据前几行:
timestamp,gx,gy
0.000,-0.012,0.008
0.005,-0.015,0.010
0.010,-0.010,0.006
验证方法
运行以下命令处理测试视频:
python stabilize.py
判定标准:
- 程序正常结束且无报错,输出视频文件可正常播放;
- 输出视频时长与输入一致,分辨率不变;
- 目视检查输出视频:画面中的静态物体(如墙壁边缘)在抖动场景下比原视频更平稳,平移幅度明显减小;
- 若原视频包含明显的上下或左右抖动,补偿后这些抖动应被显著抑制。
关键说明
- 本实现假设陀螺仪时间戳与视频帧时间戳已对齐(同一时钟源),实际使用时需先进行时间同步校准;
- 焦距参数
focal_length_px需根据相机内参设定,典型运动相机广角模式约为400~600像素; - 平移补偿仅适用于小角度抖动(<5°),大角度旋转会产生透视畸变,需使用更完整的单应变换模型。