这里用到camera_discovery模块
pip install camera_discovery
from camera_discovery import CameraDiscovery
camera_ip = CameraDiscovery.ws_discovery()
print(camera_ip)
成功的话会返回一个摄像头ip列表
不过笔者在使用过程中遇到了一个莫名其妙的问题erro 101 network is unreachable
(网络不可达)可我ping的通网络,连接也没问题。查询后也没个准确的答案,只知道报错的py文件下有个sock.sendto函数,这个函数发送会有timeout时长,超过就会报上面的错误,我的方法是直接try:下运行sock.sendto函数,报错信息忽略掉,就解决问题了。
try:
sock.sendto()
except:
pass
这是Ubuntu的解决方法,Windows下会报hostname的错误,其实也不是这个错误,Windows下没有hostname -i
这个命令,所以无法获取到本机ip,直接这样输入就可以了(Ubuntu也同样适用)CameraDiscovery.ws_discovery(‘192.168.123.xxx’)
,原本以为自己添加ip就可以了,可后面运行时又报了怎么一个错误 AttributeError: module 'select' has no attribute 'poll'
,查了下是说这个模块只适用于Linux 2.5+,并不支持其他系统。poll机制是Linux内核的一种内核微调机制,由此可以说这个搜索ip的功能只能适用于Ubuntu了,但是如果你已知摄像头ip的话,下面的摄像头信息获取功能你也能在Windows使用
另外此模块还可以对摄像头做其他操作,如获取摄像头mac地址等,官网提供的导入方式如下:
from camera_discovery import CameraONVIF
Class = CameraONVIF(camera_ip, user, password)
上面的导入其实是不对的,导入方式应该改为这样
from camera_discovery.CameraDiscovery import CameraONVIF
camera = CameraONVIF(camera_ip, user, password)
camera.camera_start()#初始化摄像头相关信息,不改源码会出错
这样生成camera对象是成功了,但是初始化相关信息还是会报如下错误
onvif.exceptions.ONVIFError: Unknown error: AnySimpleType.pytonvalue() not implemented
相关获取功能还是不能使用,这个库这是bug多多,还得改改源码,改动其实很简单的,就是 import zeep
,加了个 zeep_pythonvalue
函数 以及 在 camera_start
函数下 加上句 zeep.xsd.simple.AnySimpleType.pythonvalue = self.zeep_pythonvalue
,整体改后的CameraONVIF类如下:
import subprocess
from typing import List
import WSDiscovery
from onvif import ONVIFCamera
import zeep #额外加的
def ws_discovery(scope = None) -> List:
"""Discover cameras on network using onvif discovery.
Returns:
List: List of ips found in network.
"""
lst = list()
if(scope == None):
cmd = 'hostname -I'
scope = subprocess.check_output(cmd, shell=True).decode('utf-8')
wsd = WSDiscovery.WSDiscovery()
wsd.start()
ret = wsd.searchServices()
for service in ret:
get_ip = str(service.getXAddrs())
get_types = str(service.getTypes())
for ip_scope in scope.split():
result = get_ip.find(ip_scope.split('.')[0] + '.' + ip_scope.split('.')[1])
if result > 0 and get_types.find('onvif') > 0:
string_result = get_ip[result:result+13]
string_result = string_result.split('/')[0]
lst.append(string_result)
wsd.stop()
lst.sort()
return lst
class CameraONVIF:
"""This class is used to get the information from all cameras discovered on this specific
network."""
def __init__(self, ip, user, password):
"""Constructor.
Args:
ip (str): Ip of the camera.
user (str): Onvif login.
password (str): Onvif password.
"""
self.cam_ip = ip
self.cam_user = user
self.cam_password = password
def zeep_pythonvalue(self, xmlvalue): #额外加的
return xmlvalue
def camera_start(self):
"""Start module.
"""
mycam = ONVIFCamera(self.cam_ip, 80, self.cam_user, self.cam_password, no_cache = True)
media = mycam.create_media_service()
zeep.xsd.simple.AnySimpleType.pythonvalue = self.zeep_pythonvalue #额外加的
media_profile = media.GetProfiles()[0]
self.mycam = mycam
self.camera_media = media
self.camera_media_profile = media_profile
def get_hostname(self) -> str:
"""Find hostname of camera.
Returns:
str: Hostname.
"""
resp = self.mycam.devicemgmt.GetHostname()
return resp.Name
def get_manufacturer(self) -> str:
"""Find manufacturer of camera.
Returns:
str: Manufacturer.
"""
resp = self.mycam.devicemgmt.GetDeviceInformation()
return resp.Manufacturer
def get_model(self) -> str:
"""Find model of camera.
Returns:
str: Model.
"""
resp = self.mycam.devicemgmt.GetDeviceInformation()
return resp.Model
def get_firmware_version(self) -> str:
"""Find firmware version of camera.
Returns:
str: Firmware version.
"""
resp = self.mycam.devicemgmt.GetDeviceInformation()
return resp.FirmwareVersion
def get_mac_address(self) -> str:
"""Find serial number of camera.
Returns:
str: Serial number.
"""
resp = self.mycam.devicemgmt.GetDeviceInformation()
return resp.SerialNumber
def get_hardware_id(self) -> str:
"""Find hardware id of camera.
Returns:
str: Hardware Id.
"""
resp = self.mycam.devicemgmt.GetDeviceInformation()
return resp.HardwareId
def get_resolutions_available(self) -> List:
"""Find all resolutions of camera.
Returns:
tuple: List of resolutions (Width, Height).
"""
request = self.camera_media.create_type('GetVideoEncoderConfigurationOptions')
request.ProfileToken = self.camera_media_profile.token
config = self.camera_media.GetVideoEncoderConfigurationOptions(request)
return [(res.Width, res.Height) for res in config.H264.ResolutionsAvailable]
def get_frame_rate_range(self) -> int:
"""Find the frame rate range of camera.
Returns:
int: FPS min.
int: FPS max.
"""
request = self.camera_media.create_type('GetVideoEncoderConfigurationOptions')
request.ProfileToken = self.camera_media_profile.token
config = self.camera_media.GetVideoEncoderConfigurationOptions(request)
return config.H264.FrameRateRange.Min, config.H264.FrameRateRange.Max
def get_date(self) -> str:
"""Find date configured on camera.
Returns:
str: Date in string.
"""
datetime = self.mycam.devicemgmt.GetSystemDateAndTime()
return datetime.UTCDateTime.Date
def get_time(self) -> str:
"""Find local hour configured on camera.
Returns:
str: Hour in string.
"""
datetime = self.mycam.devicemgmt.GetSystemDateAndTime()
return datetime.UTCDateTime.Time
def is_ptz(self) -> bool:
"""Check if camera is PTZ or not.
Returns:
bool: Is PTZ or not.
"""
resp = self.mycam.devicemgmt.GetCapabilities()
return bool(resp.PTZ)
然而虽然整体模块是能用了,可是里面并没有获取rtsp地址的功能,这可让我太无奈了,因为我本来就是想图省事不想写的,既然没有没办法就写一个了
def get_rtsp(self):
obj = self.camera_media.create_type('GetStreamUri')
obj.StreamSetup = {
'Stream': 'RTP-Unicast', 'Transport': {
'Protocol': 'RTSP'}}
obj.ProfileToken = self.camera_media_profile.token
res_uri = self.camera_media.GetStreamUri(obj)['Uri']
return res_uri
把上述函数加到CameraONVIF
类中就有获取rtsp流地址的功能了,这里我测了一下有些获取到的地址直接能用,有些还得在ip前面加上用户名和密码,奇奇怪怪。
到此为止这个库就完全可用了,也很奇怪为什么python关于onvif协议调用摄像头的资料那么少,让我耽搁了好多天学习关于onvif的知识
第三次bug更新
将ws_discovery
函数的ip返回机制更改一下,源代码返回的ip会有的结尾会带:,而且有些摄像头端口并不是默认的80,所以我们将端口也一并返回
def ws_discovery(scope = None) -> List:
"""Discover cameras on network using onvif discovery.
Returns:
List: List of ips found in network.
"""
lst = list()
if(scope == None):
cmd = 'hostname -I'
scope = subprocess.check_output(cmd, shell=True).decode('utf-8')
wsd = WSDiscovery.WSDiscovery()
wsd.start()
ret = wsd.searchServices()
for service in ret:
get_ip = str(service.getXAddrs())
get_types = str(service.getTypes())
for ip_scope in scope.split():
result = get_ip.find(ip_scope.split('.')[0] + '.' + ip_scope.split('.')[1])
if result > 0 and get_types.find('onvif') > 0:
#下面是更改的代码
#string_result = get_ip[result:result+13]
string_result = get_ip[result:].split('/')[0]
string_result = string_result.split(':')
if len(string_result)>1:
lst.append([string_result[0],string_result[1]])
else:
lst.append([string_result[0],'80'])
wsd.stop()
lst.sort()
return lst
之后改一下CameraONVIF类的init函数,将port参数也初始化
def __init__(self, ip,user, password, port):
"""Constructor.
Args:
ip (str): Ip of the camera.
user (str): Onvif login.
password (str): Onvif password.
"""
self.cam_ip = ip
self.cam_user = user
self.cam_password = password
self.cam_port = port#加入port参数
并在camera_start
函数里将默认的80端口改为self.cam_port
mycam = ONVIFCamera(self.cam_ip, self.cam_port, self.cam_user, self.cam_password, no_cache = True)
这样基本上所有ip的rtsp地址你都能获取到了,少数问题是摄像头鉴权问题