Python flask 视频流返回问题 & RTSP断流问题解决

Python flask 视频流返回问题 & RTSP断流问题解决

参考【项目心得 の 二】在开发时如果有具体想法需提前和前端沟通,寻求方便的解决方案

1、视频流返回结果用GET,不要POST

今天与康哥对接接口时,发现一个问题:前端(Vue)使用POST接口上传文件到后台(flask),我在后台处理后返回一个视频帧给前端,但是这时的前端界面上不能正常显示图片,然后让康哥通过GET接口测在线视频,这个是可以正常显示的,主要原因是在线检测时接口用的是GET类型,并且是后台通过图片生成器来更新前端的image_url中的图片,是的前端的img src属性会根据后端链接的变化实现前端图片的更新;而POST请求只提交一次,不能获取实时的视频流

'''舱门检测: 基于图片/视频二进制文件/网络摄像头地址'''
@manholeB.route('/predict_manhole', methods=['GET','POST'])
def predict_manhole():
    username = "wang"
    if(request.method == 'POST'):  #图片上传(type=0),视频上传(type=1)
        type = int(request.form.get("type"))
        frame = frameDetect_handler.handle_frame_with_type(username,request,type,manhole_detector)
        return Response(frame, mimetype='multipart/x-mixed-replace; boundary=frame')
    else:  #网络摄像头路径(type=2)
        type = int(request.args.get("type"))
        status = int(request.args.get("status")) #是否开启摄像头
        frame = frameDetect_handler.handle_frame_with_type(username,request, type, manhole_detector)
        return Response(frame, mimetype='multipart/x-mixed-replace; boundary=frame')
        # return jsonify({'code': 400, 'msg': '操作失败:请使用post方法'})

这里的handler.handle_frame_with_type()返回的是视频每一帧的二进制图片。

    def handle_frame_with_type(self,username,request,type,detector, status=True):
        """
        @param username: 哪个用户执行该操作
        @param type: 要检测的类型,0:图片,1:视频,2:摄像头, 并向视图返回处理后的结果, type=int
        @param handler: 检测器,舱门检测器,烟雾检测器,人脸检测器
        @param status: 是否开启摄像头:false-关闭,true-打开
        @return:
        """
        #清空用户之前的操作
        self.container_map.clearUserCamera(username)
        if(type == self.types['image']):
            return self.handle_img(request,detector)
        elif(type == self.types['video']):
            return self.handle_video(request,username,detector)
        elif(type == self.types['webCamera']):
            return self.handle_camera(request,username,detector,status)

其中handle_video为:

   def handle_video(self,request,username, detector):
        print("hello_video")
        fileStorage = request.files['videofile']
        buffer_video = fileStorage.read()

        #将二进制视频流保存成文件之后再用opencv读取 参考https://stackoverflow.com/questions/57865656/save-video-in-python-from-bytes
        TEMP_VIDEO_OUTPUT = os.path.join(rootPath,'temp.mp4')
        if os.path.isfile(TEMP_VIDEO_OUTPUT):
            os.remove(TEMP_VIDEO_OUTPUT)
        with open(TEMP_VIDEO_OUTPUT, "wb") as out_file:  # open for [w]riting as [b]inary
            out_file.write(buffer_video)
        camera = cv2.VideoCapture(TEMP_VIDEO_OUTPUT)
        #注册camera到container_map中
        self.container_map.register(username,camera)
        frame = gen_frames(camera,detector)
        return frame

解决方法

