
简介这是一份结合 Yolov5 与 Flask 的实时目标检测网页应用项目面向具备一定深度学习基础、希望把模型快速部署到网页端提供检测服务的技术人员。它解决从模型加载、图片上传到检测结果返回的完整流程适合用于算法演示或业务验证。压缩包共 15 个文件大小仅 187KB包含 4 个 Python 脚本分别承担 Flask 主程序、接口服务、推理与测试同时提供容器化部署配置、前端页面与样式、依赖清单及说明文档结构紧凑、上手成本低。已有 713 人学习下载。内容覆盖预训练模型加载、检测接口设计、图像预处理与结果格式化并附带测试脚本和部署思路开发者可在其基础上扩展自定义检测场景也可进一步优化推理速度与模型体积适合作为课程设计或项目实战参考。1. 把YOLOv5塞进Flask从命令行到网页上传一张图的距离很多人训练完YOLOv5最不想面对的就是部署。notebook里python detect.py --source xx.jpg跑得飞起可一说到要给别人用、要接到业务系统上就没了头绪。这套项目把 YOLOv5 目标检测和 Flask 框架捏在一起给出一条完整的 Web 部署路径浏览器传图、后端推理、结果回显页面和接口两种方式都齐。它适合手里有训练好的权重、想把检测能力开放成HTTP接口的开发者也适合第一次做模型部署、想找一份能照着改的参考的你。2. 项目结构与推理链路restapi.py 和 app.py 到底谁在干活2.1 先读懂这套文件的职责划分解压项目后你会看到一堆文件先别急着跑花两分钟把职责捋清楚后面改代码就不会到处乱翻。核心是app.py和restapi.py前者管Web页面和路由后者是检测逻辑的API封装。templates/index.html和static/style.css是前端部分requirements.txt管依赖Dockerfile管容器化test_request.py是接口自测脚本zidane.jpg和pytorch.png是现成的测试图readme_copy.md可以当备查阅的说明副本。文件职责备注app.pyFlask主入口页面路由与上传处理python app.py启动restapi.py检测API封装返回JSON可独立启动或由app引用templates/index.html上传表单与结果展示Jinja2模板static/style.css页面样式Flask默认静态目录requirements.txtPython依赖清单torch/flask/pillowtest_request.py模拟客户端POST图片改URL即可复用Dockerfile容器构建配置注意镜像体积见避坑LICENSE开源许可文件商用前建议读一遍这里有个容易看走眼的地方app.py和restapi.py不是二选一的关系而是两种使用方式。我见过不少人把 restapi.py 里的代码直接复制进 app.py结果路由冲突。合理的设计是让app.py按需引用restapi.py里的检测函数或者干脆用 Flask 的 blueprint 把API模块挂进来。检测逻辑抽成独立函数还有个好处——以后换 FastAPI 或加异步队列模型代码一行不用动。2.2 推理链路的核心模型加载与结果解析整个服务的命脉就两段模型怎么加载、检测结果怎么变成能扔给前端的JSON。先看加载这一段。import torch model torch.hub.load(ultralytics/yolov5, yolov5s, force_reloadFalse)torch.hub.load会从GitHub拉取 ultralytics/yolov5 仓库代码再加载yolov5s权重。yolov5s 是small版本体积小、速度快、精度够用如果你有自己的训练权重把第二个参数换成你best.pt的路径即可。force_reloadFalse这个参数很关键——它决定每次启动要不要重新下载仓库和权重。设成False第二次启动直接走本地缓存离线也能起来设成True每次启动都会去检查远程更新在有防火墙或网络受限的机器上这是启动卡死的第一大原因。接着看检测与解析。摘要里给了一个简化版实际项目里process_results是少不了的不然results对象直接塞给jsonify一定会报类型错误。补全后的完整逻辑是这样from flask import Flask, request, jsonify import torch import io from PIL import Image app Flask(__name__) model torch.hub.load(ultralytics/yolov5, yolov5s, force_reloadFalse) app.route(/detect, methods[POST]) def detect(): file request.files.get(image) if file is None: return jsonify({error: no image field}), 400 img Image.open(io.BytesIO(file.read())) results model(img, size640) df results.pandas().xyxy[0] detections [] for _, row in df.iterrows(): detections.append({ class: row[name], confidence: round(float(row[confidence]), 4), bbox: [int(row[xmin]), int(row[ymin]), int(row[xmax]), int(row[ymax])] }) return jsonify({detections: detections})逐段说。request.files.get(image)取的是表单里name为image的文件字段前端input的name必须和它对上。Image.open(io.BytesIO(file.read()))是关键一步PIL可以直接从内存字节流开图避免先把文件存到磁盘再读——Web场景下每多一次磁盘IO都是在拖慢响应。results.pandas().xyxy[0]把YOLO的输出转成pandas表格行列对应的东西有xmin/ymin/xmax/ymax边界框坐标像素单位confidence置信度0到1class/name类别索引和类别名新手最容易在这翻车results.xyxy[0]返回的是Tensor包含归一化坐标直接拿来算像素框会偏pandas()返回的是原始像素坐标配合int()转成整型正好是前端画框要的格式。权重选择上再补一句。yolov5s 精度够、速度最快要更高精度可以换 yolov5m 或 yolov5l代价是推理时间和内存上升。如果你用自己的数据集训练的 best.pt加载方式不变但要注意训练时的输入尺寸——训练用了 1280×1280推理时设 640 会丢精度反过来推理设成 1280 会显著变慢。这些参数之间的取舍没有标准答案只有场景答案。2.3 前端怎么跟后端对话模板、上传与结果绑定templates/index.html做的事很朴素一个表单带上传控件提交后把图片通过POST发给后端再把检测结果渲染回页面。Jinja2模板的核心写法如下form methodpost action/detect enctypemultipart/form-data input typefile nameimage acceptimage/* required button typesubmit开始检测/button /form {% if detections %} ul {% for d in detections %} li{{ d[class] }}置信度 {{ d[confidence] }}/li {% endfor %} /ul {% endif %}enctypemultipart/form-data是上传表单的标配少了它Flask端拿不到文件流acceptimage/*只是浏览器侧的筛选提示后端不能省校验。action/detect指向后端的POST路由注意如果用app.py启动且页面路由和API路由不同form里这个action要跟着改。服务端渲染时路由里得把结果传进模板from flask import render_template app.route(/, methods[GET, POST]) def index(): detections None if request.method POST: # 同样的推理逻辑结果赋值给 detections pass return render_template(index.html, detectionsdetections)静态资源方面static/style.css要放到static/目录下HTML里用url_for(static, filenamestyle.css)引用。Flask对静态文件目录有默认约定别改乱否则样式404报得莫名其妙。3. 从0到1跑通全流程环境、启动与HTTP验证三板斧3.1 环境准备Python版本、PyTorch与依赖安装先把运行环境装明白。这套项目依赖的核心是 torch、flask、pillowrequirements.txt里写着依赖清单。装之前确认一下Python版本PyTorch对 3.8-3.10 支持最稳3.11 或更高版本建议先查一下是否有对应wheel装不上整个应用都起不来。python -m venv venv source venv/bin/activate # Windows是 venv\Scripts\activate pip install -r requirements.txt这里有个细节值得注意torch.hub.load(ultralytics/yolov5)会自动拉取yolov5仓库的代码所以requirements.txt里通常不用显式列yolov5包。但这也意味着首次运行必须有外网且能访问GitHub。如果你在离线或受限网络环境正确做法是先把 ultralytics/yolov5 仓库 clone 到本地改用sourcelocal加载详见避坑章节。Conda用户也可以用conda create -n yolov5 python3.9起环境再走pip安装。环境装好后先用一条命令确认PyTorch和CUDA的匹配python -c import torch; print(torch.__version__, torch.cuda.is_available())输出里torch.cuda.is_available()为True说明GPU可用为False就老老实实用CPU跑。CPU推理yolov5s一张640图大约2-5秒演示够用要上生产且追求实时务必上GPU。提示生产环境装完依赖后看一眼pip show torch确认装的是CPU版还是CUDA版。带cu后缀的是CUDA版没GPU的机器装它纯属浪费体积和内存。3.2 启动服务两种启动方式的适用场景项目提供两种启动入口。python app.py启动带完整页面的应用适合给人演示或自己用浏览器点一点python restapi.py启动纯接口模式只返回JSON适合给别的系统调用。两者端口默认都是8000注意别同时起。python app.py # 页面模式访问 http://localhost:8000 python restapi.py # API模式直接POST http://localhost:8000/detect启动后看到Running on http://0.0.0.0:8000说明Flask起来了。host0.0.0.0表示监听所有网卡地址容器里和局域网内都能访问如果只需要本机调试改成127.0.0.1。启动前先在项目目录下确认没有旧进程占用端口反复改代码的经历里很常见CtrlC没杀干净后台还挂着一个旧实例新实例起不来你还在怀疑自己代码写错了。调试阶段可以debugTrue改代码自动重载报错带完整堆栈。但一旦服务要被外部访问必须关掉。Flask的debug模式自带远程控制台暴露出去等于把服务器钥匙交给别人这不是危言耸听。restapi.py 纯接口模式还有一个设计取舍跨域调用时要在响应里加CORS头Flask-CORS一行代码能解决忘了加前端浏览器会直接拦掉响应报No Access-Control-Allow-Origin header。3.3 用 test_request.py 验证接口从自测脚本到确认链路启动服务后项目里的test_request.py就是你的第一个客户端。它模拟外部调用方把zidane.jpg传给服务端检测打印返回状态码和JSON。核心写法如下import requests url http://localhost:8000/detect with open(zidane.jpg, rb) as f: resp requests.post(url, files{image: f}) print(HTTP状态码:, resp.status_code) data resp.json() for d in data.get(detections, []): print(f{d[class]}: {d[confidence]}, bbox{d[bbox]})重点看files{image: f}的键名image。Flask端用request.files.get(image)取文件键名必须两边一致改了表单的name就要同步改这里否则后端收到空值直接返回400。resp.json()把响应解析成字典前端页面拿到同样结构就能渲染。如果接口返回的不是JSON而是HTML多半是路由报错被Flask当异常页面处理了。把服务端控制台的堆栈贴出来看最常见的是PIL打不开非图片文件、或模型推理抛异常这两类往下走避坑部分都有对应解法。验证通过的标准是状态码200detections数组非空包含至少一个类别名。到这一步整条「上传→推理→JSON返回」的链路就算闭环。测试脚本还建议补几个边界场景不带文件直接POST、传个文本文件、传超大图。你的联调对象不一定会按规矩来接口先把这些情况拦住省得后面扯皮。4. 避坑指南部署YOLOv5Flask我踩过的五个坑4.1 启动卡在Downloading网络受限下的模型加载现象执行python app.py后控制台停在Downloading...很久不动最后报Connection timed out或ReadTimeoutError本地明明跑通过的代码换个网络环境就起不来。原因torch.hub.load(ultralytics/yolov5, yolov5s)首次运行要从GitHub拉取yolov5仓库代码和权重。生产服务器一般在内网或没放行到GitHub的HTTPS。默认force_reloadFalse只对本地已有缓存生效缓存不存在时照样去访问网络。解决提前把仓库和权重准备好改用本地加载git clone https://github.com/ultralytics/yolov5.git /opt/yolov5model torch.hub.load(ultralytics/yolov5, yolov5s, sourcelocal, path/opt/yolov5, force_reloadFalse)sourcelocal让torch.hub完全不联网直接从指定路径读取仓库代码权重放在仓库的weights/目录下也能识别。我习惯把整套yolov5目录打进镜像或同步到服务器之后每次启动都走本地既快又稳。注意path指向的必须是包含hubconf.py的仓库根目录不是weights子目录写错了会报找不到模型定义。4.2 并发一上来就卡死Flask开发服务器的单线程宿命现象自己点着玩一切正常几个同事同时上传图片服务瞬间失去响应吞吐量一上来请求排队越来越长最后直接超时崩溃。原因Flask自带的app.run()启动的是Werkzeug开发服务器单进程单线程。代码里的推理是同步阻塞的一个请求卡在model(img)上时后面所有请求都在排队。图片大、CPU推理慢雪崩来得更快。解决低并发场景先用threadedTrue撑一下app.run(host0.0.0.0, port8000, threadedTrue)并发真上来了换gunicorn多worker才是正规解法gunicorn -w 4 -b 0.0.0.0:8000 app:app-w 4表示4个worker进程每个进程独立加载一份模型权重等于四路推理并行。代价是内存线性增长——yolov5s权重不大但PyTorch运行时每个worker要吃几百MB内存2GB内存的机器跑4个worker会吃力按实际资源减到2个。另外gunicorn只支持LinuxWindows用户要么换WSL要么先忍着头用threaded模式。4.3 上传图片报400或413请求体限制与格式陷阱现象小图测试通过换手机拍的照片直接返回413或400后端request.files.get(image)取到None请求都拦在最前面。原因常见两个来源。一是部署链路里有限制请求体大小的配置比如Nginx默认client_max_body_size是1MBFlask端如果显式配了MAX_CONTENT_LENGTH也会拦。二是前端表单漏了enctypemultipart/form-data浏览器把文件当普通表单字段提交后端拿到的不是文件流。解决Flask端显式调大限制同时前端表单补上enctypeapp.config[MAX_CONTENT_LENGTH] 16 * 1024 * 1024 # 16MBform methodpost action/detect enctypemultipart/form-data再给检测接口加一道解码保护因为有些图片其实是损坏文件或改后缀的文本PIL解不开会直接抛异常把路由带崩try: img Image.open(io.BytesIO(file.read())) img img.convert(RGB) except Exception: return jsonify({error: invalid image}), 400convert(RGB)顺手处理了带透明通道的PNG——YOLO模型输入要求三通道直接推理四通道图在某些torch版本下会报维度错误这种错最隐蔽报错信息跟图片格式八竿子打不着。4.4 Docker镜像体积爆炸一个torch毁掉整个镜像现象按常规流程写好Dockerfiledocker build完一看镜像快2GB推到仓库慢、拉下来更慢。原因pip install torch默认装的是带CUDA的完整版几个G的依赖全进镜像了。GPU机器上用没问题但如果构建的是CPU推理镜像全是冤枉体积。还有一部分体积来自.dockerignore没配好本地的venv、.git、缓存全被复制进镜像。解决CPU场景下显式指定CPU版torch并写好.dockerignorepip install torch --index-url https://download.pytorch.org/whl/cpu__pycache__/ *.pyc venv/ .git/ models/ *.pt_注意没有GPU的服务器千万别装CUDA版torch镜像体积是一个教训推理速度还一点没提升。Dockerfile里用多阶段构建把依赖层单独缓存也能显著降低重复构建成本。别小看这一步镜像从2GB压到1GB以内部署一次省不少时间。4.5 端口被占用8000不是你想用就能用现象Address already in useFlask起不来或者之前的进程还占着窗口新开终端再跑总是连到旧实例上改了代码却看不到效果。原因上次CtrlC没杀干净进程或者同机其他服务占用了8000端口。Flask只在启动时绑定端口几遍启动点下来残留进程全挂在8000上。解决先查再杀lsof -i :8000 # 看谁占了 kill -9 PID # 按PID杀掉项目里app.run(port8000)是写死的多个服务要共存时改成port8001或者用环境变量os.getenv(PORT, 8000)传入。我有一次线上排查半天最后发现是CI脚本同时拖了两个实例抢同一个端口。从那以后固定端口一律写成配置项不再硬编码在代码里。5. 进阶把demo打磨成值得长期用的检测服务能跑和能用之间还隔着几步。我每次把这种模型服务交出去之前都会强制做三件事。5.1 模型预热别让第一个请求等十几秒Flask启动后第一个请求会慢到离谱因为PyTorch在第一次推理时才做CUDA初始化、算子编译这类惰性操作。解决方法是启动前先用一张占位图跑一遍with torch.no_grad(): model(torch.zeros(1, 3, 640, 640))这段放在app.run之前服务开始监听端口时模型已经是热的。用户看到的第一个请求响应时间从十几秒掉到正常水平。torch.zeros(1, 3, 640, 640)是一个全零的占位张量形状和真实输入一致推理一次把该初始化的都初始化完然后扔进model内部被复用。注意包在torch.no_grad()里避免预热过程被记入计算图白占内存。5.2 置信度过滤接口层做好第一道筛选默认置信度阈值0.25会把很多低质量框一起返回。接口层加一个可选的conf参数results model(img, size640) df results.pandas().xyxy[0] df df[df[confidence] request.args.get(conf, 0.4, typefloat)]request.args.get(conf, 0.4, typefloat)的意思是从URL查询参数里读?conf0.5这样的值没传就用0.4。调用方对精度和召回率的要求不一样把这个决定权留给业务侧比自己闷头调阈值灵活得多。前端展示时同样过滤一次不然满屏乱框看着像模型失效。5.3 结构化错误与超时兜底所有异常统一返回JSON而不是HTML错误页app.errorhandler(Exception) def handle_error(e): return jsonify({error: str(e), detections: []}), 500调用方至少能稳定拿到一个JSON结构去解析而不是面对一堆堆叠信息。再做一层超时保护防止模型偶发卡死把整个请求挂住gunicorn配置里加--timeout 30就能让worker在30秒后主动放弃。这三步做完这个服务才勉强算得上可交付。以后再部署这类模型服务我都会强制走一遍预热、置信度过滤、结构化错误别嫌麻烦——被线上调接口的人追着骂过就懂了。希望帮到你。本文还有配套的精品资源点击获取