news 2026/10/5 4:22:13

挖掘机叉车工程车辆检测数据集:VOC与YOLO双格式5067张详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
挖掘机叉车工程车辆检测数据集:VOC与YOLO双格式5067张详解

简介:这是一套面向目标检测与工程车辆识别场景的标注数据集,包含5067张真实工程现场图片,覆盖挖掘机、叉车、装载机、压路机、卡车、混凝土运输车及工人共7个类别,适用于智慧工地、车辆安全管理、施工进度监测等场景的模型训练与算法验证。数据包同时提供Pascal VOC格式的xml标注与YOLO格式的txt标注,便于直接接入主流检测框架,无需额外转换;图片与标注文件一一对应,数量完整,可满足中高级开发者开展精细调参、类别平衡分析等需求。压缩包共2000个文件,以xml标注文件为主,另有使用说明txt文件,整体大小约321.86MB,文件命名规整,目录便于批量读取。目前已有586人学习使用,适合计算机视觉学习者、算法工程师以及相关课题研究者快速获取高质量工程车辆数据,降低数据采集与标注成本,集中精力优化模型性能。

1. 挖掘机叉车工程车辆检测数据集:VOC+YOLO双格式5067张,开局先看三个数字

做工地场景目标检测的人,大概率都经历过这种尴尬:在网上找工程车辆数据集,要么只给 VOC 格式,得自己写脚本转 YOLO,要么图倒是不少,标注里混着大量difficult=1的脏框,训练出来的模型在工地上漏检成筛子。这个名为“挖掘机叉车工程车辆检测数据集VOC+YOLO格式5067张7类别”的资源,解决的正是这两个痛点——它一次性给了 Pascal VOC 的 XML 和 YOLO 的 TXT 两套标注,图片、XML、TXT 各 5067 个,一一对应,无需格式转换直接喂给 YOLOv5/YOLOv8。适合正在做智慧工地、工程机械识别、安防巡检这类项目的人。我的建议是,解压之后别急着开训,先把文件命名规则、XML 字段内容和 TXT 五列坐标之间的换算关系搞清楚,后面能省出大量调格式的时间。

2. 压缩包结构拆开看:XML字段、TXT五列数与文件命名规律

2.1 解压后先看目录树:为什么文件名里有 firc_pic 前缀

拿到.7z压缩包后,我习惯先解压到纯英文路径下,比如D:/datasets/engineering_vehicle。解压完目录结构大概是这样的:

engineering_vehicle/ ├── 使用前必读.txt ├── firc_pic_99.jpg ├── firc_pic_99.xml ├── firc_pic_99.txt ├── firc_pic_101.jpg ├── firc_pic_101.xml ├── firc_pic_101.txt ├── firc_pic_3977.jpg ├── firc_pic_3977.xml ├── firc_pic_3977.txt └── ...

命名规律很直观:每张.jpg图片对应同名.xml和同名.txt,三个文件共享一个firc_pic_编号的主文件名。这个命名约定意味着,你在写数据校验脚本时,不需要去解析 XML 里的filename字段来匹配图片,直接用 Python 的glob拿到所有 jpg 列表,再检查同名 xml 和 txt 是否存在即可。需要注意的是,有少量图片的编号是跳过的,不是严格从 1 连续编到 5067,所以依赖数量去反推文件名是个坑,老老实实遍历目录才是正解。

提示:解压 7z 文件在 Windows 下建议用 7-Zip 或 Bandizip,右键解压即可。Linux 服务器上用sudo apt install p7zip-full,然后执行7z x engineering_vehicle.7z。如果你习惯在 PyCharm 的终端里操作,同样可以直接敲这个命令,比拖来拖去省事。

2.2 XML标注结构:bndbox四坐标与difficult字段的坑

打开任意一个 XML 文件,结构是标准的 Pascal VOC 格式,核心部分如下:

<annotation> <folder>firc_pic</folder> <filename>firc_pic_3977.jpg</filename> <source> <database>Unknown</database> </source> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <segmented>0</segmented> <object> <name>Excavator</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>452</xmin> <ymin>301</ymin> <xmax>893</xmax> <ymax>712</ymax> </bndbox> </object> </annotation>

