☰
LED数码管数据集构建与YOLOv8训练全指南
2026/10/13 13:34:48 网站建设 项目流程

简介:LED数码管数据集面向从事计算机视觉与数字识别方向的机器学习学习者与开发者,可用于训练和评估ATM屏幕读取、仪表盘读数识别等需要解析七段数码管显示的应用模型。资源包共2000个文件,以18901张jpg图像为主体,另含少量o、xml、makefile及rundetector等构建与配置文件,压缩包约19.12MB,gz格式便于传输解压。图像像素直接对应各段LED的亮灭状态,适合作为监督学习的基础样本,配合灰度化、二值化、边缘检测等预处理手段,可支撑CNN、SVM等模型的训练与交叉验证。目前已有2016人学习下载,读者可据此搭建从数据预处理、模型训练到性能评估的完整流程,并借助附带的构建目录理解模型在特定平台上的调试与部署方式,为工业自动化读数、智能家居显示控制等场景提供可复用的实践参考。

1. 从一块报废的电路板说起:LED数码管数据集到底解决什么问题

去年帮一个做工业仪表的团队看产线质检方案,他们的痛点是:仪表盘上的LED数码管读数,靠人眼比对,一天看几千块板子,漏检率随班次直线上升。想上视觉检测,卡在第一步——没有数据。网上能找到的公开数据集,要么是手写数字,要么是自然场景里的门牌号,跟七段数码管那种「笔画由独立LED段拼出来、亮灭组合成字符」的成像特性完全对不上。这就是LED数码管数据集存在的意义:它专门收录数码管字符的图像,标注每个字符的类别和位置,让检测或识别模型能学到「哪几段亮、哪几段灭」对应的数字或字母。

这个方向适合三类人:做工业质检的视觉工程师、搞仪表自动读数的嵌入式开发者、以及需要训练小字符识别模型但找不到对口数据的研究者。它不解决通用OCR问题,只解决「发光段拼字符」这一类特定成像。理解这一点,后面的采集、标注、训练才不会跑偏。

2. 拆解LED数码管数据集:从成像特性到标注格式

2.1 数码管成像和普通字符的三个本质差异

第一个差异是发光特性。LED段是自发光的,在暗环境下对比度极高,但亮段边缘会有光晕,尤其是红色和绿色数码管,光晕会让相邻段在低分辨率下糊成一片。普通印刷字符是反射光成像,边缘锐利,两者预处理策略完全不同。

第二个差异是字符结构。七段数码管只有7个发光段加一个小数点,能表达的字符集有限——0到9,加上A、b、C、d、E、F等十六进制字符,再加个别符号。这意味着类别数远小于通用OCR,但类间差异极小:比如「8」和「9」只差左下那一段,「6」和「5」只差右上那一段。模型必须对单段亮灭极其敏感。

第三个差异是背景干扰。工业场景里数码管常嵌在黑色面板上,旁边可能有指示灯、丝印文字、反光边框。数据集如果只裁剪数码管区域,模型上线后遇到整板图像就会崩。所以标注策略要提前想清楚:是只标数码管字符框,还是连面板一起标。

2.2 标注格式选型:检测框、分割掩码还是关键点

常见做法有三种。检测框(Bounding Box)最通用,适合YOLO系列和Faster R-CNN,标注成本低,一个字符一个矩形框。分割掩码(Mask)适合段级分析,能精确到每一段亮灭,但标注成本高,需要逐像素勾画。关键点(Keypoint)适合固定视角的仪表,标出每个数码管字符的中心或角点,配合透视变换做矫正。

我一般推荐从检测框起步。原因很实际:工业质检里最终要的是「读数对不对」,不是「哪一段坏了」。检测框加分类头就能覆盖绝大多数需求。只有当你要做段级故障诊断——比如判断某一段LED是否老化变暗——才需要上分割。

