首页 / 资讯中心 / 文章详情

Yolov5目标检测Flask Web部署实战:从模型到API服务

Yolov5目标检测Flask Web部署实战:从模型到API服务 ★ FEATURED ARTICLE
简介面向深度学习入门与Web开发实践者这份YOLOv5与Flask结合的目标检测Web部署资源完整演示了从模型加载、图片上传、后端推理到结果返回的整套流程。项目包含可运行的Flask服务端代码、简洁的前端交互页面、Docker部署配置以及接口测试脚本便于在本地或云服务器快速复现也为二次开发预留了清晰的扩展点。压缩包共15个文件整体约187KB主要含4个Python源码、Markdown说明文档、HTML/CSS页面、Dockerfile以及配置与示例图片等结构紧凑适合对照学习。目前已有713人学习下载可帮助开发者快速打通目标检测模型的Web化落地是理解模型服务化封装的高性价比入门案例。1. Yolov5 目标检测的 Flask Web 部署这份资源到底在解决什么问题Yolov5 目标检测模型本身并不难跑通难的从来不是那行model(img)而是把它变成别人能通过网页上传一张图、等两秒、拿到检测框的可交付物。这份资源做的就是这件事用 Flask 把 Yolov5 包成一个轻量 Web 服务既有表单上传页面也有 REST API。它适合两类人——刚把 Yolov5 训练跑通、却不知道怎么往产品里放的初学者以及需要一个内部验证工具的工程师。项目代码量不大结构清清楚楚花半小时就能复现后续换上自己的权重文件就是一套完整的「训练到上线」闭环。2. 先拆资源目录结构、推理链路与「先跑通模型再谈 Web」2.1 资源里每个文件是干什么的拿到压缩包第一件事不是跑是先把文件盘明白。这份资源的核心文件不多我按「入口 → 模板 → 测试 → 部署」四类给你理一遍。文件作用我的建议app.pyFlask 入口处理页面路由和图片上传检测表单演示就改这个restapi.py只提供 JSON 接口的独立 API 服务前后端分离时用这个templates/index.htmlJinja2 模板页面上传表单和结果展示改交互就改这里static/style.css页面样式不影响功能可后调requirements.txt依赖清单必须核对版本重点排查Dockerfile容器化部署上线时用注意系统库tests_scripts/test_request.py模拟客户端请求 /detect 的冒烟脚本改完代码先跑它test_inference.py不经过 Web 直接调模型的脚本排查问题首选README.md资源说明先读它再动手zidane.jpg自带测试图验证模型是否正常看到这你大概明白了app.py 和 restapi.py 功能是重叠的但路线不同。app.py 走的是「渲染 HTML 表单再回显结果图片」的路线restapi.py 走的是「只收 POST、只回 JSON」的路线。前者适合演示和内部工具后者适合给前端页面或小程序做后端。文件列表里的 test_inference.py 是整条链路的摸底工具我建议你任何改动之前先跑一遍它确认模型层没崩再碰 Web 层。LICENSE、.gitattributes 这类文件属于仓库配置运行时用不到直接忽略。2.2 torch.hub.load 加载 yolov5 的三种姿势无论是 app.py 还是 restapi.py核心加载代码都是同一句 torch.hub.load。这个函数看着简单但参数之间差别很大我拆开讲。import torch # 方式一从 GitHub 仓库加载官方预训练模型开箱即用 model torch.hub.load(ultralytics/yolov5, yolov5s, force_reloadFalse) # 方式二加载本地权重适合换成自己训练过的模型比如鸟类自建数据集 model torch.hub.load(ultralytics/yolov5, custom, pathweights/best.pt, force_reloadFalse) # 方式三显式指定本地仓库路径离线服务器部署必备 # model torch.hub.load(/home/user/yolov5, custom, pathweights/best.pt, sourcelocal)参数说明第一行的 ultralytics/yolov5 是 GitHub 仓库名torch.hub.load 会按需下载权重。yolov5s 是 7.3MB 左右的轻量版往上是 m、l、x 三个版本精度依次变高、耗时依次变长。第二行的 custom path 是部署自己模型的标配写法path 指向你训练出的 .pt 权重。你要做鸟类目标检测就把 path 换成自己数据集训出来的 best.pt。第三行 sourcelocal 适合服务器连不上外网的场景需要先把整个 yolov5 仓库目录传上去。逻辑说明我在实际项目里从来不写死方式一。一旦训练了自己的数据集就得改成 custom path否则加载的还是官方 COCO 80 类模型检测结果跟你的业务目标完全对不上。资源里的 restapi.py 默认用官方权重是为了开箱即用你要是上了自己的模型唯一要改的就是这一行加载代码。2.3 推理管线预处理、模型推断和后处理很多人以为model(img)就结束了但排错时你必须知道它内部走了哪几步。Yolov5 的推理流程可以拆成三段第一步 letterbox 预处理把任意尺寸图像等比缩放到 640×640空白部分补灰保证不拉伸变形第二步进 backbone neck head 做卷积推断输出三组不同尺度的预测第三步 NMS 非极大值抑制把重叠的边界框合并同时过滤掉置信度低于阈值的框。这三步里最容易出问题的是后处理参数。torch.hub 加载的模型在推断时会带默认配置conf_thres 是 0.25iou_thres 是 0.45。但如果你在自定义数据集上目标太小、遮挡太多默认阈值会让你什么都检测不出来。常见做法是在调用时直接传参# 调低置信度阈值适合小目标或遮挡场景 results model(img, size640, conf_thres0.1, iou_thres0.5) # 只看置信度高于 0.5 的结果接口返回更干净 results model(img, size640, conf_thres0.5)conf_thres 影响召回率调低会多出框但也会带来误检iou_thres 影响重叠框的合并严格程度目标密集场景要调低。这两个 yolo 超参数玩明白了比换大模型见效还快。另一个关键参数是 size640 是速度和精度的平衡点。如果你的机器是 CPU 单核跑我建议改成 320速度差不多能快一倍代价是小目标会漏一些。2.4 test_inference.py先跑通模型再碰 Web我拆这种资源的标准流程是先不碰 Flask直接用 test_inference.py 验证权重文件能不能出结果。能出结果Web 层的问题才不会和模型层混在一起。import torch from PIL import Image # 加载同一份权重保证测试环境和 Web 环境一致 model torch.hub.load(ultralytics/yolov5, yolov5s) # 读取自带测试图并推断 img Image.open(zidane.jpg) results model(img, size640) # 打印 pandas DataFrame这是排查结果最直观的方式 print(results.pandas().xyxy[0]) # 渲染带框图片并保存肉眼确认比数字更可靠 results.save(runs/detect/)逻辑说明results.pandas().xyxy[0] 会输出一张表格每条记录包含 xmin、ymin、xmax、ymax 四个坐标以及 confidence、class、name 三列。坐标是 letterbox 处理过但已经映射回原图的画框可以直接用。results.save 生成带检测框的图片看这张图比看数字更能确认模型有没有跑偏。资源里自带 zidane.jpg我一般会再塞两三张自己的图进去一张有大目标、一张全是小目标、一张什么目标都没有专门看模型在空图上会不会乱框。参数说明xyxy 是左上角和右下角坐标格式跟 xywh中心点加宽高不一样前端画框时别搞混。test_inference.py 跑通的标准是图片能保存、终端能打印出至少一条记录。如果这里就报错后面 Flask 相关的问题都不用查先把环境和权重解决掉。3. 把检测服务封装成 Flask 接口表单上传与 REST API 两条路线3.1 表单上传Jinja2 模板 app.py 的完整链路app.py 是这套资源的主入口职责是「渲染页面 处理上传 回显结果」。整条链路是浏览器 GET / 返回 index.html 表单用户选图点提交POST /detect后端解析图片、调模型、把带框图回传。下面这个版本是 app.py 里最常见的写法from flask import Flask, render_template, request, send_file import torch from PIL import Image import io app Flask(__name__) # 模型全局加载一次别放在路由里每次请求都 load model torch.hub.load(ultralytics/yolov5, yolov5s) app.route(/, methods[GET]) def index(): return render_template(index.html) app.route(/detect, methods[POST]) def detect(): file request.files.get(image) if not file: return no image, 400 # 文件流直接转 PIL 图片不用先存盘 img Image.open(file.stream) results model(img, size640) # 画框并转成 PNG 返回给前端 rendered results.render()[0] img_out Image.fromarray(rendered) buf io.BytesIO() img_out.save(buf, formatPNG) buf.seek(0) return send_file(buf, mimetypeimage/png) if __name__ __main__: app.run(host0.0.0.0, port8000)逻辑说明核心是 request.files.get(image)它拿到的是上传的文件对象file.stream 是文件流PIL 的 Image.open 可以直接读省了一步临时文件落盘。results.render() 返回带框的 RGB 数组转成 PNG 直接回给前端浏览器就能看到标注结果。这里不走 JSON 是因为表单页直接显示图片更直观适合内部工具和演示环境。参数说明app.run 的 host 改成 0.0.0.0是为了让同一局域网的其他机器也能访问默认 127.0.0.1 只有本机能打开。port8000 是监听端口如果 80 被占用就换 8080、8001 这类端口但要记得调用方和防火墙同步改。模板里 index.html 的 form 必须设置 enctypemultipart/form-data否则 Flask 拿不到文件这个坑后面会专门讲。3.2 REST APIrestapi.py 与统一 JSON 返回表单上传适合人用接口更适合程序用。restapi.py 的思路是把检测结果序列化成 JSON交给前端、小程序或者另一个服务去消费。它跟 app.py 的差别只在于路由和返回方式from flask import Flask, request, jsonify import torch from PIL import Image app Flask(__name__) model torch.hub.load(ultralytics/yolov5, yolov5s) app.route(/detect, methods[POST]) def detect(): image request.files.get(image) if image is None: return jsonify({error: missing image}), 400 img Image.open(image.stream) results model(img, size640) # 关键把 DataFrame 转成 JSON 可序列化的结构 detections results.pandas().xyxy[0].to_dict(orientrecords) return jsonify({ success: True, count: len(detections), detections: detections }) if __name__ __main__: app.run(host0.0.0.0, port8000)逻辑说明to_dict(orientrecords) 是这个接口的灵魂。结果 DataFrame 直接返回会报 TypeError因为 Flask 的 jsonify 不认识 pandas 对象转成 record 列表后每条记录就是一个独立字典前端通过下标就能读。我通常会额外加一个 count 字段方便前端在拿到空数组时快速判断这次调用有没有检出目标。参数说明接口只接受 POST因为 GET 传图片要么用 base64 要么塞 query 参数都不适合大文件。缺图片时返回 400这是 REST 接口的基本素养。真正常被人忽略的是超时设置——Yolov5s 在 CPU 上跑一张图可能要 1 到 3 秒nginx 的 proxy_read_timeout 默认 60 秒没问题但如果是短超时的内部网关就要调大否则前端会先于模型报错。3.3 前端页面index.html 到底该怎么写资源里的 templates/index.html 承担了「选图 → 提交 → 回显」三个交互。Jinja2 模板没有魔法就是一个带表单的 HTML重点在 form 标签的属性!DOCTYPE html html head meta charsetutf-8 titleYolov5 目标检测/title /head body h2上传图片进行目标检测/h2 form action/detect methodpost enctypemultipart/form-data input typefile nameimage acceptimage/* required input typesubmit value开始检测 /form /body /html逻辑说明form 的 action 指向后端路由 /detectmethod 必须是 postenctype 必须是 multipart/form-data这三样少了任何一个Flask 的 request.files 都拿不到数据。input 的 nameimage 必须和后端 request.files.get(image) 保持一致这个对应关系写错是最常见的 400 或 None 报错来源。如果走 REST API 路线前端不用提交表单而是用 fetch 上传const formData new FormData(); formData.append(image, fileInput.files[0]); fetch(/detect, { method: POST, body: formData }) .then(res res.json()) .then(data { console.log(data.count, data.detections); });参数说明FormData.append 的 key 同样要叫 image和后端对应。前端拿到 JSON 后坐标直接用 detections 里的 xmin/ymin/xmax/ymax记得这些值都是浮点画框时要么取整要么乘上图片实际显示比例不然框会偏移。static/style.css 只是控制页面布局不影响接口逻辑可以最后再调。3.4 requirements.txt版本就是「后悔药」项目里 requirements.txt 是我每次必看也必改的文件。它决定了你在哪个依赖版本上跑而这一项能直接决定你在坑里待多久。Yolov5 官方对 torch 和 torchvision 的版本绑定非常敏感torch 2.x 和 torch 1.13 上的推理结果会有细微差异opencv-python 版本不对还可能和系统库冲突。我一般会在 requirements.txt 里锁成这一组flask2.2.5 torch1.13.1 torchvision0.14.1 opencv-python4.6.0.66 pandas1.5.3 numpy1.23.5 pillow9.4.0参数说明torch 和 torchvision 的版本必须配套torch 1.13.1 对应 torchvision 0.14.1这个映射关系在 PyTorch 官网有对照表写错会直接报 torchvision 里某个 C 库找不到。numpy 锁 1.23.5 是为了兼容 Yolov5 的推理脚本新版 numpy 2.x 在部分旧代码里会报 numpy.bool 不存在。如果你用的是 Python 3.10 以下这个组合基本不会翻车。提示装完依赖先跑一遍torch.cuda.is_available()确认环境可用再继续 Flask避免后面所有错误都指向同一处。逻辑说明我第一次图省事用 pip install ultralytics 把所有依赖装到最新版结果 torch.hub 和 torchvision 算子冲突推理时直接崩掉。那之后我养成习惯先建虚拟环境按 requirements.txt 安装跑 test_inference.py 验证能跑通再动 Flask。版本问题就是后悔药锁对了能少吃很多苦。4. 部署避坑五个最常见的翻车现场4.1 torch.hub.load 卡在 Downloading启动服务等半天现象执行 python app.py 后控制台停在一段中英文混合的 Downloading 日志上几分钟都起不来网络差的时候还会直接报连接超时。原因torch.hub.load 第一次调用会去 GitHub 拉取仓库代码和权重文件网络环境不好时连接会一直挂着。即使你之前下载过如果 force_reload 被误设成 True也会强制重下。这种情况在离线服务器上尤为常见模型没下载全整个 Web 服务就瘫在启动阶段。解决把权重文件提前准备好换成 local 方式加载。比如把 yolov5s.pt 下载好放在 weights 目录再用model torch.hub.load(ultralytics/yolov5, custom, pathweights/yolov5s.pt, force_reloadFalse)。如果服务器完全连不上外网就指定sourcelocal把整个 yolov5 仓库也传上去。这样模型加载不依赖外网启动时间从几分钟降到几秒。我一般会在项目里建一个 weights 目录把常用权重和对应说明文件一起归档换机器时直接拷贝不再浪费时间重新拉取。4.2 上传中文名图片检测结果为空或者直接报错现象上传 zidane.jpg 一切正常可用户传一张「无人机航拍01.jpg」接口返回 detections 是空数组有时甚至报 UnicodeDecodeError。原因Flask 的 request.files 拿到的文件名带中文PIL 在部分实现里处理中文路径会读取失败或者图片带了 EXIF 旋转信息PIL 默认不处理导致检测框和实际画面方向不一致看起来就像没检测到目标。解决在后端入口处统一改名接收文件后丢弃原文件名import uuid from PIL import Image # 用 uuid 重命名彻底绕开中文文件名的解码问题 filename str(uuid.uuid4()) .jpg img Image.open(file.stream)逻辑说明uuid 方案一劳永逸既符合安全规范避免路径穿越之类的文件名注入风险又绕开中文解码问题。EXIF 旋转的坑是另一个层面竖拍照片的 EXIF 里记录了旋转方向但 Image.open 不会自动应用需要先用 ImageOps.exif_transpose 把图片转正再送模型否则检测框会整体偏转 90 度。4.3 并发请求一多GPU 显存被打爆现象本地单张测试没问题部署到服务器后前端一刷新就报 CUDA out of memory或者推理越来越慢请求排队几十秒。原因模型虽然全局只加载一版但每个请求进来都会跑一次前向推理如果代码里每请求都做设备搬移显存碎片会被频繁分配释放。更常见的是多人同时上传时推理队列积压显存放不下连续批次的中间特征图。解决两条路一是控制并发二是固定显存分配。控制并发最简单的方式是用线程锁把推理部分串行化import threading infer_lock threading.Lock() app.route(/detect, methods[POST]) def detect(): # 加锁让推理串行防止多请求同时吃显存 with infer_lock: results model(img, size640)参数说明加锁的代价是请求会排队但对目标检测这种 CPU/GPU 密集操作排队比崩溃强得多。想要更高吞吐把输入 size 从 640 降到 320显存占用明显下降。再往上就是模型量化把权重转成 FP16 推理显存占用直接减半精度损失在小模型上基本看不出。这些都属于工程取舍先保稳定再谈速度。4.4 前端画框偏移框的位置和物体对不上现象JSON 返回的坐标看起来正常前端用 canvas 画框但框整体偏左上角或者框比目标小一圈。原因两个最常见的来源。第一Yolov5 返回的坐标是相对原图的但前端显示图片时用了 CSS 缩放你没有按缩放比例换算坐标。第二如果你用的是 results.render() 输出的图却忘了 Yolov5 已经在渲染时还原坐标再套一层换算就会重复偏移。解决在前端统一按图片实际显示尺寸换算// imgWidth 是图片原始宽度800 是页面实际显示宽度 const scale 800 / imgWidth; ctx.strokeRect(xmin * scale, ymin * scale, (xmax - xmin) * scale, (ymax - ymin) * scale);逻辑说明先拿图片原始尺寸和页面渲染尺寸算出 scale所有坐标都乘以这个比例。用 results.render() 输出图时坐标已经还原过只需要保证渲染图宽度和画布宽度一致。我排查这种问题时的笨办法是先固定用原图 1:1 显示跑通画框再引入缩放一旦框歪了问题只能出在 scale 换算或坐标还原这两处。4.5 Docker 容器里 PIL 和 OpenCV 缺依赖一启动就报错现象本地跑得好好的docker build 完成后 docker run日志报 libGL.so.1 找不到或者 PIL 的 _imaging 模块加载失败服务起不来。原因python:3.9-slim 这类精简镜像没有安装图形库PIL 和 OpenCV 的底层依赖 libgl1、libglib2.0-0 不在镜像里。requirements.txt 只装了 Python 包系统库必须单独装。解决Dockerfile 里在装依赖前先补系统库FROM python:3.9-slim RUN apt-get update apt-get install -y libgl1 libglib2.0-0 WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . EXPOSE 8000 CMD [python, restapi.py]逻辑说明libgl1 是 OpenCV 图像读写需要的底层库libglib2.0-0 是 PIL 二进制扩展的公共依赖缺哪个都会在 import 阶段直接炸。这两行是 Docker 化部署最常见的补丁。注意 apt-get 装完要清理 /var/lib/apt/lists 缓存不然镜像体积会大不少这是我在镜像瘦身时踩出来的经验。5. 上线前再做三件事Docker 化、GPU 加速与接口冒烟验证5.1 构建镜像并跑起来把 restapi.py 作为 CMD 入口构建并启动docker build -t yolov5-flask . docker run -d -p 8000:8000 --name yolo-web yolov5-flask参数说明-d 后台运行-p 8000:8000 把容器 8000 端口映射到宿主机--name 方便后续用 docker logs 查日志。如果服务器有 GPU加 --gpus all 就能让容器里 PyTorch 用上 GPU但前提是镜像里装的是 CUDA 版 torch。5.2 GPU 加速与量化确认 PyTorch 真的用了 GPU不要只看 nvidia-smi要在代码里验证import torch print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0))is_available() 返回 False最常见原因是 PyTorch 装成了 CPU 版需要重新安装带 CUDA 的版本。确认之后可以开 FP16 推理results model(img, size640, halfTrue)halfTrue 让权重和输入转为 FP16推理速度提升明显显存占用减半。量化是更深的优化Yolov5 官方支持 INT8 导出需要校准数据集。你要是想把模型跑到 RK3588 这类边缘板子上导出 ONNX 加 INT8 是必经之路但那是另一个大工程先把 half 用起来性价比最高。提示CPU 环境下 halfTrue 没有加速效果反而可能因算子缺失报错只有 CUDA 设备上才推荐开启。5.3 用 test_request.py 做接口冒烟验证这是上线前我最依赖的一步。用 requests 模拟真实调用import requests url http://127.0.0.1:8000/detect files {image: open(zidane.jpg, rb)} resp requests.post(url, filesfiles) print(resp.status_code) print(resp.json())逻辑说明跑通后要断言三件事状态码是 200返回体里 detections 不是空count 和 detections 的长度一致。如果前两步过了但第三步不一致多半是 JSON 序列化时丢了记录回头查 to_dict 的用法。这个脚本要留好之后每次改模型或改接口先跑它能省掉大量反复刷网页的时间。从那以后我每次改这个项目都会强制走一遍固定顺序先跑 test_inference.py 验证模型层再跑 test_request.py 验证接口层最后才允许自己动前端。这个习惯帮我挡掉了不知道多少「模型没跑通却以为是 Web 写错」的排查弯路希望你也能把这套顺序内化成自己的流程。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站