<size>里的width和height是你做坐标归一化时的分母,这个数值跟图片真实像素尺寸必须一致,否则转出来的 YOLO 坐标全是错的。<object>里<name>是类别名,<bndbox>是像素级绝对坐标,xmin/ymin是左上角,xmax/ymax是右下角。这个数据集比较友好的一点是difficult基本为 0,也就是说没有大量把模糊小目标标成“困难样本”来干扰你,训练时不用做额外的过滤逻辑。

在读取 XML 做校验时,很多人直接解析文本,遇到单个图片里多个<object>就会漏读。正确做法是用xml.etree.ElementTree的findall('object'),把一张图里所有目标都取出来,而不是只取第一个。另外,<truncated>这个字段在这份数据集里大多是 0,但如果你自己后续补充标注,截断目标建议标为 1,训练时要不要过滤它取决于你的业务场景——工地抓拍里车辆被围挡挡住一半是常态,过滤掉反而学不到这类特征。

2.3 YOLO TXT五列数据:从像素坐标到归一化坐标的换算

TXT 文件的内容长这样,每一行代表一个目标:

1 0.3495 0.4690 0.2297 0.3806 3 0.7081 0.3903 0.1531 0.2146

五个数字的含义按顺序分别是:class_id、x_center、y_center、width、height。其中class_id是类别编号,从 0 开始;后四个值全部归一化到了[0, 1]区间。转换关系是:x_center = (xmin + xmax) / 2 / image_width,width = (xmax - xmin) / image_width,y 方向同理。拿上面 XML 例子来验算:图片宽 1920,xmin 452,xmax 893,那么x_center = (452 + 893) / 2 / 1920 ≈ 0.3503,跟 TXT 里的0.3495在浮点误差范围内吻合,说明两套标注确实出自同一份 ground truth,质量是可信的。

这份数据集明确说了“不包含分割路径的txt”,指的是没有 YOLO 分割任务里那种存多边形坐标的 seg txt,只有检测用的五列矩形框 TXT。如果你原本打算拿它做实例分割训练,那要提前认清需求不匹配,这资源只服务检测任务。

3. 标注数据跑通:类别映射、train/val划分与XML/TXT互检脚本

3.1 七个类别固定顺序:class_id 错位是训练翻车第一大原因

数据集的类别名称和对应中文含义如下表,注意这个顺序就是 YOLO TXT 里 class_id 的索引顺序,也是接下来data.yaml里names列表的顺序,不要自己重新排序:

class_id英文名称中文名称
0ConcreteTruck混凝土运输车
1Excavator挖掘机
2Forklift叉车
3Loader装载机
4Steamroller压路机
5Truck卡车
6Worker工人

Loader和Truck在工程场景里长得确实像,前者是铲斗,后者是货斗,远距离小目标时模型容易混。Worker只有一类,没有细分工人是否戴安全帽,所以如果你的业务需要“未戴安全帽”这种二级分类,这个数据集只能作为基础检测模型,后面还得自己补标注。

3.2 按比例生成 train.txt 和 val.txt:别用默认的随机划分

很多教程叫你把图片和标注按 8:2 随机分到 train 和 val 目录,但工程车辆数据集有个特点——相邻编号的图片经常来自同一段视频流,场景极度相似。如果随机划分,训练集和验证集里会出现大量“孪生”图片,导致验证指标虚高。我一般会按文件编号排序后,每隔五张抽一张做验证集,或者干脆前 4000 张训练、后 1067 张验证,这样能保证验证集的场景分布更真实。

这里给一个按顺序划分的脚本,跑完会生成train.txt和val.txt,里面每行写的是图片的绝对路径,这正是 YOLOv5 和 YOLOv8 传入--data参数的常用格式之一:

import os import random random.seed(42) img_dir = "D:/datasets/engineering_vehicle" imgs = [f for f in os.listdir(img_dir) if f.endswith(".jpg")] imgs.sort(key=lambda x: int("".join(filter(str.isdigit, x)))) val_ratio = 0.2 val_count = int(len(imgs) * val_ratio) val_imgs = imgs[::5] # 每隔4张取1张,自然覆盖整个编号区间 with open("train.txt", "w") as tf, open("val.txt", "w") as vf: for img in imgs: abs_path = os.path.join(os.path.abspath(img_dir), img) if img in val_imgs: vf.write(abs_path + "\n") else: tf.write(abs_path + "\n") print(f"total: {len(imgs)}, train: {len(imgs) - len(val_imgs)}, val: {len(val_imgs)}")

