ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于 YOLO26 的包裹计数检测系统 | 完整源码分享

基于 YOLO26 的包裹计数检测系统 | 完整源码分享

基于 YOLO26 的包裹计数检测系统:从数据集到本地计数工作台

一、为什么"数包裹"这件事值得单独做一个系统

物流分拣线、仓储月台、快递驿站,每天都要回答同一个问题:这一批到底有多少件?

这个问题听起来简单,做起来麻烦。人工清点的成本不只是人力,还包括三件事:

第一,一致性差。同一个人上午和下午的清点结果可能不同;两个人同时点同一堆包裹,结果也常常对不上。堆叠、遮挡、反光、相似外观(统一的快递袋、同色纸箱)都会放大分歧。

第二,无法留痕。口头报数、纸质登记难以复核。一旦后续对账出现差异,只能重新清点,等于把成本付两遍。

第三,节拍不匹配。分拣线的节拍是秒级的,人工清点跟不上,只能抽检。

视觉计数是这类问题的自然解法:拍一张照,让模型把每个包裹框出来,框的个数就是件数,同时留下带框图片作为证据。相比"密度图回归"这类只给总数的方法,检测式计数有一个决定性优势——可解释。每一个数字都能对应到图上的一个框,人可以指着框说"这个漏了"“这个重复了”。在需要复核的业务里,这个特性比多零点几个百分点的指标更值钱。

本文记录的是这套系统的完整落地过程:数据集怎么组织、模型怎么训、阈值怎么定、界面为什么这么设计、指标怎么测、哪些地方仍然不可靠。

视频演示与源码下载

https://www.bilibili.com/video/BV17MnU69EYx/


包含:
📦完整项目源码
📦预训练模型权重
🗂️数据集

项目运行效果

  • *Web 界面总览

  • *单张图片识别效果

  • *批量图片合规检查


二、项目功能与检测类别

系统是一个本地运行的网页应用,核心功能如下:

模块行为
单张检测上传一张图片,自动推理;左侧原图、右侧带框结果图并排显示
自动计数直接给出该图检出包裹数、平均置信度、单张推理耗时
密度速读按检出数量分档(零散 / 中等 / 密集),供现场快速判断,属于提示而非模型指标
批量检测一次上传多张,处理完可逐张翻看,附汇总表
结果留档单张下载 PNG;批量打包「结果图 + 统计汇总.csv」
参数调节侧栏可调置信度与 NMS IoU,附通俗解释

数据集只有1 个类别:

ID名称界面显示覆盖范围
0Parcels包裹标准纸箱、气泡袋、编织袋、不规则形状的物流包装

单类别是一个刻意的取舍。分拣现场真正需要的粒度就是"件",把纸箱/编织袋/信封拆成三个类别,会让标注成本翻三倍,而计数结果完全一样;反而因为类别间外观高度相似,引入更多混淆。如果业务后续需要"按包装类型分别统计",再拆类也不迟,但那是另一个需求。


三、数据来源与标注规范

3.1 数据构成

本项目使用的数据集来自工作区提供的「包裹计数数据集」,随数据集附带的说明文件写明了它的定位:面向物流自动化与仓储管理场景的小目标检测与计数任务,重点是复杂背景下的包裹识别,覆盖重叠遮挡、尺度剧烈变化、纹理相似等难点。

实际文件清点结果:

划分图片数标签数说明
train55805580训练
valid198198训练期监控与早停
test7676最终评测,训练期完全未参与

图片与标签一一对应,没有孤立文件。标签全部为 YOLO txt 格式,每行class_id cx cy w h,坐标按图像宽高归一化到 0~1。

3.2 类别一致性核查

拿到数据集的第一件事不是训练,而是核对data.yaml与标签文件是否自洽。原始data.yaml存在两个问题:

  1. path字段指向一个不存在的相对目录database/...,且因字符编码问题显示为乱码;
  2. 划分路径只有文件夹名(train),没有写到images层级。

这两点会让 Ultralytics 在解析时把相对路径拼到全局datasets目录下,报"找不到图片"。项目里做了两件事:把data.yaml重写为可移植的相对写法,并在评测脚本中运行时生成一份绝对路径的临时 yaml。这样既保证仓库可以整体搬移,又不依赖具体机器。

重写后的配置:

train:train/imagesval:valid/imagestest:test/imagesnc:1names:['Parcels']

随后统计所有训练标签的类别 ID 分布,结果只有0,共 12428 个框,与nc=1、names=['Parcels']完全一致,没有越界 ID、没有空标签行。

