news 2026/10/7 1:30:17

YOLOv5生猪检测数据集:密集场景训练与调参实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
YOLOv5生猪检测数据集:密集场景训练与调参实战指南

简介:面向目标检测算法学习与落地验证的生猪检测数据集,采集自猪圈摄像头真实监控场景,单类别pig,图像以YOLOV5标准目录格式整理,训练集929张、验证集136张,可直接接入YOLOv5等框架训练,无需额外转换。所有RGB图片分辨率覆盖640至1080,单张图像包含多个目标,适合研究密集目标场景下的检测性能。资源共2000个文件,主要由1066个txt标签文件、933张jpg图像及1个py可视化脚本组成,压缩包约97.43MB;txt为YOLO格式标注,py脚本可随机读取一张图片并绘制边界框后保存,便于快速检验标注质量。此外,数据已按datasets-images-train与datasets-images-val分好目录,训练验证划分明确。目前已有609人学习使用,适合目标检测初学者、生猪养殖智能化项目开发者作为训练素材。

1. 猪圈摄像头下的生猪检测:一份YOLOv5目录格式数据集,拿来就能训

做猪只检测的人和做行人检测的人踩的坑很不一样:猪圈摄像头架得高、光线差、猪又互相贴着挤,目标密集且遮挡严重,标起来费劲,训起来也容易翻车。这份数据集是真实猪圈摄像头拍的 RGB 截图,分辨率在 640 到 1080 之间,已经按 YOLOv5 目录格式整理成训练集 929 张、验证集 136 张,只标一个类别 pig,每张图上的猪头数量普遍很多,天生就是练密集目标检测的料。对刚跑通 YOLOv5、手里缺真实场景数据的人来说,它不用改目录、不转格式、不配标签,直接就能喂给 train.py;对老手来说,它又能用来测 anchor、测 NMS 参数、测模型在小目标密集场景下的上限。下面我把目录结构、标签内容、可视化脚本和训练链路一起拆开讲。

2. 数据集拆解:目录结构、标签格式与可视化脚本

2.1 目录结构与文件命名:YOLOv5 标准 Layout

先看目录怎么摆。拿到手解压后,datasets 下面直接是 images 和 labels 两个大区,各自再分 train、val。这是 YOLOv5 最认的布局,不用额外写转换脚本,符合 ultralytics 仓库对数据目录的约定。

datasets/ ├── images/ │ ├── train/ │ │ ├── vlcsnap-2022-01-09-19h41m40s412_png.rf.5888b784a5b4f702503b2ad1620f25fd.jpg │ │ ├── vlcsnap-2022-01-08-18h26m24s718_png.rf.6d71b521ce80b3938d7ee6b8c0ea9760.jpg │ │ └── ... 共 929 张 │ └── val/ │ ├── vlcsnap-2022-01-09-19h41m43s620_png.rf.fa9060b3f23b6ce36fe653452c14d375.jpg │ └── ... 共 136 张 └── labels/ ├── train/ │ ├── vlcsnap-2022-01-09-19h41m40s412_png.rf.5888b784a5b4f702503b2ad1620f25fd.txt │ └── ... 共 929 个 └── val/ └── ... 共 136 个

每个图片文件对应的 txt 文件在 labels 目录下的同名子目录里,扩展名换一下。文件名保留了原始视频帧的命名,vlcsnap 开头说明是从 VLC 播放器截帧出来的,后面的日期时间就是帧的时刻。这个信息很有用:train 和 val 里都有不同日期的帧,说明划分时不是只按某一晚的连续帧来切的,时间分布是打散的,能降低相邻帧高度相似带来的过拟合风险,这是很多随手整理的数据集做不到的。

2.2 标签 txt 内容与类别映射

打开任意一个 txt,一行代表一个目标,共 5 个字段:

0 0.541406 0.486111 0.090625 0.158333 0 0.227344 0.441667 0.070313 0.119444 0 0.689844 0.695833 0.079688 0.180556

