使用 OpenCV 从 Flask 服务器读取 MJPEG 流 (Python)
Reading MJPEG stream from Flask server with OpenCV (Python)
我正在使用 Flask 和 flask-restful 生成 MJPEG 流。由于某些原因,我想在另一个 Python 程序中捕获此流,为此我使用了 OpenCV(3)。
问题是请求的第一帧很好。另一方面,请求的第二帧(延迟后)未正确接收,并抛出错误:
[mpjpeg @ 0000017a86f524a0] Expected boundary '--' not found, instead found a line of 82 bytes
多次。
我相信这是因为框架的边界是手动设置的。我会把有问题的代码放在下面。
MJPEG 流生成:
## Controller for the streaming of content.
class StreamContent(Resource):
@classmethod
def setVis(self, vis):
self.savedVis = vis
def get(self):
return Response(gen(VideoCamera(self.savedVis)),
mimetype='multipart/x-mixed-replace; boundary=frame')
## Generate a new VideoCamera and stream the retrieved frames.
def gen(camera):
frame = camera.getFrame()
while frame != None:
yield (b'--frame\r\n'
b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n\r\n')
time.sleep(0.07)
frame = camera.getFrame()
## Allows for the reading of video frames.
class VideoCamera(object):
def __init__(self, vis):
#object we retrieve the frame from.
self.vis = vis
## Get the current frame.
def getFrame(self):
image = self.vis.mat_frame_with_overlay
# We are using Motion JPEG, but OpenCV defaults to capture raw images,
# so we must encode it into JPEG in order to correctly display the
# video/image stream.
ret, jpeg = cv2.imencode('.jpg', image)
return jpeg.tobytes()
MJPEG 流检索:
"""
Get a single frame from the camera.
"""
class Image(Resource):
def get(self):
camera = VideoCamera()
return Response(camera.getSingleFrame(), mimetype='image/jpeg')
"""
Contains methods for retrieving video information from a source.
"""
class VideoCamera(object):
def __del__(self):
self.video.release()
@classmethod
def setVideo(self, video):
self.video = video
## Get the current frame.
def getSingleFrame(self):
self.startVideoFromSource(self.video)
ret, image = self.video.read()
time.sleep(0.5)
ret, image = self.video.read()
# We are using Motion JPEG, but OpenCV defaults to capture raw images,
# so we must encode it into JPEG in order to correctly display the
# video/image stream.
ret, jpeg = cv2.imencode('.jpg', image)
self.stopVideo()
return jpeg.tobytes()
def stopVideo(self):
self.video.release()
Anabad(及其他):
哎呀,这个问题已经有一段时间了。如果我没记错的话,简短的回答是:不,我从来没能让它正常工作。
摄像头同时被多个程序访问(当请求多次发送到API时,多个线程开始读取摄像头)摄像头无法处理。正确处理此问题的最佳方法(在我看来)是在单独的 class 线程中读取相机,并为 API 使用观察者模式。每次来自客户端的新请求读取相机时,观察者将在新帧可用后发送它们。
这解决了摄像头被多个classinstances/threads访问的问题,这就是为什么这行不通的原因。
解决这个问题,它应该可以正常工作。
也许现在回答为时已晚,但我遇到了同样的问题并找到了解决方案。
错误 [mpjpeg @ 0000017a86f524a0] Expected boundary '--' not found, instead found a line of 82 bytes
是来自 ffmpeg
的错误消息,OpenCV 将其用作后端的 mjpeg 图像解码器。
这意味着图像流式传输为 mpjpeg
(= 多部分 jpeg 数据),但未找到分隔每个 jpeg 图像的边界(因此解码器无法解码图像)。
边界应该以--
开始,但是问题中写的服务器声明边界只是frame
这里:mimetype='multipart/x-mixed-replace; boundary=frame')
这部分应该像 mimetype='multipart/x-mixed-replace; boundary=--frame')
我还发现边界和图像数据之间的线分隔是强制性的。
(因为 ffmpeg
由 Ubuntu 18.04 及更高版本提供?)
请参阅 mjpg 服务器的另一个实现。 ( https://github.com/ros-drivers/video_stream_opencv/blob/e6ab82a88dca53172dc2d892cd3decd98660c447/test/mjpg_server.py#L75 )
希望对您有所帮助。
更改帧生成器对我有用:
yield (b'--frame\r\n'
b'Content-Type:image/jpeg\r\n'
b'Content-Length: ' + f"{len(frame)}".encode() + b'\r\n'
b'\r\n' + frame + b'\r\n')
我是新手,但这绝对是一个多线程问题,因为它有时只在我重新加载页面时发生。
轻松修复:
camera.py
import cv2, threading
lock = threading.Lock()
camIpLink = 'http://user:password@my.cam.lan.ip/with/video/footage'
cap = cv2.VideoCapture(camIpLink)
def getFrame():
global cap
with lock:
while True:
try:
return bytes(cv2.imencode('.jpg', cap.read()[1])[1])
except:
print("Frame exception")
cap.release()
cap = cv2.VideoCapture(camIpLink)
server.py
from camera import getFrame
def gen():
while True:
frame = getFrame()
try:
yield (b'--frame\r\n'
b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')
except:
print("Yield Exception")
return
@app.route("/cam", methods=['GET']) # todo authentication
def camVideoFootage():
return Response(gen(),
mimetype='multipart/x-mixed-replace; boundary=frame')
我通过反复试验做了一些错误处理。
希望对您有所帮助!
我知道这有点具体,但我在使用 Mobotix 相机时遇到了这个错误,不得不在流中传递一个额外的参数 URL needlength
以要求它向我发送图片边界。
这样我就可以使用 OpenCV 读取数据而不会出现边界错误。
此参数未在任何地方记录,但在相机的帮助页面上:
http://camera_url/cgi-bin/faststream.jpg?help
它说:
需要长度
需要内容长度
为服务器推送流中的每一帧发送 HTTP 内容长度。
注意:此选项对浏览器没有用。
所以我不得不修改流 URL 使其看起来像:
http://camera_url/control/faststream.jpg?stream=full&needlength
我的猜测是其他情况可能有类似的原因,OpenCV 没有找到预期的图像边界标记。
我正在使用 Flask 和 flask-restful 生成 MJPEG 流。由于某些原因,我想在另一个 Python 程序中捕获此流,为此我使用了 OpenCV(3)。 问题是请求的第一帧很好。另一方面,请求的第二帧(延迟后)未正确接收,并抛出错误:
[mpjpeg @ 0000017a86f524a0] Expected boundary '--' not found, instead found a line of 82 bytes
多次。
我相信这是因为框架的边界是手动设置的。我会把有问题的代码放在下面。
MJPEG 流生成:
## Controller for the streaming of content.
class StreamContent(Resource):
@classmethod
def setVis(self, vis):
self.savedVis = vis
def get(self):
return Response(gen(VideoCamera(self.savedVis)),
mimetype='multipart/x-mixed-replace; boundary=frame')
## Generate a new VideoCamera and stream the retrieved frames.
def gen(camera):
frame = camera.getFrame()
while frame != None:
yield (b'--frame\r\n'
b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n\r\n')
time.sleep(0.07)
frame = camera.getFrame()
## Allows for the reading of video frames.
class VideoCamera(object):
def __init__(self, vis):
#object we retrieve the frame from.
self.vis = vis
## Get the current frame.
def getFrame(self):
image = self.vis.mat_frame_with_overlay
# We are using Motion JPEG, but OpenCV defaults to capture raw images,
# so we must encode it into JPEG in order to correctly display the
# video/image stream.
ret, jpeg = cv2.imencode('.jpg', image)
return jpeg.tobytes()
MJPEG 流检索:
"""
Get a single frame from the camera.
"""
class Image(Resource):
def get(self):
camera = VideoCamera()
return Response(camera.getSingleFrame(), mimetype='image/jpeg')
"""
Contains methods for retrieving video information from a source.
"""
class VideoCamera(object):
def __del__(self):
self.video.release()
@classmethod
def setVideo(self, video):
self.video = video
## Get the current frame.
def getSingleFrame(self):
self.startVideoFromSource(self.video)
ret, image = self.video.read()
time.sleep(0.5)
ret, image = self.video.read()
# We are using Motion JPEG, but OpenCV defaults to capture raw images,
# so we must encode it into JPEG in order to correctly display the
# video/image stream.
ret, jpeg = cv2.imencode('.jpg', image)
self.stopVideo()
return jpeg.tobytes()
def stopVideo(self):
self.video.release()
Anabad(及其他):
哎呀,这个问题已经有一段时间了。如果我没记错的话,简短的回答是:不,我从来没能让它正常工作。
摄像头同时被多个程序访问(当请求多次发送到API时,多个线程开始读取摄像头)摄像头无法处理。正确处理此问题的最佳方法(在我看来)是在单独的 class 线程中读取相机,并为 API 使用观察者模式。每次来自客户端的新请求读取相机时,观察者将在新帧可用后发送它们。
这解决了摄像头被多个classinstances/threads访问的问题,这就是为什么这行不通的原因。 解决这个问题,它应该可以正常工作。
也许现在回答为时已晚,但我遇到了同样的问题并找到了解决方案。
错误 [mpjpeg @ 0000017a86f524a0] Expected boundary '--' not found, instead found a line of 82 bytes
是来自 ffmpeg
的错误消息,OpenCV 将其用作后端的 mjpeg 图像解码器。
这意味着图像流式传输为 mpjpeg
(= 多部分 jpeg 数据),但未找到分隔每个 jpeg 图像的边界(因此解码器无法解码图像)。
边界应该以--
开始,但是问题中写的服务器声明边界只是frame
这里:mimetype='multipart/x-mixed-replace; boundary=frame')
这部分应该像 mimetype='multipart/x-mixed-replace; boundary=--frame')
我还发现边界和图像数据之间的线分隔是强制性的。
(因为 ffmpeg
由 Ubuntu 18.04 及更高版本提供?)
请参阅 mjpg 服务器的另一个实现。 ( https://github.com/ros-drivers/video_stream_opencv/blob/e6ab82a88dca53172dc2d892cd3decd98660c447/test/mjpg_server.py#L75 )
希望对您有所帮助。
更改帧生成器对我有用:
yield (b'--frame\r\n'
b'Content-Type:image/jpeg\r\n'
b'Content-Length: ' + f"{len(frame)}".encode() + b'\r\n'
b'\r\n' + frame + b'\r\n')
我是新手,但这绝对是一个多线程问题,因为它有时只在我重新加载页面时发生。 轻松修复:
camera.py
import cv2, threading
lock = threading.Lock()
camIpLink = 'http://user:password@my.cam.lan.ip/with/video/footage'
cap = cv2.VideoCapture(camIpLink)
def getFrame():
global cap
with lock:
while True:
try:
return bytes(cv2.imencode('.jpg', cap.read()[1])[1])
except:
print("Frame exception")
cap.release()
cap = cv2.VideoCapture(camIpLink)
server.py
from camera import getFrame
def gen():
while True:
frame = getFrame()
try:
yield (b'--frame\r\n'
b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')
except:
print("Yield Exception")
return
@app.route("/cam", methods=['GET']) # todo authentication
def camVideoFootage():
return Response(gen(),
mimetype='multipart/x-mixed-replace; boundary=frame')
我通过反复试验做了一些错误处理。 希望对您有所帮助!
我知道这有点具体,但我在使用 Mobotix 相机时遇到了这个错误,不得不在流中传递一个额外的参数 URL needlength
以要求它向我发送图片边界。
这样我就可以使用 OpenCV 读取数据而不会出现边界错误。
此参数未在任何地方记录,但在相机的帮助页面上:
http://camera_url/cgi-bin/faststream.jpg?help
它说:
需要长度
需要内容长度
为服务器推送流中的每一帧发送 HTTP 内容长度。
注意:此选项对浏览器没有用。
所以我不得不修改流 URL 使其看起来像:
http://camera_url/control/faststream.jpg?stream=full&needlength
我的猜测是其他情况可能有类似的原因,OpenCV 没有找到预期的图像边界标记。