
本文介绍一种基于 flask 和 opencv 的端到端视频处理方案:在浏览器中实时调用摄像头录制视频,并在服务端为每一帧动态添加红色(rgb: 0,0,255)边框,最终生成并提供带边框的 mp4 文件供下载。
本文介绍一种基于 flask 和 opencv 的端到端视频处理方案:在浏览器中实时调用摄像头录制视频,并在服务端为每一帧动态添加红色(rgb: 0,0,255)边框,最终生成并提供带边框的 mp4 文件供下载。
在原始实现中,用户通过前端 MediaRecorder 录制视频并保存为本地 Blob,再上传或触发后端处理——但该流程存在关键缺陷:边框是在录制完成后的离线转码阶段添加的,而 MediaRecorder 生成的 MP4 文件常因编码兼容性问题导致 cv2.VideoCapture 读取失败或帧尺寸异常,致使 cv2.copyMakeBorder 无法正确应用。更严重的是,原始代码中 VideoWriter 初始化时仍使用原始帧尺寸 (frame_width, frame_height),未适配加边框后的新分辨率,造成写入失败或画面裁剪。
本教程采用更鲁棒、更符合生产逻辑的架构:服务端直连摄像头持续采集 → 实时预处理(如镜像)→ 流式传输至前端预览 → 同步后处理(逐帧加边框并写入文件)。整个流程避免了浏览器端视频编码/解码的不可控因素,确保边框精准、稳定地叠加在每一帧上。
✅ 核心改进点
- 边框在录制过程中实时添加:不再依赖后期转码,杜绝因视频容器格式(如 H.264 vs MJPEG)引发的读取失败;
- VideoWriter 分辨率严格匹配加边框后尺寸:out = cv2.VideoWriter(..., (w + 2×t, h + 2×t)),其中 t 为边框厚度;
- 协程解耦预/后处理逻辑:video_writer 协程专注“接收帧 → 加边框 → 写磁盘”,主流函数专注“捕获 → 预处理 → 流式响应”,职责清晰、可扩展性强;
- 零前端 JavaScript 视频处理负担:所有图像操作均在服务端完成,兼容性高,调试简单。
? 完整可运行代码(app.py)
import time
from flask import Flask, render_template_string, Response
import cv2
def coroutine(func):
def start(*args, **kwargs):
cr = func(*args, **kwargs)
next(cr)
return cr
return start
@coroutine
def video_writer(dst_video, frame_width, frame_height):
border_thickness = 20
border_color = (0, 0, 255) # BGR order for OpenCV
# ✅ 关键:输出尺寸 = 原始尺寸 + 双侧边框
out = cv2.VideoWriter(
dst_video,
cv2.VideoWriter_fourcc(*'MJPG'),
10,
(frame_width + 2 * border_thickness, frame_height + 2 * border_thickness)
)
try:
while True:
frame = (yield)
# ✅ 实时加边框(BORDER_CONSTANT + RGB→BGR 转换已隐含)
bordered_frame = cv2.copyMakeBorder(
frame,
top=border_thickness,
bottom=border_thickness,
left=border_thickness,
right=border_thickness,
borderType=cv2.BORDER_CONSTANT,
value=border_color
)
out.write(bordered_frame)
except GeneratorExit:
pass
finally:
out.release()
def stream_main(target, path_video):
cap = cv2.VideoCapture(0)
if not cap.isOpened():
raise RuntimeError("Failed to open camera")
frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
# 初始化协程(传入视频路径和尺寸)
writer = target(path_video, frame_width, frame_height)
def gen_frame_content(img_bytes):
return b''.join([
b'--frame\r\nContent-Type: image/jpeg\r\n\r\n',
img_bytes,
b'\r\n'
])
while True:
ret, frame = cap.read()
if not ret:
break
# ✅ 后处理:写入带边框视频(同步)
writer.send(frame)
# ✅ 预处理:供前端显示(如水平翻转,提升用户体验)
display_frame = cv2.flip(frame, 1)
# 编码为 JPEG 并流式返回
success, buffer = cv2.imencode('.jpg', display_frame)
if not success:
continue
yield gen_frame_content(buffer.tobytes())
time.sleep(0.04) # 控制约 25 FPS
cap.release()
writer.close()
# —— 路由配置 ——
app = Flask(__name__)
OUTPUT_VIDEO_PATH = 'recorded_with_border.avi' # 推荐 .avi + MJPEG,兼容性优于 MP4
@app.route('/')
def index():
return render_template_string('''
<h2>? Live Recording with Red Border</h2>
<p>Camera starts automatically. To stop recording, close this tab or reload.</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/ai/3838" title="ljg-skills"><img
src="https://img.php.cn/upload/ai_manual/001/246/273/178599577233497.png" alt="ljg-skills" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/ai/3838" title="ljg-skills" class="overflowclass">ljg-skills</a>
<p class="overflowclass">ljg-skills是一款AI提示词工具,'Prompt之神'李继刚开源的 AI 技能集。</p>
</div>
<a rel="nofollow" href="/ai/3838" title="ljg-skills" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
@@##@@
''')
@app.route('/video_feed')
def video_feed():
return Response(
stream_main(video_writer, OUTPUT_VIDEO_PATH),
mimetype='multipart/x-mixed-replace; boundary=frame'
)
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5000)
⚠️ 注意事项与最佳实践
- 视频格式选择:推荐使用 .avi + MJPG 编码(如示例),因其帧内压缩、无跨帧依赖,cv2.VideoWriter 兼容性远高于 H.264 MP4。若需 MP4,须改用 cv2.VideoWriter_fourcc(*'avc1') 并确保系统安装 ffmpeg 支持;
- 边框颜色注意:OpenCV 使用 BGR 顺序,(0, 0, 255) 表示红色(非 RGB),无需额外转换;
- 性能提示:边框厚度、分辨率、帧率需权衡。border_thickness=20 在 640×480 下效果明显;若 CPU 较弱,可降低 time.sleep() 或 VideoWriter FPS;
- 停止机制:当前方案通过页面关闭/刷新终止录制(GeneratorExit 捕获)。如需显式“停止按钮”,可在 Flask 中增加 /stop_recording 路由,通过全局状态控制 cap 和 writer 生命周期;
- 生产部署:务必添加异常处理(如磁盘满、权限拒绝)、视频路径安全校验(防路径遍历),并考虑使用 threading.Lock 保护共享资源。
✅ 验证效果
启动服务后访问 http://localhost:5000/,摄像头自动开启,画面实时显示(已镜像)。同时,recorded_with_border.avi 在后台持续写入——打开该文件,将看到每帧四周均有 20 像素宽的纯红色边框,且无黑边、拉伸或闪烁。
此方案将视频处理逻辑完全收口于服务端,规避了浏览器编码差异与前后端数据传递损耗,是构建可靠视频标注、教学录制、安防预览等系统的坚实基础。










