Onvif协议实战:5分钟快速搭建Python版摄像头控制脚本(附Profile S配置)
最近在做一个智能安防项目,需要对接不同品牌的网络摄像头。刚开始以为只要拿到RTSP地址就能搞定,结果发现各家厂商的PTZ控制接口五花八门,有的用私有协议,有的用HTTP API,调试起来简直让人崩溃。直到同事提醒我:“试试Onvif吧,现在主流摄像头都支持这个标准协议。”这才让我打开了新世界的大门。
Onvif协议本质上是一套基于Web Services的开放标准,它让不同品牌的网络摄像头能够用统一的方式通信。想象一下,你不再需要为海康、大华、宇视每个品牌都写一套控制代码,只需要实现一次Onvif客户端,就能控制所有兼容设备。这对于物联网开发者来说,简直是解放生产力的利器。
今天我要分享的,就是如何用Python快速搭建一个Onvif控制脚本,重点聚焦在Profile S这个最常用的视频流配置上。我会从环境搭建、设备发现、PTZ控制到快照获取,一步步带你走完整个流程,还会分享几个实际调试中遇到的坑和解决方案。无论你是刚接触Onvif的新手,还是需要快速集成摄像头控制功能的工程师,这篇文章都能给你实用的参考。
1. 环境准备与基础概念理解
在开始写代码之前,我们需要先搞清楚几个关键概念。Onvif协议虽然标准化,但它的实现方式比较特殊——它基于SOAP协议,使用XML格式进行数据交换。这意味着我们不是直接调用RESTful API,而是通过发送特定的XML请求来操作设备。
Profile S是Onvif中最基础也最常用的配置集,它定义了视频流相关的基本功能:
- 设备发现(WS-Discovery)
- 设备管理(获取设备信息、网络配置)
- 媒体服务(获取视频流URI、编码配置)
- PTZ控制(云台转动、变焦、预置位)
- 快照获取(获取当前画面截图)
对于大多数监控场景,Profile S已经足够覆盖核心需求。如果你的摄像头支持本地存储或高级视频分析,可能还需要关注Profile G或Profile T,但今天我们专注在Profile S的实现上。
1.1 Python环境与依赖安装
我推荐使用Python 3.7及以上版本,因为一些较新的库对异步支持更好。首先创建一个干净的虚拟环境:
python -m venv onvif_env
source onvif_env/bin/activate # Linux/Mac
# 或者
onvif_env\Scripts\activate # Windows
接下来安装核心依赖。这里有个小坑需要注意:Onvif协议基于SOAP,而Python中最常用的SOAP库是zeep。但直接安装onvif库时,它可能会依赖旧版本的zeep,导致兼容性问题。我建议分开安装:
pip install zeep>=4.0.0
pip install onvif-zeep
为什么选择onvif-zeep而不是原始的python-onvif?因为后者已经很久没更新了,而onvif-zeep基于zeep 4.0+,支持更现代的Python版本,而且错误处理更友好。另外,我们还需要安装一些辅助工具:
pip install requests # 用于下载快照
pip install python-dotenv # 管理配置文件(可选)
安装完成后,可以通过一个简单的测试来验证环境:
import zeep
print(f"zeep版本: {zeep.__version__}")
from onvif import ONVIFCamera
print("onvif-zeep导入成功")
如果这两行代码都能正常执行,说明基础环境已经就绪。
1.2 理解Onvif的通信模型
在开始编码前,花几分钟理解Onvif的通信模型能帮你少走很多弯路。Onvif设备通常通过HTTP/HTTPS提供服务,每个功能对应一个WSDL(Web Services Description Language)文件,这些文件定义了可用的操作和数据结构。
典型的Onvif设备会提供以下几个核心服务端点:
| 服务名称 | 默认路径 | 主要功能 |
|---|---|---|
| 设备服务 | /onvif/device_service | 获取设备信息、系统时间、网络配置 |
| 媒体服务 | /onvif/media_service | 获取视频流URI、编码配置、快照 |
| PTZ服务 | /onvif/ptz_service | 云台控制、预置位管理 |
| 事件服务 | /onvif/events_service | 订阅设备事件(移动侦测等) |
| 成像服务 | /onvif/imaging_service | 图像参数调整(亮度、对比度等) |
当你连接到一个Onvif摄像头时,实际上是在与这些服务端点进行SOAP通信。好消息是,onvif-zeep库已经帮我们封装了这些细节,我们只需要关注业务逻辑即可。
2. 设备发现与连接建立
设备发现是Onvif交互的第一步。在实际项目中,你可能需要自动发现局域网内的摄像头,而不是手动配置IP地址。Onvif使用WS-Discovery协议进行设备发现,这是一个基于UDP多播的标准协议。
2.1 使用WS-Discovery发现设备
下面是一个简单的设备发现脚本,它会扫描局域网内所有支持Onvif的设备:
from onvif import ONVIFCamera
import socket
import time
def discover_onvif_devices(timeout=5):
"""
发现局域网内的Onvif设备
Args:
timeout: 发现超时时间(秒)
Returns:
list: 发现的设备信息列表,每个元素是(ip, port, device_service_url)元组
"""
# WS-Discovery多播地址和端口
MULTICAST_GROUP = "239.255.255.250"
MULTICAST_PORT = 3702
# 构建Probe消息(简化版)
probe_message = '''<?xml version="1.0" encoding="UTF-8"?>
<e:Envelope xmlns:e="http://www.w3.org/2003/05/soap-envelope"
xmlns:w="http://schemas.xmlsoap.org/ws/2004/08/addressing"
xmlns:d="http://schemas.xmlsoap.org/ws/2005/04/discovery"
xmlns:dn="http://www.onvif.org/ver10/network/wsdl">
<e:Header>
<w:MessageID>uuid:{message_id}</w:MessageID>
<w:To e:mustUnderstand="true">urn:schemas-xmlsoap-org:ws:2005:04:discovery</w:To>
<w:Action e:mustUnderstand="true">http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</w:Action>
</e:Header>
<e:Body>
<d:Probe>
<d:Types>dn:NetworkVideoTransmitter</d:Types>
</d:Probe>
</e:Body>
</e:Envelope>'''
# 生成唯一的Message ID
import uuid
message_id = str(uuid.uuid4())
probe_message = probe_message.format(message_id=message_id)
devices = []
try:
# 创建UDP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.settimeout(timeout)
# 发送Probe消息
sock.sendto(probe_message.encode(), (MULTICAST_GROUP, MULTICAST_PORT))
# 接收响应
start_time = time.time()
while time.time() - start_time < timeout:
try:
data, addr = sock.recvfrom(4096)
ip = addr[0]
# 解析响应(简化处理,实际项目需要完整解析XML)
if b"NetworkVideoTransmitter" in data:
# 从响应中提取设备服务URL
# 这里简化处理,实际需要解析XML获取XAddrs
devices.append({
'ip': ip,
'raw_response': data.decode('utf-8', errors='ignore')[:200] # 只取前200字符
})
except socket.timeout:
break
except Exception as e:
print(f"设备发现失败: {e}")
finally:
sock.close()
return devices
# 使用示例
if __name__ == "__main__":
print("正在扫描局域网内的Onvif设备...")
found_devices = discover_onvif_devices(timeout=3)
if found_devices:
print(f"发现 {len(found_devices)} 个设备:")
for i, device in enumerate(found_devices, 1):
print(f"{i}. IP: {device['ip']}")
print(f" 响应摘要: {device['raw_response']}")
else:
print("未发现任何Onvif设备")
注意:上面的发现代码是简化版本,实际生产环境中建议使用成熟的WS-Discovery库,如
wsdiscovery,它能更完整地处理各种响应和错误情况。
2.2 建立设备连接
发现设备后,我们需要建立连接。这里假设我们已经知道了设备的IP地址、端口、用户名和密码。大多数摄像头的Onvif服务运行在80端口(HTTP)或443端口(HTTPS)。
class OnvifCameraController:
def __init__(self, ip, port, username, password):
"""
初始化Onvif摄像头控制器
Args:
ip: 摄像头IP地址
port: Onvif服务端口(通常是80或443)
username: 摄像头用户名
password: 摄像头密码
"""
self.ip = ip
self.port = port
self.username = username
self.password = password
# 设备服务URL(大多数摄像头使用这个路径)
self.device_service_url = f"http://{ip}:{port}/onvif/device_service"
# 初始化ONVIFCamera对象
self.camera = None
self.media_service = None
self.ptz_service = None
def connect(self):
"""
连接到摄像头并初始化服务
Returns:
bool: 连接是否成功
"""
try:
print(f"正在连接摄像头 {self.ip}:{self.port}...")
# 创建ONVIFCamera实例
self.camera = ONVIFCamera(
self.ip,
self.port,
self.username,
self.password,
wsdl_dir=None # 自动下载WSDL文件到临时目录
)
# 更新设备服务URL(有些摄像头可能使用不同的路径)
devicemgmt = self.camera.create_devicemgmt_service()
capabilities = devicemgmt.GetCapabilities({'Category': 'All'})
# 获取设备信息
device_info = devicemgmt.GetDeviceInformation()
print(f"连接成功!")
print(f"设备型号: {device_info.Model}")
print(f"制造商: {device_info.Manufacturer}")
print(f"固件版本: {device_info.FirmwareVersion}")
print(f"序列号: {device_info.SerialNumber}")
# 初始化媒体服务
self.media_service = self.camera.create_media_service()
# 初始化PTZ服务(如果设备支持)
try:
self.ptz_service = self.camera.create_ptz_service()
print("设备支持PTZ控制")
except Exception as e:
print(f"设备不支持PTZ控制或PTZ服务初始化失败: {e}")
self.ptz_service = None
return True
except Exception

&spm=1001.2101.3001.5002&articleId=149902120&d=1&t=3&u=1149b8e41bf04bf580f493c69315bc84)
224

被折叠的 条评论
为什么被折叠?



