当前位置:   article > 正文

读取多个(海康\大华)网络摄像头的视频流 (使用opencv-python),解决实时读取延迟问题_实时检测调用函数视频流很慢怎么办

实时检测调用函数视频流很慢怎么办

实时读取视频流(封面使用五个摄像头是因为我手头最多只有五个),解决实时读取延迟卡顿问题

做计算机视觉的算法开发,可能会碰到实时获取图像并处理的问题,我写了一个简单的实例,可以实时读取多个网络摄像头。运行视频预览如下↓ (可以看到视频播放流畅,达到30fps,同时延迟小于0.3s)

 

实时读取多个网络摄像头

 

2018-06-17 初版 Yonv1943 2018-06-25 小修改,deamon,setattr(), if is_opened
2018-07-02 添加单摄像头run(): # single camera,和多摄像头run_multi_camera()
2018-11-21 单线程读取单个摄像头,多进程读取多个摄像头
2019-02-14 将“多线程”改正为“多进程”谢谢 

@知who

 的纠正
2019-05-04 增加大华摄像头rtsp协议 我在标题里不小心用爱发电了,为了公平起见,我把海康竞争对手大华也加上去。网络摄像头基本都支持rtsp协议,所以可以用本方法读取。
2019-07-04 增加将多个摄像头的画面有序地读取到同一个程序 run_multi_camera_in_a_window()
2019-09-06 回复评论:目标检测
2019-10-17 回复评论:图片刚放进去队列就取出来,队列里面岂不是没有图片?
2019-10-31 回复评论:网络卡顿而无法获取视频信息,这个应该怎么处理?
2019-11-13 回复评论:为何有 ipv4 与ipv6两种地址?

完整版Demo:

实现上述功能的完整示例Demo (已经尽可能短),你也可以在我的GitHub上下载到最新的代码,如果星星多,那么我还会继续加功能:

  1. import cv2
  2. import time
  3. import multiprocessing as mp
  4. """
  5. Source: Yonv1943 2018-06-17
  6. https://github.com/Yonv1943/Python/tree/master/Demo
  7. """
  8. def image_put(q, name, pwd, ip, channel=1):
  9. cap = cv2.VideoCapture("rtsp://%s:%s@%s//Streaming/Channels/%d" % (name, pwd, ip, channel))
  10. if cap.isOpened():
  11. print('HIKVISION')
  12. else:
  13. cap = cv2.VideoCapture("rtsp://%s:%s@%s/cam/realmonitor?channel=%d&subtype=0" % (name, pwd, ip, channel))
  14. print('DaHua')
  15. while True:
  16. q.put(cap.read()[1])
  17. q.get() if q.qsize() > 1 else time.sleep(0.01)
  18. def image_get(q, window_name):
  19. cv2.namedWindow(window_name, flags=cv2.WINDOW_FREERATIO)
  20. while True:
  21. frame = q.get()
  22. cv2.imshow(window_name, frame)
  23. cv2.waitKey(1)
  24. def run_multi_camera():
  25. # user_name, user_pwd = "admin", "password"
  26. user_name, user_pwd = "admin", "admin123456"
  27. camera_ip_l = [
  28. "172.20.114.26", # ipv4
  29. "[fe80::3aaf:29ff:fed3:d260]", # ipv6
  30. # 把你的摄像头的地址放到这里,如果是ipv6,那么需要加一个中括号。
  31. ]
  32. mp.set_start_method(method='spawn') # init
  33. queues = [mp.Queue(maxsize=4) for _ in camera_ip_l]
  34. processes = []
  35. for queue, camera_ip in zip(queues, camera_ip_l):
  36. processes.append(mp.Process(target=image_put, args=(queue, user_name, user_pwd, camera_ip)))
  37. processes.append(mp.Process(target=image_get, args=(queue, camera_ip)))
  38. for process in processes:
  39. process.daemon = True
  40. process.start()
  41. for process in processes:
  42. process.join()
  43. if __name__ == '__main__':
  44. run_multi_camera()

