视频流基础:视频文件读取与属性获取、摄像头实时捕获、帧率控制与时间戳
做目标跟踪,第一步不是写算法,而是搞定视频流。我见过太多同学算法写得漂亮,结果卡在视频读取上——要么帧率不对,要么时间戳乱跳,最后跟踪效果一塌糊涂。今天咱们就把这块地基打牢。
视频文件读取:OpenCV 的 cv2.VideoCapture
说白了,视频就是一串连续的图片。OpenCV 用 cv2.VideoCapture 来搞定这件事。我个人习惯把视频文件路径直接传进去:
import cv2
cap = cv2.VideoCapture('test_video.mp4')
if not cap.isOpened():
print("文件打不开,检查路径或编码格式")
exit()
嗯,这里要注意:isOpened() 一定要检查。我在项目中遇到过,明明文件存在,但 OpenCV 就是打不开——后来发现是视频编码格式不兼容。建议先用 VLC 转成 H.264 的 MP4,兼容性最好。
属性获取:别小看这几行代码
视频文件里藏着很多信息。帧宽、帧高、总帧数、帧率——这些属性对后续处理至关重要。你想想看,如果不知道视频的帧率,你怎么计算目标运动的速度?
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = cap.get(cv2.CAP_PROP_FPS)
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
print(f"分辨率: {width}x{height}")
print(f"帧率: {fps:.2f}")
print(f"总帧数: {total_frames}")
| 属性 | OpenCV 常量 | 说明 |
|---|---|---|
| 帧宽 | CAP_PROP_FRAME_WIDTH | 单位像素 |
| 帧高 | CAP_PROP_FRAME_HEIGHT | 单位像素 |
| 帧率 | CAP_PROP_FPS | 每秒帧数 |
| 总帧数 | CAP_PROP_FRAME_COUNT | 视频总长度 |
| 当前帧位置 | CAP_PROP_POS_FRAMES | 从0开始 |
CAP_PROP_FPS 返回 0。这是因为部分摄像头驱动不返回这个属性。遇到这种情况,手动计时测帧率更靠谱。
摄像头实时捕获:从文件到实时的切换
从摄像头读取,其实就是把文件路径换成设备索引号。0 代表第一个摄像头,1 代表第二个,以此类推。
cap = cv2.VideoCapture(0) # 默认摄像头
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
为什么我要单独提 set()?因为摄像头默认分辨率可能很低。我调试时遇到过,摄像头标称支持 1080p,但 OpenCV 默认只给 640x480。手动设置一下,效果立竿见影。
set() 不生效,试试用 cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('M','J','P','G')) 切换编码格式。有些摄像头在 MJPG 模式下才能输出高分辨率。
帧率控制:别让程序跑飞了
视频文件有固定的帧率,但摄像头读取时,如果不加控制,程序会以「能读多快读多快」的方式运行。这在目标跟踪里是大忌——跟踪算法需要稳定的帧间隔。
我常用的方法是 time.sleep() 配合目标帧率计算延时:
import time
target_fps = 30
frame_time = 1.0 / target_fps
while True:
start_time = time.time()
ret, frame = cap.read()
if not ret:
break
# 处理帧(目标跟踪、显示等)
cv2.imshow('Frame', frame)
# 计算剩余等待时间
elapsed = time.time() - start_time
sleep_time = max(0, frame_time - elapsed)
time.sleep(sleep_time)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
你想想看,如果目标帧率是 30fps,每帧应该间隔 33ms。但处理一帧可能只用了 5ms,剩下的 28ms 如果不等待,程序就会以 200fps 运行——这会导致 CPU 满载,而且时间戳完全乱掉。
时间戳:跟踪系统的「心跳」
时间戳是目标跟踪的灵魂。没有准确的时间戳,你无法计算目标的速度、加速度,也无法做多传感器融合。
我建议用两种方式获取时间戳:
- 系统时间戳:
time.time()或cv2.getTickCount() - 视频帧索引:
cap.get(cv2.CAP_PROP_POS_FRAMES)
import cv2
import time
cap = cv2.VideoCapture('video.mp4')
fps = cap.get(cv2.CAP_PROP_FPS)
while True:
ret, frame = cap.read()
if not ret:
break
# 获取时间戳(秒)
timestamp_sys = time.time()
frame_idx = cap.get(cv2.CAP_PROP_POS_FRAMES)
timestamp_video = frame_idx / fps
# 在帧上绘制时间戳
cv2.putText(frame, f"Video: {timestamp_video:.3f}s",
(10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
cv2.putText(frame, f"System: {timestamp_sys:.3f}s",
(10, 70), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
cv2.imshow('Timestamp Demo', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
知识体系总览
下面这张图,是我自己总结的视频流处理核心逻辑。你看一遍,心里就有谱了。
从这张图可以看出来,视频流处理其实就三步:获取源 → 控制节奏 → 打时间戳。每一步都有坑,但踩过之后,后面的路就顺了。
好了,视频流基础就聊到这儿。记住一句话:视频流是目标跟踪的「水管」,水管不通,再好的算法也流不出来。把读取、属性、帧率、时间戳这四个点吃透,后面的实战才能游刃有余。