标注时有个容易忽略的点:小数点和负号要不要单独成类。如果读数有小数,小数点必须标,否则「12.5」会被识别成「125」。负号同理。建议在类别表里单独列出,不要合并到数字类里。

2.3 一份可用的数据集该有的目录结构和字段

下面是一个我常用的目录组织方式,适配YOLO格式,也方便转COCO:

led_digit_dataset/ ├── images/ │ ├── train/ │ │ ├── board_0001.jpg │ │ └── ... │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ │ ├── board_0001.txt │ │ └── ... │ ├── val/ │ └── test/ ├── classes.txt └── dataset.yaml

classes.txt每行一个类别名,顺序对应标注文件里的类别索引。dataset.yaml写训练路径和类别数。标注文件每行格式是class_id x_center y_center width height,坐标归一化到0到1。

注意:归一化坐标必须用图像实际宽高算,不要用裁剪后的尺寸。如果先裁剪再标注,裁剪图和原图的坐标映射关系要单独记录,否则验证时框会错位。

2.4 采集阶段必须控制的四个变量

采集不是拿手机随便拍。数码管数据集的采集要控制:曝光时间、环境光照、拍摄角度、数码管型号。

曝光时间决定亮段是否过曝。过曝会让段边缘光晕扩大,相邻段粘连。建议用固定曝光,手动调到一个亮段清晰、暗段不噪点的值。环境光照要覆盖至少两种:全暗环境和有环境光干扰的环境。全暗环境考验模型对发光区域的定位,有环境光考验抗干扰。拍摄角度要包含正视角和±15度以内的偏角,超过15度字符形变太大,标注框会不准。数码管型号至少覆盖共阳和共阴各一种,因为两者亮灭逻辑相反,成像上暗段和亮段的对比关系不同。

3. 从零构建LED数码管数据集:采集、标注、增强的完整链路

3.1 用OpenCV做批量预处理和自动裁剪

采集回来的原图往往包含整块仪表板,直接标注效率低。先用传统视觉方法把数码管区域裁出来,能省大量标注时间。下面这段代码用颜色阈值加轮廓检测做粗裁剪:

import cv2 import numpy as np import os def crop_led_region(img_path, save_path): img = cv2.imread(img_path) hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # 红色数码管:色相在0-10和170-180两段 mask1 = cv2.inRange(hsv, (0, 80, 80), (10, 255, 255)) mask2 = cv2.inRange(hsv, (170, 80, 80), (180, 255, 255)) mask = cv2.bitwise_or(mask1, mask2) # 形态学膨胀,把断开的段连成整体 kernel = np.ones((5, 5), np.uint8) mask = cv2.dilate(mask, kernel, iterations=2) contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if not contours: return False # 取最大轮廓的外接矩形,外扩10像素留边 c = max(contours, key=cv2.contourArea) x, y, w, h = cv2.boundingRect(c) pad = 10 x1 = max(0, x - pad) y1 = max(0, y - pad) x2 = min(img.shape[1], x + w + pad) y2 = min(img.shape[0], y + h + pad) crop = img[y1:y2, x1:x2] cv2.imwrite(save_path, crop) return True if __name__ == "__main__": src_dir = "raw_images" dst_dir = "cropped_images" os.makedirs(dst_dir, exist_ok=True) for name in os.listdir(src_dir): if name.lower().endswith((".jpg", ".png")): crop_led_region(os.path.join(src_dir, name), os.path.join(dst_dir, name))

逻辑说明:先把BGR转HSV,因为HSV对光照变化比RGB鲁棒。红色数码管的色相分布在0-10和170-180两段,所以要两个mask取并集。膨胀操作是为了把同一字符的多个亮段连成一个连通域,否则每个段一个轮廓,裁剪会碎。最后取最大轮廓的外接矩形,外扩10像素防止边缘切掉光晕。

