树莓派Flask内存捕获摄像头图像并提供服务报错排查
解决方案:Flask+Waitress内存处理树莓派摄像头图像重复请求报错
问题根源
Flask的send_file默认会关闭传入的文件/流对象(包括BytesIO),如果复用同一个全局BytesIO实例,第一次请求后它就会被关闭,后续请求调用时就会触发ValueError: I/O operation on closed file。另外Waitress是多线程服务器,多请求同时操作摄像头也会导致冲突。
核心解决思路
- 每次请求生成新的BytesIO流,避免复用已关闭的对象
- 用线程锁保护摄像头操作,防止多线程并发访问冲突
- 禁用缓存,避免浏览器或Flask缓存旧图像
完整代码示例(基于Picamera2,树莓派OS新版)
from flask import Flask, send_file from io import BytesIO from picamera2 import Picamera2 import threading app = Flask(__name__) # 全局初始化摄像头,避免每次请求重新启动,提升性能 camera = Picamera2() camera.configure(camera.create_still_configuration(main={"size": (640, 480)})) camera.start() # 线程锁,解决Waitress多线程并发访问摄像头的冲突 camera_lock = threading.Lock() @app.route('/image.jpg') def get_image(): with camera_lock: # 每次请求创建新的BytesIO流 img_stream = BytesIO() # 将摄像头图像直接写入内存流 camera.capture_file(img_stream, format='jpeg') # 将流指针移到起始位置,确保send_file能完整读取 img_stream.seek(0) # 返回流时禁用缓存,避免浏览器或Flask复用旧资源 return send_file( img_stream, mimetype='image/jpeg', cache_timeout=0, add_etag=False ) @app.route('/') def status_json(): return {"status": "running", "service": "camera-stream"} if __name__ == '__main__': from waitress import serve # 绑定所有网卡,允许外部访问 serve(app, host='0.0.0.0', port=5000)
兼容旧版Picamera的代码
如果你的树莓派使用的是旧版picamera库,替换摄像头部分代码即可:
from flask import Flask, send_file from io import BytesIO import picamera import threading app = Flask(__name__) camera = picamera.PiCamera() camera.resolution = (640, 480) camera_lock = threading.Lock() @app.route('/image.jpg') def get_image(): with camera_lock: img_stream = BytesIO() camera.capture(img_stream, format='jpeg') img_stream.seek(0) return send_file( img_stream, mimetype='image/jpeg', cache_timeout=0, add_etag=False ) # 其余代码同上
关键细节说明
- 线程锁的必要性:Waitress默认是多线程模式,多个请求同时调用摄像头会导致图像捕获失败或硬件冲突,
camera_lock确保同一时间只有一个请求操作摄像头。 - 禁用缓存的参数:
cache_timeout=0告诉浏览器不要缓存图像,add_etag=False避免Flask生成ETag标识导致的缓存复用问题。 - 不要复用BytesIO:每次请求创建新的
BytesIO实例,彻底避免"已关闭文件"的报错,因为send_file处理完当前流后会自动关闭它,不影响后续请求。
内容的提问来源于stack exchange,提问作者user898082
相关产品推荐
相关产品推荐