imgs.sort用数字部分排序,是为了保证同一个场景的视频帧不会集中在某一段训练集或验证集里。val_imgs = imgs[::5]是步长抽样,比random.shuffle更稳。如果你希望验证集场景差异更大,可以把步长改成 7 或 11,但注意验证集数量会变化,val_ratio就只是参考值了。

3.3 XML/TXT 互检脚本:一次性找出五个常见标注错误

拿到数据集我做的第一件事不是开训,而是跑一遍互检。对照 XML 的像素坐标重新算出归一化坐标,再跟 TXT 里的数值比对,误差超过 0.01 就报警。这个脚本能筛掉坐标错位、类别错位、文件名对不上、空标注、越界框这五类问题。

import os import xml.etree.ElementTree as ET annot_dir = "D:/datasets/engineering_vehicle" errors = [] for xml_file in os.listdir(annot_dir): if not xml_file.endswith(".xml"): continue tree = ET.parse(os.path.join(annot_dir, xml_file)) root = tree.getroot() # 读图片尺寸 size = root.find("size") img_w = int(size.find("width").text) img_h = int(size.find("height").text) # 读所有 object for obj in root.findall("object"): name = obj.find("name").text bndbox = obj.find("bndbox") xmin = int(bndbox.find("xmin").text) ymin = int(bndbox.find("ymin").text) xmax = int(bndbox.find("xmax").text) ymax = int(bndbox.find("ymax").text) # 越界检查 if xmax > img_w or ymax > img_h: errors.append(f"{xml_file}: bbox out of range {name} ({xmin},{ymin},{xmax},{ymax})") # 空框检查 if xmax <= xmin or ymax <= ymin: errors.append(f"{xml_file}: empty bbox {name}") print(f"checked {len([f for f in os.listdir(annot_dir) if f.endswith('.xml')])} xml files") for err in errors[:20]: print(err) print(f"error count: {len(errors)}")

这个脚本不依赖 TXT 也能先跑一遍,主要抓越界框和空框。正常的检测数据集不应该出现xmax > img_w,但如果在做数据增强或者裁剪时操作不当,就会产生这种“脏框”,训练时 loss 直接飘红。跑完之后你会发现这份数据集很干净,基本零报错,这已经是难得的好底子了。

3.4 类别标签对齐:在训练前做一次 class_id 频率统计

最后一个建议是统计一下每个类别的目标数量,确认没有某个类完全没被训练到。YOLO 训练时如果某个类在训练集里目标数太少,模型对它的 recall 会特别低。

cat train.txt | xargs -I {} dirname {} | uniq

这个命令只是确认路径没问题。真正要做的是写脚本遍历 TXT,统计每个 class_id 出现的次数。如果你的验证集里某个类的样本数为 0,在算 mAP 时那个类就会被跳过,导致你看到的总 mAP 虚高,实际部署到工地根本检测不出压路机。这类问题在数据划分时就要避免。

4. YOLOv8训练自己的数据集:目录骨架、data.yaml与超参起点

4.1 目录骨架怎么搭:train和val放平级还是带子目录

虽然我们前面用train.txt的方式指定训练集,但 YOLOv8 更推荐把图片和标签按固定结构摆放,然后写data.yaml。两种方式我都试过,结论是:小数据集用 txt 列表省事,但要换机器训练、改路径时容易踩坑;固定目录结构虽然前期多几步移动文件,但后续加数据、换环境都稳定。我自己更习惯用images和labels分目录的结构,因为 YOLOv8 官方默认就是按这个来匹配标注的。

最终目录长这样:

engineering_vehicle/ ├── images/ │ ├── train/ │ │ ├── firc_pic_99.jpg │ │ └── ... │ └── val/ │ ├── firc_pic_3977.jpg │ └── ... ├── labels/ │ ├── train/ │ │ ├── firc_pic_99.txt │ │ └── ... │ └── val/ │ ├── firc_pic_3977.txt │ └── ... └── data.yaml