这一步看着琐碎,但它能挡掉最费时间的一类故障:类别数写错时,模型不会报错,只会安静地把所有目标学成背景,训完指标全 0,你还得从头查。

3.3 标注规范里那些"看不见"的约定

数据集说明中明确了几条边界,这些边界会直接影响模型行为:

  • 只标包裹,不标传送带、设备、人手。计数任务的纯净度优先于"把画面里所有东西都标出来"。
  • 完全不可见的遮挡不标。压在底下的包裹,如果连一条边都看不到,标出来的框就是猜的,会让模型学到错误的外观。
  • 部分可见的按可见部分标。这条约定意味着同一个包裹在不同角度下框的大小不一致,是标注噪声的主要来源之一。
  • 尺度跨度极大。从近景占画面三分之一的纸箱,到远景几十像素的小件,都在同一个数据集里。这也是这类数据集难训的根本原因。

理解这些约定,才能在后面读懂混淆矩阵和漏检图:模型漏掉的那些目标,很多本来就是"标注规范允许不标"的目标,单纯看召回率会误判模型能力。


四、数据划分与泄漏风险

4.1 现有划分

数据集已经给出 train / valid / test 三段,比例为 5580 : 198 : 76。这个比例有两个明显特征:

  • 验证集和测试集都很小(198 / 76)。测试集只有 114 个标注框,一次漏检就会让召回率变动近 0.9 个百分点。
  • 验证集偏小导致早停信号噪声大。训练时patience=50,如果验证集抖动,最佳权重可能落在并不最优的 epoch 上。

我没有重新划分数据。原因是:训练结果是在原始划分下产生的,重划会让"训练曲线"与"评测数据"对不上,等于用一套新数据去解释一份旧模型。要做严谨的重新划分,必须重新训练,那是另一件事。

4.2 泄漏风险要正视

这份数据集的文件名带有明显的增强痕迹,例如:

025_jpg.rf.3ad8b39d4f175d13e30bedfcc7d8de78.jpg 26c0f42ba54f6b6a-Copy_jpg.rf.a1642402c3955ec83fbf9b7cdbc9914c.jpg

中间那段rf.<hash>是离线增强流程生成的标识,-Copy说明同一张原图存在多个副本。这带来一个真实存在的风险:

同一张原图的不同增强版本,可能同时落进 train 和 test。

如果发生这种情况,测试集会包含模型在训练时见过的内容(只是换了亮度、缩放或翻转),指标就会虚高。更隐蔽的一种是"同一段视频抽帧":相邻帧几乎一样,一帧在 train、一帧在 test,模型只要记住背景就能命中。

我能做的核查与结论:

  1. 按文件名主干(去掉_jpg.rf.*后缀)分组,检查同一原图版本是否跨划分出现;
  2. 视觉抽查若干测试图与训练图是否有完全相同的场景;
  3. 结论如实记录:本数据集已做文件名层面的重复检查,但没有做像素级去重,也没有做视频级场景去重;因此下面给出的所有指标都应理解为"在该划分下测得的结果",而不是"对全新场景的泛化能力"。

这一点在写任何指标时都必须带着,否则就是在拿一个可能被泄漏的测试集吹性能。

4.3 为什么不在 test 上调参

看到测试集只有 76 张,很自然会想"多跑几次 test,挑个好结果"。这个做法会让测试集实质上变成验证集,指标失去意义。本项目的规则是:

  • 阈值选择看valid;
  • 最终数字只跑一次 test,跑完即定稿,写进配置与文档;
  • 测试集数字与官方val的 mAP 并列记录,两套口径不混用。

五、模型与推理流程

5.1 模型

  • 架构:YOLO26n,单类别检测头
  • 训练起点:yolo26n.pt
  • 训练配置(来自train/训练结果/args.yaml):epochs=200、imgsz=640、batch=16、optimizer=auto、seed=0、deterministic=True、close_mosaic=10、amp=True、device='0'
  • 产物:best.pt与last.pt,各约 5.2 MB

选 n 规模的理由很实际:这个系统要跑在分拣工位的普通电脑上,甚至可能是没有独显的办公机。5 MB 的权重、CPU 单张三十几毫秒的推理,是可以接受的;换成 m 或 l,精度可能涨一点,但工位机器跑不动,系统就没人用。

