news 2026/10/10 5:00:18

配电变压器检测图像数据集:3000幅VOC标注助你快速跑通目标检测

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
配电变压器检测图像数据集:3000幅VOC标注助你快速跑通目标检测

简介:配电变压器检测图像数据集面向计算机视觉与电力巡检自动化方向的研究者,提供约3000幅配网航拍图像及对应的VOC格式标注,可用于训练YOLO、Faster R-CNN等目标检测模型,帮助识别与定位配电变压器。资源包内共2000个XML标签文件,压缩包大小282.42MB,每个XML均记录变压器边界框坐标与类别信息,便于直接构建训练集与验证集。该数据集贴近真实工业场景,适合电力设备智能监测、故障预警等应用研究。目前已有599人学习下载。通过该资源可掌握VOC标注解析、目标检测模型训练与评估流程,为自动巡检系统开发提供真实数据支撑,也是验证检测算法性能的实用素材。

1. 配电变压器检测图像数据集:3000幅VOC标签图到底能解决什么

做电网巡检目标检测时,真正让人头疼的往往不是模型结构,而是没有贴合场景的训练数据。配电变压器在巡检画面里占比小、外观相近、背景干扰多,通用目标检测数据集很难直接派上用场。这个配电变压器检测图像数据集正是冲着这个缺口来的:3000幅图像,全部用VOC标签格式标注,每一台变压器的位置、类别都在XML框里给出,适合用来验证检测算法、做迁移学习起点,也适合小团队先在本地把流程跑通,再决定要不要继续投入采集和标注。

这里有个反直觉的结论:3000幅图不算多,但比不少动辄几万张的通用数据集更适合电力场景落地。原因很简单,通用数据集里的“变压器”要么数量太少,要么来自各种非配电场景,迁移后反而拉低精度。而这份数据把配电变压器单独拎出来,目标明确,标注格式又是工程界最熟悉的VOC,拿来即用,省掉大量洗数据和重标工作。适合的人也很清楚:做输电巡检算法验证的工程师、刚接触目标检测的学生,以及需要快速产出配电设备识别Demo的团队。

2. 数据集构成与VOC标注细节:读不懂XML就没法谈训练

很多人在拿到VOC格式数据后,第一反应是直接扔给训练脚本,结果不是路径读不到,就是类别名对不上。VOC格式本身不复杂,但它有几个约定必须在动手前确认清楚,否则后面每一步都会被带偏。

2.1 目录与文件对应关系:JPEG、XML与类别列表怎么配套

这类配电变压器检测数据集最常见的目录布局,是沿用Pascal VOC那套约定。图片放在JPEGImages目录下,同名XML放在Annotations目录下,ImageSets/Main目录里放train.txt、val.txt这类训练验证划分文件。拿到手的第一步,不是去看图片长什么样,而是先核对这三部分是否一一对应。

建议先跑一段最基础的核对脚本,把有图无标签、有标签无图、XML无法解析这三种情况全部筛出来。我之前接手过一批数据,表面上看图片和XML数量一致,结果里面有十几张图对应的XML文件内容是空壳,只有annotation根节点,没有任何object子节点。这类文件在训练时不会报错,但会让模型学到不少空背景,干扰还挺隐蔽。

import os import xml.etree.ElementTree as ET img_dir = "JPEGImages" xml_dir = "Annotations" for xml_name in os.listdir(xml_dir): stem = os.path.splitext(xml_name)[0] img_path = os.path.join(img_dir, stem + ".jpg") xml_path = os.path.join(xml_dir, xml_name) if not os.path.exists(img_path): print(f"[缺失图片] {xml_path}") continue try: tree = ET.parse(xml_path) objs = tree.getroot().findall("object") if len(objs) == 0: print(f"[空标注] {xml_path}") except ET.ParseError: print(f"[XML解析失败] {xml_path}")

