参考【项目心得 の 二】在开发时如果有具体想法需提前和前端沟通,寻求方便的解决方案
今天与康哥对接接口时,发现一个问题:前端(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)
这里利用帧数求余(抽帧)来重新尝试创建连接
'''检测网络摄像头中的视频流'''
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()