需要说明的是,本文不展开 YOLO26 的网络结构细节。项目里能验证的事实是:这是一份 5.2 MB 的单阶段检测权重,用ultralytics8.4.x 加载,输入 640,输出框 + 类别 + 置信度。结构层面的具体模块设计不在本次工作的验证范围内,不做推测性描述。

5.2 推理链路

界面里的推理流程可以拆成六步:

上传字节流 → cv2.imdecode 解码为 BGR 矩阵(失败即提示"不是有效图片") → model.predict(image, conf, iou, imgsz=640) → 取 boxes:xyxy / conf / cls → result.plot() 绘制带框图 → cv2.imencode('.png') 编码为可下载字节

关键实现片段(与项目代码一致):

defdecode_image(data:bytes):buffer=np.frombuffer(data,dtype=np.uint8)returncv2.imdecode(buffer,cv2.IMREAD_COLOR)defrun_inference(model,image_bgr,conf,iou):start=time.perf_counter()result=model.predict(image_bgr,conf=conf,iou=iou,imgsz=640,verbose=False)[0]elapsed=time.perf_counter()-start count=int(len(result.boxes))ifresult.boxesisnotNoneelse0annotated=result.plot(line_width=2,font_size=10)return{"count":count,"annotated":annotated,"elapsed":elapsed}

三个工程细节值得单独说:

第一,用imdecode而不是 PIL 或st.image直接吃文件。上传控件给的是字节流,有些文件后缀是.jpg、内容其实是 webp 或损坏文件。imdecode失败会返回None,可以立刻给出"不是有效图片"的明确提示,而不是让异常冒到页面顶层。

第二,模型加载用缓存。st.cache_resource保证整个会话只加载一次best.pt。Streamlit 每次交互都会重跑脚本,如果没有缓存,每拖一次滑块就要重新加载 5 MB 权重,体验会崩。

第三,推理结果按「图片哈希 + 阈值」做会话级缓存。同一张图、同一组参数反复查看时不重复推理;参数一变,key 就变,自动失效。缓存上限 24 条,避免批量场景吃内存。

5.3 实测速度

在这台机器(i5-12400,纯 CPU,无独显)上:

  • 单张预处理 1.4 ms、推理 35.5 ms、后处理 0.3 ms;
  • 76 张测试集完整跑一遍(含官方 val 与逐框匹配两次前向)约十几秒。

这个速度意味着:批量处理 100 张大约 4~6 秒,在网页里配一个进度条就完全可接受。


六、阈值到底在调什么

界面上只有两个滑块,但它们的含义经常被误解。这里逐个拆开。

6.1 置信度阈值 conf

模型对每个候选框输出一个 0~1 的分数,表示"这里有目标"的把握。conf=0.25意味着低于 0.25 的框直接丢弃。

对计数任务的影响是单调的:

  • 调高 → 留下的框更少 → 计数偏小,漏检增加,但误检减少;
  • 调低 → 框更多 → 计数偏大,误检增加,漏检减少。

因为业务上"少数一件"和"多数一件"的代价往往不对称(对账时少一件要查,多一件也要查,但漏检更难发现),实际使用建议先用 0.25 跑一遍,再按偏差方向微调,不要一上来就把阈值压到 0.05 —— 那会把货架边缘、纸箱印刷图案都框出来。

6.2 NMS IoU 阈值

模型会为同一个目标输出多个重叠框。非极大值抑制(NMS)负责去重:两个框的 IoU 超过阈值时,只保留分数高的那个。

  • 阈值高(如 0.70)→ 去重更宽松 → 紧挨着、相互重叠的包裹更容易都被保留。密集堆叠场景需要它。
  • 阈值低(如 0.45)→ 去重更激进 → 同一个包裹被数成两个的概率下降,但相邻包裹可能被合并成一个,导致少计。

界面上的默认 0.70 是偏"保框"的设置,因为物流场景里包裹挨着放是常态,误合并比多框更难被发现。

6.3 还有一个隐藏阈值:评测匹配 IoU

评测时判断"这个预测框算不算命中",用的是另一套阈值:预测框与标注框 IoU ≥ 0.50 记为命中。

它和 NMS IoU 完全是两件事,混为一谈是评测文章里最常见的错误:

  • NMS IoU 决定"框之间怎么去重";
  • 匹配 IoU 决定"框和标注怎么算对上"。

本项目的口径固定为:conf=0.25、NMSIoU=0.70、匹配IoU=0.50。所有指标都必须带着这三个数才有意义。

