使用opencv-python读取多个(海康\大华)网络摄像头的视频流,解决实时读取延迟问题
在上一篇博客中,主要介紹了python之opencv按幀提取視頻中的圖片,但是,由于最近在做人臉識別的項目,用的是大華的監控攝像頭,我發現大華的攝像頭實時讀取延遲問題特別嚴重,尤其是主碼流,這個問題困擾了我好久,最終想到的方式就是自己實時推流,經過實踐,終于解決了實時讀取延遲問題。
前言
同樣需要準備對應的python開發環境,具體參考上一篇python之opencv按幀提取視頻中的圖片,里面介紹了詳細的需要的庫文件。
好了,既然是自己實現實時預覽推流,那就要確定使用推流方式,我這里使用的是RTSP地址和格式實現推流的,下面是我歸納的各大監控攝像頭廠商的RTSP具體推流格式。
各大攝像頭廠商RTSP推流格式
rtsp://[username]:[password]@[ip]:[port]/[codec]/[channel]/[subtype]/av_stream
說明:
username: 用戶名。例如admin。
password: 密碼。例如123456。
ip: 為設備IP。例如 192.168.0.224。
port: 端口號默認為554,若為默認可不填寫。
codec:有h264、MPEG-4、mpeg4這幾種。
channel: 通道號,起始為1。例如通道1,則為ch1。
subtype: 碼流類型,主碼流為main,子碼流為sub。
舉個栗子:
例如,請求海康攝像機通道1的主碼流,Url如下 rtsp://admin:123456@192.168.0.224:554/h264/ch1/main/av_stream rtsp://admin:123456@192.168.0.224:554/MPEG-4/ch1/main/av_stream rtsp://admin:123456@192.168.0.224:554/h264/ch33/main/av_stream //ipc例如,請求海康攝像機通道1的子碼流,Url如下: rtsp://admin:123456@192.168.0.224/mpeg4/ch1/sub/av_stream rtsp://admin:123456@192.168.0.224/h264/ch1/sub/av_stream【新版本】URL:rtsp://username:password@<address>:<port>/Streaming/Channels/<id>(?parm1=value1&parm2-=value2…)注:VLC可以支持解析URL里的用戶名密碼,實際發給設備的RTSP請求不支持帶用戶名密碼。 舉例:DS-9632N-ST的模擬通道01主碼流:rtsp://admin:123456@192.168.0.224:554/Streaming/Channels/101?transportmode=unicastDS-9016HF-ST的IP通道01主碼流:rtsp://admin:123456@192.168.0.224:554/Streaming/Channels/1701?transportmode=unicastDS-9016HF-ST的模擬通道01子碼流:rtsp://admin:123456@192.168.0.224:554/Streaming/Channels/102?transportmode=unicast (單播)rtsp://admin:123456@192.168.0.224:554/Streaming/Channels/102?transportmode=multicast (多播)rtsp://admin:123456@192.168.0.224:554/Streaming/Channels/102 (?后面可省略,默認單播)注:前面老URL,NVR(>=64路的除外)的IP通道從33開始;新URL,通道號全部按順序從1開始。rtsp://username:password@ip:port/cam/realmonitor?channel=1&subtype=0
說明:
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:admin@192.168.0.224: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:123456@192.168.0.224 :554/mpeg4
-
子碼流 rtsp://admin:123456@192.168.0.224 :554/mpeg4cif
默認IP地址:DHCP 默認(0.0.0.0)
用戶名admin 默認
密碼 空
舉個栗子:
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,分別對應主/輔/三碼流;
舉個栗子:
天地偉業
默認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:12345@192.168.200.201:554/live2.sdprtsp://[username]:[password]@[ip]/axis-media/media.amp?[videocodec]&[resolution]
說明:
username:用戶名。例如admin
password:密碼。例如12345,如果沒有網絡驗證可省略用戶名密碼部分以及@字符。
ip:為設備IP。例如192.168.0.108。
videocodec:支持MPEG、h.264等,可缺省。
resolution:分辨率,如resolution=1920x1080,若采用默認分辨率,可缺省此參數。
舉個栗子:
例如,請求某設備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推流方式,那好,第一步就先實現RTSP推流吧。代碼如下:
import cv2import time import multiprocessing as mpdef 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 cv2import time import multiprocessing as mpdef 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') # initqueues = [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 = Trueprocess.start()for process in processes:process.join()if __name__ == '__main__':run_multi_camera()當然還有更簡單的實現方式,下面看看如何利用OpenCV官網給出的視頻流讀取吧
簡單版-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(括號里面的東西進行修改),填寫上我們想要讀取的視頻流,它可以是:
具體參考: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') # initqueue = 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') # initqueues = [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 = Trueprocess.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主動將隊列里面的舊圖片刪掉,換上新圖片。通過多線程的方法:
好了,支持,多路和單路實時預覽效果,便實現了,下面看一下具體的實現效果吧:
總結
以上是生活随笔為你收集整理的使用opencv-python读取多个(海康\大华)网络摄像头的视频流,解决实时读取延迟问题的全部內容,希望文章能夠幫你解決所遇到的問題。