3、视频处理基础:视频文件读取与播放、摄像头调用、视频帧处理、视频写入与保存、帧率控制与性能优化
好,咱们进入第三章。前两章我们把 OpenCV 的环境搭好了,也搞清楚了图像是怎么在内存里存的。但做目标检测,你总不能老拿几张静态图在那测吧?真正干活的时候,要么是读一段录好的视频,要么就是直接怼摄像头实时跑。
这一章,我就带你把这套流程走通。说白了,就是学会怎么让 OpenCV 把视频一帧一帧地「喂」给你,你处理完再吐出去。这里面坑不少,尤其是帧率和性能这块,我当年刚入行的时候没少吃亏。
3.1 视频文件读取与播放
先从最简单的开始——读一个已经录好的视频文件。OpenCV 里干这事用的是 cv2.VideoCapture 这个类。
3.1.1 打开视频文件
代码极其简单,就一行:
cap = cv2.VideoCapture('test_video.mp4')
但这里有个坑,我一开始就踩过。你传进去的路径,如果视频文件不在当前工作目录下,它不会报错,但 cap.isOpened() 会返回 False。所以,我个人的习惯是,打开后立刻检查一下:
if not cap.isOpened():
print("错误:无法打开视频文件")
exit()
嗯,别嫌麻烦,这个习惯能帮你省下大量排查时间。
3.1.2 逐帧读取与播放
视频本质上就是一串连续的图片。我们用一个 while 循环,一帧一帧地读:
while True:
ret, frame = cap.read()
if not ret:
print("视频播放完毕")
break
cv2.imshow('Video Playback', frame)
# 按 'q' 键退出
if cv2.waitKey(25) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
这里 ret 是返回值,告诉你这一帧有没有读成功。读到最后一帧,ret 就会变成 False,我们就跳出循环。
cv2.waitKey(25) 里的这个 25 是毫秒数。它控制着两帧之间的等待时间,说白了就是播放速度。数值越小,播放越快。对于 30fps 的视频,理论上应该是 1000/30 ≈ 33 毫秒。
3.2 摄像头调用
读文件只是开胃菜,咱们做目标检测,大部分时候是要调摄像头的。代码几乎一样,区别就在 VideoCapture 的参数。
3.2.1 打开摄像头设备
把文件路径换成设备索引号就行:
cap = cv2.VideoCapture(0) # 0 代表默认摄像头
如果你有多个摄像头,比如外接了一个 USB 摄像头,那可能就是 1 或者 2。怎么确认?我一般写个小循环试:
for i in range(5):
cap = cv2.VideoCapture(i)
if cap.isOpened():
print(f"摄像头 {i} 可用")
cap.release()
3.2.2 设置摄像头参数
有时候默认的分辨率或者帧率不满足要求,我们可以手动设置。OpenCV 提供了一堆属性 ID:
| 属性 | OpenCV 常量 | 说明 |
|---|---|---|
| 帧宽度 | cv2.CAP_PROP_FRAME_WIDTH |
单位像素 |
| 帧高度 | cv2.CAP_PROP_FRAME_HEIGHT |
单位像素 |
| 帧率 | cv2.CAP_PROP_FPS |
每秒帧数 |
| 亮度 | cv2.CAP_PROP_BRIGHTNESS |
部分摄像头支持 |
设置方法也很直接:
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
cap.set(cv2.CAP_PROP_FPS, 30)
3.3 视频帧处理
帧读出来了,总不能直接显示吧?咱们得对它做点处理。这一节我讲几个最常用的操作,后面做 YOLO 检测时都会用到。
3.3.1 帧的预处理
最常见的预处理包括缩放、颜色空间转换和去噪:
# 缩放:YOLO 通常需要固定尺寸的输入
resized_frame = cv2.resize(frame, (416, 416))
# 颜色空间转换:OpenCV 默认是 BGR,YOLO 需要 RGB
rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# 高斯模糊去噪(可选)
blurred_frame = cv2.GaussianBlur(frame, (5, 5), 0)
我个人习惯是先缩放再转颜色空间,因为缩放后的像素少,处理起来快一点。别小看这点优化,在实时视频流里,每一毫秒都很宝贵。
3.3.2 在帧上绘制信息
做检测嘛,总得把结果画出来给人看。OpenCV 的绘图函数很强大:
# 画矩形框
cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
# 写文字标签
cv2.putText(frame, 'person', (x1, y1-10),
cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 255, 0), 2)
# 画一个中心点
cv2.circle(frame, (cx, cy), 5, (0, 0, 255), -1)
这里要注意,cv2.rectangle 的坐标是 (左上角 x, 左上角 y, 右下角 x, 右下角 y),别搞反了。我刚开始写的时候,经常把坐标顺序弄错,画出来的框歪到天边去。
3.4 视频写入与保存
处理完了,有时候需要把结果保存下来。比如你做了一个安防系统,得把检测到异常的视频片段录下来。
3.4.1 使用 VideoWriter
写入视频需要创建一个 cv2.VideoWriter 对象。你得告诉它四个东西:输出文件名、编码格式、帧率、画面尺寸。
# 定义编码器
fourcc = cv2.VideoWriter_fourcc(*'XVID')
# 或者用:cv2.VideoWriter_fourcc('X','V','I','D')
# 创建写入器
out = cv2.VideoWriter('output.avi', fourcc, 20.0, (640, 480))
编码格式有很多种,我列几个常用的:
| 编码 | 文件后缀 | 说明 |
|---|---|---|
XVID |
.avi | 兼容性好,文件较大 |
MJPG |
.avi | Motion JPEG,质量高 |
H264 |
.mp4 | 压缩率高,文件小 |
DIVX |
.avi | 老牌编码,兼容性不错 |
3.4.2 逐帧写入
写入的过程就是在循环里,每处理完一帧,就把它写进去:
while True:
ret, frame = cap.read()
if not ret:
break
# 在这里做你的处理,比如画检测框
processed_frame = process_frame(frame)
# 写入视频文件
out.write(processed_frame)
cv2.imshow('Recording...', processed_frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
out.release()
cap.release()
cv2.destroyAllWindows()
VideoWriter 的尺寸必须和你写入的帧尺寸完全一致。如果你处理时缩放了图像,记得把缩放后的尺寸传给 VideoWriter。我见过有人在这里栽跟头,写出来的视频全是绿的或者打不开。
3.5 帧率控制与性能优化
这是本章的重头戏。做实时检测,最怕的就是画面卡顿。帧率控制不好,检测结果就失去了实时性。
3.5.1 为什么需要控制帧率?
你想想看,摄像头采集的帧率可能是 30fps,但你的检测模型跑一帧可能要 50 毫秒(也就是 20fps)。如果不加控制,你的处理速度跟不上采集速度,内存里的帧队列会越堆越多,延迟越来越大。
说白了,就是「生产速度 > 消费速度」,系统迟早会崩。
3.5.2 使用时间戳控制帧率
我常用的方法是利用 cv2.getTickCount() 或者 Python 的 time.time() 来计算每帧的处理时间,然后动态调整:
import time
fps_target = 20 # 目标帧率
frame_time = 1.0 / fps_target
while True:
start_time = time.time()
ret, frame = cap.read()
if not ret:
break
# 处理帧(比如跑 YOLO 检测)
processed_frame = process_frame(frame)
cv2.imshow('Optimized', processed_frame)
# 计算处理耗时
elapsed = time.time() - start_time
# 如果处理得太快,就等一会儿
if elapsed < frame_time:
time.sleep(frame_time - elapsed)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
3.5.3 性能优化三板斧
做实时视频处理,性能优化是绕不开的。我总结了三个最有效的招:
- 降低分辨率:这是最立竿见影的方法。把 1920x1080 缩到 640x480,处理速度能快 4-5 倍。YOLO 检测通常只需要 416x416 或 320x320 的输入。
- 跳帧处理:如果模型跑得慢,可以每 2 帧或每 3 帧才做一次检测,中间的帧直接显示上一帧的检测结果。人眼其实看不太出来。
- 使用更轻量的模型:YOLOv8n 比 YOLOv8x 快好几倍,精度损失却不大。做实时应用,我一般首选 nano 或 small 版本。
3.6 本章知识体系
下面这张图把本章的核心流程串起来了,你可以对照着理一理思路:
这张图把整个流程串起来了:从视频源(文件、摄像头、图像序列)开始,经过 VideoCapture 读取,到帧处理(缩放、颜色转换、检测),最后分支到显示和保存。帧率控制贯穿整个过程,保证系统稳定运行。