6.4 一个容易被忽略的细节:计数与框数的关系

页面上显示的"检出包裹 N 件"就是最终框的数量,不是"去重后的人为估计"。这意味着:

  • 如果模型把纸箱上的印刷图案误检为包裹,计数会直接变大;
  • 如果两个包裹被 NMS 合并,计数会直接变小。

计数系统没有"平均一下"的余地,误差会原样体现。这也是为什么界面上必须同时给出带框结果图——人眼复核是最后一道防线。


七、界面设计取舍

7.1 为什么是 Streamlit,而不是桌面程序

需求很明确:给不懂技术的现场人员用,本机跑,能上传图、能看结果、能下载。

三个候选:

方案优点缺点
PyQt 桌面程序界面自由度高、可打包 exe开发慢、跨平台打包麻烦、部署要给每台机器装
Web 后端 + 前端框架可远程多端访问要维护前后端两套代码,对"本机单机使用"是过度设计
Streamlit纯 Python、几十行出一个像样界面、改一行即生效交互能力有限,复杂布局要写 CSS

选 Streamlit 的核心理由是成本与需求的匹配:使用者就在这台机器前,不需要账号体系、不需要并发、不需要远程部署。用 Streamlit 可以把工程量全部压在"推理与结果呈现"上。

7.2 视觉主题:深色控制台