解决实时读取延迟卡顿的关键代码如下,我使用Python自带的多线程队列:

  1. import multiprocessing as mp
  2. ...
  3. img_queues = [mp.Queue(maxsize=2) for _ in camera_ip_l] # queue
  4. ...
  5. q.put(frame) if is_opened else None # 线程A不仅将图片放入队列
  6. q.get() if q.qsize() > 1 else time.sleep(0.01) # 线程A还负责移除队列中的旧图
  7. ...

如果你可以成功运行上们的代码,那么恭喜。如果你已经理解代码,那么你不需要看下面的内容。


如果你想要进一步理解代码,那么下面的内容是:

OpenCV官网提供的简单版Demo(无法避免延迟卡顿)

 

简单版Demo:

如果你可以成功运行上们的代码,那么恭喜OpenCV官网给出的视频流读取示例代码,经过简单修改,如下:

  1. def run_opencv_camera():
  2. video_stream_path = 0 # local camera (e.g. the front camera of laptop)
  3. cap = cv2.VideoCapture(video_stream_path)
  4. while cap.isOpened():
  5. is_opened, frame = cap.read()
  6. cv2.imshow('frame', frame)
  7. cv2.waitKey(1)
  8. cap.release()

当 video_stream_path = 0 的时候,电脑会开启默认摄像头,比如笔记本电脑的前置摄像头 ↓

做网络安全的人最喜欢贴掉的的前置摄像头 QAQ,看到上面的白色小灯了吗?

当我们需要读取网络摄像头的时候,我们可以对 cap = cv2.VideoCapture(括号里面的东西进行修改),填写上我们想要读取的视频流,它可以是:

  • 数字0,代表计算机的默认摄像头(例如上面提及的笔记本前置摄像头)
  • video.avi 视频文件的路径,支持其他格式的视频文件
  • rtsp路径(不同品牌的路径一般是不同的,如下面举出的海康与大华,详细情况查看 附录的「关于rtsp协议」)
  1. user, pwd, ip, channel = "admin", "admin123456", "172.20.114.26", 1
  2. video_stream_path = 0 # local camera (e.g. the front camera of laptop)
  3. video_stream_path = 'video.avi' # the path of video file
  4. video_stream_path = "rtsp://%s:%s@%s/h265/ch%s/main/av_stream" % (user, pwd, ip, channel) # HIKIVISION old version 2015
  5. video_stream_path = "rtsp://%s:%s@%s//Streaming/Channels/%d" % (user, pwd, ip, channel) # HIKIVISION new version 2017
  6. video_stream_path = "rtsp://%s:%s@%s/cam/realmonitor?channel=%d&subtype=0" % (user, pwd, ip, channel) # dahua
  7. cap = cv2.VideoCapture(video_stream_path)

直接使用参考官网写出来的简单版Demo有延迟卡顿问题,如果读取速度低于视频流的输出速度,窗口显示的图片是好几秒钟前的内容。一段时间过后,缓存区将会爆满,程序报错,我可以使用rtsp读取摄像头:

  1. def run_opencv_camera():
  2. video_stream_path = 0 # local camera (e.g. the front camera of laptop)
  3. cap = cv2.VideoCapture(video_stream_path)
  4. while cap.isOpened():
  5. is_opened, frame = cap.read()
  6. cv2.imshow('frame', frame)
  7. cv2.waitKey(1000) # wait for 1000ms(1s) HERE!!!!!!!!!!!!!!
  8. cap.release()
  9. """
  10. 将等待时间修改为1秒,则摄像头显示的画面延迟增大:
  11. 本地摄像头可能不会报错
  12. 网络摄像头可能会在运行一段时间后报错
  13. ERROR 报错内容如下:(其实,如果传递给cv.imshow()函数的不是 ndarray,都会出现这个错误)
  14. libpng warning: iCCP: known incorrect sRGB profile
  15. libpng warning: iCCP: known incorrect sRGB profile
  16. libpng warning: iCCP: cHRM chunk does not match sRGB
  17. libpng warning: iCCP: known incorrect sRGB profile
  18. ...
  19. """