这段脚本的逻辑很简单:先按文件名前缀匹配图片和XML,再检查XML是否能解析、是否包含object节点。参数方面,img_dir和xml_dir按实际路径改就行;图片后缀如果不是.jpg,把第9行的后缀同步替换。跑完如果输出为空,说明目录配套没问题,可以进入下一步。多数人在这一步会偷懒跳过,但这几分钟排查,能避免后面训练到一半才发现数据文件损坏的尴尬。

2.2 配电变压器场景的常见类别设计与标注粒度

VOC标注里最重要的就是object节点下的name字段,它决定了模型最终能识别什么。配电变压器检测数据集的类别设计通常不会只写一个笼统的“变压器”,而是拆成更细的部件或状态,比如变压器本体、套管、油位表、散热器、锈蚀区域等。拆得细的好处是,模型不仅能定位设备,还能辅助判断设备状态,这对巡检场景很有价值。

但类别粒度也直接决定了标注难度。如果一份标注里“变压器本体”和“散热器”经常重叠,训练时模型容易在两个类之间摇摆。我在实际项目中一般会把目标分为三类来评估:设备整体类、设备部件类、缺陷状态类。设备整体类适合做区域定位,部件类和缺陷类适合做细粒度诊断。拿到这份3000幅的数据后,建议先把所有XML里的name去重,做一次类别频次统计,看看每类样本量是否均衡。

import os import xml.etree.ElementTree as ET from collections import Counter xml_dir = "Annotations" counter = Counter() for xml_name in os.listdir(xml_dir): xml_path = os.path.join(xml_dir, xml_name) tree = ET.parse(xml_path) for obj in tree.getroot().findall("object"): name = obj.findtext("name") if name: counter[name] += 1 for cat, cnt in counter.most_common(): print(f"{cat}: {cnt}")

这段代码的核心是counts。输出后如果发现某个类别只有几十个样本,而另一个类别有两三千个,就要提前想好处理策略:加权采样、复制粘贴增强,或者干脆在初版模型里先合并该类。类别频次统计是关键,模型loss设计、召回率评估、样本增强策略都以它为基础。很多人直接跳过这步就开始训练,等到验证集里某个类AP为0才回头查,已经浪费不少时间。

2.3 为什么选择VOC标注而不是直接给COCO或YOLO格式

拿到数据集时可能会产生一个疑问:既然现在主流检测框架都支持COCO或YOLO的txt格式,为什么还要用VOC这类相对传统的格式?我个人的理解是,VOC格式相当于一个“原始存储格式”,它信息更完整,转换成本低,而且最容易人工检查和修改。

VOC的XML里包含图片尺寸、目标类别、坐标、是否截断、是否难例等元信息,这些信息在转换时可以按需取舍。而COCO的JSON体积大、手工编辑麻烦;YOLO的txt是归一化坐标,肉眼很难直接看出标注位置对不对。所以数据集的制作者把VOC作为交付格式是合理的,使用者拿到后按自己的训练框架转换即可,这也是为什么网上大量预处理脚本都以VOC为输入。把VOC当中间格式还有个额外好处:如果后续要做类别合并或删除,直接改XML比改JSON直观得多。

这个环节的教训是:不要因为VOC格式“老”就认为它过时。凡是能稳定描述目标框、能被各种脚本解析、能追溯原始信息的格式,就是好格式。越接近原始保存状态的格式,越不容易在转换链路上把信息搞丢。

3. 训练前必做的转换:VOC转YOLO与数据集切分

VOC格式虽然便于人工检查,却不能被大多数训练脚本直接消费。实际训练前,我一般会把VOC转成YOLO的txt格式,偶尔需要COCO JSON时再做一次转换。这一章给出转换脚本和切分方法,重点说明坐标计算和数据划分这两个容易出错的环节。

3.1 VOC转YOLO txt:归一化坐标计算是整个流程的核心