参数说明:(0, 80, 80)里的80是饱和度下限,如果你的数码管偏暗,可以降到50;iterations=2控制膨胀强度,段间距大的数码管可以加到3。这个脚本只处理红色数码管,绿色数码管色相在40-80,蓝色在100-130,改一下阈值区间即可。

3.2 标注工具选型和标注规范落地

标注工具用LabelImg或CVAT都行。LabelImg轻量,适合小团队;CVAT支持多人协作和预标注,适合数据量大时。关键不是工具,是标注规范。

规范要写清楚:框的边界贴紧亮段外沿,包含光晕但不包含明显背景;小数点单独标,框的宽高不小于3像素;负号单独标;同一字符的多个段用一个框包住,不要每段一个框。标注完成后抽10%做交叉复核,两个人标同一张图,IoU低于0.85的框拿出来讨论。

有个血泪经验:标注时一定要锁死图像尺寸。如果标注过程中有人重新裁剪了图片,坐标全废。建议标注前把所有图片统一缩放到固定宽度,比如640,高度按比例。

3.3 针对数码管的数据增强策略

通用增强里,翻转要慎用。水平翻转会把「6」变成「9」的镜像,语义变了。垂直翻转同理。旋转超过10度会让段结构变形,也不建议。真正有用的是这几类:

亮度扰动:模拟不同曝光和环境光。用cv2.convertScaleAbs调alpha和beta,alpha控制对比度,beta控制亮度。范围建议alpha在0.7到1.3,beta在-30到30。

高斯噪声:模拟低质量传感器。cv2.randn加噪声后截断到0-255。

运动模糊:模拟产线传送带上的抖动。用cv2.filter2D加一个线性核,核大小3到7,方向随机。

局部遮挡:模拟面板上的污渍或反光。随机画几个黑色或白色矩形,面积不超过字符框的15%。

下面是一个增强函数的骨架:

import cv2 import numpy as np import random def augment_led(img, boxes): # 亮度对比度扰动 alpha = random.uniform(0.7, 1.3) beta = random.uniform(-30, 30) img = cv2.convertScaleAbs(img, alpha=alpha, beta=beta) # 高斯噪声 noise = np.random.randn(*img.shape) * random.uniform(5, 15) img = np.clip(img + noise, 0, 255).astype(np.uint8) # 运动模糊 if random.random() < 0.3: k = random.choice([3, 5, 7]) kernel = np.zeros((k, k)) kernel[int((k-1)/2), :] = 1.0 / k img = cv2.filter2D(img, -1, kernel) # 局部遮挡 if random.random() < 0.2: h, w = img.shape[:2] for _ in range(random.randint(1, 3)): x = random.randint(0, w-1) y = random.randint(0, h-1) bw = random.randint(5, max(6, w//10)) bh = random.randint(5, max(6, h//10)) color = random.choice([0, 255]) cv2.rectangle(img, (x, y), (x+bw, y+bh), (color,)*3, -1) return img, boxes

逻辑说明:增强只改图像像素,不改标注框坐标,所以boxes原样返回。亮度扰动用convertScaleAbs一步完成,比逐像素循环快。噪声用numpy广播加,再clip防止溢出。运动模糊核是水平方向的,如果要随机方向,可以把核旋转。遮挡用实心矩形模拟,颜色随机黑或白。

参数说明:噪声强度5到15是经验值,再大字符边缘会糊。遮挡面积控制在字符框15%以内,否则模型会学到「遮挡等于无字符」。增强后的图要和原图一起进训练集,不要只留增强图,否则模型对干净图的泛化会下降。

3.4 训练集验证集划分的坑

划分不能随机。同一块板子拍的多张图,如果有的进训练有的进验证,验证指标会虚高,因为模型见过这块板的背景和光照。正确做法是按板子划分:同一块板的所有图只进一个集合。如果数据来自多个产线,还要按产线分层抽样,保证验证集覆盖所有产线。

