Python flask 视频流返回问题 & RTSP断流问题解决
参考【项目心得 の 二】在开发时如果有具体想法需提前和前端沟通,寻求方便的解决方案
1、视频流返回结果用GET,不要POST
今天与康哥对接接口时,发现一个问题:**前端(Vue)使用
POST
接口上传文件到后台(flask),我在后台处理后返回一个视频帧给前端,但是这时的前端界面上不能正常显示图片,然后让康哥通过
GET
接口测在线视频,这个是可以正常显示的**,主要原因是在线检测时接口用的是
GET
类型,并且是**后台通过图片生成器来更新前端的
image_url
中的图片**,是的前端的
img src
属性会根据后端链接的变化实现前端图片的更新;而
POST
请求只提交一次,不能获取实时的视频流。
'''舱门检测: 基于图片/视频二进制文件/网络摄像头地址'''@manholeB.route('/predict_manhole', methods=['GET','POST'])defpredict_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()
返回的是视频每一帧的二进制图片。
defhandle_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
为:
defhandle_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)withopen(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'])defpredict_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'])defget_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
则完成指定文件的检测,代码修改如下:
defhandle_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容易出现断流,可以设置定时器来重新创建连接
这里利用帧数求余(抽帧)来重新尝试创建连接
'''检测网络摄像头中的视频流'''defgen_frames_with_webCamera(username, container_map, webCamera_url, detector, frameDraw=1):
camera = cv2.VideoCapture(webCamera_url)
frame_count =0whileTrue:
frame_count +=1# 空转且设置较低容错,避免断流if(camera.isOpened()==Falseand(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/jpegprint("webCamera detected...")yield(b'--frame\r\n'+b'Content-Type: image/jpeg\r\n\r\n'+ frame +b'\r\n')
camera.release()
版权归原作者 王小希ww 所有, 如有侵权,请联系我们删除。