YOLOv5目标检测Flask Web部署实战:从命令行到浏览器
发布时间:2026/9/28 16:25:06来源:尧图网络
简介一套基于YOLOv5与Flask的实时目标检测Web项目代码包依托PyTorch实现面向想在Web端快速落地目标检测能力的开发者和算法工程师解决从模型调用到HTTP接口对外服务的完整链路。资源共15个文件压缩包仅187KB以Python源码为主含四个源文件覆盖模型推理、REST接口与测试请求等模块同时配有HTML/CSS前端页面、Dockerfile与requirements.txt便于一键容器化部署另有Markdown文档分步说明配置过程整体结构清晰。该包已有713人学习适合具备基础Python与深度学习概念、希望实践模型服务化交付的读者。通过阅读源码可直接掌握YOLOv5权重加载、图片上传接口、检测结果JSON化输出等核心环节借助Jinja2模板还能落地简单交互页面配合测试脚本与示例图片可快速验证效果对后续生产部署有直接参考价值。1. Yolov5目标检测web部署flask框架把模型从命令行搬进浏览器的路径训练好的 Yolov5 目标检测模型多数时候活在命令行里detect.py 跑通、控制台打印坐标、结果图落在 runs/detect 目录。可真要拿给同事、客户或领导验收总不能让对方装 conda、克隆源码、敲一串命令行。Yolov5 目标检测 web 部署 flask 框架解决的正是这一层把推理逻辑包装成 HTTP 接口浏览器打开页面、上传图片、几秒内看到检测框和置信度。整个方案不依赖前端工程化也不引入消息队列一个 Python 文件加一个页面就能承载内网演示和中小并发工具适合刚训练完自己数据集、想把模型变成可点开产品的开发者。2. 先把Yolov5模型跑通环境配置、权重格式与最小推理验证2.1 用conda隔离环境Python版本和torch版本怎么定web 部署最忌讳直接在 base 环境里堆依赖。Yolov5 的 requirements.txt 里有 opencv-python、matplotlib、pandas 这些包跟 Flask 的依赖放一起早晚因为版本不一致互相顶掉。我一般先用 conda 建一个独立环境Python 版本固定在 3.9这是 Yolov5 官方支持中兼容性最好的区间后面装 torch 和 flask 都不用为语法兼容折腾。conda create -n yolo_web python3.9 -y conda activate yolo_web pip install torch1.13.1 torchvision0.14.1 --index-url https://download.pytorch.org/whl/cu118 git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt pip install flask gunicorn这里有两个值得留意的点。torch 的安装索引如果服务器只有 CPU、没有 N 卡把 cu118 换成 cpu 版本命令是pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu有显卡就保持 cu118或换成与你本地 CUDA 版本一致的索引。装错版本会在推理时报CUDA driver initialization failure这是环境层最常见的翻车点。git clone 拉的是 Yolov5 官方源码头后续 torch.hub.load 要用到这个本地目录。装完先别急着写接口输入python -c import torch; print(torch.__version__, torch.cuda.is_available())确认 torch 能导入、显卡可用性符合预期。这一步不通过后面所有报错都会指向同一个方向环境问题。见过太多人直接跳到部署代码最后 Flask 起来了、模型加载却失败回头才补环境白白浪费一晚上。2.2 用.pt还是转成.onnxweb部署前就把模型格式定下来模型格式是 web 部署最先要做的取舍很多人忽略它。用训练得到的 best.pt好处是跟在 Yolov5 源码里的推理链路完全一致不需要自己写后处理坏处是部署环境必须带全套 Yolov5 依赖torch 版本还得跟训练时接近换机器容易出幺蛾子。转成 onnx 则把推理前段变成静态图部署端只需要 onnxruntime依赖少了但 NMS 非极大值抑制这类后处理得自己写或单独打包工作量上去了。对比项.pt 原权重.onnx 导出依赖量torch 全套yolov5onnxruntime轻很多后处理yolov5自带需要自行实现或用导出脚本带的后处理推理速度常规通常略快尤其CPU场景调试难度低中中间层命名要对上适合场景首次部署、内网演示生产接口、资源受限机器头一回部署我建议直接上 .pt把业务逻辑跑通再说。生产环境一旦确定要上再花半天转 onnx 并逐层验证输出后悔药是有的但没必要在第一步就吃。转 onnx 用官方 export.py 即可python export.py --weights best.pt --include onnx导出后先跑一次 onnxruntime 的样例推理确认测试图片的检测框数量跟 pt 结果一致再切换别导完就当能用。2.3 最小推理脚本先证明模型没问题再谈接口写 Flask 之前先用一段最小脚本验证模型本身可用。这一步能把「模型问题」和「部署代码问题」切开后面排查效率高很多。import torch # sourcelocal 表示从本地yolov5源码目录加载别让它去线上拉包 model torch.hub.load(./yolov5, custom, pathweights/best.pt, sourcelocal) # 推理时传超参数等价于命令行 --conf-thres 和 --iou-thres results model(test.jpg, size640, conf_thres0.4, iou_thres0.45) results.print() # 打印每个框的坐标、置信度和类别 results.save() # 默认保存到 runs/detect/exptorch.hub.load 的第一个参数指向你刚才 clone 的 yolov5 目录sourcelocal不能省省了它会尝试去线上拉取依赖。path给的是你自己训练集练出来的 best.pt不是官方的 yolov5s.pt。第二种常见做法是不用 hub.load直接写一句from models.experimental import attempt_load再加载 best.pt但那就要求代码在 yolov5 目录下运行引用路径很容易乱hub.load 的方式对 web 项目目录结构更友好。推理超参数这里出现了第一组size640控制输入分辨率conf_thres0.4是置信度阈值iou_thres0.45是 NMS 阈值。这三个参数后面要原样搬到 Flask 接口里先在命令行试出合适组合别让前端和接口各自再调一遍。测试图片最好用训练集之外的真实照片验证的是泛化能力而不是拟合能力。3. Flask推理接口把detect.py改造成HTTP服务的三个关键改动3.1 为什么不能直接把detect.py当服务用有人图省事想用子进程调 detect.py 来响应请求这是最典型的一条弯路。detect.py 每次执行都要重新解析命令行参数、重新加载权重、再跑一次完整推理链路单次请求要背几秒的模型加载时间并发一来直接把 CPU 打满。而且它的输出是往 runs/detect 里写文件HTTP 接口要的是结构化返回你再回头解析日志和文件等于把简单问题做复杂。正确思路是把 Yolov5 封装成一个常驻内存的检测器对象Flask 进程启动时加载一次模型之后每个请求只做「图片进 → 推理 → 结果出」这一小段。模型占用的显存或内存是固定的响应时间也被压在一个可控范围内这是 web 部署的基本盘。下面三个改动就是把 detect.py 里真正干活的逻辑拆出来迁到 Flask 路由里其余文件和日志逻辑全部不碰。3.2 核心代码模型单例与预测路由import io import base64 from flask import Flask, request, jsonify import torch from PIL import Image app Flask(__name__) class Detector: 把yolov5推理封装成单例进程启动时只加载一次模型 def __init__(self, weights_path, conf0.4, iou0.45, size640): self.conf conf self.iou iou self.size size self.model torch.hub.load(./yolov5, custom, pathweights_path, sourcelocal) self.model.conf conf # 直接改model属性等价命令行超参数 self.model.iou iou self.model.eval() def predict(self, image_bytes): # 不落盘直接按字节流读取绕开路径和编码问题 img Image.open(io.BytesIO(image_bytes)).convert(RGB) results self.model(img, sizeself.size) return results.pandas().xyxy[0].to_dict(orientrecords) detector Detector(weights/best.pt) app.route(/predict, methods[POST]) def predict(): f request.files.get(image) if f is None: return jsonify({error: 缺少image字段}), 400 detections detector.predict(f.read()) return jsonify({detections: detections})关键逻辑有三处。第一Detector 在模块加载时实例化Flask 启动即完成模型加载不会等到第一个请求才现场加载。第二results.pandas().xyxy[0]把推理结果转成 pandas DataFrame再 to_dict 变成 JSON 友好的列表字段包含 xmin、ymin、xmax、ymax、confidence、class、name前端直接能用。第三图片不走硬盘f.read()直接拿字节流喂给 PIL避开文件路径、权限、编码一系列问题。启动方式上开发调试直接app.run(host0.0.0.0, port5000)。注意 Flask 自带开发服务器打印的 WARNING 说它不适合生产那是真的内网工具勉强能用对外服务务必看 4.3 节的 gunicorn 配置。另外request.files.get(image)里的 image 必须跟前端 FormData 里的字段名一致两边各叫各的请求就一直报缺少字段。3.3 返回检测框图片用render()和base64一次搞定接口只返回 JSON 坐标前端还得自己画框麻烦。更常见的做法是让后端直接把框画好图片以 base64 字符串返回前端一个img标签就显示完事这对不想引入 canvas 绘制逻辑的团队最友好。def predict_with_image(self, image_bytes): img Image.open(io.BytesIO(image_bytes)).convert(RGB) results self.model(img, sizeself.size) detections results.pandas().xyxy[0].to_dict(orientrecords) # render() 会原地给结果图画框返回numpy数组组成的列表 rendered results.render()[0] buf io.BytesIO() Image.fromarray(rendered).save(buf, formatJPEG, quality85) img_b64 base64.b64encode(buf.getvalue()).decode(utf-8) return {detections: detections, image: img_b64}这段里的results.render()是 Yolov5 自带方法它在原始图片上画好框、类别名和置信度返回 numpy 数组。这里最容易踩的坑是 render() 会修改 results 的原始数据所以如果你要同时返回 JSON 坐标和画框图必须先取 detections 再 render顺序反了坐标和图画就对不上。base64 编码后的字符串用一个字段返回前端srcdata:image/jpeg;base64, data.image直接可用不需要再为检测结果图单独开一个静态文件接口部署时少暴露一个端口和目录。提示返回 base64 图会让 JSON 响应体变大不少内网问题不大公网带宽紧张时建议加一个 need_image 查询参数只在调试和演示时返回图。4. 前端页面与并发参数上传组件、yolov5超参数与线程安全4.1 原生HTML上传与预览不引前端框架既然是给模型做快速交付前端越薄越好。一个原生 HTML 页面加一段 fetch 代码就够不需要 Vue 或 React。注意接口用 FormData 提交文件这是 multipart 表单的标准做法字段名 image 与后端对齐。input typefile idfileInput acceptimage/* button onclickupload()开始检测/button img idresultImg alt检测结果 stylemax-width: 640px; script async function upload() { const file document.getElementById(fileInput).files[0]; const form new FormData(); form.append(image, file); // 字段名要和后端 request.files.get(image) 一致 const resp await fetch(/predict, { method: POST, body: form }); const data await resp.json(); document.getElementById(resultImg).src data:image/jpeg;base64, data.image; } /script一个容易翻车的小细节不要手动给 fetch 设置Content-Type因为 FormData 的 multipart 边界字符串是浏览器自动生成的你手写 header 会把它挤掉后端解析不到文件。这个页面的完整逻辑就是把图片塞进 FormData、打给 /predict、把返回的 base64 直接灌进 img 标签三步到位。如果需要把检测框的类别和置信度显示成列表再加一段遍历 data.detections 渲染表格的代码几行 JavaScript 就能完成。4.2 yolov5超参数怎么设conf_thres、iou_thres和size的取舍web 部署里最容易被忽略的是 yolov5 超参数对用户体验的影响。训练时可以取低阈值多召回部署给外部看时一次误检比漏检更劝退。超参数应该在 Detector 的init里做成配置而不是散落在路由函数里改动时只动一处。参数命令行写法部署建议说明conf_thres--conf-thres0.4~0.5低阈值召回多误检多演示环境调到0.5以上iou_thres--iou-thres0.45NMS合并阈值一般不动size--img-size640GPU强可上1280分辨率翻倍耗时约翻倍max_det--max-det20左右限制单图最多框数防异常图刷屏size 的影响最直接。同一张图用 640 和 1280 推理耗时基本线性增长GPU 扛得住就上调CPU 跑的话 640 已是极限。max_det 很多人不知道它控制单张图最多返回多少检测框遇到杂乱场景不设上限会导致前端渲染卡顿设到 20 既保留主要目标又不至于刷屏。这几个值先在 2.3 节的最小脚本里试好再写进部署配置。4.3 线程安全与并发单进程多线程还是多workerFlask 默认是单进程多线程模式app.run() 里 threadedTrue 是默认行为。Yolov5 的推理在 GPU 上是串行的多线程并发请求会排队这没问题真正要避免的是用多进程去加载多个模型副本显存直接翻倍小显存机器当场 OOM。# 生产启动方式单worker多线程模型只加载一次 gunicorn -w 1 --threads 4 -b 0.0.0.0:5000 app:app-w 1是关键它保证只有一个 worker 进程模型只占一份显存。--threads 4让四个请求可以同时打进来排队推理比单线程灵活。如果你的机器显存够大、并发确实高再考虑-w 2配合负载均衡但前提是显存能装下两份模型。还要注意锁的问题torch 的推理在多线程同时调用同一个 model 对象时个别版本会报CUDA error: device-side assert triggered稳妥做法是把 predict 方法加一个threading.Lock包住self.model(...)调用宁可排队也不崩。提示gunicorn 在 Windows 上跑不了用pip install waitress替代命令是waitress-serve --port5000 app:app配置思想一样。5. Yolov5Flask部署常见问题五条踩坑记录与排查思路这章按「现象 → 原因 → 解决」写都是实际部署中遇到或见别人反复踩的适合当排查手册用。5.1 第一个请求特别慢后面的请求都正常现象服务刚启动第一次上传图片要等十几秒之后秒回。原因torch.hub.load 在加载权重的同时会初始化 CUDA context还会触发 JIT 缓存编译这些开销全部堆在第一个推理请求上。另外sourcelocal要是写错它还会去访问线上拉取依赖时间更不可控。解决启动时先做一次预热推理用一张纯色图或全零张量跑一遍模型。在 app 模块里加一行detector.model(torch.zeros(1, 3, 640, 640))放在 gunicorn 加载模块时执行预热完成后再对外提供请求。这也顺带验证了模型能正常前向传播等于多了一道启动自检。5.2 并发一多接口就卡死或返回502现象单张图片没问题四五个人同时点检测部分请求超时gunicorn 日志出现 worker timeout 后进程重启。原因GPU 推理是串行的多线程并发时请求在排队如果用了 gunicorn 默认配置--timeout是 30 秒排队超过 30 秒的请求被判超时杀掉 worker。日志里还会看到 worker 重启后模型又重新加载雪上加霜。解决先确认单次推理耗时假设 0.5 秒4 个并发排队最坏要 2 秒把 gunicorn 的 timeout 调到 60 秒给足余量。同时把并发量控制住前端禁用重复提交按钮后端用 4.3 节的 Lock 保证同一时刻只有一个推理在跑。并发超过单卡吞吐时结果只会是全体超时所以真正的解法是排队和限流而不是无限开线程。5.3 中文文件名上传报错或检测结果异常现象Windows 服务器上上传「测试图片.jpg」时后端抛 UnicodeDecodeError或者图片没有正确读取导致检测框为 0。原因浏览器把文件名按 UTF-8 编码放进 multipart 字段而 Windows 服务端的默认编码是 GBK。代码里如果用f.filename拼路径再 open() 打开就会在路径解析时报编码错误。解决不要在服务端用原始文件名。一是直接走f.read()字节流喂给 PIL完全绕开文件路径推荐二是确实需要落盘存档用uuid.uuid4().hex .jpg重命名。检测结果里本就有坐标和置信度前端展示时用原始文件名显示即可后端不需要保留中文名。5.4 显存翻倍或模型被重复加载现象服务启动后nvidia-smi里显存占用是单个模型的两倍左右条件差点的机器直接申请显存失败。原因最常见是torch.hub.load的 source 参数写成了默认的线上加载它在本地缓存目录里又留了一份权重另一个高频原因是 gunicorn 开多 worker 且用了 --preload每个 worker 都执行一次模块加载Detector 被初始化多份。解决sourcelocal写死权重路径用绝对路径gunicorn 用-w 1不要用 --preload 加多 worker 的组合。确认加载次数可以在 Detector 的init里打一行日志启动后数一下日志就清楚了。5.5 服务器上找不到best.pt路径现象本地开发好好的weights/best.pt搬到内网服务器用 gunicorn 启动报 FileNotFoundError。原因代码里的相对路径是相对「当前工作目录」解析的gunicorn 的启动目录和 Flask 项目目录不一致时权重、模板全都会找不到。这是部署里最典型的路径玄学。解决所有资源路径都用__file__推导的绝对路径不接受相对路径。代码写在入口文件的顶部import os BASE_DIR os.path.dirname(os.path.abspath(__file__)) WEIGHTS_PATH os.path.join(BASE_DIR, weights, best.pt)只要 best.pt 和 app.py 的相对位置不变从任何目录启动都能找到不用再猜当前工作目录在哪。6. 验证效果好坏的三个技巧压测、探活与冷启动预热这套部署值不值得上得有一组数字说了算不能靠感觉。最实际的验证方法是写一个小压测脚本多线程并发打接口统计响应时间分布。import requests from concurrent.futures import ThreadPoolExecutor URL http://127.0.0.1:5000/predict files {image: (test.jpg, open(test.jpg, rb))} def one_request(_): resp requests.post(URL, filesfiles, timeout30) return resp.elapsed.total_seconds() with ThreadPoolExecutor(max_workers4) as pool: times list(pool.map(one_request, range(20))) print(mean: %.2fs, max: %.2fs % (sum(times) / len(times), max(times)))只看平均响应时间还不够重点看 max 值。如果 max 接近 timeout 上限说明并发排队把响应时间放大了这时要么降并发、要么加机器而不是去调超参数。另一个值得养成的习惯是给服务加一个/health探活接口supervisor 或容器探针定期打它确认进程活着、模型在内存里而不是等用户点了才发现挂了。app.route(/health) def health(): return jsonify({status: ok, model_ready: detector.model is not None})最后一个技巧是冷启动预热把第一小节里的预热推理放进 gunicorn 的加载流程。现在做任何模型接口我都会把「启动预热 压测基准 探活接口」固定成项目模板这套组合帮我避开了好几次发布当天才发现的低级问题算是血泪经验换来的习惯。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网