这里最容易翻车的是图片和标签的目录层级不一致。假如 YOLO 在images/train找到一张图,它会自动去labels/train找同名 txt,一旦你把验证集的 txt 放进了labels/train,训练时验证集的标注全部读不到,mAP 直接变成 0,而且运行日志里只有 warning 没有 error,很隐蔽。移动文件我用 Python 脚本按train.txt列表来做,避免手动拖拽漏文件。

4.2 data.yaml 配置:names列表顺序绝不能动

data.yaml是模型读懂这个数据集的钥匙,关键就两点:路径和类别名。给出一个可直接用的配置:

path: D:/datasets/engineering_vehicle train: images/train val: images/val nc: 7 names: 0: ConcreteTruck 1: Excavator 2: Forklift 3: Loader 4: Steamroller 5: Truck 6: Worker

注意几个细节。path建议写绝对路径,在调试阶段能少很多“文件找不到”的报错;train和val的值是相对于path的路径,不要加./前缀,YOLOv8 解析时会做拼接,加了反而可能出问题;nc必须等于names列表长度,这个数据集是 7,别写成 7.0 或者 6。最核心的是names的顺序,索引 0 对应 TXT 里 class_id 为 0 的类别,一旦你把Excavator放到了 0 的位置,而 TXT 里 0 号是ConcreteTruck,训练出的模型就会把混凝土车识别成挖掘机,这种错位在 loss 曲线上几乎看不出来,只能靠实测图片去暴露。如果你之前用其他数据集做过迁移学习,但类别顺序不一样,加载预训练权重时会出现头部分支维度不匹配,此时要么冻结 backbone 只训 head,要么从头训。

4.3 预训练权重选型:yolov8s.pt 起步,工程车辆小目标多就上 yolov8m

训练脚本我用的是 YOLOv8 的 CLI 命令,最直观:

yolo detect train \ data=data.yaml \ model=yolov8s.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=20 \ project=runs \ name=exp_eng_vehicle

参数含义逐一说。model=yolov8s.pt会从官方权重开始微调,而不是随机初始化,这对中小数据集收敛速度影响巨大。工程车辆检测里,压路机和工人这类目标在 640 分辨率下只有二三十个像素,属于小目标范畴。如果你发现验证集 mAP 卡着上不去,把imgsz提到 1280 通常能明显改善——代价是显存占用翻倍,推理速度变慢。在 RTX 3090 或更高显存的卡上,我一般直接用imgsz=1280,如果显存只有 8G 左右,则维持 640 并加mosaic=0.5做数据增广。patience=20表示连续 20 个 epoch 验证集 loss 不下降就早停,目的是省时间。lr0=0.01是初始学习率,迁移学习场景下一般不需要动,除非训练震荡明显。

为什么选 yolov8s 而不是 n 或 m?n 模型参数太少,对工地这类背景复杂、目标密集的场景拟合不够;m 模型训练时间和显存需求会涨,但精度收益有限。对 5067 张图片的中等规模数据集,s 是性价比最稳的起点。

4.4 类别不均衡:Worker 目标小且稀疏,别指望单卡训出高分

统计一下你会发现,Truck和Excavator的样本量大,Worker和Steamroller的样本量可能只有前者的三分之一。这种不均衡会导致模型整体 mAP 看起来不错,但 Worker 类 recall 低。处理方式有三种:第一种是给Worker类在 loss 里加权重,YOLOv8 没有直接暴露这个参数,需要改源码;第二种是数据增广时对含 Worker 的图片做复制粘贴增强;第三种最省事——调低conf_thres,把置信度阈值从默认 0.25 降到 0.15,但代价是误报增加。我通常的做法是先用原数据跑一版,看每个类的单独 AP,再决定要不要增广。

5. 避坑:7z解压、标签对齐、中文路径与训练中断的排查记录

5.1 解压后 XML 文件名变成乱码

现象:在 Windows 上用自带资源管理器解压 7z 后,一批 XML 文件名出现乱码,但 jpg 正常。