下面使用使用多线程队列,解决这个延迟卡顿问题。

完整版Demo(使用多线程队列,解决延迟卡顿问题,读取多个摄像头):

  1. def image_put(q, user, pwd, ip, channel=1):
  2. cap = cv2.VideoCapture("rtsp://%s:%s@%s//Streaming/Channels/%d" % (user, pwd, ip, channel))
  3. if cap.isOpened():
  4. print('HIKVISION')
  5. else:
  6. cap = cv2.VideoCapture("rtsp://%s:%s@%s/cam/realmonitor?channel=%d&subtype=0" % (user, pwd, ip, channel))
  7. print('DaHua')
  8. while True:
  9. q.put(cap.read()[1])
  10. q.get() if q.qsize() > 1 else time.sleep(0.01)
  11. def image_get(q, window_name):
  12. cv2.namedWindow(window_name, flags=cv2.WINDOW_FREERATIO)
  13. while True:
  14. frame = q.get()
  15. cv2.imshow(window_name, frame)
  16. cv2.waitKey(1)
  17. def run_single_camera():
  18. user_name, user_pwd, camera_ip = "admin", "admin123456", "172.20.114.26"
  19. mp.set_start_method(method='spawn') # init
  20. queue = mp.Queue(maxsize=2)
  21. processes = [mp.Process(target=image_put, args=(queue, user_name, user_pwd, camera_ip)),
  22. mp.Process(target=image_get, args=(queue, camera_ip))]
  23. [process.start() for process in processes]
  24. [process.join() for process in processes]
  25. def run_multi_camera():
  26. # user_name, user_pwd = "admin", "password"
  27. user_name, user_pwd = "admin", "admin123456"
  28. camera_ip_l = [
  29. "172.20.114.26", # ipv4
  30. "[fe80::3aaf:29ff:fed3:d260]", # ipv6
  31. ]
  32. mp.set_start_method(method='spawn') # init
  33. queues = [mp.Queue(maxsize=4) for _ in camera_ip_l]
  34. processes = []
  35. for queue, camera_ip in zip(queues, camera_ip_l):
  36. processes.append(mp.Process(target=image_put, args=(queue, user_name, user_pwd, camera_ip)))
  37. processes.append(mp.Process(target=image_get, args=(queue, camera_ip)))
  38. for process in processes:
  39. process.daemon = True
  40. process.start()
  41. for process in processes:
  42. process.join()
  43. if __name__ == '__main__':
  44. # run_single_camera()
  45. run_multi_camera()
  46. pass

关键部分解释:我使用Python3自带的多线程模块,创建一个队列,线程A从通过rtsp协议从视频流中读取出每一帧,并放入队列中,线程B从队列中将图片取出,处理后进行显示。线程A如果发现队列里有两张图片(证明线程B的读取速度跟不上线程A),那么线程A主动将队列里面的旧图片删掉,换上新图片。通过多线程的方法:

  • 线程A的读取速度始终不收线程B的影响,防止网络摄像头的缓存区爆满
  • 线程A更新了队列中的图片,使线程B始终读取到最新的画面,降低了延迟
  1. import multiprocessing as mp
  2. ...
  3. img_queues = [mp.Queue(maxsize=2) for _ in camera_ip_l] # queue
  4. ...
  5. q.put(frame) if is_opened else None # 线程A不仅将图片放入队列
  6. q.get() if q.qsize() > 1 else time.sleep(0.01) # 线程A还负责移除队列中的旧图
  7. ...

模拟实时图片处理

完整版代码可以使用多线程队列,解决延迟卡顿问题,并读取多个摄像头。我们把等待时间从1毫秒,增加到1秒(1000ms),模拟实时处理图片中的某一个耗时操作。

  1. cv2.imshow(window_name, frame)
  2. cv2.waitKey(1000) # 1000ms

 

模拟耗时操作

 

