简介:这是一套基于YOLOv8的智慧码头集装箱箱号自动识别系统,主要面向计算机视觉、深度学习方向的毕业设计、课程设计或项目实践。源码由个人毕设整理完成,运行测试通过,包含完整数据集、可视化界面与部署文档,可生成混淆矩阵、F1分数曲线、精确率-召回率曲线、标签分布图等关键评估图表,便于答辩展示。压缩包共8个文件,其中3个py脚本负责模型训练、视频检测与可视化交互,3个pt文件为训练好的模型权重与最佳模型,2个txt文件提供说明与配置指引,整体仅15.91MB,轻量易部署。目前已有35人学习浏览,适合需要快速搭建目标检测演示系统的初学者或答辩前查漏补缺的学生。通过该资源可掌握YOLOv8在真实集装箱箱号识别场景中的完整流程——从数据集组织、模型训练到推理验证、界面展示,代码结构清晰,修改后也可迁移至其他字符识别或目标检测任务,实用性和完成度都比较高。
1. 集装箱箱号识别为什么适合直接用YOLOv8字符检测
集装箱箱号识别放到真实码头里,比实验室跑通要麻烦得多。箱号喷在瓦楞钢箱体表面,金属反光、油污、锈蚀、绑扎件遮挡和相机俯仰角叠加在一起,整行OCR往往在第一个字符就定位失败。这套基于YOLOv8的识别系统换了一条更稳的技术路线:不直接读整行文本,而是把箱号里的每个字符当作独立目标,用目标检测的方式先定位后分类,再按字符框的空间坐标排序拼回完整箱号。这样即使个别字符被反光吞掉,也能从前后位置关系里判断缺了几位。对做毕业设计或课程设计的人来说,它给了一条从数据集构建、模型训练、指标验证到可视化界面部署的完整链路,并且自带验证集预测结果、混淆矩阵、F1分数曲线、精确率-召回率曲线和标签分布图,拿来改一改就能适配自己的数据。
2. 数据集构建:ISO 6346字符集、YOLO标注与标签分布分析
不管是用项目自带数据集,还是重新标注一批现场图片,都要先理解箱号在YOLO坐标系下是如何表达的。这一章把数据层面的事情讲透,后面的训练和部署才不会返工。
2.1 箱号格式与检测粒度选择
集装箱箱号遵循 ISO 6346 标准:前三位是箱主代码,第四位是设备类别码,干货箱一般为大写字母 U,接着是六位注册编号,最后一位是独立的校验码,整体一共 11 个字符。字母只出现在前四位,数字占后七位,这个先验知识对后处理拼接和校验非常重要。ISO 6346 还规定字母表必须排除 I 和 O,避免与数字 1、0 在金属表面上相互混淆,所以实际字符类别是 24 个字母加 10 个数字,一共 34 类。
处理粒度方面,整行 OCR 需要先定位箱号区域再交给识别模型,定位框稍微偏移,后半段数字就会全部错位。YOLOv8 字符检测方案则把 11 个字符当作 34 类目标里的任意组合来做密集小目标检测,每个字符独立输出一个带类别和置信度的检测框,再通过坐标聚类拼出完整箱号。即使某个字符被遮挡漏检,前后框的间距也能提示这里少了一位,工程上更容易兜底。这套项目里的训练脚本、视频推理和可视化界面,全部围绕这种字符检测粒度组织,包括预训练权重里默认的 34 类分类头。
2.2 数据集目录结构与YOLO标签格式
下载资源解压后,数据集目录按 YOLO 标准格式组织,图片与标签文件同名一一对应。典型的目录结构如下:
dataset/ ├── images/ │ ├── train/ # 训练图片,建议不同时段、不同箱型混合 │ └── val/ # 验证图片,尽量与 train 不同批次采集 ├── labels/ │ ├── train/ # 每个 jpg 对应一个同名 txt │ └── val/ ├── dataset.yaml # 模型训练时读取的数据集配置 └── stats/ └── label_distribution.png # 由统计脚本生成的标签分布图每个 txt 文件里,一行描述一个字符目标,格式为class x_center y_center width height,坐标全部归一化到 0 到 1 之间。以某张训练图片为例:
12 0.5312 0.6435 0.0421 0.0912 33 0.5867 0.6421 0.0402 0.0898 7 0.6402 0.6389 0.0418 0.0907第一列是类别索引,对应 dataset.yaml 里 names 列表的位置;后面四列分别是归一化后的中心点 x、中心点 y、框宽和框高。类别顺序一旦定下来就不要在中途修改,否则重新训练时标签全部错位。项目自带的标签文件已经按固定顺序生成,如果你打算换自己的数据集,建议先用脚本跑一遍标签格式校验,再开始训练。
2.3 dataset.yaml配置与标签分布分析
dataset.yaml 是训练入口,train 和 val 路径写相对路径时,必须保证命令行当前工作目录在 dataset 的上级目录。示例如下:
# dataset/dataset.yaml path: . train: images/train val: images/val nc: 34 names: 0: '0' 1: '1' 2: '2' 3: '3' 4: '4' 5: '5' 6: '6' 7: '7' 8: '8' 9: '9' 10: 'A' 11: 'B' 12: 'C' 13: 'D' 14: 'E' 15: 'F' 16: 'G' 17: 'H' 18: 'J' 19: 'K' 20: 'L' 21: 'M' 22: 'N' 23: 'P' 24: 'Q' 25: 'R' 26: 'S' 27: 'T' 28: 'U' 29: 'V' 30: 'W' 31: 'X' 32: 'Y' 33: 'Z'names 顺序建议固定为数字在前、字母在后,并且字母表跳过 I 和 O。这样做的直接好处是类别索引和 ASCII 顺序天然一致,后处理里做类别名映射时不容易写错下标。数据准备的下一步是统计每个类别的样本数量,我在拿到新数据集时一般会先跑一个标签分布统计脚本,把类别不平衡的情况可视化出来:
# stats_label_distribution.py:统计训练集每个类别的目标数并绘制柱状图 from pathlib import Path import matplotlib.pyplot as plt import numpy as np label_dir = Path('dataset/labels/train') counts = np.zeros(34, dtype=int) for txt in label_dir.glob('*.txt'): for line in txt.read_text().strip().splitlines(): cls = int(line.split()[0]) counts[cls] += 1 labels = [str(i) for i in range(10)] + list('ABCDEFGHJKLMNPQRSTUVWXYZ') plt.figure(figsize=(14, 6)) plt.bar(range(34), counts, tick_label=labels) plt.xticks(rotation=45, fontsize=8) plt.ylabel('sample count') plt.savefig('label_distribution.png', dpi=160) plt.show()这段脚本读取全部训练标签,把每个 txt 的第一列累加到对应类别桶里,最后画出柱状图。运行之后重点关注两类问题:一是某些字母类别样本量远低于平均数,比如 Q、Z 这类冷门字符在码头上出现频率很低,检测器对尾部类别的召回率天然不佳,一个字母漏检就会导致整个箱号识别失败;二是数字与易混字母的样本比例,只要 D 和 0、B 和 8、S 和 5 这三组数据分布差距过大,混淆矩阵里就会出现明显的误检块。统计出结果后,如果某个类别低于 1000 个框,我一般会优先用复制增强或者重新采集补样本,而不是直接开训。
2.4 数据增强参数怎么调
YOLOv8 默认开启 mosaic、hsv、平移和缩放增强,但这些默认值并不完全适合字符检测场景。mosaic 把四张图拼接成一张,会导致字符这种小目标被切碎在拼接缝附近,训练后期的负面影响尤其明显。表 2-1 是我在箱号数据集上常用的增强参数配置,和默认值拉开差距的地方都做了标注。
| 参数 | 默认值 | 箱号场景建议 | 调整理由 |
|---|---|---|---|
| mosaic | 1.0 | 最后 10 轮设为 0 | 字符框小,mosaic 拼接会切碎目标 |
| degrees | 0.0 | 3 ~ 5 | 码头相机角度固定,没必要加大旋转扰动 |
| hsv_h | 0.015 | 0.01 | 过强的色相偏移会模拟出训练集不存在的金属反光色 |
| scale | 0.5 | 0.8 | 箱号字符尺度变化大,适当放大帮助小目标检测 |
| translate | 0.1 | 0.05 | 箱号位置相对固定,不需要大幅平移 |
这些参数在 train_mode.py 里通常有两种写法。一种是直接在 model.train() 里以关键字参数传入,比如close_mosaic=10表示最后 10 轮自动关闭 mosaic;另一种是在一个单独的 yaml 里定义增强配置。跑训练之前先看一眼脚本里读的是哪种方式,避免改了参数实际没生效。关于验证集划分,还需要保证 val 集和 train 集不是同一批图片的不同帧,不然标签分布图会漂亮得没有参考价值。
3. 训练与调优:C2f骨干、预训练权重与指标曲线解读
环境配置是第一个容易被卡住的环节。项目依赖的核心是 ultralytics 库,Python 3.8 及以上版本执行pip install ultralytics即可,它会把 torch 和 torchvision 一起拉起来。做完数据集校验、补齐尾部类别样本后,就可以进入正式训练。
3.1 train_mode.py的训练流程与参数含义
train_mode.py 是项目里负责模型训练的入口,核心逻辑和下面的代码骨架一致,但具体超参数要以 README.txt 里写的为准。实际运行前先确认 dataset.yaml 中的路径和当前工作目录匹配,否则会很早抛出数据集加载失败的错误。
# train_mode.py 核心训练逻辑 from ultralytics import YOLO if __name__ == '__main__': model = YOLO('yolov8n.pt') # 加载 COCO 预训练权重做迁移学习 model.train( data='dataset/dataset.yaml', epochs=120, imgsz=640, batch=16, device=0, patience=15, lr0=0.01, close_mosaic=10, name='container_ocr', )上面代码里,YOLO('yolov8n.pt')表示加载 YOLOv8n 的 COCO 预训练权重,箱号字符检测属于典型的领域迁移,直接从头训练会显著增加收敛时间;data指定数据集配置;imgsz=640是输入分辨率,字符是小目标,不建议降到 416;batch受显卡显存约束,GTX 1660Ti 这类 6GB 显存卡我把 batch 设置在 8 到 16 之间,配合默认开启的 AMP 混合精度可以正常跑完;lr0是初始学习率,字符类别数少,学习率偏大容易在早期震荡;close_mosaic=10表示最后 10 轮关闭 mosaic 增强,让模型在小目标上做精细适配。
这组超参不是唯一解。如果你的显卡显存小于 6GB,就把 batch 降到 8,并把workers同时调低,避免数据加载成为瓶颈。如果训练集图像分辨率本身就超过 1280,可以试试 imgsz 取 960,但显存占用会同步上涨,需要权衡。遇到显存溢出时先看 nvidia-smi,确认是不是别的进程占用了显存,再决定降 batch 还是降 imgsz。
3.2 预训练权重怎么选:yolov8n.pt、yolo11n.pt与best.pt
项目里同时出现了两个预训练权重文件,yolov8n.pt 是训练脚本默认加载的 YOLOv8 最小的 nano 版,特点是参数少、推理快;yolo11n.pt 是 YOLO11 的 nano 版,网络结构上把 YOLOv8 的 C2f 换成了 C3k2 模块,同样主打轻量部署。两者在训练时都可以作为起点,只是网络结构图里骨干和颈部的组合不同,最终精度也会有小幅差异。
| 权重文件 | 参数量级 | 结构特点 | 适用场景 |
|---|---|---|---|
| yolov8n.pt | 约 3.2M | C2f 骨干,梯度支路丰富 | 训练脚本默认起点,稳妥选择 |
| yolo11n.pt | 约 2.6M | C3k2 模块,计算量更低 | 对比实验或追求更高 FPS 时使用 |
| best.pt | 训练产物 | 由 train_mode.py 生成 | 推理脚本和可视化界面默认加载 |
我在箱号数据集上做对比实验时,会在 train_mode.py 里把YOLO('yolov8n.pt')替换成YOLO('yolo11n.pt'),保持其他超参数完全一致,各训一轮后再用验证集 F1 曲线对比。注意两个权重文件的类别数都是 COCO 的 80 类,它们只负责提供骨干特征提取能力,最后输出头的类别数会在训练时根据 dataset.yaml 的 nc 自动重建,不用担心 34 类字符放不进去。对毕设来说,用默认的 yolov8n.pt 训练到收敛,再换 yolo11n.pt 做一组对照,正好可以作为答辩里对比实验的素材。
3.3 训练产物解读:混淆矩阵、F1与损失曲线
训练结束后,在 runs/detect/container_ocr 目录下能看到一批分析图,这些图在答辩时是证明系统有效性的直接材料。results.png 里包含 box_loss、cls_loss、dfl_loss 三条损失曲线和 mAP50、mAP50-95 两条精度曲线,也就是常说的损失函数曲线图;观察 loss 曲线时,关注的是下降后是否呈收敛平台,而不是追求数值上无限接近零。
confusion_matrix.png 是字符混淆矩阵,第 i 行第 j 列代表真实类别 i 被预测成类别 j 的样本数,对角线越亮越好。最容易出问题的是 D 与 0、B 与 8、S 与 5 三组,钢印字体中这些字符轮廓高度相似,如果这三块附近出现亮色块,说明数据增强或分辨率还需要调整。F1_curve.png 展示不同置信度阈值下的 F1 分数,曲线最高点对应的阈值就是推理时最合适的 confidence 参数,我通常会在 0.4 到 0.55 之间取,比无脑用默认的 0.25 直观得多。PR_curve.png 则反映精确率和召回率的整体平衡,曲线越靠近右上角越好。
val_batch0_pred.jpg 这类验证集预测图,展示的是模型在未见过的图片上画出的检测框和类别标签,用来做定性检查,比如是否存在同一字符被重复检测、相邻字符框是否粘连。标签分布图由第 2 章的统计脚本生成,和这些指标曲线一起放进论文附录,能让评审快速确认实验数据的可靠性。
4. 推理落地:Detection_video.py、Visual_interface.py与箱号拼接
训练完成后,best.pt 就是整个系统真正要用的权重。项目里的推理链路分成两条:Detection_video.py 面向视频流和摄像头,Visual_interface.py 面向人工交互的可视化界面。两条链路底层走的都是同一个推理逻辑,只是输入源和结果呈现方式不同。
4.1 视频推理脚本的典型用法
Detection_video.py 一般接收权重路径、视频路径、置信度阈值和输入分辨率这几个参数。常见的启动方式如下:
# 对本地视频文件做识别 python Detection_video.py --weights best.pt --source test_video.mp4 --conf 0.45 --imgsz 640 # 对摄像头实时流做识别,source 传摄像头索引 python Detection_video.py --weights best.pt --source 0 --conf 0.45 --imgsz 640--conf是置信度阈值,低于这个值的检测框会被丢弃,取值建议先对照第 3 章 F1 曲线的峰值来确定;--source 0表示读取本机第一个摄像头,在码头顶部相机场景里通常是一个 RTSP 拉流地址。实际运行前先检查脚本里是用 argparse 解析参数还是硬编码路径,如果硬编码,直接改源码里的输入路径常量即可,README.txt 里一般已写明推荐值。
视频推理的瓶颈往往不在检测本身,而在视频解码和结果显示。使用 OpenCV 的 VideoCapture 读取高分辨率视频时,解码耗时可能比模型前向推理还高,这种情况我会先把视频帧缩放至 1280 宽度再做检测,箱号字符在 640 分辨率下已经足够检出,盲目保持原尺寸只会拖慢速度。
4.2 可视化界面Visual_interface.py的功能组织
Visual_interface.py 是整个项目里答辩演示最有冲击力的部分。界面通常分为三个区域:左侧是模型和输入源选择区,中间是实时检测结果预览,右侧是识别到的箱号文本和置信度信息。操作流程大致是加载 best.pt 权重,选择图片、视频或摄像头输入,点击开始识别后界面逐帧显示检测框并把箱号实时拼出来。
这类界面如果用的是 Tkinter,简单但刷新效率一般;如果用的是 PySide6,则要注意把模型推理放到子线程里执行,否则视频帧一过来界面就会卡死。常见做法是用 QThread 接收视频帧,推理完成后通过信号把带框图像传回主线程更新界面。这里有一个容易踩的坑:在子线程里直接访问界面控件对象,轻则警告,重则崩溃,正确做法是只发信号不传对象。如果脚本里已有这个机制,在答辩演示时就不会出现切换视频源后界面无响应的问题。
4.3 从检测框到完整箱号:行聚类与坐标排序
模型输出的只是 34 类字符的独立检测框,必须把它们按空间位置组织成字符串。由于箱体上字符是一行排列的,后处理的第一步是把所有检测框按 y 坐标聚类成行,第二步在行内按 x 坐标排序,最后拼接成箱号。这个逻辑看起来简单,但在视频帧的连续推理里,框的抖动会直接影响排序稳定性。
# sort_boxes.py:把YOLOv8输出的字符框拼成箱号 def boxes_to_code(boxes, row_thr=15): # boxes: list of [x1, y1, x2, y2, conf, cls] rows = [] for b in boxes: y_center = (b[1] + b[3]) / 2 target = None for row in rows: if abs(y_center - row['yc']) < row_thr: target = row break if target is None: rows.append({'yc': y_center, 'boxes': [b]}) else: target['boxes'].append(b) # 更新该行的平均y中心,避免累积偏移 target['yc'] = sum((x[1] + x[3]) / 2 for x in target['boxes']) / len(target['boxes']) codes = [] for row in rows: row['boxes'].sort(key=lambda x: x[0]) # 行内按x左边界排序 codes.append(''.join(class_names[int(b[5])] for b in row['boxes'])) return codes这段代码先按 y 中心点做行聚类,row_thr=15表示两个字符框的 y 中心距离小于 15 像素就视为同一行,这个阈值应根据实际图像高度等比调整,例如取图像高度的 0.01 到 0.03 倍。找到同一行的字符后,按 x 左边界排序而不是按中心点排序,因为字符框宽度差异不大但左边界更能反映真实阅读顺序。行聚类完成后,每一行拼接出的字符串就是一个候选箱号。如果画面里同时出现两行字符,比如看多了半截字体,这段代码会返回多个字符串,再由上层逻辑按长度和校验位筛选。
4.4 GPU与CPU部署的差异处理
推理阶段对速度的要求比训练阶段更高,尤其是视频流识别。NVIDIA 显卡上可以做两件事:一是开启半精度推理,GTX 16 系及以上架构都支持,显存占用和单帧延迟都能再降一档。
python Detection_video.py --weights best.pt --half二是把模型导出成 engine 格式,减少运行时构图开销。CPU 推理场景下,batch 固定为 1,输入分辨率保持 640,yolov8n 在桌面级 CPU 上单帧大约几百毫秒,勉强可用于离线图片识别,实时视频就需要考虑 GPU 或边缘设备加速。项目里可视化界面默认加载 best.pt 的路径如果找不到权重文件,界面启动时会直接报错退不出,调试时先检查weights这个全局变量是否指向真实存在的文件。
5. 进阶:ISO 6346校验位、边界样本处理与TensorRT优化
基本链路跑通后,还能做几件提升系统可靠性的工作。ISO 6346 的校验位机制可以用来过滤识别错误的箱号,TensorRT 导出则能显著降低视频推理延迟。
5.1 用ISO 6346校验位验证识别结果
ISO 6346 规定第 11 位是校验码,它由前 10 位字符按特定规则计算得到。识别结果出来后,立刻用前 10 位重新计算校验码,与第 11 位比较,不一致就说明识别有误,应当触发人工复核。校验算法如下:
def iso6346_check(container_no: str): # container_no 为前10位,返回应与第11位字符一致 mapping = {c: ord(c) - ord('A') + 10 for c in 'ABCDEFGHJKLMNPQRSTUVWXYZ'} total = 0 for i, ch in enumerate(container_no): v = mapping.get(ch, int(ch)) total += v * (2 ** i) # 权重为2的i次方 check = total % 11 return '0' if check == 10 else str(check)权重从第 0 位开始按 2 的幂递增,累加后对 11 取模,余数 10 映射为 0。使用例子:模型识别结果是 CMSU1234565,前 10 位是 CMSU123456,计算出校验码如果等于 5 则通过,否则把这条结果标记为低置信输出,弹窗提示人工确认。这个逻辑加到 Visual_interface.py 里并不复杂,但效果显著,能直接把误识别箱号挡闸在自动过闸流程之外。
5.2 现场常见坏样本的应对
码头现场的坏样本集中在几类:钢面反光、字符锈蚀、绑扎件遮挡、雨雾天模糊。针对反光,推理前对图像做 CLAHE 对比度增强能让字符轮廓更清晰;针对模糊,保持 imgsz=640 不降分辨率,同时避免把视频帧直接拉伸到 640 乘以 640。训练层面,少样本冷门字符先做复制粘贴增强,再看混淆矩阵确认 D/0、B/8、S/5 三组的误检是否被压低。这里的原则是优先补数据和增强,而不是盲目换更大的模型。
5.3 TensorRT导出与边缘设备部署
如果要将系统部署到闸口工控机或边缘盒子上,TensorRT 是性价比最高的加速手段:
yolo export model=best.pt format=engine half=True device=0导出成功后,推理脚本里把权重路径换成导出的.engine文件即可,VideoEvent 和 Detection_video.py 的其余代码不用改。TensorRT 对网络结构做层融合和精度校准,yolov8n 在常见边缘 GPU 上单帧延迟能压到个位数毫秒级,但导出与运行必须在相同显卡驱动版本环境下执行,否则 engine 序列化会失败。导出前先验证一次 FP32 权重和 engine 在同一段视频上的识别结果,确保字符类别顺序和置信度分布一致,再进入正式部署。
本文还有配套的精品资源,点击获取