在上一篇博客中,主要介紹了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:[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@<address>:<port>/Streaming/Channels/<id>(?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開始。
- 大華
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:[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
- 中維/尚維
默認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,分別對應主/輔/三碼流;
舉個栗子:
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
- Axis(安訊士)
rtsp://[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 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官網給出的視頻流讀取吧
簡單版-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(括號裏面的東西進行修改),填寫上我們想要讀取的視頻流,它可以是:
- List item數字0,代表計算機的默認攝像頭(例如上面提及的筆記本前置攝像頭)
- video.avi 視頻文件的路徑,支持其他格式的視頻文件
- rtsp路徑(不同品牌的路徑一般是不同的,如下面舉出的海康與大華)
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主動將隊列裏面的舊圖片刪掉,換上新圖片。通過多線程的方法:
- 線程A的讀取速度始終不收線程B的影響,防止網絡攝像頭的緩存區爆滿
- 線程A更新了隊列中的圖片,使線程B始終讀取到最新的畫面,降低了延遲
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還負責移除隊列中的舊圖
...
好了,支持,多路和單路實時預覽效果,便實現了,下面看一下具體的實現效果吧: