目录
前言
从本章节开始,正式进入我们的视频监控系统的设计。按照一般的逻辑思维出发,视频监控系统需要包括输入信息、处理模块以及输出信息三个部分,其中输入信息则是整个系统的开端,即网络摄像仪(IPC)实时采集的画面。一般情况下,我们可以直接通过协议从相机直接获取画面数据,但是网络相机一般会设置同时最大访问用户数(通常为5个),这就导致在复杂的系统环境中会采用硬盘录像机或流媒体来统一管理原始摄像仪画面信息并针对需要进行画面的转发,以规避网络相机同时最大访问数量的限制,且两者均通过相同的网络传输机制进行画面获取,所以在获取画面的方式上是一致。
一、如何获取网络摄像头数据?
获取网络摄像头数据是一个分为两个核心阶段的过程:第一阶段是拉流,即通过网络协议从摄像头或流媒体服务器获取压缩编码的视频数据包;第二阶段是解码,即将这些压缩的数据包解压成计算机能够理解和处理的原始像素矩阵(例如BGR或YUV格式)。这两个阶段在技术和实现上相对独立,理解其区别是构建高效系统的关键。
1. 拉流(Streaming):建立通信,获取数据包
拉流的本质是客户端与流媒体服务器之间的一次网络会话,其核心是 RTSP 协议。
RTSP 协议详解
角色定位:RTSP 是一个控制协议,如同一个播放器的遥控器。它负责发起会话、控制播放(PLAY)、暂停(PAUSE)、终止(TEARDOWN)等命令,但并不直接传输音视频数据本身。
工作流程:一个典型的 RTSP 会话建立过程包含以下几个步骤:
OPTIONS: 客户端查询服务器支持的命令。
DESCRIBE: 获取流媒体的描述信息,主要是 SDP。SDP 文件至关重要,它描述了媒体流的详细信息,包括:
媒体类型(视频、音频)
编解码格式(H.264, H.265, AAC, G.711等)
传输协议(通常是RTP over UDP/TCP)
目标端口号
SETUP: 根据 SDP 信息,为每路媒体流(如视频流和音频流)建立传输通道,协商 RTP/RTCP 使用的端口。
PLAY: 发送播放命令,服务器开始通过 RTP 协议发送数据包。
TEARDOWN: 会话结束时,终止流传输。
数据传输:实际的音视频数据是通过 RTP 协议在 SETUP 阶段协商好的端口上进行传输的。RTCP 协议则负责同步和统计信息。RTP 数据包中装载着被切割打包的 H.264 NAL 单元。
拉流工具与库
FFmpeg/Libav: 行业标准。其 libavformat 库提供了强大的协议处理能力,能熟练处理 RTSP、SDP、RTP 等,是大多数高级工具的基础。
OpenCV 的 VideoCapture: 其底层调用了 FFmpeg,但对其复杂功能的封装和暴露有限,更侧重于“一键获取帧”,而非精细控制拉流过程。
专用 SDK: 当我们采购专业的监控相机,如海康、大华等品牌,这类厂商会有内部的SDK可供使用,但是标准的RTSP协议也是支持的。
2. 解码(Decoding):从压缩数据到原始帧
解码是拉流的下一步,也是一个计算密集型的环节。它接收拉流模块传来的压缩视频数据包(例如一个H.264帧),通过解码器,输出原始的图像帧。
编解码标准
H.264 (AVC): 目前最广泛支持的格式,在兼容性和效率之间取得了最佳平衡。
H.265 (HEVC): 更高的压缩率,同等画质下带宽和存储占用比 H.264 低约 50%,但对计算能力要求更高。
MJPEG: 每一帧都是一张独立的 JPEG 图片,压缩率低但延迟极低,且CPU解码简单。
解码方式
解码的核心选择是:软件解码 还是 硬件解码。软解码即采用CPU解码,硬解码则采用专业的硬件设备进行解码,比如英伟达的NVDEC、华为升腾的DCPP等。以下我提供一个表格供大家参考。
解码方式 | 原理 | 优点 | 缺点 | 适用场景 |
---|---|---|---|---|
软件解码 | 完全依靠CPU的通用计算能力运行解码算法(如FFmpeg的h264解码器) | 通用性强,无需特定硬件,在任何机器上都能运行 | CPU占用率极高,多路解码时迅速成为系统瓶颈,功耗高 | 追求稳定性且解码路数不多、CPU资源充足 |
硬件解码 | 调用专用的硬件芯片(如GPU上的NVDEC、Intel CPU的QSV)进行解码。 | 效率极高,大幅降低CPU负载,支持超高路数并发,功耗低 | 依赖特定硬件,需要安装正确的驱动和运行库,配置稍复杂。中断处理机制复杂 | 任何生产环境,尤其是多路实时视频分析系统的必选项 |
二、代码获取网络摄像头数据
本节将提供两种主流的 Python 实现方式,分别使用 OpenCV 和 PyAV 库来完成网络摄像头的拉流和解码工作。两者底层均调用 FFmpeg,但提供的抽象层级和控制粒度不同。
1. 使用 OpenCV 实现(简洁高效)
OpenCV 提供了最高层次的抽象,将拉流、解码、帧获取封装成简单的 VideoCapture 接口,非常适合快速开发和原型验证。
代码示例:基础拉流与解码
"""
# 安装 OpenCV 的核心库和 GUI 功能(imshow)
pip install opencv-python
# 如果您还需要额外的贡献模块(如 SIFT),安装这个
# pip install opencv-contrib-python
"""
import cv2
import time
def read_camera_with_opencv(rtsp_url):
"""
使用OpenCV从RTSP流中读取并解码视频帧(软件解码)
"""
# 创建VideoCapture对象
cap = cv2.VideoCapture(rtsp_url)
# 检查流是否成功打开
if not cap.isOpened():
print(f"Error: Cannot open RTSP stream: {rtsp_url}")
return
# 获取视频的基本信息(可选)
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))
print(f"Stream opened. FPS: {fps}, Resolution: {width}x{height}")
frame_count = 0
start_time = time.time()
try:
while True:
# read() 方法完成了拉流和解码的所有工作,返回retval和image
ret, frame = cap.read()
if not ret:
print("Failed to grab frame. Stream may have ended.")
# 简单的重连机制,等待2秒后尝试重新连接
time.sleep(2)
cap.release()
cap = cv2.VideoCapture(rtsp_url)
if not cap.isOpened():
print("Reconnection failed.")
break
else:
print("Reconnected to stream.")
continue
# 此处获得的就是解码后的BGR格式的numpy数组帧
# 可以在这里进行你的AI处理,例如目标检测
# process_frame_for_ai(frame)
frame_count += 1
# 计算并打印实时FPS(每秒帧数)
if frame_count % 30 == 0:
elapsed = time.time() - start_time
current_fps = 30 / elapsed
print(f"Current FPS: {current_fps:.2f}")
start_time = time.time()
# 显示画面(在服务器上运行可注释掉以节省资源)
cv2.imshow('RTSP Stream (OpenCV)', frame)
# 按 'q' 键退出循环
if cv2.waitKey(1) & 0xFF == ord('q'):
break
except KeyboardInterrupt:
print("Stream reading interrupted by user.")
finally:
# 释放资源
cap.release()
cv2.destroyAllWindows()
if __name__ == "__main__":
# 替换成你摄像头的实际RTSP URL
rtsp_url = "rtsp://username:password@your_camera_ip:554/stream_path"
read_camera_with_opencv(rtsp_url)
OpenCV 方案特点分析:
优点:代码极其简洁,API 易用,适合初学者和快速验证。
缺点:
黑盒操作:拉流和解码过程被高度封装,难以进行精细控制(如缓冲区大小、解码参数优化)。
稳定性:内置的网络异常处理和重连机制较弱,长时间运行在高网络抖动环境下可能不稳定。
性能:默认使用CPU软解,在多路视频时压力巨大,且难以直接指定使用更高效的硬件解码器。
2. 使用 PyAV 实现(精细控制)
PyAV 是 FFmpeg 的 Python 绑定,它提供了更底层的访问和控制能力,允许开发者清晰地分离拉流和解码步骤,并进行更专业的配置。
代码示例:基础拉流与解码
"""
! !!! 推荐版本 11.0.0
pip install av==11.0.0
"""
import av
import cv2 # 仅用于最后显示,也可以用其他库
import time
def open(rtsp, timeout):
ret = False
try:
options = {
"analyzeduration": "10000000", # 设置analyzeduration选项为10秒
"probesize": "5000000", # 设置probesize选项为5000000字节
'rtsp_transport': 'tcp', # 设置RTSP传输协议,可以是"tcp"或"udp"
'max_delay': '50000', # 设置最大延迟
'stimeout': str(timeout * 1000000), # 设置超时时间,单位是微秒
}
res = av.open(rtsp, timeout=timeout,options=options)
ret = True
except:
res = None
return ret, res
def read_camera_with_pyav(rtsp_url):
"""
使用PyAV从RTSP流中读取并解码视频帧(软件解码)
PyAV提供了对FFmpeg底层API的直接绑定,控制力更强。
"""
# 创建容器对象,打开RTSP流
ret,container = open(rtsp_url, timeout=10)
if !ret:
# 无法获取,提前退出
return None
# 获取视频流
video_stream = container.streams.video[0]
print(f"Stream info: {video_stream}")
print(f"Codec: {video_stream.codec_context.name}, Format: {video_stream.format}")
frame_count = 0
start_time = time.time()
try:
# 从容器中解码视频包
for packet in container.demux(video_stream):
# 解复用器可能也会输出空包,需要跳过
if packet.size == 0:
continue
# 对一个Packet进行解码,可能得到0个或多个Frame
for frame in packet.decode():
# 将解码后的帧转换为BGR24格式的numpy数组
# PyAV默认解码出的帧可能是YUV420P等格式,需要转换以供OpenCV使用
img = frame.to_ndarray(format='bgr24')
frame_count += 1
# 计算并打印实时FPS
if frame_count % 30 == 0:
elapsed = time.time() - start_time
current_fps = 30 / elapsed
print(f"Current FPS: {current_fps:.2f}")
start_time = time.time()
# 显示画面
cv2.imshow('RTSP Stream (PyAV)', img)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 循环结束后关闭容器
container.close()
except KeyboardInterrupt:
print("Stream reading interrupted by user.")
except Exception as e:
print(f"An error occurred: {e}")
finally:
cv2.destroyAllWindows()
if not container.closed:
container.close()
if __name__ == "__main__":
# 替换成你摄像头的实际RTSP URL
rtsp_url = "rtsp://username:password@your_camera_ip:554/stream_path"
read_camera_with_pyav(rtsp_url)
PyAV 方案特点分析:
优点:
透明可控:清晰地区分了 demux(解复用/拉流)和 decode(解码)过程,可以深入了解和处理每个环节。
强大配置:可以轻松设置各种FFmpeg参数,如超时、传输协议(rtsp_transport)、缓冲区大小等,稳健性更强。
格式灵活:可以自由获取不同格式(YUV, RGB, BGR)的原始帧数据,方便不同下游处理库使用。
性能潜力:虽然本例是软解,但PyAV能更 straightforward 地配置硬件解码(后续章节会讲)。
缺点:
学习曲线:需要理解容器(Container)、流(Stream)、包(Packet)、帧(Frame)等概念,API稍复杂。
代码量:实现相同功能所需的代码比OpenCV更多。
方案对比
特性 | opencv | pyav |
---|---|---|
易用性 | ⭐⭐⭐⭐⭐(极高) | ⭐⭐⭐(中等) |
控制粒度 | ⭐(低,黑盒) | ⭐⭐⭐⭐⭐(高,可精细调控) |
稳定性 | ⭐⭐⭐(依赖底层实现) | ⭐⭐⭐⭐(可配置性强,更稳健) |
适用场景 | 快速原型、初学者、简单应用 | 生产环境、多路流、高性能要求、需要深度定制 |
三、常用工具介绍
1.VLC的安装与使用
我们除了采用代码进行原始流的获取,还会借助三方稳定的工具进行网络视频流的测试,这里推荐vlc。
- 点击vlc进行安装
- 根据要求安装好后,我们打开vlc,即可完成安装
2.liveNVR的安装与使用
相信小伙伴在学习过程中不一定有真实的IPC进行学习和测试,所以我们依据已学知识,我们可以采用流媒体进行RTSP视频流的模拟。此处介绍一个收费,但是很好用的工具liveNVR(免费期内抓紧使用)。
- 点击liveNVR即可进入下载界面,这里依据自己的系统版本选择,本机为windows,如下图所示
- 直接解压即可使用,双击运行,等待终端运行,系统会有运行提示
- 在浏览器上输入localhost:10800,首次登陆默认admin和admin,登录后需要修改密码
- 登录成功后点击通道配置,选择对应通道的编辑进行配置,即可上传本地视频进行视频流获取
- 在配置完视频之后,我们通过基础配置,设置rtsp的端口号即可进行rtsp流的获取
- 打开vlc,在媒体-打开网络串流窗口输入对应通道的rtsp地址,点击播放即可查看画面
大家伙也可以采用代码直接进行访问,实现拉流解码的第一步
总结
本节简介了拉流解码的一般性流程,分享了opencv和pyav两种拉流解码的方式,除此之外,还介绍了VLC和liveNVR两种工具,以便初学者进行rtsp流的模拟和实验仿真。
下期预告
- 拉流的基本原理
- 通过rtsp协议流程进行通信(补充到基础知识与原理)
- H264和H265的区别
- H264数据包解析(补充到基础知识与原理)
- 多路视频管理模块的设计