简介:面向新能源汽车充电口识别场景,这套VOC标注格式的数据集覆盖特斯拉、CCS1、CCS2、CHAdeMO、Type1、Type2等主流充电接口类型,适合视觉检测算法工程师、自动驾驶或充电设施相关技术人员用于模型训练、精度评估与算法迭代。压缩包共2000个文件,全部为VOC标准xml标注文件,每个文件包含单一样本的类别与边界框标注信息,整体约137.67MB,可直接用于目标检测模型的数据准备。已有589人浏览学习,在充电口识别相关数据集中具备一定参考价值。数据集提供多类型接口的细粒度标签,识别精确度达到91.1%,可支撑充电口识别模型的训练与评估,也便于开展数据增强、类别均衡或模型调优,有助于减少自建标注样本的时间成本,为新能源汽车充电口的自动识别与多制式兼容性判断提供扎实标注数据。
1. 充电口识别:标签比模型更值钱
自动充电机器人对准车身侧面的充电口时,真正决定插枪成败的不是机械臂的毫米级定位,而是它把面前的东西认成了CCS2还是CHAdeMO。这两类接口的占用面积和插针布局完全不同,一旦分类互换,插枪机构轻则报警重则损坏口盖。新能源电车的充电口识别也因此不是普通目标检测任务:六类接口在外观上高度相似,且训练样本的视角、光照、反光差异极大。这份资源把目前常见的充电口类型整理成VOC标记格式,压缩包内XML标注齐全,整理后训练检测网络可复现约91.1%的精度,适合正在做自动充电、充电枪引导或车辆充电口质检的视觉工程师。
2. VOC标记结构与CCS1/CHAdeMO类别定义
2.1 六类接口的外观差异:这是分类先验
在打开XML之前,先建立类别先验。CCS1和CCS2都是Combo标准,它们在交流插座下方增加两个直流大针脚,针脚粗且间距固定,区别只在于交流段底座是Type1还是Type2。Type1多见于北美,是五边形轮廓的7针交流口;Type2多见于欧洲,是圆角矩形7针交流口;CHAdeMO来自日本,轮廓更接近正方形,带有一个明显的圆形通信接口。特斯拉的插件轮廓在六类里最窄,接口上部有锁扣,底部没有额外直流孔。车辆表面曲率、颜色和反光会让这些特征在图像里模糊,分类时必须同时看轮廓宽高比和针脚排布。
| 类别 | 常见市场 | 关键视觉特征 | 最容易混淆对象 |
|---|---|---|---|
| Tesla | 北美、中国部分超充 | 窄跑道形轮廓,按钮区在上方 | Type2、CCS2 |
| CCS1 | 北美 | Type1七针下方加两个直流孔,上下双层 | CCS2、Type2 |
| CCS2 | 欧洲 | Type2七针下方加两个直流孔,外壳方正 | CCS1、Type2 |
| CHAdeMO | 日本、欧洲 | 近似正方形的圆角轮廓,圆形通信针 | Type2、CCS2 |
| Type1 | 北美、日本 | 五边形轮廓,七针横排,无下排孔 | CCS1 |
| Type2 | 欧洲 | 圆角矩形七针,内部有挡板 | CCS2、Tesla |
这张表对应到标注时,需要把“特斯拉”统一写成Tesla,CHAdeMO不要写ChadeMo。原始压缩包标题里的ChadeMo是拼写变体,实际标准叫CHAdeMO。如果训练框架以标签文本区分类别,大小写不一致会让同一个接口被拆成两个类,CHAdeMO样本本来就少,再被拆开就没法收敛。我的建议是在使用前先做一次字符串归一化,把所有标签统一成首字母大写、其余小写的形式。
这六个类别并不是等价的。CCS1和CCS2的差别仅在交流针脚区,Type1与CCS1在普通视角下几乎一样,唯一的稳定特征是CCS1下方多出的两个直流孔;训练时如果图像来自低分辨率摄像头,这两个孔在缩放到640后经常只剩下两三个像素。这也是为什么这类任务不能只看总体准确率,必须单独看CCS1的recall。我在实际项目中会先检查训练图的分辨率分布,低于960x540的样本要单独复制一份做增强,否则模型学到的是插口周围的塑料壳纹理。
2.2 Roboflow导出的XML长什么样
这份zip里没有像公开数据集那样规整的images和labels目录,文件命名是connector_127_png_jpg.rf.40e2d9f8438e16707d6c8d78f8b4fb96.xml这种形式。这个命名是Roboflow导出VOC格式时的特征:connector_127是原图名,png_jpg是Roboflow生成的副本格式,rf.后面的哈希用于区分同一张图的不同导出副本。如果一个xml对应一张图片,图片文件名就是前面去掉.xml后的.jpg。解析时不要自己改文件名,否则图片和标签的对应关系会断。
<annotation> <folder>roboflow</folder> <filename>connector_127_png_jpg.rf.40e2d9f8438e16707d6c8d78f8b4fb96.jpg</filename> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>CCS2</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>328</xmin> <ymin>411</ymin> <xmax>712</xmax> <ymax>689</ymax> </bndbox> </object> </annotation>bndbox保存的是原始像素坐标,不是归一化坐标。width和height就是图片分辨率,坐标必须落在0到width、0到height之间。如果一个xml里有多辆车的多个充电口,就会有多个<object>块,每个块有自己的name和bndbox。Roboflow导出时还可能附带<segmented>等其他标签,训练用不到,直接忽略即可。如果这批XML是公开数据集中下载的,需要注意zip里可能只有xml没有jpg;此时需要先把对应原图补齐再进训练流程,否则后面所有脚本都会空转。
3. 从VOC清洗到YOLO格式:坐标归一化的正确姿势
训练前第一件事不是转格式,而是看类别分布。比如Type1的样本量远多于CHAdeMO时,模型会把所有偏圆的接口都判成Type1。先解析全部XML,统计每个<name>出现次数,顺便找出损坏文件。这个脚本在任何有XML格式的数据集上都通用,不限于充电口识别。
3.1 解析XML统计类别分布
import glob import xml.etree.ElementTree as ET from collections import Counter xml_files = glob.glob("/data/charging_inlet/*.xml") counter = Counter() broken = [] for xf in xml_files: try: tree = ET.parse(xf) except ET.ParseError as e: broken.append((xf, str(e))) continue root = tree.getroot() for obj in root.findall("object"): label = obj.find("name").text.strip() counter[label] += 1 print("label count:", dict(counter)) print("broken xml:", broken)这里glob的模式按实际目录改,如果XML分散在train/valid/test三个子目录,需要把模式换成**/*.xml并在glob.glob里加recursive=True。统计分析中label拼写不统一是最常见的坑,比如CCS1和ccs1并存,合并后数量才真实。如果某个类少于20个框,直接用这个类训练会得不到稳定梯度,需要先做类别重采样或复制增强。损坏XML的数量如果超过1%,要回头检查是不是文件没下载完整,而不是直接删除。
提示:统计类别时同时记录每个标签框的面积,CHAdeMO的框通常比Type2小,面积分布能告诉你是不是存在大量套错的框。
3.2 转换为YOLO txt:归一化坐标
YOLO训练框架不直接读VOC XML,需要把绝对像素坐标转成相对坐标,每个txt文件的一行对应一个框:class x_center y_center width height。转换时最忌讳的是用固定宽度1280、固定高度720去归一化,因为压缩包里的原图分辨率不统一。
import glob import os import xml.etree.ElementTree as ET CLASSES = ["Tesla", "CCS1", "CCS2", "CHAdeMO", "Type1", "Type2"] OUT_DIR = "labels_yolo" def normalize(xml_file): tree = ET.parse(xml_file) root = tree.getroot() size = root.find("size") img_w = float(size.find("width").text) img_h = float(size.find("height").text) lines = [] for obj in root.findall("object"): name = obj.find("name").text.strip() if name not in CLASSES: name = {"tesla": "Tesla", "ccs1": "CCS1", "ccs2": "CCS2", "chademo": "CHAdeMO", "type1": "Type1", "type2": "Type2"}.get(name) if name is None: continue cls_idx = CLASSES.index(name) box = obj.find("bndbox") xmin = float(box.find("xmin").text) ymin = float(box.find("ymin").text) xmax = float(box.find("xmax").text) ymax = float(box.find("ymax").text) x_center = (xmin + xmax) / 2.0 / img_w y_center = (ymin + ymax) / 2.0 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h if min(w, h) <= 0 or w > 1 or h > 1: continue lines.append(f"{cls_idx} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") return "\n".join(lines), os.path.basename(xml_file).replace(".xml", ".txt") os.makedirs(OUT_DIR, exist_ok=True) for xf in glob.glob("/data/charging_inlet/*.xml"): content, out_name = normalize(xf) if content: with open(os.path.join(OUT_DIR, out_name), "w") as f: f.write(content)转换时除以img_w和img_h而不是固定值,是因为训练集中可能有1920x1080也有1280x720。过滤掉宽高非正数或超过1的框,这类标注通常是标注工具导出的脏数据,留着会让loss变成nan。上面的字典映射同时兼容了大小写不一致的情况,把chademo归一化成CHAdeMO,避免同一个类在标签文件里被拆成两类。
3.3 训练/验证划分的细节
Roboflow原生导出时如果选了train/valid/test比例,会直接生成三个目录;如果下载的只有单个标注文件夹,就要自己划分。按8:1:1随机划分是常见做法,但必须保证同一张图片的所有框都在同一个集合里,不能一张图的标注一半在train一半在val。
import random, shutil, pathlib random.seed(42) src = pathlib.Path("/data/charging_inlet/images") dst = pathlib.Path("/data/charging_inlet/split") for d in ("train", "val", "test"): (dst / d / "images").mkdir(parents=True, exist_ok=True) (dst / d / "labels").mkdir(parents=True, exist_ok=True) files = list(src.glob("*.jpg")) random.shuffle(files) n = len(files) cuts = [int(n*0.8), int(n*0.9)] for idx, f in enumerate(files): if idx < cuts[0]: split = "train" elif idx < cuts[1]: split = "val" else: split = "test" lab = pathlib.Path("/data/charging_inlet/labels") / (f.stem + ".txt") shutil.copy(f, dst / split / "images" / f.name) if lab.exists(): shutil.copy(lab, dst / split / "labels" / lab.name)随机种子42保证每次划分结果固定。注意这里按文件名配对,如果xml转换为txt时文件名被改过,需要保持与jpg同名,否则训练时图像和标签对不上,yolo不会报错只会忽略所有目标。划分完成后检测一下每个集合的标签覆盖率,用下面命令快速对比:
cd /data/charging_inlet/split for d in train val test; do img=$(ls $d/images | wc -l) lab=$(ls $d/labels | wc -l) echo "$d images=$img labels=$lab" done如果labels数量比images少,说明有图无标签,直接用循环比对文件名把缺失标签的图移出训练集。这一步不做,训练过程会显得很正常,但验证集里这张图永远没有预测目标,mAP会莫名偏低。
4. 基于YOLOv8的充电口识别训练与91.1%精度复现
充电口目标在图像中属于中等尺度目标,宽高比相对固定,YOLOv8s在640分辨率下推理延迟能压到几十毫秒,适合机械臂实时插枪。Faster R-CNN在小目标、密集场景更好,但这里的误分类更多来自形状相似而非尺度差异,单阶段检测加数据增强在几百张图上更容易收敛。摘要给出的91.1%在目标检测里一般对应mAP50口径,也就是IoU阈值为0.5时的平均精度,不是分类准确率;把它当成验证集上的基准线更合理。
4.1 data.yaml与训练命令
把上一章的split目录组织好之后,写一份数据描述文件。六个类名的顺序必须和转换脚本里CLASSES列表保持一致,否则Tesla标签会落到CCS1的类别索引上,训练出来的模型完全没法用。
path: /data/charging_inlet/split train: train/images val: val/images nc: 6 names: ['Tesla', 'CCS1', 'CCS2', 'CHAdeMO', 'Type1', 'Type2']yolo detect train \ data=charging_inlet.yaml \ model=yolov8s.pt \ epochs=200 \ imgsz=640 \ batch=16 \ optimizer=AdamW \ lr0=0.001 \ lrf=0.01 \ patience=30 \ mosaic=1.0 \ mixup=0.2 \ fliplr=0.5 \ cache=ram \ project=charging_yolo \ name=v8s参数表里几个直接影响精度的点:imgsz不能低于512,CCS1和CCS2的区分点在底部两个直流孔,缩到448时这些孔可能只剩几个像素;batch要按显存调整,batch小就同步调低lr0;mosaic把四张图拼在一起,能缓解CHAdeMO样本少的问题,但最后10个epoch建议关掉,否则拼接边界会让验证集mAP出现抖动。
| 参数 | 建议值 | 说明 |
|---|---|---|
| imgsz | 640 | 保留Type1/Type2内部针脚纹理,960更稳但显存翻倍 |
| batch | 16 | 以单卡显存为准,batch小则调低lr0 |
| optimizer | AdamW | 小数据集收敛比SGD快,mAP波动略大 |
| mosaic | 1.0 | 缓解CHAdeMO样本少的问题;最后阶段关掉 |
| mixup | 0.2 | 提升形状相似类别的泛化,过大会导致边缘模糊 |
| patience | 30 | 验证集mAP连续30轮不升就停,避免过拟合 |
训练过程中如果loss变成nan,优先检查三件事:XML转换出的坐标是否越界、data.yaml里nc是否与实际类别数一致、batch是否超过显存导致混合精度溢出。这三个原因在充电口数据集上发生的概率远高于模型结构问题。
4.2 验证指标与混淆矩阵解读
训练结束后单独跑一次验证,conf设低是为了让每个候选框都参与mAP计算,否则一些低置信度的真样本会被提前过滤掉,指标虚高。
yolo detect val \ data=charging_inlet.yaml \ model=charging_yolo/v8s/weights/best.pt \ split=val \ conf=0.001 \ iou=0.6看结果时重点盯三类指标:CCS1的recall、CHAdeMO的precision、Tesla的recall。CCS1和CCS2混淆,是因为两个直流孔在图像里太小;CHAdeMO precision低,是因为Type2倒置后轮廓接近;Tesla recall低,通常因为深色车漆导致整个充电口区域对比度不足。混淆矩阵在charging_yolo/v8s/confusion_matrix.png,如果两个类别连续多轮互混,先补这两个类的近距离样例,而不是调NMS阈值。
5. 从验证集到实车:部署时最容易翻车的三个细节
5.1 充电口 vs 充电枪:负样本不能省
在真实场景,摄像头经常看到插枪头而不是插座,训练集如果只有插座,模型会把枪头误报成CCS2。常见做法是收集几百张枪头照片作为负样本,单独建一个plug类,让模型把充电枪和插座区分开。如果不想加类别,就在NMS之后检查检测框的宽高比,充电口的框通常更宽扁,枪头更窄长。
5.2 低光与反光:用颜色增强替代换模型
夜间充电口识别,红外补光下Type1和Type2轮廓几乎一样,此时不需要换大模型,在原有best.pt上做颜色增强重训一轮就行:
yolo detect train \ model=charging_yolo/v8s/weights/best.pt \ data=charging_inlet.yaml \ epochs=50 \ imgsz=640 \ hsv_h=0.015 \ hsv_s=0.7 \ hsv_v=0.4 \ degrees=5 \ translate=0.1 \ scale=0.5 \ fliplr=0.5 \ cache=Truehsv_v=0.4把亮度变化范围拉大,让模型不依赖金属反光位置;degrees=5限制旋转,因为充电口不会倒置,过度旋转会产生伪样本。重新训练时用小学习率,epochs给50就够,再长会破坏之前学到的轮廓特征。
5.3 导出ONNX并调整NMS阈值
验证完直接导出ONNX,在工控机上用onnxruntime跑推理,可以摆脱PyTorch环境依赖:
yolo export model=charging_yolo/v8s/weights/best.pt format=onnx opset=12 imgsz=640 dynamic=True simplify=True导出后建议conf阈值取0.35到0.45,iou阈值取0.45到0.5。如果类间重叠严重,把conf调高,因为充电口场景宁可漏检也不要误报。如果部署的是Tesla专用识别,直接把conf提到0.65,漏检的影响远小于把Type2误判成CCS2后的插枪动作。
本文还有配套的精品资源,点击获取