界面刻意做成深色:

  • 现场工位灯光复杂,深色界面在强光下反光更少;
  • 结果图本身是浅色照片,深色背景能让图片成为视觉焦点,而不是和白底融为一体;
  • 主色用琥珀橙(#ff9f43),与"物流/纸箱"的行业联想一致,同时和检测框的配色形成互补。

具体做法是:主题色、背景色、文案、密度分档阈值全部写进configs/project.yaml,app/streamlit_app.py里没有一处硬编码的"包裹"字样。这套代码直接复制到"人数计数"项目,只换配置就能换主题,这是刻意的复用设计。

7.3 布局:为什么上传区在主区、参数在侧栏

常见的错误是把所有东西堆在侧栏:模式、参数、上传、结果。侧栏窄,图片塞进去只剩一小块,根本看不清框准不准。

本项目的布局原则:

  • 侧栏放"决定怎么算"的东西:检测模式、置信度、IoU、模型状态、使用步骤;
  • 主区放"要看的东西":标题、指标、上传、原图与结果对照、统计卡片、下载。

这样做的副作用是首屏偏长,但保证了"结果图足够大"这个最核心的体验点。

7.4 原图与结果图并排,而不是只给结果

这是一个刻意的取舍。只给带框结果图,看起来很"AI",但复核的人无法判断框是不是漏了——因为原图上的目标已经被框盖住了。

并排显示后,人眼可以左右扫:左边数一遍、右边数一遍,差异一眼可见。这个设计比任何指标都更能建立信任。

7.5 批量结果的翻页,而不是长列表

批量上传 50 张,最自然的做法是一张接一张往下铺。问题是很卡:50 张带框图同时渲染,浏览器内存直接吃掉几百 MB。

所以批量模式采用单张展示 + 上一张/下一张翻页,所有结果缓存在st.session_state里,翻页只是切换索引,不重新推理。汇总表放在下方,方便一次性看全。

7.6 ZIP 打包用内存而不是临时文件

批量下载要打 zip。最直觉的写法是tempfile.NamedTemporaryFile,然后在 Windows 上删临时文件——正好踩中PermissionError:文件还被 Streamlit 持有,删不掉。

项目改成纯内存:

buffer=io.BytesIO()withzipfile.ZipFile(buffer,"w",zipfile.ZIP_DEFLATED)asarchive:forrecordinrecords:archive.writestr(f"result_{stem}.png",record["png"])archive.writestr("统计汇总.csv",summary.to_csv(index=False).encode("utf-8-sig"))

utf-8-sig是为了让 Excel 直接双击打开 CSV 时不乱码,这是给非技术用户准备的小细节。

7.7 指标展示的"门槛"设计

这一条是原则性问题:界面上出现的每一个性能数字,都必须是实测的,且必须注明口径。

配置里有一个开关app.metrics.display_accuracy,配合两个硬条件——检测率与正确率同时 ≥ 85%,页面才渲染指标卡片。低于门槛时,页面完全不出现任何准确率/精度数字,也不写"效果良好""准确率高"这类模糊评价。

门槛不是装饰。它强迫作者在"想展示数字"和"数字不够好"之间做选择,而不是用模糊措辞糊弄过去。本项目的实测结果见下一节。


八、评估方法

8.1 口径定义

评测脚本scripts/eval_test.py固定了以下设定:

项取值
数据划分test(76 张,未参与训练与早停)
置信度阈值0.25
NMS IoU0.70
匹配 IoU0.50
输入尺寸640

匹配规则:预测框按置信度降序,与标注框做贪心匹配;每个标注框最多被命中一次,每个预测框最多匹配一个标注框。IoU ≥ 0.50 记为命中。

  • 检测率(Recall)= 命中标注框数 ÷ 标注框总数
  • 正确率(Precision)= 命中预测框数 ÷ 预测框总数
  • 计数平均绝对误差(MAE)= 每张图 |检出数 − 标注数| 的平均值

另外调用 Ultralytics 官方val输出 mAP50 / mAP50-95,与上面的逐框匹配口径并列记录,不互相替代。官方val的 P/R 是在最佳 F1 置信度下取的,与固定 0.25 的口径不同,所以两套数字不能混着引用。

8.2 实测结果

test 划分(76 张 / 114 个标注框):

指标数值说明
标注框总数114—
预测框总数111conf=0.25 下
命中数97IoU≥0.50
检测率85.09%97 / 114
正确率87.39%97 / 111
计数 MAE0.197每张平均偏差 0.2 件
mAP50(官方 val)87.16%最佳 F1 置信度
mAP50-95(官方 val)62.77%同上

必须同时说明的三点:

  1. 测试集很小。114 个框意味着单次漏检影响约 0.9 个百分点,85.09% 与 85% 的门槛只差 0.09 个百分点,属于"刚好过线",不应解读为"稳定达标"。
  2. mAP50-95 只有 62.77%。这说明框的定位精度还有明显提升空间:框大致框住了,但贴合度不够。对计数任务影响不大(计数只数个数),但如果业务要做体积估算、面单定位,这个数字就不能用了。
  3. 计数 MAE 0.197是在这个划分上的结果,且测试集大多只有 2~7 个目标。目标数量越多,累积误差越大,不能线性外推。

8.3 从混淆矩阵与训练曲线能看出什么

train/训练结果/里保留了完整训练产物:results.csv、results.png、PR/F1 曲线、混淆矩阵、val_batch*_pred.jpg。

最终 epoch 的训练损失为 box 0.3395、cls 0.13313、dfl 0.00655;验证损失 box 0.38297、cls 0.36325。验证分类损失明显高于训练,是典型的验证集分布与训练集不同的表现——考虑到验证集只有 198 张,且增强副本分布不均,这个差距在预期之内。

val_batch*_pred.jpg是最值得逐张看的文件:它把模型在验证批上的预测直接画出来,漏检和误检一目了然,比任何曲线都直观。

8.4 我不用哪些指标

以下指标在本项目中没有测量,因此不会出现在页面上:

  • 端到端业务准确率(需要真实业务对账数据,本项目没有);
  • 与人工清点的一致性(需要双人独立清点作为参照);
  • 不同光照/机位下的稳定性(需要受控采集实验)。

写清楚"没测什么",和写清楚"测了什么"同样重要。


九、部署细节

9.1 环境

  • Python 3.10~3.13(本项目在 3.13 上完成全部实测)
  • Windows 10/11 64 位;macOS、Linux 同理
  • CPU 即可运行;有 NVIDIA 显卡可换 CUDA 版 PyTorch 提速

依赖清单(requirements.txt)核心只有几项:ultralytics、torch/torchvision、opencv-python、streamlit、pandas、numpy、PyYAML。

9.2 实测踩到的坑

坑一:PyTorch 版本与系统运行库不匹配。
最初安装的是最新版 CPU 轮子,import torch直接抛OSError: [WinError 1114] ... c10.dll。换用更成熟的 2.7.1+cpu 后恢复正常。这类问题的排查顺序是:先确认 Python 版本受支持,再换稳定版轮子,最后才怀疑路径。

坑二:中文路径。
本项目最终放在含中文的目录下也能跑,但底层库对非 ASCII 路径的支持并不一致。教程里统一建议放到D:\projects\parcel_counting这类纯英文路径,是为了让零基础用户少踩一类无法解释的错。

坑三:data.yaml的相对路径。
Ultralytics 解析相对路径时会拼到全局datasets目录,而不是 yaml 所在目录。这导致的报错是"找不到图片",很容易误判成数据丢失。项目里用运行时生成绝对路径临时 yaml 的方式绕开。

9.3 启动

streamlit run app/streamlit_app.py--server.port8501

局域网访问需要加--server.address 0.0.0.0,并在防火墙弹窗时允许专用网络。完整步骤、常见报错、卸载重装都写在部署安装教程.md里,面向零基础读者。

9.4 边界与权限

界面只做浏览器文件上传,不调用摄像头、麦克风、本地文件系统扫描。因此不涉及摄像头权限授权,也不需要在系统设置里开权限。唯一涉及"权限"的地方是监听端口时的防火墙提示。


十、局限性

诚实列出来,比藏着好。

  1. 单类别、单场景分布。模型只认"包裹",不认托盘、笼车、周转箱。换到外观差异极大的场景(比如全黑色防水袋),需要重新采集样本。
  2. 严重堆叠会少计。压在底层完全不可见的包裹,标注规范里本就不标,模型也不可能检出来。这不是模型缺陷,是任务边界。
  3. 反光与强光会多计。覆膜纸箱的高光、地面反光可能被误检。测试集里少量样本体现了这一点。
  4. 测试集太小。76 张 / 114 个框,指标置信区间很宽。任何"提升了 2 个点"的结论都需要更大测试集才能成立。
  5. 未做跨视频/跨场景去重核查。如第四节所述,同一原图的增强副本是否跨划分,只做了文件名层面检查。指标应理解为"该划分下的结果"。
  6. mAP50-95 偏低。框贴合度一般,不适合做需要精确边界的下游任务。
  7. 没有并发与鉴权。Streamlit 会话模型不适合多用户同时使用;部署成"给全公司用"的服务需要额外改造。

十一、运维与迭代方向

按投入产出比排序,我会这么做:

第一优先:扩充并清洗测试集。现在最大的问题不是模型,而是评测样本太少、可能含泄漏。做法是:从不同日期、不同机位、不同光照采集独立样本,做像素级去重(感知哈希 + 人工复核),把测试集做到 500 张以上。这一步做完,所有指标才有讨论价值。

第二优先:针对性补数据。从误检/漏检样本里挑出典型场景(强反光、极端小目标、密集堆叠),定向补充标注。相比无脑加数据,这种"错误驱动"的补数据效率高得多。

第三优先:阈值自适应。现在 conf 是人工调。可以按画面目标密度做自适应:稀疏画面用 0.30,密集画面用 0.20,减少现场人员的调参负担。但这需要先在验证集上验证"密度—最优阈值"的关系,不能拍脑袋。

第四:模型导出与加速。scripts/export_model.py已经支持导出 ONNX 等格式。若要在工位机上进一步压延迟,可以试 ONNX Runtime;若要在边缘盒子上跑,可以考虑 TensorRT。这两条都还没在本项目实测,属于待验证方向。

第五:接入业务系统。计数结果目前靠人工下载。要真正进入流程,需要提供 HTTP 接口,把"件数 + 带框图 + 时间戳 + 工位号"写进 WMS/MES。这一步涉及系统对接与权限设计,超出本项目的范围。

第六:长期运行监控。上线后真正的风险是"数据分布漂移":换了包装、换了相机、换了灯光,模型悄悄退化而没人发现。可行做法是定期抽样人工复核,统计检出数与人工数的偏差,超过阈值就触发重训。


十二、总结

这套系统做的事情可以用一句话概括:把"这一堆有多少件"从口头估计变成可复核的图像证据。

技术栈并不复杂:单类别 YOLO26n、640 输入、两个滑块、一个 Streamlit 页面。真正花心思的地方在三处:

  • 数据层面:把data.yaml的路径与类别一致性核清楚,正视增强副本与跨划分泄漏的风险,不把 76 张测试集的结果当成泛化能力;
  • 工程层面:字节流解码的错误处理、模型与推理的缓存、批量结果的翻页与内存打包,这些决定了"能不能用";
  • 表达层面:指标只在达标且注明口径时出现,不写模糊评价,把"没测什么"也写清楚。

实测结果摆在这里:test 划分 76 张、114 个标注框,conf=0.25、NMSIoU=0.70、匹配IoU≥0.50下,检测率 85.09%、正确率 87.39%、mAP50 87.16%、计数 MAE 0.197。这个水平能支撑"辅助计数 + 人工复核"的用法,但离"无人值守自动对账"还有距离,尤其是测试集规模与 mAP50-95 两项短板。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进