YOLO系列训练框架需要的是每个目标一行文本,格式为“类别ID 中心点x 中心点y 宽度 高度”,且四个坐标值都必须归一化到0到1之间。转换的核心是把XML里的绝对像素坐标换算成相对整张图的归一化坐标,换算公式是:中心点x=(xmin+xmax)/2/图宽,框宽=(xmax−xmin)/图宽。

import os import xml.etree.ElementTree as ET from pathlib import Path def voc_to_yolo(xml_path, out_dir, classes): tree = ET.parse(xml_path) root = tree.getroot() size = root.find("size") img_w = int(size.find("width").text) img_h = int(size.find("height").text) out_lines = [] for obj in root.findall("object"): name = obj.findtext("name") if name not in classes: continue cls_id = classes.index(name) box = obj.find("bndbox") xmin = float(box.findtext("xmin")) ymin = float(box.findtext("ymin")) xmax = float(box.findtext("xmax")) ymax = float(box.findtext("ymax")) 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 # 防止边缘目标归一化后超出[0,1],做个截断更保险 x_center = min(max(x_center, 0.0), 1.0) y_center = min(max(y_center, 0.0), 1.0) w = min(max(w, 0.0), 1.0) h = min(max(h, 0.0), 1.0) out_lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") out_path = Path(out_dir) / (Path(xml_path).stem + ".txt") out_path.write_text("\n".join(out_lines), encoding="utf-8") xml_dir = "Annotations" out_dir = "labels" classes = ["transformer", "bushing", "oil_gauge", "radiator"] os.makedirs(out_dir, exist_ok=True) for xml_name in os.listdir(xml_dir): if xml_name.endswith(".xml"): voc_to_yolo(os.path.join(xml_dir, xml_name), out_dir, classes)

这段脚本里的classes列表必须和训练配置里的类别顺序完全一致,否则会出现类别ID错位。比如训练配置里把bushing排在第一个,而脚本里把transformer排在第一个,模型就会把套管当成变压器学习。这个错误不会报错,只会让精度变得很奇怪。坐标截断那三行是防止目标刚好压在图片边缘时,归一化值略大于1导致后处理出错。文本里的:.6f是保留六位小数,对小目标检测来说精度足够,没必要保留更多位。

3.2 按类别均衡划分训练集与验证集:随机切分容易翻车

很多人在切分数据集时不假思索地随机抽取,但配电变压器数据集往往类别分布不均。如果随机切分时把某个小类的样本全部划进训练集,验证集里这一类的AP永远算不出来,甚至报“找不到该类别”的错误。我习惯用分层抽样,保证每个类在训练集和验证集中都有一定比例。

import os import random from collections import defaultdict random.seed(42) xml_dir = "Annotations" label_to_files = defaultdict(list) for xml_name in os.listdir(xml_dir): xml_path = os.path.join(xml_dir, xml_name) tree = ET.parse(xml_path) names = set() for obj in tree.getroot().findall("object"): name = obj.findtext("name") if name: names.add(name) key = tuple(sorted(names)) label_to_files[key].append(os.path.splitext(xml_name)[0]) train_files = [] val_files = [] for key, file_list in label_to_files.items(): random.shuffle(file_list) val_count = max(1, int(len(file_list) * 0.15)) val_files.extend(file_list[:val_count]) train_files.extend(file_list[val_count:]) with open("train.txt", "w") as f: f.write("\n".join(train_files)) with open("val.txt", "w") as f: f.write("\n".join(val_files))

这段脚本按“该类组合”分组后按比例切分,比单纯按单类切分更稳。val_count取max(1, 15%)是为了防止某个类组合只有一两张时验证集直接为空。固定random.seed(42)很关键,否则每次运行切分结果都不一样,后续实验对比就无法保证公平。train.txt和val.txt里写的是文件名前缀,训练框架加载时通常会自动拼接图片和标注路径。

3.3 转换结果自检:把标注框画回图像看一眼

