基于YOLOv12的硬币识别检测系统:从数据集构建到桌面应用部署实践
发布时间:2026/10/22 14:34:11 锦皓数字建站

清点整理一大堆零钱的时候我突然冒出一个想法能不能直接丢一张硬币照片进去让程序自己告诉我这里面有几枚25美分、几枚10美分、总额是多少这个念头后来逐渐落地成了一整套基于YOLOv12深度学习的美国硬币识别检测系统包括Python项目源码、训练好的模型权重、带UI界面和登录注册功能的完整桌面应用。这篇文章就是我整个开发过程的真实记录从数据集怎么凑、模型为什么选YOLOv12、训练时踩了哪些坑到界面怎么集成、部署阶段如何应对反光和遮挡一步一步说清楚。如果你也想用深度学习做一个实物识别项目不满足于跑通官方Demo而是想把它做成一个能给别人演示甚至试用的产品那这篇应该能帮你少走不少弯路。1. 数据关硬币识别系统最难的不是模型是数据1.1 先摸清家底美国硬币的面额体系与识别难点做任何检测项目的第一步都不是急着训练而是把检测对象彻底搞清楚。美国硬币常规流通的主要有六种面额1美分、5美分、10美分、25美分、50美分和1美元。表面看起来就六类但实际做起来会发现不少麻烦。先看一组公开的物理参数面额直径mm边缘颜色视觉特征正面主图案识别难度1美分Penny19.05铜红色辨识度高林肯头像低5美分Nickel21.21银灰色偏暗杰斐逊头像中10美分Dime17.91银白色尺寸最小罗斯福头像中25美分Quarter24.26银白色边缘有齿纹华盛顿头像中50美分Half Dollar30.61银白色体积大肯尼迪头像低1美元Dollar26.50金色/银色皆有萨卡加维亚或总统像中高这里有几个容易翻车的点。10美分和1美分的直径只差1.14毫米单靠尺寸非常容易混淆但好在颜色差异大一个银白一个铜红。5美分和25美分直径相差3毫米多但5美分因为材质原因颜色偏黄暗25美分则更亮白如果打了强光两者视觉上很容易接近。更麻烦的是流通硬币普遍有磨损边缘齿纹不清晰、图案细节丢失这些都会直接影响模型的特征提取。还有一个容易忽略的现实问题50美分和1美元硬币在国内兑换渠道里并不常见真实样本采集难度大后续做数据集时必然会遇到类别不均衡。这些都决定了我们不能把项目当成一个简单的六分类问题来处理。1.2 三种数据获取方式公开数据集、实拍补充、合成增强确定目标后我开始搭数据集。经验之谈是永远不要指望一口气找到一个完美的现成数据集公开数据打底、自己实拍补强、再用合成数据调优这才是最靠谱的组合。最初我先从公开的数据集托管平台上下载了一批硬币图片大概有几千张涵盖了六种面额在不同背景下的照片。这一步的好处是省时间但问题也很明显很多公开数据集的拍摄场景过于理想背景干净、光照均匀、硬币摆放整齐跟实际使用场景脱节。如果你训练的模型只在这样的数据上验证效果一定会虚高。第二步是实拍补充。我自己找了一批不同品相的硬币包括大量带有明显划痕、氧化、变色甚至部分图案磨损的旧币然后换着场景拍木质桌面、深色绒布、白色A4纸、甚至直接放在键盘上。光照也做了变化有室内LED灯、窗边自然光、以及昏暗环境下的补光。这一步补了大概1000多张图片直接决定了模型的泛化能力。第三步是合成增强。我写了一个简单的脚本将单枚硬币从原图中抠出来随机旋转、缩放、摆放到各种纯色或纹理背景上再叠加高斯噪声、亮度扰动和轻微模糊。这样既扩充了样本量也模拟了硬币散落、相互靠近的真实场景。合成数据在项目初期的价值很大能快速验证标注有没有问题但我必须提醒一句合成数据和真实图像的分布始终有差异比例不要超过训练集的30%否则会拉低真实场景下的表现。1.3 标注规范边界框怎么框才不会坑到模型数据凑齐后是标注环节。我用常见的图像标注工具画边界框原则只有一条边界框要贴着硬币的外缘但不要包含明显的阴影区域。硬币是圆形物体边界框天然会带上四个角的背景区域这没关系YOLO系列模型本身就能适应这种情况。真正需要注意的是硬币边缘和背景对比度低时容易把框画大一圈或者反过来把边齿切掉一小块这两种情况都会让模型学到错误的边缘特征。标注类别时我直接用面额英文名加分类编号比如penny_1c、nickel_5c、dime_10c、quarter_25c、half_dollar_50c、dollar_1尽量不用纯数字因为模型输出的类别ID记起来容易乱。遇到多枚硬币叠在一起的情况我的处理方式是对于遮挡超过三分之一的硬币直接跳过不标对于轻微重叠的两枚硬币各自画框让模型自己去学习可见部分的特征。这个策略在前期特别管用模型不会因为大量遮挡样本而变得过于激进。标注完一定要做一次全量抽查重点看两类问题一是类别标错比如把5美分标成25美分二是框偏移框中心偏了半个硬币半径。这些错误哪怕只占1%~2%也会在训练时产生大量噪声导致个别类别的AP值莫名上不去。2. 选型逻辑为什么我最终锁定了YOLOv122.1 从YOLOv5到YOLOv12版本交替背后的关键变化目前的YOLO系列经过多次迭代早已不是当年那个只有检测头的简单结构。YOLOv5的成功在于把工程易用性做到了极致YOLOv8引入了Anchor-Free和C2f模块训练和部署变得更加灵活YOLOv9和YOLOv10又在可逆架构和免NMS推理上做了探索。到了YOLOv12比较大的变化是引入了区域注意力机制开始尝试替换结构中一部分传统的卷积模块这对小目标和类间相似目标的特征提取是有增益的。硬币检测恰好就是典型的类间相似、小目标密集场景6种面额之间形状高度接近区别主要藏在颜色、纹路和微小的尺寸差里。YOLOv12的注意力结构能在特征提取阶段更关注这些细节区域而不是只盯着整体轮廓这也是我选择它的核心原因。当然版本越新不等于效果一定越好这里有个很现实的前提你的数据集质量和数量要配得上模型的能力。如果你的样本本身就模糊不清再强的注意力机制也救不回来。我的判断是这个项目的数据规模约5000~8000张和类别难度用YOLOv12是完全合理的尤其是需要部署成桌面应用时新版本在训练和推理接口上也比老版本更顺手。2.2 为什么不选传统视觉方案硬币看似简单实则难搞有朋友问过我硬币不是圆形的吗直接用边缘检测加霍夫变换找圆再做模板匹配不就行了这个思路理论上可行但实际操作中会被三个问题打得很痛。一是背景干扰。真实的硬币照片背景往往是凌乱的桌面边缘检测会把笔、本子、指纹、木纹全部当成边界霍夫变换找出来的圆里混杂大量假阳性。二是尺度和旋转。同一枚硬币拍摄距离不同会导致直径从100像素到400像素变化模板匹配需要多尺度多角度穷举计算量爆炸而且一旦硬币磨损导致图案变化模板就失效。三是颜色和纹理信息的浪费。1美分和10美分尺寸很接近但颜色完全不一样传统几何方法只盯着形状等于把最重要的判别信息丢了。深度学习方案的好处是把用什么特征来区分这件事交给网络自己学习。模型会同时利用颜色、纹理、边缘、尺寸关系等多维信息对光照变化和背景变化的鲁棒性远高于手工特征。所以在实际项目里YOLO这类目标检测模型几乎成了标准答案。2.3 模型规模与部署场景的匹配YOLOv12提供了不同规模的选择从轻量版到高精度版依次是n、s、m、l、x。很多初学者一上来就选最大的模型理由是精度肯定更高但在硬币检测这个任务上实验数据并不完全支持这个直觉。我最初在训练时对比了s和m两个规模在单卡环境、batch size为16、训练300轮的情况下s模型的mAP50能到95.8%m模型是97.2%精度差距不到1.5个百分点但m模型的参数量大约是s模型的两倍多推理速度下降了接近30%。考虑到这个系统最终要跑在带界面的桌面应用里用户大概率用的还是普通办公电脑甚至笔记本选一个推理速度更友好的模型规模更现实。所以最终方案是如果只做单张图片上传检测训练m模型做离线分析没问题如果要接摄像头实时视频流就换s模型。一套数据训练出两个档位的模型通过切换权重文件来适配不同场景这个思路比死磕单个模型实用得多。3. 训练实录三次失败后我把loss曲线彻底看懂了3.1 环境准备与数据划分版本对齐是第一优先级开始训练前第一步是确认环境。我的组合是Python 3.10、PyTorch 2.1.0、CUDA 11.8配合YOLOv12对应的官方Python包。这里要特别强调不要小看版本对齐这个问题很多人翻车都是因为PyTorch和CUDA版本不匹配导致模型在GPU上卡死或者报莫名其妙的显存错误。数据目录结构我参照官方约定的格式来组织# coin_dataset.yaml path: ./datasets/coins train: images/train val: images/val nc: 6 names: [penny_1c, nickel_5c, dime_10c, quarter_25c, half_dollar_50c, dollar_1]数据集划分上训练集占70%验证集占20%测试集占10%。有一个细节值得专门说明划分时要按拍摄场景而不是按图片张数来切。如果同一块桌面上连续拍的几十张图全部进了训练集验证集里只有另一个场景的图片模型在验证集上的指标就会比较难看。我按场景分组后先随机选场景再在场景内分配图片这样能最大程度模拟真实使用时的分布变化。3.2 参数调试记录从默认参数到针对性调整我第一次训练直接用了默认参数结果mAP50虽然上了90%但mAP50-95只有70%出头而且验证集上25美分和5美分的混淆比较明显。做了几轮针对性调整后最终参数如下参数第一次默认最终调整调整原因img_size640640硬币目标较小保持原尺寸batch1616受显存限制epochs300400后期AP仍在缓慢上升lr00.010.005降低初始学习率减少震荡mosaic1.00.5大量Mosaic增强会让硬币形变过度hsv_h0.0150.02略微提升颜色扰动增强对光照变化适应性fliplr0.50.5保持默认其中Mosaic增强的调整是我反复对比后发现的。硬币是刚性物体过强的Mosaic拼接会在同一张图里产生大量不符合真实比例的混叠区域模型容易学到背景混乱的特征。降到0.5后再配合0.2的MixUp增强验证集上的稳定性和精度反而都有提升。3.3 指标解读mAP50只是及格线mAP50-95才是真相训练过程中我会盯着四条曲线看训练损失、验证损失、mAP50和mAP50-95。很多人只看mAP50到95%就觉得自己模型很好了实际上mAP50是允许预测框和真实框有50%重叠的宽松指标根本反映不出定位精度。在硬币检测里你要的不只是认出面额还要框得准因为后续金额统计和计数都依赖框的坐标。mAP50-95是把IoU阈值从0.5到0.95每隔0.05取一个档次的平均值它更严苛也更接近真实应用的评价标准。我的模型最终mAP50-95稳定在82%左右对比第一轮的70%已经有了明显提升。为了加速收敛我在300轮位置手动降低了学习率后面的100轮让损失曲线基本走平然后依靠patience50的早停机制防止过拟合。训练结束后我导出了一份混淆矩阵发现容易出错的正是5美分和25美分、10美分和1美分这两对。25美分的直径虽然比5美分大3毫米但在某些透视角度下这3毫米的差距在图像中很小1美分和10美分则主要靠颜色区分一旦光线偏黄银色10美分也可能被染成暖色调。这个结果直接指导了下一步的数据增强策略——我在训练数据中加重了白平衡抖动的比例。4. 界面落地登录注册和检测UI是怎么串起来的4.1 技术栈选型PySide6与Tkinter的取舍模型训练好了接下来就是把它包成一个像样的桌面工具。Python做界面的选择无非是Tkinter、PySide6/PyQt5、Kivy这几个方向。Tkinter胜在轻量、环境自带但控件样式老旧做一个带图片缩放、表格展示、状态栏的产品化界面需要大量额外工作量。PySide6是Qt的官方Python绑定控件成熟、样式现代支持QSS皮肤定制还内置了线程信号机制很适合用来承接模型推理这种耗时操作。我最终选了PySide6。理由是它能在UI线程里保持流畅交互的同时通过信号槽把模型推理放到后台线程避免界面卡死。这点在做检测功能时特别关键否则用户点一下检测按钮就要等几秒体验非常差。4.2 登录注册模块实现密码加密与用户会话界面第一步从登录注册开始。这个模块看起来简单但交互逻辑和数据处理有讲究。用户注册时前后端直接接触的是用户名和密码密码绝对不可以明文保存到数据库。我在项目里用了一套加盐哈希的方案import hashlib import os def hash_password(password: str) - str: salt os.urandom(16).hex() hashed hashlib.sha256((salt password).encode()).hexdigest() return f{salt}${hashed} def verify_password(password: str, stored: str) - bool: salt, hashed stored.split($) return hashlib.sha256((salt password).encode()).hexdigest() hashed用户数据存储用SQLite就足够无需引入重型的数据库服务。注册逻辑里我还加了两次密码输入一致性校验、用户名查重、密码长度至少六位且必须包含字母和数字这三道检查。登录成功后生成一个会话标记记录当前用户ID和登录时间这样检测记录表格里就能存下哪个用户在哪次登录后检测了哪些图片。4.3 检测主界面图片上传、实时推理与金额汇总主界面布局分三个区域左侧是图片预览区右侧是检测结果显示区底部是统计工具栏。用户点击选择图片按钮后用QFileDialog打开文件选择框预览原图后点击开始检测程序调用训练好的模型进行推理。核心调用逻辑是这样的import cv2 from ultralytics import YOLO model YOLO(best.pt) def detect_coins(image_path: str): img cv2.imread(image_path) results model(img, conf0.4) detections [] for box in results[0].boxes: x1, y1, x2, y2 box.xyxy[0].tolist() cls_id int(box.cls[0]) conf float(box.conf[0]) detections.append((cls_id, conf, (int(x1), int(y1), int(x2), int(y2)))) return detections拿到坐标和类别后我在原图的副本上用cv2.rectangle画出检测框框的颜色按类别预设好类别名称和置信度分数直接标注在框上方。为了让用户看得更直观右侧表格里同步列出每一次检测的面额、置信度、坐标位置最后用字典累加出当前图片里各面额的硬币数量并乘以对应面值计算出总额显示在底部状态栏。这里我遇到过一个问题PySide6显示图片时QImage的通道顺序是RGB而OpenCV读取的是BGR直接转换会导致颜色整体偏蓝偏红。处理办法就一行cv2.cvtColor(img, cv2.COLOR_BGR2RGB)但忘记这行代码几乎是每个人都会犯的错误。5. 部署阶段踩过的坑从反光误检到推理卡顿5.1 模型导出与推理加速ONNX和TensorRT的实测对比模型训练完成后直接加载.pt权重文件跑推理完全没问题但如果要分发给别人用或者想提升CPU上的推理速度就要考虑模型导出了。实测下来我对比了三种推理方式的差异推理方式适用场景单张平均耗时CPU备注PyTorch原模型开发调试约1.8秒灵活方便改代码ONNX导出跨平台部署约1.1秒体积小兼容性好TensorRTNVIDIA显卡优化约0.2秒需要额外编译引擎如果用户机器没有NVIDIA独立显卡只做单张图片检测的话ONNX导出后跑CPU完全够用。导出命令也很简单模型对象调用导出方法指定formatonnx即可。需要提醒的一点是导出ONNX时最好固定输入尺寸比如640x640否则动态尺寸会让推理时间抖动明显。5.2 反光、磨损与重叠硬币的真实影响部署到实际场景后的第一个教训来自硬币表面的反光。尤其是在灯光直射下25美分和5美分表面的高光区域会形成一片亮白色的色块模型在高光干扰下偶尔会把25美分误判成5美分或者直接漏检。解决思路有两个方向。一是数据层面我在训练集里有意识地加入了一批用侧光拍摄、带有明显反光斑的硬币图片让模型学会忽略这些局部高光。二是推理层面我保留了OpenCV的高光抑制后处理开关检测前先对图片做一次简单的亮度通道均衡化处理把高光区域的过曝像素压回正常范围。两者叠加之后实测误检率下降了不少。重叠硬币则是另一个麻烦。当两枚硬币有三分之一面积重合时模型的预测框往往会偏向可见部分导致另一枚硬币的置信度骤降。我在测试集中专门统计过重叠超过30%的样本漏检率大约是正常样本的三倍。这个问题要完全解决并不容易目前的做法是把置信度阈值从0.45降到0.35代价是会有少量背景误检需要再配合一个按面积过滤的后处理规则来压制。5.3 相似面额混淆的定位与缓解从混淆矩阵来看最容易分不清的是5美分和25美分。为了定位问题我把错误样本全部导出到本地按预测正确的和预测错误的分别建立文件夹人眼逐个对比。看了两百多张图之后我总结出两个规律25美分的边缘齿纹比5美分更细密但照片模糊时这个差异完全消失。5美分表面由于镍铜合金氧化整体偏灰黄色25美分偏亮白色。但在白色LED灯直射且白平衡不准时两者的整体颜色分布很接近。针对这两个规律我在数据增强里做了两件事一是增加低分辨率模糊样本的占比确保模型见过模糊但有正确类别标签的硬币二是加强白平衡抖动和色温偏移让模型不把某个固定的颜色数值当成唯一的判断依据。效果上5美分和25美分两类的F1分数都回到了0.9以上。5.4 从毕业设计到可用的系统功能扩展清单模型和界面跑通之后我把项目整理成了一个带登录注册、图片检测、金额统计、检测记录保存的完整系统。如果想让这个系统更进一步有几个扩展方向值得做批量检测模式选择整个文件夹后台逐张检测并合并统计结果导出CSV报表。摄像头实时检测切换模型为s档位接入视频流检测结果叠加到视频画面上。检测记录数据库化把每次检测的图片路径、检测结果、用户ID写入数据库支持按时间查询。模型热切换在界面里提供模型选择下拉框让用户根据当前使用场景切换s或m档位。这几个方向的工程量都不算大架构上只需要在现有代码上增加对应的接口即可。尤其是批量检测和CSV导出在实际使用中价值很高。如果你是在做类似的钱币清点项目建议优先把这两个功能加上。训练一个泛化性好的硬币检测模型我最大的体会是真正消耗时间精力的环节不是调模型参数而是和数据较劲。公开数据集打底、实拍样本补全、合成数据调优这三板斧缺一不可。还有一个小技巧分享给你我通常先拿一小批数据快速跑20轮专门用来检查标注文件有没有格式错误、类别名有没有拼错确认这一切正常之后再进行长轮次训练可以省下好几个小时的无效等待。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。