划分比例建议训练70%、验证15%、测试15%。如果类别不平衡——比如「1」的样本远多于「8」——划分时要分层,保证每个集合里各类别比例接近。

4. 用YOLOv8训练LED数码管检测模型:配置、训练、导出

4.1 环境准备和数据集配置文件

先装依赖。用conda建环境,避免和系统Python冲突:

conda create -n led_digit python=3.10 -y conda activate led_digit pip install ultralytics opencv-python numpy

dataset.yaml内容如下:

path: /data/led_digit_dataset train: images/train val: images/val test: images/test nc: 12 names: ['0','1','2','3','4','5','6','7','8','9','dot','minus']

nc是类别数,这里12类:0到9加小数点和负号。names顺序必须和标注文件里的class_id一致,错一个位置整个训练就废了。

4.2 训练命令和关键参数怎么设

yolo detect train \ data=dataset.yaml \ model=yolov8n.pt \ epochs=150 \ imgsz=640 \ batch=16 \ lr0=0.01 \ lrf=0.01 \ patience=30 \ augment=True \ mosaic=0.5 \ degrees=5.0 \ translate=0.1 \ scale=0.3 \ fliplr=0.0 \ flipud=0.0 \ project=led_runs \ name=exp1

参数说明:model=yolov8n.pt用nano版起步,数码管类别少、特征简单,nano通常够用,不够再换s。epochs=150配合patience=30,30轮验证指标不升就早停。lr0=0.01是初始学习率,lrf=0.01是最终学习率因子,余弦退火到初始的1%。mosaic=0.5是马赛克增强概率,数码管场景建议降到0.5,默认1.0会让四张图拼接后字符太小。degrees=5.0旋转角度限制在5度内,fliplr=0.0和flipud=0.0关掉翻转,原因前面讲过,翻转会改变字符语义。scale=0.3缩放幅度,模拟不同拍摄距离。

4.3 训练过程看什么指标

训练日志里重点看三个:metrics/mAP50、metrics/mAP50-95、train/box_loss。mAP50到0.95以上基本可用,mAP50-95到0.7以上说明框的位置也准。box_loss持续下降但mAP不升,可能是过拟合,看验证loss是否回升。

如果mAP卡在0.6上不去,先查标注。用yolo detect val跑验证集,把预测结果和标注叠在一起可视化,看是漏标、错标还是框不准。数码管数据集最常见的标注问题是小数点漏标和相邻字符框重叠。

4.4 导出ONNX和推理部署

yolo export model=led_runs/exp1/weights/best.pt format=onnx imgsz=640 simplify=True

导出后推理:

from ultralytics import YOLO model = YOLO("best.onnx", task="detect") results = model("test_board.jpg", conf=0.4, iou=0.5) for r in results: for box in r.boxes: cls_id = int(box.cls[0]) conf = float(box.conf[0]) xyxy = box.xyxy[0].tolist() print(f"class={model.names[cls_id]} conf={conf:.2f} box={xyxy}")

conf=0.4是置信度阈值,数码管场景建议0.4到0.5,太低会误检背景里的反光点。iou=0.5是NMS的IoU阈值,相邻字符框重叠多时可以降到0.4。

5. 避坑指南:LED数码管数据集从采集到部署的五个翻车现场

现象一:模型在验证集上mAP很高,上线后整板图像全漏检。原因:训练时用的是裁剪后的数码管小图,模型学到了「图像中心就是字符」的先验,上线遇到整板大图,字符只占一小块,模型找不到。解决:训练集里混入至少30%的整板原图,标注只标数码管区域,让模型学会在背景中定位。

现象二:相邻数字的框粘在一起,NMS后只剩一个。原因:数码管字符间距小,光晕导致两个字符的预测框IoU超过0.5,被NMS合并。解决:把NMS的IoU阈值降到0.3到0.4,或者改用Soft-NMS。更根本的办法是标注时框贴紧亮段,不要外扩太多。