字段含义依次是:类别编号、归一化中心 x、归一化中心 y、归一化宽度 w、归一化高度 h。注意这里的坐标全部除了图像宽高,范围在 0 到 1 之间,和 YOLOv5 的 letterbox 处理直接兼容,不需要自己再归一化。类别编号是 0,对应附带 txt 类别文本文件里的 pig。整个数据集只有这一个类别,所以单类别训练时不需要改任何类别数。

有个细节值得确认:每张图的 txt 行数是不是和图上猪头数一致。多标、漏标在密集场景很常见。我一般会先统计一下每张 txt 的行数分布,看看有没有 0 行文件——如果 train 里有空 txt,训练时这张图会被当成背景图,影响不大;但如果 val 里有空 txt,做 mAP 评估时它会拉低召回率,容易被误判成模型漏检,其实是标注缺失。这份数据集的简介里没提空标签情况,到手后建议先扫一遍。

2.3 可视化脚本:怎么把边界框画回图上

项目附带了一个可视化 py 文件,作用是把某张图片和它对应的 txt 标签读进来,画上边界框后输出到当前目录。脚本不用改就能跑,入口通常是传入图片路径,脚本自动在同目录下找同名 txt。以下是这类脚本的核心逻辑,我按常见实现补全了一份:

import cv2 import sys from pathlib import Path def draw_boxes(img_path, out_dir="."): img = cv2.imread(str(img_path)) if img is None: raise ValueError(f"无法读取图片: {img_path}") h, w = img.shape[:2] txt_path = Path(img_path).with_suffix(".txt") if not txt_path.exists(): print(f"警告: 找不到标签 {txt_path}") return boxes = [] with open(txt_path, "r") as f: for line in f: parts = line.strip().split() if len(parts) != 5: continue cls, cx, cy, bw, bh = (float(x) for x in parts) x1 = int((cx - bw / 2) * w) y1 = int((cy - bh / 2) * h) x2 = int((cx + bw / 2) * w) y2 = int((cy + bh / 2) * h) boxes.append((x1, y1, x2, y2)) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, "pig", (x1, max(0, y1 - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) out_path = Path(out_dir) / f"vis_{Path(img_path).stem}.jpg" cv2.imwrite(str(out_path), img) print(f"已保存: {out_path},共 {len(boxes)} 个目标") if __name__ == "__main__": if len(sys.argv) != 2: print("用法: python visualize.py <图片路径>") sys.exit(1) draw_boxes(sys.argv[1])

逻辑说明:读取图片后先拿宽高,再把 txt 里的归一化中心点和宽高反算成像素坐标。x1、y1 是左上角,x2、y2 是右下角,用 max 防止类别文本在画布上方越界。这一步就是可视化脚本的全部核心,没有任何玄学,就是坐标反变换。参数说明:out_dir控制输出目录,默认当前目录;颜色 (0,255,0) 是绿色 BGR,适合在暗色猪圈背景上显示;字体大小 0.6 对 640 分辨率合适,如果是 1080 图片可以调到 0.8。运行方式:python visualize.py datasets/images/train/vlcsnap-2022-01-09-19h41m40s412_png.rf.xxx.jpg,脚本会在当前目录生成 vis_ 开头的新图。如果一次性想检查很多张,可以加一层循环,遍历某个目录下所有图片,批量生成预览图,这个在最后一章细说。

3. 把数据集接到 YOLOv5 训练链路:从配置 yaml 到跑通 train.py

3.1 数据集 yaml 怎么写

YOLOv5 官方仓库训练时需要一个 yaml 文件,声明 train 和 val 路径以及类别名。这份数据集的目录名是 datasets,我一般把 yaml 放在 YOLOv5 工程根目录下:

# pig.yaml train: datasets/images/train val: datasets/images/val nc: 1 names: ['pig']

train 和 val 指向图片目录即可,YOLOv5 会自动去对应 labels 目录找 txt,它默认把 images 替换成 labels。这里有个新手常犯的错:path 参数不是必须的,如果你写了绝对路径,务必保证路径里没有中文和空格。数据集本身是相对路径友好的,直接放在 YOLOv5 工程目录下最省事。参数说明:train、val 字段除了相对路径,也可以用绝对路径,但绝对路径换机器后就要改;nc 是类别数,这里固定 1;names 列表的顺序要和 txt 里的类别编号对应,只有 0 号,所以列表里放一个 pig。写完 yaml 后,可以用一行代码快速验证:

python -c "import yaml; print(yaml.safe_load(open('pig.yaml')))"

能正常输出字典,说明 yaml 语法没问题,再进下一步。实际使用中,我还会顺手检查一下图片数量是否和标签数量一致,防止 images 或 labels 里多出孤儿文件。孤儿文件会让训练时报错,提示找不到对应标签。

3.2 训练命令与参数选择

因为数据集只有 1065 张图(929 训练 + 136 验证),属于小型数据集,训练参数不能照搬 COCO 那套。我先给一份能直接跑的基线命令:

python train.py --data pig.yaml --weights yolov5s.pt --img 640 --batch 16 --epochs 100 --workers 4 --cache ram --device 0

逻辑说明:--weights yolov5s.pt表示用 s 模型预训练权重做迁移学习,小数据集从 COCO 预训练开始收敛更快也更稳;--cache ram把图片一次性加载进内存,避免每个 epoch 重复读磁盘,这个数据集总共 98MB,完全放得下,能显著减少训练时间;--img 640是输入分辨率,和数据集图片原始尺寸的下限一致,不需要强行拉高。参数说明:batch 要看显存,16 适用于 8GB 左右显卡,如果显存小就降到 8;epochs 100 对 1 类别小数据集基本够用,我看 loss 曲线到 60 轮左右会稳定;workers 在 Windows 上建议设 0,否则有时会卡数据加载;device 0 是第一块 GPU,没有 GPU 就改成 cpu,但训练速度会慢十倍以上。

训练过程中要盯两个指标:box_loss和obj_loss。如果 obj_loss 震荡明显,优先检查是否开了多尺度训练。YOLOv5 默认开了--multi-scale,对分辨率不统一的数据集有好处,但也可能让小目标样本在缩放时变得过小。这份数据集图片分辨率在 640 到 1080,开多尺度没问题,但如果发现后期 mAP 不稳,可以把这个参数关掉,固定 img 尺寸训练。

3.3 验证与测试流程

训练结束后,官方会自动跑一次 val,在 runs/val/exp 目录下生成混淆矩阵和 PR 曲线。如果想手动对验证集再做一次评估,用 val 命令:

python val.py --data pig.yaml --weights runs/train/exp/weights/best.pt --img 640 --task val --verbose

这个命令输出的 mAP@0.5 和 mAP@0.5:0.95 是关键指标。对单类别密集场景,单看 mAP@0.5 容易自我感觉良好,我会重点看 mAP@0.5:0.95,它和框的定位精度强相关,猪只密集时边界框稍微偏一点就可能在 IoU 0.5 时重叠,拉低分数。--verbose参数会打印每一张图的检测结果,方便快速定位哪些图漏检严重。注意 val.py 里的--task val用的是 yaml 里的 val 路径,如果目录结构没问题,不需要额外改代码。

测试视频或新图片时,用 detect.py:

python detect.py --weights runs/train/exp/weights/best.pt --source test_pig_video.mp4 --conf 0.25 --iou 0.45 --save-txt

这个命令会输出带框的视频帧和 txt 检测结果。我习惯把检测 txt 和标签 txt 放在同一目录下,用脚本算一下每张图的误检和漏检数量,比肉眼盯着视频效率高得多。

4. 密集小目标场景的调参思路:anchors、imgsz 与置信度阈值

4.1 为什么默认 anchor 不理想

YOLOv5 默认 anchor 是按 COCO 80 类目标统计出来的,从几像素的小目标到几百像素的大目标都有。猪圈摄像头拍到的猪只,在 640 分辨率下单个目标宽度往往只有 40 到 120 像素,属于中小目标偏多,而且猪身体互相贴在一起,先验框的形状如果太方正,回归起来就吃力。我拿到数据集后第一件事是统计所有标签的宽高比分布,看它偏向横条还是竖条。写个临时脚本:

import glob import numpy as np txts = glob.glob("datasets/labels/train/*.txt") wh = [] for t in txts: with open(t) as f: for line in f: parts = line.split() if len(parts) == 5: w, h = float(parts[3]), float(parts[4]) wh.append([w, h]) wh = np.array(wh) print("目标数量:", len(wh)) print("宽度中位数:", np.median(wh[:, 0])) print("高度中位数:", np.median(wh[:, 1])) print("宽高比中位数:", np.median(wh[:, 0] / wh[:, 1])) print("面积中位数:", np.median(wh[:, 0] * wh[:, 1]))

这个统计能直接指导要不要用--anchor auto重新生成 anchor。YOLOv5 训练时加--anchor auto,它会在前 50 轮用 k-means 统计当前数据集的先验框,替换掉默认值。对这份数据我建议开启,因为单类别目标形状高度一致,自动生成的 anchor 会比 COCO 默认值精确得多。注意:如果训练中途发现 anchor 变化导致 loss 突然跳一下,那是正常的,k-means 重新聚类后模型需要重新适应,只要后面能降下来就没问题。

4.2 改 anchor 与输入尺寸

修改输入尺寸是另一条路。数据集图片是 640 到 1080,说明摄像头截图的分辨率不是统一值。训练时--img 960能保留更多小目标细节,但显存占用会变大,batch 就要相应减小。我的经验是:先跑 640 基线,看漏检集中在哪些尺寸的目标,如果小目标漏检多,再试 img 960,batch 从 16 降到 8。同时可以手动把自动生成的 anchor 写进模型 yaml。YOLOv5 的 yolov5s.yaml 里 anchor 定义是这样的格式:

anchors: - [10,13, 16,30, 33,23] # P3/8 - [30,61, 62,45, 59,119] # P4/16 - [116,90, 156,198, 373,326] # P5/32

每行三个 anchor,分别对应 8 倍、16 倍、32 倍下采样特征图。你可以在训练完一轮后,去 runs/train/exp 目录下看生成的 anchor 文件,用 YOLOv5 提供的 k-means 脚本重新得到新 anchor,再替换进来训第二轮。这里有个坑:anchor 顺序必须从小到大排列,否则训练会震荡,Loss 出现明显的锯齿状。我通常在--anchor auto跑完一轮后,从 runs/train/exp/anchors 目录取回 anchor 值,写死到 yaml 里,再固定--noautoanchor训练,这样能复现结果,也方便调参对比。

4.3 后处理 NMS 与 conf 阈值

模型训练好以后,推理时的置信度阈值和 NMS 的 IoU 阈值对密集目标影响非常大。猪只互相遮挡,同一个猪可能会被预测出多个重叠框,默认 NMS IoU 阈值 0.45 会把重叠的猪框合并掉,导致少检;设高了又会把本应合并的框留下来,出现大量假阳。我的习惯是从 0.25 置信度开始看效果,猪只特征明显,真目标的 conf 通常都在 0.5 以上。

python detect.py --weights runs/train/exp/weights/best.pt --source test_images/ --conf 0.25 --iou 0.45 --save-txt

--save-txt会把检测结果保存成 txt,方便对比标签。密集场景下我一般把--iou调到 0.5,让 NMS 更积极合并框,减少同一目标重复框。如果发现漏检,优先降--conf而不是降--iou,因为降 iou 会把相邻猪合并,损失真实目标。这个调参顺序不要反。还有一个技巧:在 detect.py 前加一行--agnostic-nms,对不同类别不做区分直接 NMS,单类别场景下这个参数没有意义,但如果你后续加了第二类,比如人,开启它反而会把猪和人叠在一起时误合并,所以我一般保持默认。

5. 避坑与常见问题:七个真实翻车点

5.1 标签坐标归一化与图像尺寸不匹配

现象:用可视化脚本画框,发现框和猪的位置错位,有的框偏向画面一侧。

原因:txt 的坐标是归一化的,反算像素时必须乘图片真实宽高。如果图片是从视频截帧后又被缩放过的,宽度高度变了,但 txt 没有重新归一化,就会错位。常见于手动截图后直接拿来训练。

解决:先确认每张图片的实际分辨率,再检查 txt 里的坐标值是否全部在 0 到 1 之间。如果有大于 1 的值,说明是像素坐标混进来了,需要重新归一化。对这份数据集,由于它本身就是从 VLC 截帧并同步生成的标注,理论上不会出现,但下载到其他数据集时这个坑必查。我的脚本里加了一个断言:

for v in (cx, cy, bw, bh): assert 0.0 <= v <= 1.0, f"坐标超出范围: {line}"

5.2 训练集和验证集重叠导致指标虚高

现象:训练 loss 正常下降,验证 mAP 高达 0.98,但到实际猪圈里跑检测,漏检惨不忍睹。

原因:train 和 val 来自同一个视频片段,相邻几帧的图像信息几乎一致,模型相当于见过答案。很多公开数据集按帧连续划分,前后 5 帧被分进两个集合,就出现这个现象。

解决:拿到数据集后,要检查相同时间戳的帧是否同时出现在 train 和 val。这份数据集文件名里带日期时间,可以脚本提取时间戳,计算 train 和 val 文件名的时间戳集合交集。如果交集比例高,就要手动打散重建验证集。我一般会保留时间隔离:同一秒内的帧只能出现在一个划分里。示例脚本:

from pathlib import Path import re def get_ts(name): m = re.search(r'(\d{4}-\d{2}-\d{2}-\d{2}h\d{2}m\d{2}s\d{3})', name) return m.group(1) if m else None train_ts = {get_ts(p.name) for p in Path("datasets/images/train").glob("*.jpg")} val_ts = {get_ts(p.name) for p in Path("datasets/images/val").glob("*.jpg")} print("交集:", len(train_ts & val_ts))

如果交集不为零,就要重新划分验证集。常见做法是按时间戳排序后,每 10 帧取 1 帧进 val,其余进 train,保证验证集和训练集在时间上不重合。

5.3 摄像头截图噪点多导致漏检

现象:模型在验证集 mAP 还行,但在夜间或强逆光截图上漏检率明显上升。

原因:猪圈摄像头画质差、红外补光、灰尘遮挡,很多截图有大量噪点。数据增强里的 mosaic 和 hsv 变化能缓解一部分,但本质问题还是训练集中这类低照度样本比例不够。

解决:把可视化脚本跑一遍,挑出低亮度样本,用--cache训练时观察它们的 loss 是否偏高。如果确实低照度样本少,可以把数据集的图像做亮度抖动增强,或者用简单的 gamma 校正复制一批样本。注意这份数据集的图片分辨率到 1080,说明有部分帧是高清源,混入低清摄像头截图时模型会偏向学习高清纹理,容易忽略低清图的细节。我通常会在训练集里手动混入 10% 的模糊副本,让模型对清晰度差异不那么敏感。

5.4 类别编号错位:单类别也要检查重复标注

现象:训练日志显示 nc=1,但可视化时发现某些框画到了疑似猪以外的东西上,比如食槽、人腿。

原因:数据标注粒度不统一,有些标注员把露出的猪头标了,有些把半截身体也标了。单类别数据集也会出现语义不一致,模型学到的特征就混乱。

解决:用可视化脚本随机抽 200 张图,人工过一遍,重点看面积特别小和特别大的框。如果发现同类目标框选范围差异过大,考虑统一标注规范,或者用清洗脚本去掉面积小于 0.01 的框。密集场景下小框更有可能是误标。我常用的面积过滤:

area = bw * bh if area < 0.005: print("疑似过小框:", img_name, line)

5.5 可视化脚本路径分隔符与中文目录问题

现象:在 Windows 下运行脚本,提示找不到 txt 或保存路径报错。

原因:脚本里用了 Path 拼接,但 Windows 下如果参数带了中文绝对路径,OpenCV 的 imread 和 imwrite 对非 ASCII 路径支持不稳定,会静默失败,返回空图或写入失败。

解决:把脚本和图片放在同一个纯英文路径下运行;如果必须用中文路径,就用cv2.imdecode先读取字节再解码。这类问题在 Linux 服务器上基本不存在,但在本机预览时会翻车。我的习惯是可视化脚本里统一用 pathlib,输出路径指定为绝对英文路径,问题就少一半。

5.6 训练时类别权重失衡:单类别也会失灵

现象:训练到后期,预测框大量集中在画面中央,边缘的猪很难检出。

原因:猪圈摄像头通常是俯视,画面中心是食槽区域,猪聚集多;边缘走道猪少。模型学到的位置先验会偏向中心,边缘目标被当作背景。这和类别无关,是空间分布不均衡导致的。

解决:在训练时开启 mosaic 增强,它会把四张图拼在一起,打乱空间分布,缓解位置先验。如果开了 mosaic 还不够,可以手动检查 val 中边缘目标的召回率,单独统计中心区域和边缘区域的检测结果。常见做法是在标注脚本里按坐标把目标分成 center 和 edge 两组,分别计算 AP,定位问题。

5.7 多尺度训练导致 val 指标不稳定

现象:同一份权重,跑两次 val,mAP 差 2 个百分点以上。

原因:YOLOv5 的 val.py 在推理时也会随机选择尺度(取决于--img和--augment参数),如果没固定--img尺寸,结果会有波动。

解决:评估时务必固定--img,并且不加--augment。我通常在提交结果前,用同一命令连续跑三次 val,取中位数作为最终指标。如果三次波动超过 0.5%,说明训练还没收敛或 val 集太小。这份数据集的 val 只有 136 张,波动稍大是正常的,看趋势不要看单次数值。

6. 用可视化脚本做数据体检:三个小技巧让标注质量看得见

6.1 抽检策略:按目标密度分层抽样

均匀随机抽图容易漏掉极端情况。我习惯先把训练集图片按 txt 行数分成三档:1 到 3 个目标的稀疏图、4 到 8 个的普通图、9 个以上的密集图,每档各抽 20%,而不是全量随机。这样能同时看到稀疏场景的误检和密集场景漏检。

6.2 在脚本里加一列统计:输出每张图的框数

在可视化脚本的写图之前,插入一行统计,把所有框数打印出来:

print(f"{img_path.name}: {len(boxes)} pig(s)")

如果发现某张图框数明显低于肉眼可见的猪数量,就是漏标,及时回补。这个检查比看 mAP 数字更直观,mAP 是汇总值,单图标注质量问题会被平均掉。

6.3 批量生成预览图,用缩略图给整个集合作体检

单张逐图看效率低,我写了一个批量版本,把每张图缩到 320 宽,画框后拼成 4x4 网格,生成一张总预览图。这样几百张图很快就能扫完。代码思路是循环调用 draw_boxes,最后用 numpy 的 hstack 和 vstack 拼接,不额外依赖库。从那以后我每次拿到新数据集,都强制走一遍「分层抽检 → 统计框数 → 拼图预览」这三步,再决定要不要进训练管道。视觉上过一遍,比任何指标都让人安心。希望帮到你。

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

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

ADC0809底层原理:解构模数转换的六大物理铁律

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

作者头像 李华
网站建设 2026/10/7 1:29:04

国内STM32真实可用参考设计资源地图

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

作者头像 李华
网站建设 2026/10/7 1:28:29

DDRPHY分段CTS实战:解决SoC时钟树时序收敛难题

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

作者头像 李华
网站建设 2026/10/7 1:28:27

S7-200 SMART闭环步进控制:从失步检测到位置校验的完整指南

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

作者头像 李华
网站建设 2026/10/7 1:27:47

BMS开发为何离不开HIL?从电池建模到故障注入的完整测试指南

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

作者头像 李华
网站建设 2026/10/7 1:27:38

STM32原理图与PCB设计实战:从最小系统到布局布线避坑指南

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

作者头像 李华