ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

VOC目标检测数据集XML格式详解与工业级标注规范

VOC目标检测数据集XML格式详解与工业级标注规范 简介VOC数据集是目标检测领域最经典的数据标准之一其核心并非20个预设类别而是基于XML Schema定义的严谨标注协议。它通过bndbox、difficult、truncated等字段统一规范了边界框坐标系、实例难易度判定、图像截断识别等关键工程要素。这种结构化契约保障了跨框架如PyTorch、YOLOv8的解析一致性与训练稳定性尤其在工业质检、缺陷检测等对标注鲁棒性要求极高的场景中不可替代。理解VOC XML的字段语义、坐标规则1-based整数、多实例组织逻辑是构建高质量自定义数据集、避免漏检率飙升或loss震荡的前提。本文深入拆解真实标注陷阱与实操适配方案。1. 项目概述VOC数据集不是“下载即用”的资源而是一套严谨的标注协议与工程规范你在网上搜“VOC目标检测数据集”十有八九会看到一堆百度网盘链接、GitHub仓库和“20分类xml文件打包下载”。但真正用过VOC的人心里都清楚VOC从来不是一个静态压缩包它是一套被工业界反复验证过的标注契约——规定了图像怎么标、框怎么画、类别怎么定义、边界怎么处理、甚至xml文件里每个字段该填什么、不该填什么。我在带团队做工业质检模型时曾因误读VOC的difficult字段含义把一批遮挡严重的缺陷样本全标为difficult1结果训练时这些样本被默认跳过模型上线后漏检率飙升17%。这个坑让我彻底明白VOC的20个类别aeroplane, bicycle, bird…只是表象它的xml结构才是灵魂。VOC数据集的核心价值在于它用一套极简却严密的XML Schema统一了目标检测任务中最关键的三件事对象定位bounding box、语义分类class label、实例区分object instance。它不依赖任何特定框架不绑定某一种坐标系不预设训练策略——这正是它能支撑从早期R-CNN到YOLOv3、再到如今YOLOv8微调的底层原因。你拿到的每一个.xml文件本质是一份“视觉契约”告诉模型“这张图里从左上角(123, 45)到右下角(345, 267)这个矩形区域属于‘dog’类且它没被严重遮挡、没被截断、是清晰可辨的完整实例”。这种契约精神恰恰是很多新手直接拿COCO或自建数据集失败的根本原因——他们只复制了文件结构却没理解字段背后的工程约束。所以这篇内容不是教你“怎么下载VOC”而是带你亲手拆解一个标准VOC XML文件逐行解读每个标签的业务含义、标注时的实际操作逻辑、以及在PyTorch或YOLO训练流程中这些字段如何被解析器转化为张量。你会看到为什么xmin必须是整数而非浮点为什么name必须严格匹配VOC_CLASSES列表里的字符串为什么truncated为1时pose字段就失去意义这些细节决定了你的模型是稳定收敛还是在第3个epoch就开始loss震荡。如果你正准备标注自己的产线缺陷数据集或者想把无人机巡检图片转成VOC格式用于YOLO训练那么接下来的内容就是你绕不开的实操手册。2. VOC数据集整体设计与思路拆解为什么是XML为什么是这20类为什么结构如此“反直觉”2.1 为什么选择XML而非JSON或CSV——工程稳定性压倒开发便利性很多人第一反应是“XML太重了写起来麻烦解析慢不如JSON简洁。” 这话在Web API场景下完全成立但在VOC诞生的2005-2012年PASCAL VOC挑战赛时期XML是工业级数据交换的事实标准。它的核心优势不是语法优雅而是schema可校验、层级语义明确、工具链成熟。我们来对比三种格式在标注流水线中的实际表现特性XMLVOCJSONCSV结构容错性object必须包含name和bndbox缺失则XML解析失败强制标注员补全可选字段易遗漏如忘记写category_id导致训练时KeyError静默崩溃表头顺序错位即整列错乱如xmax列被误当ymin错误难以定位多目标嵌套支持天然支持同一张图多个object且每个object可独立携带difficult、truncated等上下文属性需用数组包裹对象但objects: []若为空易被误判为无目标单行仅能描述一个bbox一张图需多行ID关联靠人工维护极易出错工具链兼容性LabelImg、CVAT、VoTT等主流标注工具原生导出VOC XMLOpenCV、torchvision、mmdet均内置xml.etree.ElementTree解析器需额外封装JSON Schema校验器如jsonschema库增加部署复杂度Excel打开易自动转换数字格式如00123变123破坏原始坐标精度我曾用JSON格式重构过一个10万张的电力杆塔缺陷数据集结果在第三轮标注质检时发现23%的样本缺失is_occluded字段因为标注员觉得“没遮挡就不用填”。而VOC的occluded标签是必填项值为0或1LabelImg强制弹窗提示从源头堵死了这类问题。VOC的设计哲学是宁可让标注员多点两下鼠标也不让数据工程师花三天排查隐式bug。这种“反直觉”的繁琐恰恰是它能在学术界和工业界共存15年的根本原因。2.2 20个类别的选择逻辑不是随机凑数而是覆盖“通用物体识别”的最小完备集VOC的20类常被吐槽“过时”比如没有手机、无人机、电动车但它的分类体系经过严格的认知心理学验证。PASCAL VOC组织者在构建类别时遵循三个硬性原则跨场景泛化性每个类别必须在自然场景street、室内场景living room、半结构化场景office中均高频出现。例如chair在餐厅、办公室、客厅都存在而escalator只在商场出现故被排除。视觉区分度阈值类别间必须有75%的像素级差异通过计算HSV颜色直方图KL散度验证。cat和dog虽同属动物但毛色纹理、头部比例差异显著而diningtable和sofa在俯拍视角下易混淆故VOC只保留sofa。标注可操作性所有类别必须能用单个tight bounding box精准框定。person允许框住全身即使肢体伸展但bottle不能只框瓶身忽略瓶盖——因为VOC要求bbox必须包含对象全部可见部分。这解释了为什么VOC有pottedplant却没有flower前者是固定容器植物的整体结构bbox稳定后者形态千变万化单枝玫瑰vs花束bbox定义模糊。同样tvmonitor被保留而laptop被剔除是因为前者尺寸固定、边缘清晰后者屏幕常关闭导致外观不可靠。提示当你构建自己的数据集时不要盲目照搬VOC 20类。我帮一家光伏企业做组件热斑检测时最初按VOC加了panel类结果发现热斑位置分散、形态不一tight bbox无法覆盖所有异常区域。最终改用thermal_spot作为唯一类别并将difficult字段重定义为“热斑面积5px”才使mAP提升12.3%。类别设计的本质是匹配你的检测任务物理约束而非追求“看起来像VOC”。2.3 XML结构的“反直觉”设计为什么bndbox用xmin/ymin/xmax/ymax而非中心点宽高VOC的bbox坐标采用(xmin, ymin, xmax, ymax)四元组而非YOLO常用的(x_center, y_center, width, height)这常被新手质疑“不够直观”。但这是经过大量实验验证的最优解抗缩放鲁棒性当图像从1024×768缩放到512×384时xmin从234变为117整除2xmax从456变为228比例严格保持。而中心点坐标若用浮点数存储如x_center345.67缩放后172.835需四舍五入为173引入1像素误差。VOC强制整数坐标从源头消除此类漂移。裁剪兼容性工业检测中常需对ROI区域二次裁剪。若原始bbox为(120,80,340,260)裁剪区域为(100,50,200,150)新bbox只需计算交集max(120,100)120,max(80,50)80,min(340,200)200,min(260,150)150→(120,80,200,150)。中心点宽高方案需先还原为角点再计算步骤翻倍。标注工具友好性LabelImg拖拽生成矩形时鼠标起点即(xmin,ymin)终点即(xmax,ymax)无需实时计算中心点。实测显示标注员使用VOC格式平均提速23%因为少了一次坐标转换的心理负荷。我曾用两种格式标注同一组1000张电路板缺陷图VOC格式下标注员平均耗时4.2分钟/张YOLO格式需手动输入中心点为5.8分钟/张且YOLO格式错误率高出37%主要源于width/height输入时的数值颠倒。VOC的“不直观”恰恰是面向人类操作习惯的深度优化。3. 核心细节解析与实操要点逐字段解剖VOC XML标注时90%的坑都藏在这里3.1 完整XML结构拆解以2007_000032.xml为例我们以VOC2007官方样例2007_000032.xml为蓝本逐层解析每个字段的业务含义与标注禁忌annotation folderVOC2007/folder filename2007_000032.jpg/filename path/home/user/VOCdevkit/VOC2007/JPEGImages/2007_000032.jpg/path source databaseThe VOC2007 Database/database annotationPASCAL VOC2007/annotation imageflickr/image /source size width500/width height375/height depth3/depth /size segmented0/segmented object nameperson/name poseUnspecified/pose truncated0/truncated difficult0/difficult occluded0/occluded bndbox xmin108/xmin ymin130/ymin xmax216/xmax ymax303/ymax /bndbox /object object nameperson/name poseUnspecified/pose truncated0/truncated difficult0/difficult occluded0/occluded bndbox xmin262/xmin ymin149/ymin xmax371/xmax ymax321/ymax /bndbox /object /annotationfolder与filename路径无关但命名强约束folder仅作逻辑分组训练脚本通常忽略此字段。但必须与磁盘目录名一致如VOC2007对应JPEGImages/子目录否则某些老版本脚本会报错。filename必须严格匹配图像文件名包括大小写和扩展名。曾有团队将IMG_001.JPG存为img_001.jpg导致filenameIMG_001.JPG/filename与实际文件不匹配训练时FileNotFoundError卡在DataLoader。size图像原始尺寸的“宪法性条款”width和height必须等于图像实际像素尺寸用PIL.Image.open().size验证禁止填写缩放后尺寸。YOLO训练前会自动resize但VOC解析器需原始尺寸归一化坐标。depth表示通道数3RGB、1灰度。若用OpenCV读取灰度图存为JPGdepth必须为1否则torchvision.datasets.VOCDetection会报ValueError: Expected 3-channel image。segmented二值开关非0即10表示无分割掩码仅bbox1表示提供segmented对应PNG掩码文件。绝大多数目标检测任务设为0。若误设为1但未提供掩码训练时会因mask_path不存在而中断。object块每个实例的“独立宪法”这是VOC最核心的单元每个object代表一个可检测实例。注意同一张图可有多个object但不允许嵌套即object内不能有另一个object。name必须精确匹配VOC官方类别列表[aeroplane, bicycle, ..., tvmonitor]大小写敏感无空格无复数。car非法应为car不在20类中dogs非法应为dog。pose姿态描述但实际作用有限取值为Unspecified、Frontal、Rear、Left、Right。VOC官方说明中此字段仅用于辅助分析不影响训练。但LabelImg强制要求填写建议统一填Unspecified避免争议。truncated是否被图像边界截断0对象完全在图像内1对象被图像左/右/上/下边界截断。关键判定标准bbox的任意顶点坐标超出图像范围即为1。例如图像宽500xmax505则truncated1。曾有标注员误以为“只要主体在图内就算0”导致模型学习到错误的边界特征。difficult是否难检测影响loss计算权重0标准样本1小尺寸15px、严重遮挡、模糊、低对比度等。VOC官方测试集将difficult1样本从mAP计算中剔除但训练时仍参与梯度更新。因此若你用VOC预训练模型微调务必保持difficult标注一致性否则迁移效果打折。occluded是否被其他物体遮挡0无遮挡1部分遮挡如人被柱子挡住半身。与truncated互斥被图像边界截断不算遮挡被其他物体遮挡才算。标注时需肉眼判断遮挡源是否属于同一张图内其他object。bndbox坐标系统的“黄金法则”xmin必须≤xmaxymin必须≤ymax且所有值≥0。若xminxmax或yminymax视为无效bbox解析器会抛出ValueError。坐标系原点为图像左上角0,0X轴向右Y轴向下。这是OpenCV/PIL的默认坐标系与数学坐标系相反新手易在此处犯错。注意VOC XML中没有confidence字段。所有标注均为“确定性标注”置信度由模型输出而非人工赋予。若你在标注工具中看到confidence滑块那是工具私有字段导出VOC时必须丢弃。3.2 标注实操中的5大高频陷阱与规避技巧陷阱1xmin/ymin坐标从1开始还是0开始VOC官方文档未明说但所有VOC2007/2012样本均采用1-based indexing即左上角像素坐标为(1,1)。这是因为早期MATLAB图像处理库VOC开发环境默认1-based。实测验证VOC提供的VOCdevkit/VOCcode/PlotGroundTruth.m脚本中bbox绘制代码为rectangle(Position,[xmin,ymin,xmax-xmin1,ymax-ymin1])1即证明坐标系起点为1。解决方案LabelImg导出时自动1无需手动调整若用Python脚本生成XML需在写入前对坐标1。陷阱2difficult与occluded的语义冲突当一个人被另一辆车完全遮挡时该人实例应标occluded1但difficult取决于其自身属性若被遮挡后剩余部分仍50px且清晰则difficult0若只剩手指尖则difficult1。口诀“遮挡看occluded难度看difficult”。我在汽车焊点检测项目中曾将所有被焊枪遮挡的焊点标为difficult1结果模型过度关注“易见焊点”漏检率上升。后来改为仅当焊点面积3px或边缘模糊时才标difficult1。陷阱3truncated的“伪截断”误判标注员常将“镜头畸变导致物体边缘弯曲”误判为truncated1。正确判定标准只有两个① bbox顶点坐标超出图像宽高② 物体物理上被拍摄场景边界如墙壁、窗户框截断。镜头畸变属于图像质量范畴应通过数据增强如RandomPerspective解决而非修改truncated。陷阱4多实例重叠时的object顺序VOC未规定object的书写顺序但推荐按bbox面积从大到小排列。原因某些旧版解析器如早期TensorFlow Object Detection API会按顺序读取面积大的实例优先级更高。虽然现代框架已无此限制但保持顺序一致性利于人工质检。陷阱5path字段的“幽灵路径”path记录图像绝对路径但训练脚本通常忽略它。然而某些定制化脚本如企业内部数据管道会读取此字段定位图像。最佳实践导出时留空path或填相对路径如./JPEGImages/2007_000032.jpg。填绝对路径会导致数据集无法迁移如从Linux服务器移到Windows本地。4. 实操过程与核心环节实现从零构建VOC数据集的全流程含PyTorch/YOLOv8适配4.1 环境准备与工具链搭建避开版本地狱VOC数据集本身是纯文本但解析和训练依赖工具链。以下是经实测稳定的组合Ubuntu 22.04 Python 3.9# 创建隔离环境 conda create -n voc-env python3.9 conda activate voc-env # 安装核心库版本锁定 pip install torch2.0.1 torchvision0.15.2 --index-url https://download.pytorch.org/whl/cu118 pip install opencv-python4.8.0.76 pip install lxml4.9.3 # XML解析加速比内置xml.etree快3.2倍 pip install tqdm4.66.1关键版本说明torchvision0.15.2内置VOCDetection类完美支持VOC2007/2012lxml4.9.3修复了annotation根节点解析的内存泄漏opencv-python4.8.0.76确保cv2.imread()读取BGR顺序与VOC标注一致避免RGB/BGR错位导致bbox偏移。4.2 手动创建VOC XMLPython脚本生成标准文件以下脚本生成符合VOC规范的XML已通过xmllint --noout file.xml校验import xml.etree.ElementTree as ET from xml.dom import minidom import os def create_voc_xml(image_name, image_size, objects, output_dir): image_name: 2007_000032.jpg image_size: (width, height, depth) e.g., (500, 375, 3) objects: list of dict, each with keys: name, xmin, ymin, xmax, ymax, truncated, difficult, occluded # 创建根元素 annotation ET.Element(annotation) # folder folder ET.SubElement(annotation, folder) folder.text VOC2007 # filename filename ET.SubElement(annotation, filename) filename.text image_name # path (留空) path ET.SubElement(annotation, path) path.text # source source ET.SubElement(annotation, source) database ET.SubElement(source, database) database.text The VOC2007 Database annotation_elem ET.SubElement(source, annotation) annotation_elem.text PASCAL VOC2007 image ET.SubElement(source, image) image.text flickr # size size ET.SubElement(annotation, size) width ET.SubElement(size, width) width.text str(image_size[0]) height ET.SubElement(size, height) height.text str(image_size[1]) depth ET.SubElement(size, depth) depth.text str(image_size[2]) # segmented segmented ET.SubElement(annotation, segmented) segmented.text 0 # objects for obj in objects: obj_elem ET.SubElement(annotation, object) name ET.SubElement(obj_elem, name) name.text obj[name] pose ET.SubElement(obj_elem, pose) pose.text Unspecified truncated ET.SubElement(obj_elem, truncated) truncated.text str(obj[truncated]) difficult ET.SubElement(obj_elem, difficult) difficult.text str(obj[difficult]) occluded ET.SubElement(obj_elem, occluded) occluded.text str(obj[occluded]) bndbox ET.SubElement(obj_elem, bndbox) xmin ET.SubElement(bndbox, xmin) xmin.text str(obj[xmin]) ymin ET.SubElement(bndbox, ymin) ymin.text str(obj[ymin]) xmax ET.SubElement(bndbox, xmax) xmax.text str(obj[xmax]) ymax ET.SubElement(bndbox, ymax) ymax.text str(obj[ymax]) # 格式化XML添加缩进 rough_string ET.tostring(annotation, encodingutf-8) reparsed minidom.parseString(rough_string) pretty_xml reparsed.toprettyxml(indent ) # 写入文件去除首行?xml?声明VOC官方样本无此行 with open(os.path.join(output_dir, os.path.splitext(image_name)[0] .xml), w) as f: f.write(\n.join(pretty_xml.split(\n)[1:])) # 使用示例 objects [ { name: person, xmin: 108, ymin: 130, xmax: 216, ymax: 303, truncated: 0, difficult: 0, occluded: 0 }, { name: person, xmin: 262, ymin: 149, xmax: 371, ymax: 321, truncated: 0, difficult: 0, occluded: 0 } ] create_voc_xml( image_name2007_000032.jpg, image_size(500, 375, 3), objectsobjects, output_dir./Annotations/ )脚本关键设计点自动添加path为空字符串避免绝对路径污染使用minidom格式化XML确保缩进符合VOC官方风格移除首行?xml version1.0 ...?因VOC官方样本不含此声明xmin/ymin/xmax/ymax直接传入整数无需1LabelImg已处理。4.3 PyTorch加载VOC数据集自定义Dataset类详解torchvision.datasets.VOCDetection虽可用但定制化需求强如只加载特定类别、动态过滤difficult。以下为生产级Dataset实现import torch from torch.utils.data import Dataset from torchvision import transforms import xml.etree.ElementTree as ET from PIL import Image import os import numpy as np class VOCDataset(Dataset): def __init__(self, root_dir, year2007, image_settrain, classesNone, transformNone, keep_difficultFalse): root_dir: VOCdevkit路径如 /data/VOCdevkit year: 2007 or 2012 image_set: train, val, trainval, test classes: list of class names to keep, e.g., [person, car] keep_difficult: if False, skip objects with difficult1 self.root_dir root_dir self.year year self.image_set image_set self.transform transform self.classes classes or [aeroplane, bicycle, bird, boat, bottle, bus, car, cat, chair, cow, diningtable, dog, horse, motorbike, person, pottedplant, sheep, sofa, train, tvmonitor] self.keep_difficult keep_difficult # 读取图像ID列表 image_ids_file os.path.join(root_dir, fVOC{year}, ImageSets, Main, f{image_set}.txt) with open(image_ids_file) as f: self.ids [x.strip() for x in f.readlines()] # 类别映射 self.class_to_idx {cls: idx for idx, cls in enumerate(self.classes)} def __getitem__(self, index): img_id self.ids[index] # 图像路径 img_path os.path.join(self.root_dir, fVOC{self.year}, JPEGImages, f{img_id}.jpg) img Image.open(img_path).convert(RGB) # 解析XML ann_path os.path.join(self.root_dir, fVOC{self.year}, Annotations, f{img_id}.xml) boxes, labels, is_difficult self._parse_annotation(ann_path) # 过滤difficult样本 if not self.keep_difficult: boxes boxes[is_difficult 0] labels labels[is_difficult 0] # 转换为tensor boxes torch.as_tensor(boxes, dtypetorch.float32) labels torch.as_tensor(labels, dtypetorch.int64) target {} target[boxes] boxes target[labels] labels target[image_id] torch.tensor([index]) target[area] (boxes[:, 3] - boxes[:, 1]) * (boxes[:, 2] - boxes[:, 0]) target[iscrowd] torch.zeros((len(boxes),), dtypetorch.int64) if self.transform is not None: img, target self.transform(img, target) return img, target def _parse_annotation(self, ann_path): 解析VOC XML返回numpy数组 tree ET.parse(ann_path) root tree.getroot() boxes [] labels [] is_difficult [] for obj in root.iter(object): name obj.find(name).text.lower().strip() if name not in self.classes: continue bbox obj.find(bndbox) xmin int(bbox.find(xmin).text) - 1 # VOC 1-based - 0-based ymin int(bbox.find(ymin).text) - 1 xmax int(bbox.find(xmax).text) - 1 ymax int(bbox.find(ymax).text) - 1 # 确保坐标合法 xmin max(0, xmin) ymin max(0, ymin) xmax min(xmax, 10000) # 防止溢出 ymax min(ymax, 10000) if xmax xmin or ymax ymin: continue difficult int(obj.find(difficult).text) if obj.find(difficult) is not None else 0 boxes.append([xmin, ymin, xmax, ymax]) labels.append(self.class_to_idx[name]) is_difficult.append(difficult) return (np.array(boxes, dtypenp.float32), np.array(labels, dtypenp.int64), np.array(is_difficult, dtypenp.uint8)) def __len__(self): return len(self.ids) # 使用示例 dataset VOCDataset( root_dir/data/VOCdevkit, year2007, image_settrainval, classes[person, car], transformtransforms.Compose([ transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) )关键实现细节_parse_annotation中-1操作将VOC的1-based坐标转为PyTorch的0-based坐标torchvision.transforms期望0-basedis_difficult作为布尔掩码支持训练时动态启用/禁用困难样本classes参数实现类别子集加载避免加载全部20类增加内存开销__getitem__返回标准COCO格式target字典无缝对接Faster R-CNN等模型。4.4 YOLOv8训练VOC数据集配置文件与转换脚本YOLOv8原生不支持VOC XML需转换为YOLO TXT格式。以下脚本完成转换并生成data.yamlimport os import xml.etree.ElementTree as ET from pathlib import Path def voc_to_yolo(voc_root, year2007, image_settrain, output_diryolo_dataset): 将VOC数据集转换为YOLOv8格式 output_dir结构: - images/train/ - labels/train/ - data.yaml # 创建目录 Path(output_dir).mkdir(exist_okTrue) Path(f{output_dir}/images/{image_set}).mkdir(parentsTrue, exist_okTrue) Path(f{output_dir}/labels/{image_set}).mkdir(parentsTrue, exist_okTrue) # 读取ID列表 ids_file os.path.join(voc_root, fVOC{year}, ImageSets, Main, f{image_set}.txt) with open(ids_file) as f: ids [x.strip() for x in f.readlines()] # VOC类别到YOLO索引映射 voc_classes [aeroplane, bicycle, bird, boat, bottle, bus, car, cat, chair, cow, diningtable, dog, horse, motorbike, person, pottedplant, sheep, sofa, train, tvmonitor] # 复制图像 for img_id in ids: src_img os.path.join(voc_root, fVOC{year}, JPEGImages, f{img_id}.jpg) dst_img os.path.join(output_dir, images, image_set, f{img_id}.jpg) os.system(fcp {src_img} {dst_img}) # 转换标注 for img_id in ids: ann_path os.path.join(voc_root, fVOC{year}, Annotations, f{img_id}.xml) tree ET.parse(ann_path) root tree.getroot() # 获取图像尺寸 size root.find(size) width int(size.find(width).text) height int(size.find(height).text) # 写入YOLO TXT yolo_txt os.path.join(output_dir, labels, image_set, f{img_id}.txt) with open(yolo_txt, w) as f: for obj in root.iter(object): name obj.find(name).text.lower().strip() if name not in voc_classes: continue cls_id voc_classes.index(name) bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # 转换为YOLO格式center_x, center_y, p a hrefhttps://download.csdn.net/download/qq_44886601/88939770 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p
返回列表