在上一篇博客中,主要介绍了python之opencv按帧提取视频中的图片,但是,由于最近在做人脸识别的项目,用的是大华的监控摄像头,我发现大华的摄像头实时读取延迟问题特别严重,尤其是主码流,这个问题困扰了我好久,最终想到的方式就是自己实时推流,经过实践,终于解决了实时读取延迟问题。
同样需要准备对应的python开发环境,具体参考上一篇python之opencv按帧提取视频中的图片,里面介绍了详细的需要的库文件。
好了,既然是自己实现实时预览推流,那就要确定使用推流方式,我这里使用的是RTSP地址和格式实现推流的,下面是我归纳的各大监控摄像头厂商的RTSP具体推流格式。
举个栗子:
例如,请求海康摄像机通道1的主码流,Url如下
rtsp://admin:[email protected]:554/h264/ch1/main/av_stream
rtsp://admin:[email protected]:554/MPEG-4/ch1/main/av_stream
rtsp://admin:[email protected]:554/h264/ch33/main/av_stream //ipc
例如,请求海康摄像机通道1的子码流,Url如下:
rtsp://admin:[email protected]/mpeg4/ch1/sub/av_stream
rtsp://admin:[email protected]/h264/ch1/sub/av_stream
【新版本】URL:
rtsp://username:password@>:>/Streaming/Channels/>(?parm1=value1&parm2-=value2…)
注:VLC可以支持解析URL里的用户名密码,实际发给设备的RTSP请求不支持带用户名密码。
举例:
DS-9632N-ST的模拟通道01主码流:
rtsp://admin:[email protected]:554/Streaming/Channels/101?transportmode=unicast
DS-9016HF-ST的IP通道01主码流:
rtsp://admin:[email protected]:554/Streaming/Channels/1701?transportmode=unicast
DS-9016HF-ST的模拟通道01子码流:
rtsp://admin:[email protected]:554/Streaming/Channels/102?transportmode=unicast (单播)
rtsp://admin:[email protected]:554/Streaming/Channels/102?transportmode=multicast (多播)
rtsp://admin:[email protected]:554/Streaming/Channels/102 (?后面可省略,默认单播)
注:前面老URL,NVR(>=64路的除外)的IP通道从33开始;新URL,通道号全部按顺序从1开始。
说明:
username: 用户名,例如admin。
password: 密码,例如admin。
ip: 为设备IP,例如192.168.0.224。
port: 端口号默认为554,若为默认可不填写。
channel: 通道号,起始为1;例如通道2,则为channel=2。
subtype: 码流类型,主码流为0(即subtype=0);子码流为1(即subtype=1)。
举个栗子:
例如,请求某设备的通道2的子码流,Url如下
rtsp://admin:[email protected]:554/cam/realmonitor?channel=2&subtype=1
默认IP地址:192.168.0.224
用户名: admin
密码空:123456
端口:TCP端口:34567 和 HTTP端口:80,onvif端口是8899
举个栗子:
RTSP地址:rtsp://192.168.0.224 :554/user=admin&password=123456&channel=1&stream=0.sdp?real_stream
192.168.0.224 这个是被连接的设备的IP
554这个是RTSP服务的端口号,可以在设备的网络服务里面更改
user=admin这个是设备的登录用户名
password= 123456
channel=1 第一通道
stream=0.sdp?主码流
stream=1.sdp?副码流
默认IP地址:192.168.0.224
用户名admin
密码123456
端口:http端口80 数据端口8091 RTSP端口554 ONVIF端口 80
举个栗子:
RTSP地址(不需要密码):
主码流地址:rtsp://192.168.0.224 :554/mpeg4
子码流地址:rtsp://192.168.0.224 :554/mpeg4cif
RTSP地址(需要密码):
主码流 rtsp://admin:[email protected] :554/mpeg4
子码流 rtsp://admin:[email protected] :554/mpeg4cif
举个栗子:
RTSP地址:rtsp://0.0.0.0:8554/live1.264(次码流)
rtsp://0.0.0.0:8554/live0.264 (主码流)
九安
RTSP地址:rtsp://IP:port(website port)/ch0_0.264(主码流)
rtsp://IP:port(website port)/ch0_1.264(子码流)
技威/YOOSEE
默认IP地址:DHCP 用户名admin 密码123456
RTSP地址:主码流:rtsp://IPadr:554/onvif1
次码流:rtsp://IPadr:554/onvif2
onvif端口是5000
设备发现的端口是3702
V380
默认IP地址:DHCP 用户名admin 密码空/admin
onvif端口8899
RTSP地址:主码流rtsp://ip//live/ch00_1
子码流rtsp://ip//live/ch00_0
宇视
默认IP地址: 192.168.0.13/DHCP 默认用户名 admin 和默认密码 123456
端口:HTTP 80/RTSP 554/HTTPS 110(443)/onvif端口 80
RTSP地址:rtsp://用户名:密码@ip:端口号/video1/2/3,分别对应主/辅/三码流;
举个栗子:
rtsp://admin:[email protected]:554/video1,就表示主码流;
rtsp://admin:[email protected]:554/video2,表示子码流;
rtsp://admin:[email protected]:554/video3,表示3码流;
天地伟业
默认IP地址:192.168.1.2 用户名“Admin”、密码“1111”
onvif端口号“8080”
RTSP地址:rtsp://192.168.1.2
巨龙/JVT
默认IP地址:192.168.1.88 默认用户名 admin 默认密码admin
RTSP地址:
主码流地址:rtsp://IP地址/av0_0
次码流地址:rtsp://IP地址/av0_1
onvif端口 2000
海清
RTSP地址:rtsp://用户名:密码@ip:端口号/av0_0
D-Link
rtsp://[username]:[password]@[ip]:[port]/[channel].sdp
说明:
username:用户名。例如admin
password:密码。例如12345,如果没有网络验证可直接写成rtsp:// [ip]:[port]/[channel].sdp
ip:为设备IP。例如192.168.0.108。
port:端口号默认为554,若为默认可不填写。
channel:通道号,起始为1。例如通道2,则为live2。
举个栗子:
例如,请求某设备的通道2的码流,URL如下
rtsp://admin:[email protected]:554/live2.sdp
举个栗子:
例如,请求某设备h264编码的1280x720的码流,URL如下:
rtsp:// 192.168.200.202/axis-media/media.amp?videocodec=h264&resolution=1280x720
好了。支持,市场上主流的监控摄像头RTSP推流就介绍完毕了,接下来就实战RTSP实时推流吧。这里一大华摄像头为栗子。
在上一篇,我们知道了,开启实时预览的方式,需要开启opencv VideoCapture,细心一点你会发现,在上一篇中有这样的代码如下:
# 导入所需要的库
import cv2
import numpy as np
# 读取视频文件
videoCapture = cv2.VideoCapture("test.mp4")
# 通过摄像头的方式
# videoCapture=cv2.VideoCapture(1)
经过分析,你会发现,我们只需要把cv2.VideoCapture(“test.mp4”)这里做成实时推流的即可。
在前面,我们知道了大华摄像头的RTSP推流方式,那好,第一步就先实现RTSP推流吧。代码如下:
import cv2
import time
import multiprocessing as mp
def image_put(q, name, pwd, ip, channel=1):
//使用占位符,动态的代替ip地址,用户名,密码,预览通道等参数
cap = cv2.VideoCapture("rtsp://%s:%s@%s//Streaming/Channels/%d" % (name, pwd, ip, channel))
if cap.isOpened():
print('HIKVISION')
else:
cap = cv2.VideoCapture("rtsp://%s:%s@%s/cam/realmonitor?channel=%d&subtype=0" % (name, pwd, ip, channel))
print('DaHua')
while True:
q.put(cap.read()[1])
q.get() if q.qsize() > 1 else time.sleep(0.01)
def image_get(q, window_name):
cv2.namedWindow(window_name, flags=cv2.WINDOW_FREERATIO)
while True:
frame = q.get()
cv2.imshow(window_name, frame)
cv2.waitKey(1)
def run_multi_camera():
# user_name, user_pwd = "admin", "password"
user_name, user_pwd = "admin", "admin123456"
camera_ip_l = [
"192.168.35.121", # ipv4
"[fe80::3aaf:29ff:fed3:d260]", # ipv6
# 把你的摄像头的地址放到这里,如果是ipv6,那么需要加一个中括号。
]
上面,我们知道了,如何实现实时预览,下面就解决一下核心问题,实时读取延迟问题,代码如下:
import multiprocessing as mp
...
img_queues = [mp.Queue(maxsize=2) for _ in camera_ip_l] # queue
...
q.put(frame) if is_opened else None # 线程A不仅将图片放入队列
q.get() if q.qsize() > 1 else time.sleep(0.01) # 线程A还负责移除队列中的旧图
...
好了,完成了,这俩步,就可以解决实时读取延迟问题了,最后附上完整代码。
import cv2
import time
import multiprocessing as mp
def image_put(q, name, pwd, ip, channel=1):
cap = cv2.VideoCapture("rtsp://%s:%s@%s//Streaming/Channels/%d" % (name, pwd, ip, channel))
if cap.isOpened():
print('HIKVISION')
else:
cap = cv2.VideoCapture("rtsp://%s:%s@%s/cam/realmonitor?channel=%d&subtype=0" % (name, pwd, ip, channel))
print('DaHua')
while True:
q.put(cap.read()[1])
q.get() if q.qsize() > 1 else time.sleep(0.01)
def image_get(q, window_name):
cv2.namedWindow(window_name, flags=cv2.WINDOW_FREERATIO)
while True:
frame = q.get()
cv2.imshow(window_name, frame)
cv2.waitKey(1)
def run_multi_camera():
# user_name, user_pwd = "admin", "password"
user_name, user_pwd = "admin", "admin123456"
camera_ip_l = [
"192.168.35.121", # ipv4
"[fe80::3aaf:29ff:fed3:d260]", # ipv6
# 把你的摄像头的地址放到这里,如果是ipv6,那么需要加一个中括号。
]
mp.set_start_method(method='spawn') # init
queues = [mp.Queue(maxsize=4) for _ in camera_ip_l]
processes = []
for queue, camera_ip in zip(queues, camera_ip_l):
processes.append(mp.Process(target=image_put, args=(queue, user_name, user_pwd, camera_ip)))
processes.append(mp.Process(target=image_get, args=(queue, camera_ip)))
for process in processes:
process.daemon = True
process.start()
for process in processes:
process.join()
if __name__ == '__main__':
run_multi_camera()
当然还有更简单的实现方式,下面看看如何利用OpenCV官网给出的视频流读取吧
经过简单修改,如下:
def run_opencv_camera():
video_stream_path = 0 # local camera (e.g. the front camera of laptop)
cap = cv2.VideoCapture(video_stream_path)
while cap.isOpened():
is_opened, frame = cap.read()
cv2.imshow('frame', frame)
cv2.waitKey(1)
cap.release()
当 video_stream_path = 0 的时候,电脑会开启默认摄像头,比如笔记本电脑的前置摄像头 。
当我们需要读取网络摄像头的时候,我们可以对 cap = cv2.VideoCapture(括号里面的东西进行修改),填写上我们想要读取的视频流,它可以是:
user, pwd, ip, channel = "admin", "admin123456", "192.168.35.121", 1
video_stream_path = 0 # local camera (e.g. the front camera of laptop)
video_stream_path = 'video.avi' # the path of video file
video_stream_path = "rtsp://%s:%s@%s/h265/ch%s/main/av_stream" % (user, pwd, ip, channel) # HIKIVISION old version 2015
video_stream_path = "rtsp://%s:%s@%s//Streaming/Channels/%d" % (user, pwd, ip, channel) # HIKIVISION new version 2017
video_stream_path = "rtsp://%s:%s@%s/cam/realmonitor?channel=%d&subtype=0" % (user, pwd, ip, channel) # dahua
cap = cv2.VideoCapture(video_stream_path)
具体参考:OpenCV官网给出的视频流读取示例代码
好了,到此,我们就解决实时读取延迟问题,但是,目前我们只是监控一路,如何监控多路,解决实时读取延迟问题了,其实很简单,因为每一路是独立,互不干涉,下面就实战多个摄像头。
有了单路的思路,你会发现,多路只要使用多线程队列,就能解决延迟卡顿问题,读取多个摄像头。
def image_put(q, user, pwd, ip, channel=1):
cap = cv2.VideoCapture("rtsp://%s:%s@%s//Streaming/Channels/%d" % (user, pwd, ip, channel))
if cap.isOpened():
print('HIKVISION')
else:
cap = cv2.VideoCapture("rtsp://%s:%s@%s/cam/realmonitor?channel=%d&subtype=0" % (user, pwd, ip, channel))
print('DaHua')
while True:
q.put(cap.read()[1])
q.get() if q.qsize() > 1 else time.sleep(0.01)
def image_get(q, window_name):
cv2.namedWindow(window_name, flags=cv2.WINDOW_FREERATIO)
while True:
frame = q.get()
cv2.imshow(window_name, frame)
cv2.waitKey(1)
def run_single_camera():
user_name, user_pwd, camera_ip = "admin", "admin123456", "192.168.35.121"
mp.set_start_method(method='spawn') # init
queue = mp.Queue(maxsize=2)
processes = [mp.Process(target=image_put, args=(queue, user_name, user_pwd, camera_ip)),
mp.Process(target=image_get, args=(queue, camera_ip))]
[process.start() for process in processes]
[process.join() for process in processes]
def run_multi_camera():
# user_name, user_pwd = "admin", "password"
user_name, user_pwd = "admin", "admin123456"
camera_ip_l = [
"172.20.114.26", # ipv4
"[fe80::3aaf:29ff:fed3:d260]", # ipv6
]
mp.set_start_method(method='spawn') # init
queues = [mp.Queue(maxsize=4) for _ in camera_ip_l]
processes = []
for queue, camera_ip in zip(queues, camera_ip_l):
processes.append(mp.Process(target=image_put, args=(queue, user_name, user_pwd, camera_ip)))
processes.append(mp.Process(target=image_get, args=(queue, camera_ip)))
for process in processes:
process.daemon = True
process.start()
for process in processes:
process.join()
if __name__ == '__main__':
# run_single_camera()
run_multi_camera()
pass
关键部分解释:
我使用Python3自带的多线程模块,创建一个队列,线程A从通过rtsp协议从视频流中读取出每一帧,并放入队列中,线程B从队列中将图片取出,处理后进行显示。线程A如果发现队列里有两张图片(证明线程B的读取速度跟不上线程A),那么线程A主动将队列里面的旧图片删掉,换上新图片。通过多线程的方法:
import multiprocessing as mp
...
img_queues = [mp.Queue(maxsize=2) for _ in camera_ip_l] # queue
...
q.put(frame) if is_opened else None # 线程A不仅将图片放入队列
q.get() if q.qsize() > 1 else time.sleep(0.01) # 线程A还负责移除队列中的旧图
...