注:此处的视频与文章开头的视频不同,因为左下角摄像头模拟了耗时1秒的图片处理延时操作,模拟实时图片传入处理速度慢的函数后 的情况。与此同时左上角的视频作为对照。

可以看到,左上角是正常读取窗口,20fps,延迟0.4秒。而左下角的模拟延迟视频显示窗口变为1fps,但是延迟没有变化,依然是0.4秒,可以说:做到了读取实时图片的效果。所有代码都可以从我的GitHub下载到完整注释版


附录是对评论区的回复,集中了常见问题。

关于rtsp协议:

通过rtsp协议读取视频流: 

@对镜子傻笑

 下面依次是我在网络上查到的海康与大华 rtsp 读取路径。经过测试,我手头的海康摄像头支持前面两种读取方式(新旧两种)。大华摄像头用第三种读取方式。

 

  1. video_stream_path = "rtsp://%s:%s@%s/h264/ch%s/main/av_stream" % (user, pwd, ip, channel) # HIKIVISION old version 2015
  2. video_stream_path = "rtsp://%s:%s@%s//Streaming/Channels/%d" % (user, pwd, ip, channel) # HIKIVISION new version 2017
  3. video_stream_path = "rtsp://%s:%s@%s/cam/realmonitor?channel=%d&subtype=0" % (user, pwd, ip, channel) # dahua
海康、大华IpCamera RTSP地址和格式(原创,旧版)- 2014年08月12日 23:01:18 xiejiashu
最新(2017)海康摄像机、NVR、流媒体服务器、回放取流RTSP地址规则说明 - 2017年05月13日 xiejiashu

我在Win10、Ubuntu16 系统上,可以直接使用使用rtsp协议读取网络摄像头视频流。如果你碰到问题,那么你可能需要安装XviD 与 FFmpeg ,如下:

  1. Ubuntu16.04下安装FFmpeg(超简单版)
  2. sudo add-apt-repository ppa:djcj/hybrid # 添加源
  3. sudo apt-get update # 更新源
  4. sudo apt-get install ffmpeg # 安装源
  5. XviD-1.3.5(XviD is an MPEG-4 compliant video CODEC.)
  6. http://www.linuxfromscratch.org/blfs/view/svn/multimedia/xvid.html
  7. Download (HTTP): http://downloads.xvid.org/downloads/xvidcore-1.3.5.tar.gz

我用过的摄像头主要有(都支持rtsp协议) :

  • 海康人脸摄像头 XXX
  • 海康星光夜视XXX
  • 海康DS-IPC-B12
  • 大华云台DH-PTZ12203UE-GN-P
  • @瞎bb队长

     一般的,搜索的时候带上rtsp就可以了

关于读取卡顿:

我已经在很多摄像头上面实验过了,本文使用的代码的性能如下:

环境:局域网 + 6个不同的POE供电海康摄像头

  • 工控机(赛扬1.9G*4),CPU Celeron(R) J1900 ,4个摄像头,1080P,最慢的延迟1.2秒
  • 笔记本(i3移动版 2.4G*4),CPU i3-3110M ,6个摄像头,1080P,最慢的延迟 0.4秒

备注:如果出现卡顿,请检查 网络使用率,CPU使用率。

特别备注:如果你只有一个摄像头,为了测试多个摄像头的读取效果,你开启了多个读取窗口同时读取同一个摄像头,你会发现:“对于一般的摄像头,开启两个以上就会卡顿”,而这样操作是不对的。因为摄像头本身也是一个“服务器”,它无法为多个目标传输视频流,会遇到传输瓶颈。

这段代码已经在多种环境下,在多个不同的摄像头上面测试过了,我认为它是可靠的。如果出问题,请在评论留言,不要私信我。我会抽时间把这篇文章以及代码整理一下的。

Python的列表解析:

使用列表解析,可以缩短代码,但是对于列表解析是否提高了Python代码的可读性,仍然是有争议的。

  1. processes = []
  2. for queue, camera_ip in zip(queues, camera_ip_l):
  3. processes.append(mp.Process(target=image_put, args=(queue, user_name, user_pwd, camera_ip)))
  4. processes.append(mp.Process(target=image_get, args=(queue, camera_ip)))
  5. for process in processes:
  6. process.daemon = True
  7. process.start()
  8. for process in processes:
  9. process.join()
  10. # 上面的代码,我完全可以写成下面的形式,这是Python的列表解析功能
  11. processes = [mp.Process(target=queue_img_put, args=(queue, user_name, user_pwd, camera_ip)),
  12. mp.Process(target=queue_img_get, args=(queue, camera_ip))]
  13. [setattr(process, "daemon", True) for process in processes] # process.daemon = True # 设置进程守护
  14. [process.start() for process in process_l]
  15. [process.join() for process in process_l]

有序地收集多个摄像头拍摄的画面,并显示出来 (回复

@Haulz

 等人)

本文的封面图片采用了一个主进程打开了了5个子进程,每个子进程负责一个摄像头实时画面的读取与显示。

如果你需要收集多个摄像头拍摄的画面(也就是将5个子进程拍摄到的图片收集 (collect) 到一个进程中去),那么我们需要这样子处理:(将前文出现过的两个函数稍作修改,即可得到,代码同步更新到Github,觉得有用就给星星吧

  1. run_multi_camera_in_a_window() 修改自 run_multi_camera()
  2. image_collect() 修改自 image_get()
  3. def image_collect(queue_list, camera_ip_l):
  4. import numpy as np # 实际使用的时候记得放在外面
  5. """show in single opencv-imshow window"""
  6. window_name = "%s_and_so_no" % camera_ip_l[0]
  7. cv2.namedWindow(window_name, flags=cv2.WINDOW_FREERATIO)
  8. while True:
  9. imgs = [q.get() for q in queue_list]
  10. imgs = np.concatenate(imgs, axis=1)
  11. cv2.imshow(window_name, imgs)
  12. cv2.waitKey(1)
  13. # """show in multiple opencv-imshow windows"""
  14. # [cv2.namedWindow(window_name, flags=cv2.WINDOW_FREERATIO)
  15. # for window_name in camera_ip_l]
  16. # while True:
  17. # for window_name, q in zip(camera_ip_l, queue_list):
  18. # cv2.imshow(window_name, q.get())
  19. # cv2.waitKey(1)
  20. def run_multi_camera_in_a_window():
  21. user_name, user_pwd = "admin", "admin123456"
  22. camera_ip_l = [
  23. "172.20.114.196", # ipv4
  24. "[fe80::3aaf:29ff:fed3:d260]", # ipv6
  25. # 我在这里分别用ipv4 与ipv6 打开了两个摄像头,只有一个摄像头的话就填写一个IP
  26. ]
  27. mp.set_start_method(method='spawn') # init
  28. queues = [mp.Queue(maxsize=4) for _ in camera_ip_l]
  29. processes = [mp.Process(target=image_collect, args=(queues, camera_ip_l))]
  30. for queue, camera_ip in zip(queues, camera_ip_l):
  31. processes.append(mp.Process(target=image_put, args=(queue, user_name, user_pwd, camera_ip)))
  32. for process in processes:
  33. process.daemon = True # setattr(process, 'deamon', True)
  34. process.start()
  35. for process in processes:
  36. process.join()

实现效果截图:把两个摄像头收集到的实时画面传给同一个进程:

合并为一张图片,以便于显示在同一个cv2.imshow() 窗口内(这里画面模糊是因为镜头距离成像物体太近了)

注释部分的实现效果截图:(打开多个OpenCV imshow 窗口,且画面有序显示,不混淆)

把两个摄像头收集到的实时画面传给同一个进程,再由不同窗口显示出来

本来以为这个实现是很简单了,没想到已经有超过3个人私信问过相同的问题了,所以我写下这部分的内容。因为觉得没有技术含量,不足以写入正文,因此放在了文末的附录内。

视频读取已经结束, 而程序没有自动退出 @风荷一一(这个问题不太值得回复)

问题描述 @风荷一一:“将rtsp换成本地视频后,当视频结束后会出现报错。cpp:352: error: (-215:Assertion failed),且继续占用CPU”

  1. cap = cv2.VideoCapture(video_stream_path)
  2. is_opened = cap.isOpened()
  3. while is_opened: # 如果视频读取完毕,那么 is_opened 为False,循环自动跳出
  4. is_opened, frame = cap.read()
  5. cv2.imshow('frame', frame)
  6. cv2.waitKey(1)
  7. cap.release()

在显示之前做一个目标检测,怎么实现? 

@哥白尼的天体论

曾伊言:实时视频传入深度学习目标检测模型进行检测​zhuanlan.zhihu.com

把读取模块与计算模块分开,详细内容请看上面的文章「实时视频传入深度学习目标检测模型进行检测」:

  1. 很明显,单线程是低效率的,CPU工作的时候,GPU在围观,反之亦然:
  2. 线程0: CPU读取图片→ GPU处理图片(如:目标检测)→ ... ...
  3. 使用多线程的一个简单高效的方案:
  4. 线程0: CPU读取图片↘ CPU读取图片↘ ... ...
  5. 线程1: GPU处理图片 GPU处理图片 ... ...

刚刚放进去一张图片,然后马上取出来,队列里面岂不是一张图片也没有? (虽然你后面似乎知道答案了,自己把评论删除掉,但是问这个问题的人太多了)

@TuringYang

程序其实是这样运作的:image_put 与 image_get 是同时运行的。image_put 放入图片后,如果没有 image_get 把图片取走,那么 image_put 就自己把图片取走,实时替换成新的图片。

  1. def image_put(q, user, pwd, ip, channel=1):
  2. ...
  3. while True:
  4. q.put(cap.read()[1]) # 刚刚放进去一张图片
  5. q.get() if q.qsize() > 1 else time.sleep(0.01) # 然后马上取出来
  6. def image_get(q, window_name):
  7. ...
  8. while True:
  9. frame = q.get() # 等待队列放入图片,如果队列里面没有图片,那么它会「阻塞」在这里
  10. cv2.imshow(window_name, frame)
  11. cv2.waitKey(1)

当 image_get 运行到 q.get() 处,如果队列里面没有图片,那么它会在这里等,直到 image_put 把图片放入队列,它把这张图片取走后才继续往下运行。由于image_get 一直在队列处等候,因此它总是可以在image_put 把图片删除前 抢先把图片取走,这是这个程序做到实时更新并且减少延迟的基本方法。

网络卡顿而无法获取视频信息,应该处理? 

@Wang

这篇文章解决的是:由“处理图片的速度”慢于“摄像头拍摄产生实时图像的速度”所导致的延迟。而不是 由网络条件不好导致“接收图片的速度”过慢导致的延迟。解决方法:

  • 减少视频流大小:降低帧率、减小画幅、降低码流、主码流→辅码流、H264→H265等
  • 清空摄像头缓存:刷新与摄像头的连接,重新运行 cap = cv2.VideoCapture(***)

问题分析:工作中的摄像头会把未被接受的视频流保存在自己的缓存里,如果缓存满了它就会报错(接收端会有xxxxxxxx sRGB xxxx 之类的报错)。只要清空摄像头缓存,就能解决这个问题,因此我们可以刷新与网络摄像头的连接 来掩盖这个问题。

能够应对网络延迟的视频流协议应该是:RTMP、WebRTC 之类的协议。而RTSP不在此列。

 

 

关于私信:

不建议通过私信与我进行交流,有问题请写在评论区。它的好处:常见的问题可以被所有人看到;节省时间;避免重复回答。

 

在评论区指出的问题,我会修改到正文中,并注明贡献者的名字。

在评论区提出的问题,我可能会尝试解答,并添加到正文中。

交流是促进社区与自身成长的重要途径,欢迎评论,谢谢大家。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/738692
推荐阅读
相关标签
  

闽ICP备14008679号