现象三:小数点被识别成噪声过滤掉。原因:小数点面积小,训练时正样本少,模型倾向于把它当背景。解决:在损失函数里给小目标更高权重,或者单独为小数点做一轮过采样。YOLOv8里可以调box损失的gain,但更简单的是复制含小数点的图多次进训练集。

现象四:换一种颜色的数码管,模型直接失效。原因:训练集只有红色数码管,模型学到了红色这个捷径特征,换成绿色就认不出。解决:采集阶段就覆盖红、绿、黄、蓝至少两种颜色,或者在预处理里把图像转灰度,强制模型学形状而不是颜色。转灰度会损失一些信息,但泛化性更好。

现象五:训练loss正常下降,但推理时框的位置整体偏移。原因:标注时用的图像尺寸和推理时输入尺寸不一致,归一化坐标算错了。比如标注时图是1280宽,推理时resize到640,如果标注文件里的坐标是按1280算的,但训练时YOLO按640读,框就会缩一半。解决:标注前统一所有图片到固定尺寸,或者确保标注工具输出的归一化坐标是基于原图宽高的。

6. 进阶技巧:用合成数据补足长尾类别和极端光照

真实采集有个绕不开的问题:某些字符组合出现频率极低,比如「-8.8」这种带负号和小数点的读数,可能几千张图里才有一张。模型对长尾类别永远学不好。这时候合成数据是性价比最高的补充手段。

合成数码管的思路是:用字体文件渲染字符,叠加到真实背景上,再模拟发光效果。七段数码管有专门的字体,比如「DSEG7」系列,渲染出来就是段式结构。下面是一个合成脚本的核心逻辑:

from PIL import Image, ImageDraw, ImageFont import numpy as np def synth_led_digit(char, font_path, size=64, color=(255, 0, 0)): # 渲染字符到透明底图 font = ImageFont.truetype(font_path, size) img = Image.new("RGBA", (size*2, size*2), (0, 0, 0, 0)) draw = ImageDraw.Draw(img) draw.text((size//2, size//2), char, font=font, fill=color+(255,)) # 加高斯模糊模拟光晕 from PIL import ImageFilter glow = img.filter(ImageFilter.GaussianBlur(radius=3)) # 原图和光晕叠加 result = Image.alpha_composite(glow, img) return result def paste_on_background(digit_img, bg_path, position): bg = Image.open(bg_path).convert("RGBA") bg.paste(digit_img, position, digit_img) return bg.convert("RGB")

逻辑说明:先用PIL的truetype渲染字符,字体用段式字体保证结构正确。渲染到RGBA透明图,方便后续叠加。高斯模糊半径3是模拟光晕,半径越大光晕越强,按实际数码管调。alpha_composite把光晕和原图叠加,得到发光效果。最后贴到真实背景图上,位置随机但不要超出边界。

参数说明:size=64是字符渲染尺寸,实际贴图时可以缩放。color按数码管颜色设,红色(255,0,0),绿色(0,255,0)。模糊半径3到5对应不同光晕强度,建议两种都生成,增加多样性。

合成数据的使用比例建议不超过总训练集的30%。合成数据太干净,和真实数据分布有差距,比例太高会让模型对真实图的性能下降。我一般把合成数据全部放训练集,验证集和测试集只用真实数据,这样指标才反映真实水平。

还有一个技巧是「混合增强」:把合成字符贴到真实背景后,再过一遍前面写的增强函数,加噪声、模糊、遮挡。这样合成图看起来更接近真实采集。我试过纯合成和混合增强的对比,混合增强的模型在真实测试集上mAP能高5到8个点。

最后说个习惯:每次训练完,我一定把验证集里预测错的图单独存一个文件夹,按错误类型命名——漏检、误检、框偏、类别错。攒够一批就看一眼,往往能发现标注规范里没写清楚的边界情况。这个习惯帮我省了很多次重新标注的功夫。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询