使用opencv-python讀取多個(海康\大華)網絡攝像頭的視頻流,解決實時讀取延遲問題

在上一篇博客中,主要介紹了python之opencv按幀提取視頻中的圖片,但是,由於最近在做人臉識別的項目,用的是大華的監控攝像頭,我發現大華的攝像頭實時讀取延遲問題特別嚴重,尤其是主碼流,這個問題困擾了我好久,最終想到的方式就是自己實時推流,經過實踐,終於解決了實時讀取延遲問題。

前言

同樣需要準備對應的python開發環境,具體參考上一篇python之opencv按幀提取視頻中的圖片,裏面介紹了詳細的需要的庫文件。

好了,既然是自己實現實時預覽推流,那就要確定使用推流方式,我這裏使用的是RTSP地址和格式實現推流的,下面是我歸納的各大監控攝像頭廠商的RTSP具體推流格式。

各大攝像頭廠商RTSP推流格式

  1. 海康實時流
    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開始。
 
  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
  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?副碼流

  1. 天視通

默認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

  1. 中維/尚維
    默認IP地址:DHCP 默認(0.0.0.0)
    用戶名admin 默認
    密碼 空

舉個栗子:

RTSP地址:rtsp://0.0.0.0:8554/live1.264(次碼流)
rtsp://0.0.0.0:8554/live0.264 (主碼流)
  1. 九安
    RTSP地址:rtsp://IP:port(website port)/ch0_0.264(主碼流)
    rtsp://IP:port(website port)/ch0_1.264(子碼流)

  2. 技威/YOOSEE
    默認IP地址:DHCP 用戶名admin 密碼123456
    RTSP地址:主碼流:rtsp://IPadr:554/onvif1
    次碼流:rtsp://IPadr:554/onvif2
    onvif端口是5000
    設備發現的端口是3702

  3. V380
    默認IP地址:DHCP 用戶名admin 密碼空/admin
    onvif端口8899
    RTSP地址:主碼流rtsp://ip//live/ch00_1
    子碼流rtsp://ip//live/ch00_0

  4. 宇視
    默認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碼流;
  1. 天地偉業
    默認IP地址:192.168.1.2 用戶名“Admin”、密碼“1111”
    onvif端口號“8080”
    RTSP地址:rtsp://192.168.1.2

  2. 巨龍/JVT
    默認IP地址:192.168.1.88 默認用戶名 admin 默認密碼admin
    RTSP地址:
    主碼流地址:rtsp://IP地址/av0_0
    次碼流地址:rtsp://IP地址/av0_1
    onvif端口 2000

  3. 海清
    RTSP地址:rtsp://用戶名:密碼@ip:端口號/av0_0

  4. 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
  1. 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(括號裏面的東西進行修改),填寫上我們想要讀取的視頻流,它可以是:

  1. List item數字0,代表計算機的默認攝像頭(例如上面提及的筆記本前置攝像頭)
  2. video.avi 視頻文件的路徑,支持其他格式的視頻文件
  3. 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主動將隊列裏面的舊圖片刪掉,換上新圖片。通過多線程的方法:

  1. 線程A的讀取速度始終不收線程B的影響,防止網絡攝像頭的緩存區爆滿
  2. 線程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還負責移除隊列中的舊圖
...

好了,支持,多路和單路實時預覽效果,便實現了,下面看一下具體的實現效果吧:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章