网络摄像头rtsp流延迟无法解决,改用Mjpeg流成功保证低延迟稳定传输,并成功解决opencv对Mjpeg流支持问题

最近做的一个小项目,是需要通过一个网络实时将画面传输给后端进行处理。因为涉及到对运动的捕捉,延迟要求较为严格。我的网络摄像头是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可以支持多个流的同时捕获及更新,但我只需要对一个摄像头进行读取就行,因此我删去了处理多个流的部分。

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
    
# ___________________

然后要做的,就是在推理函数里使用这个加载器,就能无缝衔接过去啦

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值