最近做的一个小项目,是需要通过一个网络实时将画面传输给后端进行处理。因为涉及到对运动的捕捉,延迟要求较为严格。我的网络摄像头是Z CAM E2 M4,内置了rtsp流。使用rtsp流时总会产生2秒的延迟。直接使用ffmpeg播放依然存在2s延迟。尝试过修改缓冲大小无果。查阅相机文档发现有个mjpeg流,在浏览器中直接打开异常稳定且低延迟。于是尝试换用mjpeg流。
opencv对mjpeg流支持存在问题
当我直接使用opencv对mjpeg流进程读取时,总会出现“边界符未找到”的报错。经查阅这个问题出自opencv底层编译过程中,致使无法对流的编码方式进行更改。
cap = cv2.videocapture("ip Mjpeg url")
cap.read()
#报错:
出现报错:
[mpjpeg @ 000001e35d8c2d40] Expected boundary '--' not found, instead found a line of 15 bytes
[mpjpeg @ 000001e35d8c2d40] Expected boundary '--' not found, instead found a line of 10 bytes
给👴干蒙了。网上有方法说是使用带着gstreamer的opencv就可以实现,但是折腾了4天自己编译opencv最后一堆失败实属绷不住。决定手撕流请求
手动解析Mjpeg流
具体代码参考了这篇文章:
https://blog.csdn.net/Barry_J/article/details/101280263
效果很好,可以稳定解析mjpeg流
整合到yoloV5项目
项目使用了yolov5进行对目标的监测,并且使用了deepsort进行目标实时跟踪。主体代码是根据github上的开源项目改的:
https://github.com/mikel-brostrom/yolo_tracking
他是使用了yolo5提供的tuils的LoadStream加载器进行视频流加载。对rtsp视频流支持没问题。但是,由于其也是基于opencv写的加载器,对mjpeg支持就会出现问题。为了尽量少的减少对整体项目的修改(我已经把后面的模块写的差不多了重构真的会死人),决定手撕一个类似的加载器。
参考Loadstream的写法,其是通过一个迭代器来完成对视频流的持续读取和更新的。Loadstream可以支持多个流的同时捕获及更新,但我只需要对一个摄像头进行读取就行,因此我删去了处理多个流的部分。文章来源:https://www.toymoban.com/news/detail-724014.html
import requests
# _______________________________________________________________________
class LoadStreams_Mjpeg: # just for mjpeg
def __init__(self, sources="ip", img_size=640, stride=32):
self.mode = 'Mjpegstream'
self.img_size = img_size
self.stride = stride
self.imgs = [None]
self.sources = sources
self.rect = 1
cap = cv2.VideoCapture(sources)
w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
self.fps = cap.get(cv2.CAP_PROP_FPS) % 100
_, self.imgs = cap.read() # guarantee first frame
thread = Thread(target=self.update, daemon=True)
print(f' success ({w}x{h} at {self.fps:.2f} FPS).')
thread.start()
def update(self):
r = requests.get(self.sources, stream=True)
if(r.status_code == 200):
bytes_arr = bytes()
frame_count = 0
for chunk in r.iter_content(chunk_size=1024):
bytes_arr += chunk
a = bytes_arr.find(b'\xff\xd8')
b = bytes_arr.find(b'\xff\xd9')
if a != -1 and b != -1:
jpg = bytes_arr[a:b+2]
bytes_arr = bytes_arr[b+2:]
i = cv2.imdecode(np.fromstring(jpg, dtype=np.uint8), cv2.IMREAD_COLOR)
self.imgs = i #更新帧
# 此处的i就是预测需要用的数据结构,可以将此处i放到共享变量,或者队列中供detect使用
# cv2.imshow('i', i)
frame_count+=1
# print("FPS:",frame_count/(end_time-start_time)
else:
print("Received unexpected status code {}".format(r.status_code))
def __iter__(self):
self.count = -1
return self
def __next__(self):
self.count += 1
img0 = [self.imgs.copy()]
if cv2.waitKey(1) == ord('q'): # q to quit
cv2.destroyAllWindows()
raise StopIteration
# Letterbox
img = [letterbox(x, self.img_size, auto=self.rect, stride=self.stride)[0] for x in img0]
# Stack
img = np.stack(img, 0)
# Convert
img = img[:, :, :, ::-1].transpose(0, 3, 1, 2) # BGR to RGB, to bsx3x416x416
img = np.ascontiguousarray(img)
return self.sources, img, img0, None
def __len__(self):
return 0 # 1E12 frames = 32 streams at 30 FPS for 30 years
# ___________________
然后要做的,就是在推理函数里使用这个加载器,就能无缝衔接过去啦文章来源地址https://www.toymoban.com/news/detail-724014.html
到了这里,关于网络摄像头rtsp流延迟无法解决,改用Mjpeg流成功保证低延迟稳定传输,并成功解决opencv对Mjpeg流支持问题的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!