4、视频流基础:视频编码原理(H.264/H.265)、封装格式(MP4/RTSP/RTMP)、OpenCV读取视频流的基本操作
好,咱们进入第四章。这一章可以说是整个课程的「地基」——视频流基础。你想想看,做边缘端实时视频分析,如果连视频怎么编码、怎么封装、怎么用OpenCV读出来都不清楚,后面那些AI模型推理、性能优化根本无从谈起。
我个人习惯,在讲任何实战之前,先把「原材料」搞明白。视频流就是我们的原材料。今天咱们就把它拆开揉碎了讲清楚。
4.1 视频编码原理:H.264 vs H.265
先说编码。为什么需要编码?原始视频数据量太大了。举个例子,一张1080p的RGB图像,分辨率1920x1080,每个像素3字节,一帧就是6MB左右。30帧每秒,一秒就是180MB。这谁受得了?
所以必须压缩。H.264和H.265是目前最主流的两种编码标准。
4.1.1 H.264(AVC)
H.264也叫AVC(Advanced Video Coding),2003年推出的标准。到现在快二十年了,依然统治着大部分视频场景。我在项目中遇到过很多次,客户说「我摄像头输出的是H.264」,基本上十有八九都是它。
H.264的核心思想是「帧内预测 + 帧间预测」。说白了就是:
- 帧内预测:利用同一帧内相邻像素的相似性来压缩。比如一块蓝天,我只存一个颜色和范围,不存每个像素。
- 帧间预测:利用前后帧的相似性。比如一个人站着不动,背景不变,我只存变化的部分。
H.264定义了三种帧类型:
| 帧类型 | 全称 | 说明 |
|---|---|---|
| I帧 | 关键帧 | 完整编码的一帧,不依赖其他帧。相当于「快照」 |
| P帧 | 前向预测帧 | 参考前面的I帧或P帧,只存差异 |
| B帧 | 双向预测帧 | 参考前后帧,压缩率最高,但计算量大 |
4.1.2 H.265(HEVC)
H.265也叫HEVC(High Efficiency Video Coding),2013年推出的。它是H.264的升级版,目标很明确:在同等画质下,码率再降一半。
怎么做到的?核心改进包括:
- 更大的编码块:H.264最大16x16像素,H.265最大64x64像素。大块编码效率更高。
- 更灵活的划分:H.265支持四叉树划分,可以更精细地适配图像内容。
- 更好的帧内预测:预测方向从H.264的9种增加到35种。
嗯,这里要注意:H.265虽然压缩率更好,但计算复杂度也更高。在边缘端设备上,比如树莓派、Jetson Nano,解码H.265可能需要硬件支持。我曾经在一个项目里,用纯CPU软解H.265的4K视频,结果帧率只有个位数……后来换了带硬件解码的模块才搞定。
- 带宽有限、存储有限 → 选H.265
- 边缘设备性能较弱 → 选H.264(硬件支持更广泛)
- 兼容性优先 → 选H.264
4.2 封装格式:MP4 / RTSP / RTMP
编码是压缩视频数据,封装是把压缩后的数据打包成文件或流。很多人容易把编码和封装搞混,其实它们是两回事。
4.2.1 MP4
MP4是最常见的文件封装格式。它基于ISO Base Media File Format,可以装H.264、H.265、AAC音频等。MP4的特点是:
- 支持随机访问(快进快退)
- 有moov box存储元数据(时长、分辨率等)
- 适合存储和点播
但MP4有个坑:它的moov box默认在文件末尾。如果你用OpenCV直接读一个没处理过的MP4文件,可能需要等它下载完才能开始播放。我建议用工具把moov box移到文件头,比如用qt-faststart。
4.2.2 RTSP
RTSP(Real Time Streaming Protocol)是实时流传输协议。它本身不传输数据,而是充当「遥控器」,控制RTP/UDP或TCP来传输视频流。
RTSP的特点:
- 支持实时传输,延迟低
- 支持VCR操作(播放、暂停、快进)
- 常用于IP摄像头、监控系统
在OpenCV中,RTSP地址通常长这样:rtsp://username:password@192.168.1.100:554/stream1
read()卡死。解决方案是加一个超时重连机制,或者用GStreamer管道来增强稳定性。
4.2.3 RTMP
RTMP(Real-Time Messaging Protocol)是Adobe推出的,主要用于直播推流。它基于TCP,延迟比RTSP稍高,但穿透防火墙的能力更强。
RTMP的特点:
- 推流为主,拉流也可以
- 需要Flash或专用播放器(现在逐渐被HLS/WebRTC取代)
- 常用于直播平台、视频会议
在边缘端,如果你需要把分析结果推送到云端或直播平台,RTMP是个不错的选择。但要注意,RTMP默认只支持H.264和AAC。
4.3 OpenCV读取视频流的基本操作
好,理论讲完了,咱们来点实际的。OpenCV读取视频流,核心就两个类:VideoCapture和VideoWriter。
4.3.1 读取本地视频文件
import cv2
# 打开视频文件
cap = cv2.VideoCapture('test.mp4')
# 检查是否打开成功
if not cap.isOpened():
print("无法打开视频文件")
exit()
# 获取视频属性
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))
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
print(f"帧率: {fps}, 分辨率: {width}x{height}, 总帧数: {total_frames}")
# 逐帧读取
while True:
ret, frame = cap.read()
if not ret:
break
# 在这里做你的AI分析
# process_frame(frame)
cv2.imshow('Video', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
4.3.2 读取RTSP摄像头流
import cv2
# RTSP地址
rtsp_url = "rtsp://admin:123456@192.168.1.100:554/stream1"
# 创建VideoCapture对象
cap = cv2.VideoCapture(rtsp_url)
# 设置缓冲区大小(减少延迟)
cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
# 设置解码后端(推荐使用FFMPEG)
cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('H', '2', '6', '4'))
while True:
ret, frame = cap.read()
if not ret:
print("读取失败,尝试重连...")
cap.release()
cap = cv2.VideoCapture(rtsp_url)
continue
# 显示帧率
cv2.putText(frame, f"FPS: {cap.get(cv2.CAP_PROP_FPS):.2f}",
(10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
cv2.imshow('RTSP Stream', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
CAP_PROP_BUFFERSIZE这个参数很关键。默认缓冲区可能很大,导致你读到的是几秒前的画面。设成1可以降低延迟,但要注意网络波动时可能会丢帧。我一般设成2-3,平衡一下。
4.3.3 常用属性设置
| 属性 | OpenCV常量 | 说明 |
|---|---|---|
| 帧宽 | CAP_PROP_FRAME_WIDTH | 设置/获取视频宽度 |
| 帧高 | CAP_PROP_FRAME_HEIGHT | 设置/获取视频高度 |
| 帧率 | CAP_PROP_FPS | 获取视频帧率(只读) |
| 缓冲区大小 | CAP_PROP_BUFFERSIZE | 设置内部缓冲区帧数 |
| 解码器 | CAP_PROP_FOURCC | 设置编码格式(如H264) |
| 超时 | CAP_PROP_OPEN_TIMEOUT_MSEC | 设置打开超时(毫秒) |
4.3.4 性能优化小技巧
在边缘端做实时分析,性能是命根子。我总结了几条经验:
- 降低分辨率:如果AI模型不需要全分辨率,先resize再推理。比如1080p缩放到640x360,计算量直接降到1/9。
- 跳帧处理:如果模型推理速度跟不上视频帧率,可以每N帧处理一次。比如30fps的视频,每3帧处理一次,相当于10fps的推理频率。
- 多线程读取:把视频读取和AI推理放在不同线程,避免I/O阻塞推理。
- 使用硬件解码:如果边缘设备支持,用GStreamer或FFMPEG的硬件加速后端。
# 多线程读取示例(简化版)
import threading
import queue
frame_queue = queue.Queue(maxsize=10)
def read_frames(cap):
while True:
ret, frame = cap.read()
if not ret:
break
if not frame_queue.full():
frame_queue.put(frame)
# 主线程
cap = cv2.VideoCapture(rtsp_url)
reader_thread = threading.Thread(target=read_frames, args=(cap,))
reader_thread.start()
while True:
if not frame_queue.empty():
frame = frame_queue.get()
# 在这里做AI推理
# result = model.predict(frame)
cv2.imshow('Stream', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
好了,这一章的内容就到这里。视频编码、封装格式、OpenCV读取,这三块是后面所有实战的基础。下一章我们会讲如何用OpenCV做视频预处理,包括缩放、裁剪、颜色空间转换等。到时候咱们再聊。