转换脚本写完,输出文件看起来也生成了,但坐标方向、类别名有没有错位,光看txt文本很难发现。我的做法是挑20到30张图,把转换后的txt标注框画回原图,保存为可视化结果,人工快速过一遍。

import cv2 def draw_yolo_label(img_path, txt_path, classes, out_path): img = cv2.imread(img_path) h, w = img.shape[:2] with open(txt_path, "r", encoding="utf-8") as f: for line in f: parts = line.strip().split() if len(parts) != 5: continue cls_id = int(parts[0]) x_center = float(parts[1]) * w y_center = float(parts[2]) * h bw = float(parts[3]) * w bh = float(parts[4]) * h x1 = int(x_center - bw / 2) y1 = int(y_center - bh / 2) x2 = int(x_center + bw / 2) y2 = int(y_center + bh / 2) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, classes[cls_id], (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imwrite(out_path, img) draw_yolo_label("JPEGImages/sample.jpg", "labels/sample.txt", ["transformer", "bushing", "oil_gauge", "radiator"], "check/sample.jpg")

自检时重点看三处:框是不是明显偏移、框是否小于目标实际范围、类别文本是否和目标实物匹配。如果一个框整体往左上方偏了半个身位,多半是归一化公式用错;如果框的宽或高明显不对,多半是宽高计算时把坐标顺序搞反了。这步自检虽然机械,但能省下后面训练时的排障时间。

4. 用3000幅数据把检测模型跑通:最小训练流程与参数调整

数据和转换都准备好了,接下来是训练环节。很多人一上来就想调大模型、堆参数,但小数据集更看重的是流程能不能平稳跑通、指标能不能反映真实水平。这一章按最小可行方案来写,先跑通再优化。

4.1 训练数据与类别配置文件:先让框架认识你的数据

以YOLO系检测框架为例,训练前需要准备两个文件:一个是数据配置文件,写清楚训练集路径、验证集路径和类别数量;另一个是模型配置文件,写清楚结构或选择预训练权重。数据配置文件的格式在不同版本里略有差异,但核心字段一致。常见写法是:

train: /data/train.txt val: /data/val.txt nc: 4 names: ['transformer', 'bushing', 'oil_gauge', 'radiator']

train和val字段指向的就是上一章切分生成的txt文件,nc是类别数,names列表必须和转换脚本里的classes顺序一致。这里最容易犯的错是names顺序和classes不一致,或者nc填错,训练时不报错,但预测结果全乱。我一般在写完配置文件后,会先打印一行“类别顺序校验”把两个来源的顺序打出来对比一下,确认无误再跑训练。

4.2 模型选择与训练参数:图像尺寸、batch、学习率怎么定

3000幅图的数据量,不建议一开始就上最大最重的模型。常见做法是先选一个基础版本作为基线,比如输入尺寸640,预训练权重用检测模型自带的那套。这样跑一轮的速度快,显存占用也能被大多数显卡接受。表里是我在这种数据集上经常用的初始参数。

参数推荐起点调整建议
输入图像尺寸640x640目标太小就升到960,否则保持640
batch size16显存不够降到8,但学习率也对应降低
初始学习率0.01收敛不稳就降到0.001
训练轮数100看验证集mAP不再上升就提前停
数据增强mosaic、随机翻转小目标多时关闭重mosaic避免切碎目标
冻结训练前50轮冻结骨干数据少时能防止过拟合且加速收敛

图像尺寸的选择直接决定小目标能不能被检出。配电变压器在巡检图中往往只占200x200像素甚至更小,如果输入尺寸降到416,目标区域可能只剩几十个像素,特征被压缩得很严重。所以我一般从640起步,如果验证集显示小目标AP明显偏低,再尝试输入960,代价是训练时间变长。batch size和学习率需要联动调整,batch越大,学习率可以适当调高,但3000张图的数据量本身不大,不用刻意追求大batch。

运行训练的命令比较直接,核心是把数据配置文件和预训练权重传进去。epoch先设到100,打开早停机制,关注验证集指标。训练过程中不要频繁改参数,每次只改一个变量,否则出了问题根本不知道是哪一步导致的。

4.3 训练后验收:别只看总mAP,要看每个类别的AP分布

训练结束时框架会输出一张指标表和一系列曲线图,很多人只看最后的mAP数字,觉得差不多就收工了。但在配电变压器检测场景里,总mAP高很可能是因为“变压器本体”这个大头类做得好,而“油位表”“锈蚀区域”等小类可能AP接近0。所以验收时我会把每个类别的AP单独列出来看。

import numpy as np # 假设从验证结果文件里读出了各类别AP ap_dict = { "transformer": 0.92, "bushing": 0.78, "oil_gauge": 0.31, "radiator": 0.55, } for cat, ap in ap_dict.items(): print(f"{cat}: {ap:.2f}") if ap < 0.5: print(f" -> {cat} 类别偏弱,需要补数据或调权重")

如果发现某类AP明显低于平均,优先排查的不是模型,而是数据:该类样本数是否太少、标注框是否偏差大、该类别在验证集里的样本是不是多为极端角度。类别AP分布是数据质量最直接的镜子,先把数据问题解决,再谈模型优化。

5. 配电变压器检测数据集训练避坑记录:四个高频翻车现场与解决

训练这类数据集时,我踩过不少坑,也帮别人排查过不少奇怪现象。下面四条是最常遇到的,按“现象、原因、解决”的思路写清楚,供直接对照。

5.1 训练Loss到一半突然变NaN:XML里混进了空类别名或非法坐标

现象:训练到某个epoch时loss变成NaN,之后指标全部失效;重启训练后可能在同一个epoch附近再次崩溃。原因:转换脚本遇到某个XML中object的name字段为空,或者bndbox坐标写成0到图片宽之外的异常值。归一化后可能出现负值或无穷值,模型反向传播时梯度就炸了。解决:转换前对XML做全量清洗,遇到空name直接跳过该目标,遇到xmax小于xmin的非法框先打印出来人工检查。

for obj in root.findall("object"): name = obj.findtext("name") if not name or name == "": print(f"[跳过空类别] {xml_path}") continue xmin = float(box.findtext("xmin")) xmax = float(box.findtext("xmax")) if xmax <= xmin: print(f"[非法框] {xml_path}: xmax={xmax}, xmin={xmin}") continue

这个习惯能用一分钟代码避免几小时的排障。每一批新拿到的数据,都值得先做一次这样的异常框检查,尤其是由外包标注团队生成的数据,坐标手滑填错并不罕见。

5.2 推理框整体偏移,目标被框住但位置不准:坐标归一化时用错了图宽高

现象:模型训练的loss正常,验证mAP也不算差,但画出来的框总是偏左或偏上半个身位,看起来像所有框统一平移了一段距离。原因:VOC的XML尺寸字段和实际图片尺寸不一致。有些数据集在标注时用了缩略图,XML里存的size是缩略图的宽高,但实际训练加载的是原图,导致归一化坐标被错误还原。解决:转换前先读取实际图片尺寸,以实际尺寸为准做归一化,不信任XML里的size。

import cv2 img = cv2.imread(img_path) actual_h, actual_w = img.shape[:2] xml_w = int(size.find("width").text) xml_h = int(size.find("height").text) if actual_w != xml_w or actual_h != xml_h: print(f"[尺寸不一致] {xml_name}: XML={xml_w}x{xml_h}, 实际={actual_w}x{actual_h}")

这个问题隐蔽在训练指标上基本看不出来,只能靠可视化检查发现。如果发现所有框统一偏移,第一时间就去对比图片实际尺寸和XML尺寸,大概率能直接命中。

5.3 少样本类别AP一直为0:类别不平衡被验证集放大

现象:训练结束后,某个类别的AP在验证集上始终是0,但该类在训练集里确实存在。原因:该类样本总量太少,比如只有30个框,其中大部分在训练集,验证集只有两三个,预测时只要稍微偏差一点,IoU不达标就被判为漏检,AP自然趋近0。解决:先把切分改为分层抽样,确保验证集里有该类样本;再考虑对少样本类别做复制粘贴增强或过采样,最后才考虑调整损失函数权重。顺序不能反,数据层面能解决的问题,不要绕到损失函数里去硬调。

5.4 两次训练结果差异巨大,验证指标忽高忽低:切分种子和随机种子没固定

现象:同样一份数据,参数完全一样,第一次训练mAP0.75,第二次变成0.68,且验证集曲线波动剧烈。原因:切分数据集时没有固定随机种子,两次训练用了不同的训练验证划分;或者训练框架没固定全局随机种子,数据加载顺序变化导致结果波动。解决:切分时固定seed,训练命令里固定框架的随机种子。实验对比的commit里也要记录seed值,否则后续想复现结果都没办法。这也是实验规范性的底线,数据量越小,随机性影响越大,3000张图的数据尤其明显。

6. 在3000幅数据之上再进一步:难例挖掘与数据闭环

把基础流程跑通之后,接下来的提升点不在模型结构上,而在数据闭环上。我习惯的做法是,每次训练后把验证集里预测置信度不高但真实存在的目标,以及置信度很高却完全预测错的目标,单独导出成难例清单,按批次人工过一遍。这个习惯看起来原始,但对小数据集非常有效。

值得投入的下一步工作,是围绕这些难例做定向补充。比如验证集里发现大量“逆光环境下的变压器本体”漏检,那就收集几十张同类图像补充到训练集;发现“套管被散热器遮挡”导致误判,就针对这类遮挡角度做复制粘贴增强。3000张图只是一个起点,真正决定检测上限的是你能否把现场反馈源源不断地变成新的标注样本,形成迭代闭环。

我在做这类项目时,最后的验收标准永远不是训练集上的精度,而是拿一批从未见过的现场照片让模型跑一遍,统计漏检和误检。每次跑完把失败样本截下来,按原因分类建文件夹,两三个月后回看,会发现早期那些让人头疼的场景样本已经被数据闭环慢慢补上了。数据驱动不是一句口号,而是每天多花十分钟把错题收集起来,再把它变成下一轮训练的数据。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/10 4:59:42

别再理论搬运了!AI辅助人文社科写作的论证激活指南

好写作AI&#xff1a;别当“理论搬运工”&#xff01;人文社科论证的终极艺术——把理论用活前阵子帮一位朋友看论文&#xff0c;三十多页的稿件里&#xff0c;福柯、布迪厄、吉登斯轮番登场&#xff0c;几乎每段都有引文&#xff0c;注释密密麻麻。可通篇读下来&#xff0c;我…

作者头像 李华
网站建设 2026/10/10 4:57:13

有奖答题页源码拆解:多语言切换、答题状态机与抽奖概率控制

简介&#xff1a;一套基于HTML、CSS和JavaScript构建的有奖答题互动网页设计源码&#xff0c;同时融合多语言技术&#xff0c;可支持不同语言环境的知识竞赛与教育培训场景。压缩包共1645个文件&#xff0c;约60.48MB&#xff0c;其中以HTML/CSS/JS前端文件为主&#xff0c;包含…

作者头像 李华
网站建设 2026/10/10 4:57:08

SAS硬盘维修实战:用xchdd从掉线到复活全记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/10 4:57:08

速达财务SSTD3G_server_6.37服务端安装配置与排错指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/10 4:56:42

PCA9422+PIC18F85K22嵌入式电源管理完整方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/10 4:56:31

PCA9422+STM32F723ZE:便携设备电源管理方案深度解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华