原因:压缩包内的文件名编码与系统默认编码不一致。7z 压缩时使用 UTF-8 编码文件名,旧版 WinRAR 或 Windows 自带解压工具在中文系统下可能按 GBK 解码,导致乱码。

解决:换成 7-Zip 官方版解压,右键选择“提取到当前文件夹”,文件名通常能正确还原。Linux 下用p7zip解压基本不会遇到这个问题,因为默认走 UTF-8。如果已经解压坏了,不要手动一个个改文件名,写个 Python 脚本按 jpg 文件名反查,把 xml 重命名回去。

5.2 训练时报错 “AssertionError: train: No labels in *.txt”

现象:YOLOv8 训练开始后立即报错,提示 labels 目录下找不到 txt 文件。

原因:最常见的是 labels 目录和 images 目录结构不对应,或者把数据集放在了中文路径下。YOLO 的Path模块在 Windows 上对中文路径支持一直有历史包袱,我在D:/数据集/工程车辆这种路径下训练必报错。

解决:把数据集整体移动到纯英文路径,例如D:/datasets/eng_vehicle。另外确认 labels 子目录的名称必须是labels,不是label也不是Annotations。这个数据集解压后默认是 xml/txt 跟图片混在一个文件夹,你自己划分目录时千万别把 txt 放错到labels/train外层,YOLO 严格按labels/train去找。

5.3 训练 loss 正常下降但 mAP 一直为零

现象:训练日志里 box_loss 和 cls_loss 都在下降,但每次验证的 mAP 都是 0。

原因:验证集 txt 文件内容为空,或者验证集图片路径在data.yaml里写错。这种情况常常发生在你手动分目录时,把某些 txt 文件漏拷了,或者拷贝到了labels/train而不是labels/val。mAP 为 0 的另一个可能是类别 ID 全错——如果 TXT 里出现了 class_id 7 或更大的数字,而nc只设了 7,YOLO 会静默过滤掉这些框,导致验证时一个目标都匹配不上。

解决:回到 3.3 节的互检脚本,跑一遍统计每个 TXT 的 class_id 最大值。如果超过 6,说明数据集里有超出 7 类范围的标注,需要过滤掉这些行再训练。

5.4 验证集图片尺寸不一致引发训练中断

现象:训练到一半,报 “image is too large or too small” 之类的尺寸错误,或者某个 batch 的 tensor shape 对不上。

原因:工程车辆图片可能来源不同,有的分辨率是 1920×1080,有的是 1280×720,甚至可能有竖构图。YOLOv8 内部会做 letterbox 缩放,但如果某张图的宽或高小于最终输入尺寸的 1/3,数据加载器会跳过它,极端情况下跳过的图太多,batch 拼接时就出问题。

解决:如果你遇到这种问题,先做一次全量检查,找出尺寸异常的图片,要么删除,要么统一 resize。这个数据集整体比较规整,但多留个心眼没坏处。

5.5 用 PyCharm 终端跑 7z 命令出现 “command not found”

现象:在 PyCharm 的 Terminal 里输入7z x file.7z,提示命令不存在。

原因:Windows 系统的终端默认用的是 cmd,而 7-Zip 安装后通常不自动把7z.exe所在目录加入 PATH,cmd 自然找不到命令。

解决:以下两种方式任选其一。第一种,用安装目录的完整路径:"C:/Program Files/7-Zip/7z.exe" x engineering_vehicle.7z。第二种,把 7-Zip 安装目录手动加入系统环境变量 PATH。如果你用的是 Bandizip,命令是bz,同样需要配置环境变量。Linux 服务器上则先确认p7zip-full装没装,再执行which 7z排查。

6. 训练完怎么验收:用 mAP50、损失曲线和三类边界 case 做最终判读

训练结束后,runs/exp_eng_vehicle/目录下会生成results.png、confusion_matrix.png和val_batch0_pred.jpg。我验收模型从来不看单独某一个指标,而是按顺序看三样东西。