前端通过将上传的图片文件封装在formdata中,并通过POST接口想要得到后台的图片处理结果,由于img标签的src会随着get请求的更新而进行图片更新,所以分步解决:

  • POST接口将上传的文件保存到本地,并向前台返回带本地文件路径(file_path) 的图片/视频处理的GET接口路由(由于前端浏览器缓存问题,相同的GET地址会优先读取浏览器缓存中的数据,因此需要在GET链接上加上时间戳!!!)。

        '''舱门检测: 基于图片/视频二进制文件/网络摄像头地址'''
      @manholeB.route('/predict_manhole', methods=['GET','POST'])
      def predict_manhole():
          username = "wang"
          if(request.method == 'POST'):  #图片上传(type=0),视频上传(type=1)
              type = int(request.form.get("type"))
              file_path = temp_save_path
              file_path = frameDetect_handler.handle_frame_with_type(username,request,type,manhole_detector,
                                                                     file_path)  #完成图片、视频上传到本地
              timeStamp = str(time.mktime(time.localtime(time.time())))
              data = "http://" + ip + ":" + port + "/get_manhole_frame?type=" + str(type) + "&file_path=" + file_path + "&timeStamp=" + timeStamp  #返回文件访问路径
              return jsonify({'data': data})
          else:  #网络摄像头路径(type=2)
              type = int(request.args.get("type"))
              frame = frameDetect_handler.handle_frame_with_type(username,request, type, manhole_detector)
              return Response(frame, mimetype='multipart/x-mixed-replace; boundary=frame')
    
  • GET接口会利用file_path参数,获取刚才前端上传的文件,通过图片生成器向前端返回图片

    '''predict_manhole POST请求返回的路由,用于给前端发送二进制视频流'''
    @manholeB.route('/get_manhole_frame', methods=['GET'])
    def get_manhole_frame():
        username = "wang"
        file_path = request.args.get("file_path")
        type = int(request.args.get("type"))
        frame = frameDetect_handler.handle_frame_with_type(username, request, type, manhole_detector,
                                                               file_path)  # 完成图片、视频上传到本地
        return Response(frame, mimetype='multipart/x-mixed-replace; boundary=frame')
        # return jsonify({'finish' : True})  #已经检测完毕
    

handle_frame_with_type()通过判断接口是POST还是GET,如果是POST完成文件上传,如果是GET则完成指定文件的检测,代码修改如下:

    def handle_frame_with_type(self,username,request,type,detector,file_path):
        """
        @param username: 哪个用户执行该操作
        @param type: 要检测的类型,0:图片,1:视频,2:摄像头, 并向视图返回处理后的结果, type=int
        @param handler: 检测器,舱门检测器,烟雾检测器,人脸检测器
        @param file_path: frame写入的文件路径
        """
        #清空用户之前的操作
        self.container_map.clearUserCamera(username)
        if(type == self.types['image']):
            if(request.method == 'POST'):  #post请求进行文件上传操作
                return self.upload_img(request,file_path)
            elif (request.method == 'GET'):  # GET请求返回视频帧
                return self.handle_img(detector, file_path)

        elif(type == self.types['video']):
            if (request.method == 'POST'):  # post请求进行文件上传操作
                return self.upload_video(request,file_path)
            elif(request.method == 'GET'):  # GET请求返回视频帧
                return self.handle_video(username, detector, file_path)

        elif(type == self.types['webCamera']):
            return self.handle_camera(request,username,detector)

2、RTSP容易出现断流,可以设置定时器来重新创建连接

这里利用帧数求余(抽帧)来重新尝试创建连接

'''检测网络摄像头中的视频流'''
def gen_frames_with_webCamera(username, container_map, webCamera_url, detector, frameDraw=1):
    camera = cv2.VideoCapture(webCamera_url)
    frame_count = 0
    while True:
        frame_count += 1
        # 空转且设置较低容错,避免断流
        if(camera.isOpened() == False and (frame_count % frameDraw == 0)):
            if(container_map.getCamera_fromUser(username) != None):   #用户将camera置为空,则无需重新创建camera
                camera = cv2.VideoCapture(webCamera_url)
            else:
                break
        # 一帧帧循环读取摄像头的数据
        # 将读取视频帧的任务交给videoStream类来处理(决定是返回单帧frame,还是count帧frames)
        success, frame = camera.read()
        if success and (frame_count % frameDraw == 0):  # 抽帧处理
            container_map.register(username,camera)
            frame = cv2.resize(frame, (640, 480))
            frame = detector.detect(frame)
            # frame = cv2.resize(frame, (640, 480))
            # 将每一帧的数据进行编码压缩,存放在memory中
            ret, buffer = cv2.imencode('.jpg', frame)

            frame = buffer.tobytes()
            # 使用yield语句,将帧数据作为响应体返回,content-type为image/jpeg
            print("webCamera detected...")
            yield (b'--frame\r\n' +
                   b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')
    camera.release()

你可能感兴趣的:(项目,#,python,#,计算机视觉,flask,python,后端)