本地目标检测实战:用YOLO实现“双果冻双红宝石”计数识别

发布时间:2026/8/31 2:49:27
本地目标检测实战:用YOLO实现“双果冻双红宝石”计数识别 如果你在日志里看到“检测到双果冻双红宝石”第一反应可能是某个游戏成就解锁或者某个盲盒开奖提示。但这行字放到计算机视觉项目里其实是一个目标检测系统的条件输出程序在图片里同时识别到两个“果冻”类目标和两个“红宝石”类目标于是按预定规则打印了这条结构化日志。这类需求在本地部署场景里很常见做图片素材整理的人想自动把包含特定物品的画面挑出来做视频关键帧分析的人想快速统计某一类物体出现的次数做本地自动化流程的人想把“见物计数”的判定结果暴露成一个 HTTP 接口给其他脚本调用。套路都差不多目标检测模型负责找目标后处理代码负责计数和规则判定最后再把结果输出成文本、JSON 或日志。这篇文章就以“检测到双果冻双红宝石”为示例任务完整走一遍可本地运行的目标检测方案。你会看到环境怎么准备、模型怎么部署、单张图和批量图怎么测、数量条件怎么触发、最后怎么封装成接口服务。你可以把“果冻”“红宝石”替换成自己的目标类别流程完全一样。整套方案 CPU 可跑NVIDIA GPU 会更快有显存更好没有也能先出结果。实际显存占用取决于模型版本、输入图片分辨率和批量大小文章后面会给你一套自己观察和调优的方法。1. 核心能力速览能力项说明项目类型本地目标检测与条件识别主要功能单图检测、多目标计数、批量识别、HTTP API 服务检测框架YOLO 系列以 ultralytics 为例支持平台Windows / Linux 均可硬件要求CPU 可推理NVIDIA GPU 可加速显存占用取决于模型版本、输入尺寸和批量大小需按实际环境测试启动方式Python 脚本运行可选 Web API 服务API 能力POST 图片返回 JSON 检测结果批量任务支持目录批量处理适合场景图片素材打标、自定义目标筛选、物体数量统计、接口集成学习从能力表可以看出这个方案的核心不是做一个全新算法而是把目标检测模型、计数逻辑、批量处理和接口服务串起来。模型可以选择 YOLOv8n 这样的小模型快速验证流程也可以换成更大的模型提高精度。2. 适用场景与使用边界这个方案适合几类人。第一类是本地图片管理需求较多的开发者图片数量大了之后靠人工翻找特定目标非常低效让程序先筛一遍可以省下大量时间。第二类是正在学习目标检测的开发者通过一个完整闭环理解“模型推理 → 结果解析 → 业务规则触发”的链路。第三类是需要在本地小规模提供检测服务的团队先把功能跑通再考虑要不要迁移到服务器。不适合的场景要先说清楚。高并发在线识别不适合直接用这套简单方案需要加队列、负载均衡和 GPU 资源池。对延迟要求极高的实时识别场景需要优化推理线程和输入流水线。另外如果目标类别变化频繁需要持续维护训练数据不是简单换一个模型路径就能解决。使用边界要重点强调几个安全合规问题。目标是物品时如果素材来自互联网要确认图片版权和用途授权如果目标是人物面部或声音相关必须获得当事人明确授权不能用于未经允许的监控、追踪或伪装。不要把这套技术用于任何自动操作游戏、绕过平台安全机制或批量抓取他人数据的场景。技术本身是中性的但使用方式必须合法、有授权、可审计。本文所有检测目标仅作为技术演示实际项目请自行替换为有授权来源的数据。3. 环境准备与前置条件在开始之前先确认本机环境。推荐使用 Python 3.10 或更高版本安装虚拟环境工具避免依赖冲突。下面是完整的检查清单检查项推荐配置操作系统Windows 10/11、Ubuntu 20.04/22.04Python3.10 及以上包管理pip建议配 venvGPU 加速可选NVIDIA 显卡 CUDA 环境磁盘空间预留 5GB 以上模型文件和数据分开存放端口8000 或自定义启动前检查是否被占用创建虚拟环境并安装依赖python -m venv venv # Windows venv\Scripts\activate # Linux source venv/bin/activate pip install -U pip pip install ultralytics flaskultralytics会自动拉取 PyTorch 相关依赖。如果你有 NVIDIA GPU建议根据 PyTorch 官网选择对应 CUDA 版本的安装命令如果没有 GPUCPU 版本的 PyTorch 也能完成全部测试只是速度慢一些。安装完成后可以快速验证python -c from ultralytics import YOLO; print(ultralytics ok)项目目录建议按下面的方式整理后面所有脚本都会基于这个结构detect-project/ ├── inputs/ # 待检测图片 ├── outputs/ # 检测结果 ├── models/ # 模型文件 ├── uploads/ # API 上传文件临时目录 ├── detect_single.py # 单图检测脚本 ├── detect_batch.py # 批量检测脚本 ├── app.py # API 服务 └── condition_check.py # 数量判定脚本4. 目标检测模型部署与启动4.1 模型说明YOLO 系列默认的预训练模型是在 COCO 数据集上训练的能识别 person、dog、cat 等 80 类常见目标。但默认模型里没有“jelly”和“ruby”这两个类别所以第一步要分清两种用法流程验证直接用yolov8n.pt可以跑通图片检测、批量处理、API 调用但检测出的类别是 COCO 类别。自定义目标识别准备“果冻”“红宝石”等自定义类别的标注数据用 YOLO 做一次微调训练生成自己的模型文件。先做流程验证再考虑训练自定义模型这是最稳的路径。4.2 加载预训练模型首次运行会自动下载模型文件需要保持网络通畅。下载完成后模型会缓存在本地后续不需要重复下载。from ultralytics import YOLO # 加载 YOLOv8n 小模型适合 CPU 和低显存环境 model YOLO(yolov8n.pt) # 查看模型支持哪些类别 print(model.names)如果你训练好了自定义模型只需要把路径替换掉model YOLO(models/my_custom.pt)4.3 自定义类别训练说明如果要做真实的“双果冻双红宝石”识别需要先收集包含这两个目标类别的图片建议每类至少 100 张以上。然后使用标注工具给目标画框并打上类别标签标注完成后会得到 YOLO 格式的 txt 标注文件。数据集目录可以这样准备datasets/ ├── images/ │ ├── train/ │ └── val/ └── labels/ ├── train/ └── val/然后写一个数据集配置文件例如custom.yamlpath: datasets train: images/train val: images/val names: 0: jelly 1: ruby执行训练yolo detect train datacustom.yaml modelyolov8n.pt epochs100 imgsz640训练结束后模型会保存在runs/detect/train/weights/best.pt后面所有脚本都在这个模型上测试。这里不展开调参细节只需要知道训练流程是完整可跑的。5. 功能测试与效果验证5.1 单张图片检测写一个单图检测脚本输入图片路径输出检测结果和可视化图片。这个步骤用来确认模型能正常加载、推理、保存结果。from ultralytics import YOLO model YOLO(models/best.pt) # 自定义模型也可换成 yolov8n.pt image_path inputs/test.jpg results model(image_path, conf0.35) # 保存带框的输出图 for i, result in enumerate(results): result.save(foutputs/result_{i}.jpg) # 打印检测到的目标信息 for result in results: for box in result.boxes: cls_id int(box.cls[0]) conf float(box.conf[0]) xyxy [round(float(x), 2) for x in box.xyxy[0]] print(f类别: {model.names[cls_id]}, 置信度: {conf:.3f}, 框: {xyxy})运行后观察两点一是图片是否能在outputs目录生成二是控制台是否打印出目标信息。如果模型在图片里什么都没框出来可以先降低置信度阈值比如把conf0.35改成conf0.15排查是不是阈值太高。判断成功标准输出图片存在并且检测框与真实目标位置基本吻合。5.2 数量判定与“双果冻双红宝石”触发这是整个方案里最接近标题需求的部分。检测结果不能只看一张图片里有没有目标还要统计每个类别的数量并在满足条件时输出提示。下面用一个脚本实现目标计数和条件判定。from ultralytics import YOLO model YOLO(models/best.pt) def count_targets(image_path): results model(image_path, conf0.35) jelly_count 0 ruby_count 0 for result in results: for box in result.boxes: cls_id int(box.cls[0]) label model.names[cls_id] if label jelly: jelly_count 1 elif label ruby: ruby_count 1 return jelly_count, ruby_count if __name__ __main__: image_path inputs/test.jpg jelly_count, ruby_count count_targets(image_path) if jelly_count 2 and ruby_count 2: print(检测到双果冻双红宝石) else: print(f当前数量果冻 {jelly_count} 个红宝石 {ruby_count} 个)这个脚本的价值在于把“检测”和“业务规则”解耦。模型只负责给出每个目标的类别和位置后处理代码负责统计和条件判断。以后要改成“检测到三果冻一红宝石”只需要改最后的计数判断逻辑。预期结果如果测试图片确实包含两个果冻和两个红宝石控制台打印“检测到双果冻双红宝石”否则打印实际数量。判断成功的标准是数量统计结果与人工观察一致并且条件触发逻辑准确。常见失败原因类别名不匹配。YOLO 训练数据的类别名叫什么脚本里的label就要对应什么。如果训练时类别叫jelly脚本里写成jelly_bean就会导致计数永远为 0校验这一点最快的方法是打印label值。5.3 批量检测目录图片单张测试通过后可以批量处理一个目录下所有图片。批量脚本会读入inputs下的所有图片逐张检测并保存编号结果。from pathlib import Path from ultralytics import YOLO model YOLO(models/best.pt) input_dir Path(inputs) output_dir Path(outputs) output_dir.mkdir(exist_okTrue) image_suffix {.jpg, .jpeg, .png, .bmp} images [p for p in input_dir.iterdir() if p.suffix.lower() in image_suffix] print(f找到 {len(images)} 张图片开始批量检测...) for idx, img_path in enumerate(images): try: results model(str(img_path), conf0.35) save_path output_dir / f{img_path.stem}_result.jpg results[0].save(str(save_path)) print(f[{idx 1}/{len(images)}] 完成: {img_path.name}) except Exception as e: print(f[{idx 1}/{len(images)}] 失败: {img_path.name}, 错误: {e})批量检测最大的问题是单张异常可能中断整个流程所以脚本里用了try except单张失败不会影响后面的图片。这里还需要加入“计数器”因为我们最终要做条件判定。可以在批量循环里复用count_targets的逻辑把所有图片的统计结果汇总到result_summary.jsonimport json summary [] for img_path in images: jelly_count, ruby_count count_targets(str(img_path)) summary.append({ image: img_path.name, jelly_count: jelly_count, ruby_count: ruby_count, trigger: jelly_count 2 and ruby_count 2 }) with open(outputs/result_summary.json, w, encodingutf-8) as f: json.dump(summary, f, ensure_asciiFalse, indent2)批量任务的判断标准所有图片都处理完成JSON 文件里的数量和触发标记与人工抽查结果一致。6. 接口 API 调用示例如果想把检测能力交给其他程序调用可以封装成一个 HTTP 接口。这里用 Flask 写一个轻量服务接收上传图片返回检测结果 JSON。from flask import Flask, request, jsonify from ultralytics import YOLO import os app Flask(__name__) model YOLO(models/best.pt) upload_dir uploads os.makedirs(upload_dir, exist_okTrue) app.route(/detect, methods[POST]) def detect(): f request.files.get(image) if not f: return jsonify({error: no image}), 400 ext os.path.splitext(f.filename)[1] or .jpg save_path os.path.join(upload_dir, ftemp_{next(os.walk(upload_dir))[2].__len__()}{ext}) f.save(save_path) results model(save_path, conf0.35) detections [] jelly_count 0 ruby_count 0 for result in results: for box in result.boxes: label model.names[int(box.cls[0])] conf float(box.conf[0]) bbox [round(float(x), 2) for x in box.xyxy[0]] detections.append({ label: label, confidence: conf, bbox: bbox }) if label jelly: jelly_count 1 elif label ruby: ruby_count 1 return jsonify({ detections: detections, count: len(detections), jelly_count: jelly_count, ruby_count: ruby_count, trigger: jelly_count 2 and ruby_count 2 }) if __name__ __main__: app.run(host127.0.0.1, port8000)启动服务python app.py启动后可以用 curl 测试接口。如果图片路径或文件上传字段名不一致需要改成实际的字段名。curl -X POST http://127.0.0.1:8000/detect \ -F imageinputs/test.jpg返回结果类似下面这样{ detections: [ { label: jelly, confidence: 0.86, bbox: [120.5, 45.2, 230.8, 178.3] }, { label: ruby, confidence: 0.92, bbox: [310.2, 80.1, 395.6, 190.9] } ], count: 2, jelly_count: 2, ruby_count: 2, trigger: true }Python 调用示例import requests url http://127.0.0.1:8000/detect files {image: open(inputs/test.jpg, rb)} response requests.post(url, filesfiles, timeout60) print(response.status_code) print(response.json())接口设计上建议把“检测结果”和“业务判定结果”分开返回。detections是模型的原始输出jelly_count、ruby_count、trigger是后处理结果。这样调用方既可以用原始数据也可以直接使用判定结果。接口服务如果需要给别人用建议启动时绑定内网 IP 或 127.0.0.1不要直接暴露公网如需公网访问要加身份校验和访问频率限制。7. 资源占用与性能观察这部分是本地部署最容易忽略的。模型能不能跑是一回事跑得快不快、内存占用高不高是另一回事。不同模型版本、输入尺寸和 batch size 会有明显差异。查看 GPU 显存占用可以在另一个终端运行nvidia-smi -l 1如果看到nvidia-smi命令不存在说明没有安装 NVIDIA 驱动或者没有 GPU只能使用 CPU 推理。CPU 推理时观察资源占用可以打开任务管理器或系统监视器重点看 CPU 使用率和内存占用。影响性能的主要因素有三个模型版本。YOLOv8n 是最小的模型速度最快精度相对低一些YOLOv8x 精度高但速度慢很多。低配环境优先用 n 或 s 版本。输入图片尺寸。默认 imgsz 是 640图片尺寸越大计算量越大。批量处理大量图片时可以用imgsz640先跑通再根据实际效果调整。批量图片的并发处理方式。上面的脚本是逐张循环简单稳定但 GPU 利用率不高。更高效的方式是使用 YOLO 自带的 batch 预测接口传入一个图片列表模型内部做 batch 推理。不过批量推理显存占用会上升显存紧张时不适合一次传太多。降低资源占用的方法使用小模型、压缩输入尺寸、降低并发 batch、把图片先缩放到合适尺寸再检测。如果出现显存不足优先把imgsz从 640 降到 480或者从 320 开始测试。如果启动 API 服务后端口被占用会弹出类似Address already in use的报错。更换端口可以直接修改app.run里的port参数或者先查找并结束占用进程。Windows 下用netstat -ano | findstr 8000查找进程号Linux 下用lsof -i:8000。8. 常见问题与排查方法问题现象可能原因排查方式解决方案安装依赖失败Python 版本不匹配或网络不稳定查看 pip 错误信息升级 Python 到 3.10使用国内镜像源创建新虚拟环境首次运行下载模型很慢模型文件较大网络带宽受限观察日志中的下载进度手动下载模型文件放到models目录修改代码路径检测结果为空置信度阈值太高降低 conf 阈值测试将conf0.35改为conf0.15数量统计始终为 0类别名不匹配打印 label 值核对训练数据类别名和脚本中的 label 字符串CUDA 不可用驱动版本或 PyTorch 版本不对运行python -c import torch; print(torch.cuda.is_available())按 PyTorch 官网安装匹配 CUDA 版本或退回 CPU 推理显存不足输入尺寸过大或 batch 太大查看 nvidia-smi 显存使用降低 imgsz、使用小模型、减少 batchAPI 端口被占用已有服务占用 8000 端口查看端口占用修改app.run的 port或结束占用进程批量任务中途卡住单张图片异常或资源不足查看输出日志卡在哪张图增加 try except 和超时控制先移除异常图片输出质量不稳定模型训练数据不够或类别分布不均衡对比多张图片的检测结果扩充训练数据增加训练轮数调整置信度阈值批量任务最容易出现的问题是单张异常导致整个队列中断。建议在任何批量脚本中保留日志输出和异常捕获每处理完一张图片就写一行状态记录这样即使中途挂掉也能从日志里定位到具体是哪张图出的问题。9. 最佳实践与使用建议第一第一次测试不要直接上大规模批量任务。先用两三张图片跑通流程确认类别名、输出目录、模型路径都没问题再放开到全部图片。这样可以避免批量任务跑了一半才发现类别不匹配浪费时间。第二文件目录要分开管理。inputs、outputs、models、uploads四个目录从第一天就分开模型文件、待检测数据、检测结果不要混在一起。批量检测后把result_summary.json和可视化输出结果放在同一个日期子目录下方便追溯。第三批量任务加日志和失败重试。简单的try except只能保证单张失败不中断但不会自动重试。可以做一个简单的重试机制比如对失败的图片最多重试两次如果仍然失败就写入单独的failed.txt最后人工检查。第四API 服务要注意访问边界。接口服务默认只监听127.0.0.1这意味着只能本机访问。如果确实需要局域网其他机器访问再把host改成0.0.0.0但一定要加访问控制比如简单的 Token 校验避免服务暴露后被滥用。第五涉及人脸、声音、版权素材时必须确认授权。本文示例的“果冻”“红宝石”是普通物品没有隐私问题但如果检测目标换成人物面部、特定品牌标识、有版权限制的画面你必须确保数据来源合法、用途合规发布和使用前要有人工复核。10. 总结与下一步这个方案最有价值的部分不是某一个检测模型而是把“目标检测 → 数量统计 → 条件触发 → 接口服务”这条链路串起来了。从单张图片到批量目录再到 HTTP API每一步都能单独测试也能组合使用。最值得先验证的功能是单图检测和数量统计先把这两个跑通再考虑批量任务和 API 封装。最容易踩的坑有两个。一是类别名不匹配导致计数永远为 0调试时先打印 label 确认二是默认模型没有自定义类别概念直接用yolov8n.pt检测“果冻”和“红宝石”必然拿不到结果必须走训练流程。后续可以扩展的方向很多。可以把检测结果接一个通知机器人当触发“双果冻双红宝石”条件时自动推送消息可以把批量检测接入定时任务每天自动扫描指定文件夹可以为每个目标生成裁剪图方便做进一步的分类或检索。也可以把这套代码封装成 Docker 镜像迁移到服务器上运行但这时的显存占用和并发能力就需要重新测试。如果你只是临时处理几百张图片这个方案已经够用。建议先保存一份最小可运行配置一张测试图、一个自定义模型、一个批量检测脚本、一个 API 脚本后续新项目直接改类别名和模型路径几分钟就能跑起来。