先看results.png里的mAP50和mAP50-95。工程车辆是中等尺寸目标,7 类数据 5067 张,用 yolov8s 训 100 个 epoch,合理预期是 mAP50 在 0.85 以上,mAP50-95 在 0.55 到 0.65 之间。如果 mAP50 超过 0.9,先别高兴,去看验证集是不是和训练集场景太接近——我之前就吃过亏,某次验证集 mAP50 高达 0.94,结果在同一工地不同角度的摄像头下一测,挖掘机漏检了 30%。然后看confusion_matrix.png,重点关注Loader和Truck这对易混类,如果混淆矩阵里它们互相错认的比例超过 5%,就要考虑在训练集里多补充装载机侧视角的图片。

最后建议你亲自跑一段视频实测,挑三类目标各 5 段:远距离小目标、遮挡目标、逆光目标。写个最简单的预测脚本验证:

yolo detect predict \ model=runs/exp_eng_vehicle/weights/best.pt \ source=test_video.mp4 \ conf=0.25 \ iou=0.5 \ save=True

conf=0.25是默认阈值,如果发现工人这类小目标大量漏检,把这个值往下调,比如 0.15,但误报会增多。iou=0.5是 NMS 的 IoU 阈值,目标密集叠加场景(比如多台挖掘机相邻作业)可以考虑调低到 0.4,减少框被抑制的情况。

这三步全部通过后,模型才可以考虑放进项目里做二次开发。从那以后我每次拿到任何数据集,都强制先跑一遍标注互检、再做目录划分、最后才进训练,哪怕数据是别人整理好的也绝不跳过。希望这份工程车辆数据集能帮你少踩几个坑,顺利把模型跑起来。

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

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

混合多云架构现代化:数据流转、整合与冷数据管理实战

简介&#xff1a;IBM架构现代化转型之路实践分享以PDF文档形式呈现&#xff0c;浓缩了IBM大中华区实验室服务团队在架构现代化领域的实战经验&#xff0c;面向企业IT架构师、云平台负责人及数字化转型决策者。文档基于2,131位CEO调研洞察&#xff0c;指出全球数据量已达44ZB&am…

作者头像 李华
网站建设 2026/10/5 4:21:44

可持续收入分析:收入结构、可重复率与现金流闭环

做可持续收入分析&#xff0c;最怕的是把“账面上好看”当成“业务里健康”。我见过太多老板拿着月报兴奋地说“这个月收入涨了30%”&#xff0c;结果一拆结构发现&#xff0c;涨的部分全部来自一个一次性大客户&#xff0c;普通客户的复购率反而在往下掉。这种收入&#xff0c…

作者头像 李华
网站建设 2026/10/5 4:21:43

Tabbit AI浏览器公测体验:AI Agent如何重塑效率工具

这两天&#xff0c;技术群里聊得最热的不是哪家又发了大模型&#xff0c;而是光年之外团队的Tabbit AI浏览器宣布进入公测。听到“AI浏览器”这个词&#xff0c;我第一反应其实是有点疲劳——过去一年各种挂着AI名头的浏览器我看过不少&#xff0c;大部分是传统浏览器套一个对话…

作者头像 李华
网站建设 2026/10/5 4:20:45

Claude Sonnet 5.5 跨模态实测:视频剪辑、浏览器自动化与原生开发

1. 从一条更新日志说起&#xff1a;这次升级到底动了哪些真格的地方Claude Sonnet 5.5 推送更新的那天&#xff0c;我正好在赶一个跨端小工具的原型。原本只是顺手点了个升级&#xff0c;结果接下来两天基本没干别的&#xff0c;全在拿它跑各种以前需要来回切工具链的活儿。视频…

作者头像 李华
网站建设 2026/10/5 4:20:45

机器人日志系统十年演进:从printf到AI日志分析

凌晨两点半&#xff0c;手机响了。项目群里发来一张截图&#xff0c;车间里那台协作机器人又停在半路&#xff0c;机械臂悬在一个奇怪的角度&#xff0c;液晶屏上只有一行“joint_3_error”。我揉了揉眼睛打开电脑&#xff0c;SSH进工控机&#xff0c;先把/var/log/按时间排了一…

作者头像 李华
网站建设 2026/10/5 4:20:27

示教器白键自定义:实现KUKA机器人一键触发复杂工艺

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

作者头像 李华