news 2026/9/24 12:14:49

智能零售柜商品检测数据集:113类商品与YOLO11跨平台训练指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
智能零售柜商品检测数据集:113类商品与YOLO11跨平台训练指南

简介:面向智能零售柜商品检测与通用新零售场景的目标检测数据集,共5000张真实监控场景采集的高质量商品图片,覆盖罐装饮料、袋装零食等常见零售品类,标注113个商品类别。标注采用LabelImg完成,精度较高,提供VOC、COCO、YOLO三种标准格式标签,可直接用于YOLO等主流目标检测算法训练,也可作为新零售场景检测任务的数据补充。资源包为单个PDF说明文档(5.77MB),内含数据集基本情况、标注格式说明、部分缩略图截图及实际获取方式;同时附赠YOLO11一键训练脚本,支持GPU、CPU及Mac(M芯片)三平台运行,并附带博主训练日志供参考。使用该数据集可完成从数据准备、模型训练到结果复现的完整流程,当前已有413人学习,适合具备一定目标检测基础、希望快速获取高质量零售商品数据并投入训练与项目验证的开发者与研究人员。

1. 智能零售柜商品检测数据集:5000张图、113类商品与三平台YOLO11脚本能解决的问题

做智能零售柜的项目,最头疼的往往不是模型选型,而是数据。市面上的公开数据集要么是通用场景(比如COCO的80类),要么是货架场景但类别太少,真正贴近零售柜监控视角、带完整标注的商品数据非常难找。这份数据集来自真实零售柜监控场景,5000张图,113个商品类别,覆盖罐装饮料、袋装零食等常见SKU,标注用的是labelimg,同时给出VOC、COCO、YOLO三种格式的标签,拿到手不用再做格式转换。更关键的是,它附带一套YOLO11一键训练脚本,支持GPU、CPU、Mac M芯片三平台,这意味着即使你没有NVIDIA显卡,也能在Mac上把模型跑起来。适合谁用?正在做零售柜商品检测、新零售场景目标检测的算法工程师和研究生,以及在找一份能直接训练、类别覆盖度够高的数据集做实验的从业者。

2. 数据集解剖:从拍摄场景到113类标注的背后逻辑

2.1 真实零售柜监控场景与通用数据集的区别

这份数据集最核心的价值在于它的拍摄视角和场景分布。智能零售柜的摄像头通常安装在柜体内部上方,以俯视或斜俯视角度拍摄层板上的商品,光线条件复杂,有柜内LED补光,也有环境光干扰,玻璃门还会造成反光。这种视角和光照在COCO、VOC这些通用数据集里几乎找不到,用通用数据集训练出来的模型部署到零售柜上,经常出现检测框偏移、小目标漏检的问题。

数据集的商品类别集中在罐装饮料类和袋装零食类,这也是零售柜的主流SKU。罐装商品的特征是圆柱体侧面有标签纹理,袋装零食则是软包装、形状不规则,这两种商品的检测难点完全不同。罐装商品容易在边缘处产生反光导致标签区域过曝,袋装零食则因为包装褶皱导致轮廓不稳定。数据集能覆盖到这些差异,说明采集时是经过场景设计的,不是随手拍的图片堆砌。

针对真实场景还有一个容易被忽略的点:商品之间的遮挡。零售柜里的商品是紧密排列的,层板边缘的商品经常被旁边的商品挡住一部分,这在训练时对NMS的阈值设置和anchor的设计都有影响。如果你的项目是从零开始做零售柜检测,这份数据能帮你省掉大量数据采集和清洗的时间。

2.2 labelimg标注与三种格式的内在一致性

数据集采用labelimg标注,这是目标检测领域最常用的标注工具之一,操作简单,支持VOC格式的xml输出。标注质量高不高,关键看两点:一是框是否贴边,二是类别是否准确。从数据集的使用体验看,大部分标注框都贴合商品轮廓,没有明显的偏移和漏标。

三种格式的标签并不是独立标注三遍,而是基于原始标注自动转换生成。VOC格式是xml文件,每个文件对应一张图片,内部用<object>节点记录类别名和四点坐标。COCO格式是单个json文件,所有图片和标注信息统一管理,内部用images数组和annotations数组分别存图片信息和标注框信息。YOLO格式是txt文件,每行一个目标,格式为class_id x_center y_center width height,坐标值是相对于图片宽度和高度的归一化值。

# VOC格式的xml关键结构示例 <annotation> <filename>img_0001.jpg</filename> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>coca_cola_330ml</name> <bndbox> <xmin>356</xmin> <ymin>482</ymin> <xmax>511</xmax> <ymax>618</ymax> </bndbox> </object> </annotation>

这段xml表示图片img_0001.jpg中有一个类别为coca_cola_330ml的目标,检测框左上角坐标是(356, 482),右下角是(511, 618)。xml格式的优点是可读性强,适合人工检查和二次编辑;缺点是文件数量多,每张图一个xml,在数据集规模大时文件管理有些繁琐。

# COCO格式json的annotations数组核心字段 { "images": [ {"id": 1, "file_name": "img_0001.jpg", "width": 1920, "height": 1080} ], "annotations": [ { "id": 1, "image_id": 1, "category_id": 5, "bbox": [356, 482, 155, 136], "area": 21080, "iscrowd": 0 } ], "categories": [ {"id": 5, "name": "coca_cola_330ml"} ] }

COCO格式的bbox记录的是[x, y, width, height],注意不是[x1, y1, x2, y2],这一点在手工处理json时特别容易搞混。category_id从1开始编号,与categories数组中的id对应。COCO格式的重要优势是统一管理、加载方便,PyTorch的视觉库和Detectron2都原生支持这种格式。

# YOLO格式txt,每行一个目标 # class_id x_center y_center width height(归一化坐标) 5 0.2258 0.5093 0.0807 0.1259 5 0.3812 0.4981 0.0786 0.1315 12 0.6094 0.5231 0.0851 0.1183

YOLO格式的坐标是归一化的,计算方式为:x_center = (xmin + xmax) / 2 / image_widthwidth = (xmax - xmin) / image_width。如果用脚本批量生成,需要注意浮点精度,通常保留6位小数即可,过多位数反而没有意义。

2.3 113个类别的分布观察

拿到数据后我习惯先统计一下各类别的目标数量分布,确认是否有类别严重不平衡的问题。零售柜场景下这种情况很常见:某个爆款饮料的样本可能占30%,而角落里一些冷门零食只有几十张图。这直接影响模型训练时的loss权重和最终检测精度。

常见做法是统计每个类别的框数量,然后计算类别频率分布。如果发现长尾明显,可以在训练参数里调整cls_loss的权重,或者在数据加载时做类别重采样。数据集附带的训练日志里应该能看到mAP的per-class结果,重点关注那些AP偏低的类别,通常是样本量少或外观相似度高(比如不同口味的同品牌饮料)的类别。

3. 三种格式标签的工程化处理:坐标转换与一致性校验

3.1 格式转换脚本:VOC转YOLO的完整实现

数据集提供了三种格式的标签,但项目实战中经常需要在自己定义的目录结构之间切换。比如你想用Ultralytics YOLO11训练,默认的目录结构是images/trainlabels/train分开存放;而如果你要跑mmdetection,可能就需要COCO格式。虽然数据集已经给了三种格式,但理解转换逻辑仍然很重要,因为你可能会往数据集里补充自己的图片。

VOC转YOLO是最常见的需求。VOC的坐标是绝对像素值,YOLO需要的是归一化坐标,转换时要注意边界情况:目标框理论上不会超出图片边界,但标注时手滑可能产生越界,转换前最好做一次clip操作。

import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_file, class_names, img_width, img_height): tree = ET.parse(xml_file) root = tree.getroot() yolo_lines = [] for obj in root.iter('object'): name = obj.find('name').text if name not in class_names: continue class_id = class_names.index(name) bndbox = obj.find('bndbox') xmin = float(bndbox.find('xmin').text) ymin = float(bndbox.find('ymin').text) xmax = float(bndbox.find('xmax').text) ymax = float(bndbox.find('ymax').text) # 坐标越界保护,避免归一化后出现负数或大于1的值 xmin = max(0, min(xmin, img_width - 1)) xmax = max(0, min(xmax, img_width - 1)) ymin = max(0, min(ymin, img_height - 1)) ymax = max(0, min(ymax, img_height - 1)) x_center = (xmin + xmax) / 2 / img_width y_center = (ymin + ymax) / 2 / img_height w = (xmax - xmin) / img_width h = (ymax - ymin) / img_height yolo_lines.append(f"{class_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") return yolo_lines

这段代码的核心步骤是:解析xml定位到每个object节点,取出类别名和边界框坐标,做越界裁剪后计算归一化的中心点和宽高,最后格式化为YOLO要求的字符串。class_names参数是你自己定义的类别列表,顺序就是最终训练时的类别id顺序。

有一个细节新手容易踩:VOC的xmin和xmax可能是字符串,也可能是float,如果用int()强转再除宽度,在图片尺寸不是整数时会有精度损失。稳妥的做法是一律用float()解析,最后输出时再统一格式化为6位小数。

3.2 COCO格式的校验脚本:bbox为负值的排查

COCO格式因为只有一个大json,出问题时排查范围不容易定位。最常见的问题是bbox坐标出现负值或宽高为0。这种情况通常发生在标注时画框不规范,或者转换脚本里坐标计算错误。

我一般会写一个快速校验脚本:

import json def check_coco_annotation(json_path, img_size_map): with open(json_path, 'r') as f: coco_data = json.load(f) errors = [] for ann in coco_data['annotations']: image_id = ann['image_id'] bbox = ann['bbox'] # [x, y, w, h] x, y, w, h = bbox img_w, img_h = img_size_map.get(image_id, (None, None)) if img_w is None: errors.append(f"annotation {ann['id']}: image_id {image_id} not found") continue # 检查坐标越界和宽高异常 if x < 0 or y < 0: errors.append(f"annotation {ann['id']}: bbox 坐标出现负值: {bbox}") if w <= 0 or h <= 0: errors.append(f"annotation {ann['id']}: bbox 宽高异常: {bbox}") if x + w > img_w or y + h > img_h: errors.append(f"annotation {ann['id']}: bbox 超出图片边界: {bbox}") return errors # 使用示例 img_size_map = { 1: (1920, 1080), # image_id: (width, height) 2: (1920, 1080), } errors = check_coco_annotation('annotations/instances_train.json', img_size_map) if errors: print(f"共发现 {len(errors)} 个问题标注") for e in errors[:20]: print(e) else: print("校验通过")

这个脚本的核心思路是建立image_id到图片尺寸的映射,然后逐条检查bbox是否越界、宽高是否合法。实际使用时img_size_map要从COCO的images数组里动态构建,不用手写。校验脚本的价值在于训练前发现问题,否则训练到一半因为标注数据异常导致loss不收敛,定位问题要花更多时间。

3.3 YOLO格式的类别id映射陷阱

YOLO格式的txt文件里存的是类别id,这个id是整数,对应训练配置文件里的类别列表索引。如果数据集提供了classes.txtdata.yaml,用它作为唯一真源,不要自己猜。常见的问题是自己新建了一个类别顺序不同的yaml,结果所有标签的类别全部错位。训练出来模型能跑,但检测结果张冠李戴,这种错误最难排查。

# data.yaml 示例 names: 0: coca_cola_330ml 1: sprite_330ml 2: lays_classic 3: oreo # ... 共113个类别

建议拿到数据集后先做的事是:打开data.yaml和任意一个txt标签,确认txt里的第一个数字对应的类别名和yaml里索引为这个数字的类别名一致。这一步只需要30秒,但能避免训练一天后发现类别全部错位的惨剧。

4. YOLO11一键训练脚本:GPU与Mac M芯片的配置差异

4.1 三平台训练脚本的架构设计

数据集附带YOLO11训练脚本,号称支持GPU、CPU、Mac M芯片三平台。脚本的核心逻辑就是检测当前运行环境,自动选择合适的设备配置,然后调用YOLO11的训练入口。这个设计解决了初学者最常见的困境:GPU显存不够怎么调batch size?Mac上能不能跑YOLO?

# 训练脚本的命令行入口示例 python train.py --data data.yaml --epochs 100 --batch-size 16 --imgsz 640

脚本内部会先检查torch.cuda.is_available(),如果为True则用GPU;否则检查torch.backends.mps.is_available()判断是否为Apple Silicon;两者都没有就退化为CPU训练。这里的核心是batch-size的自动适配:GPU显存越小,batch size自动调低。

4.2 GPU平台训练:显存、batch size与混合精度

GPU训练时最关键的参数是batch size和混合精度。YOLO11默认使用AMP(Automatic Mixed Precision),即FP16和FP32混合训练,能在显存占用基本不变的情况下把训练速度提高一倍左右。

# GPU训练,显存8GB的显卡推荐配置 python train.py --data data.yaml --epochs 100 --batch-size 16 --imgsz 640 --device 0 --amp
  • --device 0指定使用第一张GPU,多卡环境用--device 0,1--device 0,1,2,3
  • --amp开启混合精度,脚本默认开启,显式写上更明确
  • --imgsz 640是输入图片尺寸,零售柜商品检测建议至少640,因为罐装饮料上的小字标签需要足够的分辨率才能检测到

8GB显存跑YOLO11s(small版本),batch size最多开到16,如果显存不足会自动OOM报错,这时把batch size降到8即可。不建议用YOLO11x版本在8GB显存上训练,参数量太大,batch size只能开到4,收敛效果反而不好。

4.3 Mac M芯片:MPS加速的注意事项

Mac M芯片跑YOLO11是可用的,靠的是PyTorch的MPS(Metal Performance Shaders)后端。脚本会自动检测MPS是否可用,但这里有几个明显的坑:

# Mac M芯片训练,MPS自动启用 python train.py --data data.yaml --epochs 50 --batch-size 8 --imgsz 640 --device mps

MPS后端的不稳定主要在两个方面:一是某些算子还没有实现MPS版本,运行时会报not implemented错误;二是显存管理机制不同于CUDA,遇到内存不足时表现不直观。我的经验是MPS训练时batch-size不要超过8,否则容易触发内存压力导致训练中断。另外--workers参数在Mac上建议设为0,因为macOS的multiprocessing和PyTorch的DataLoader配合经常出问题,训练进程会卡住不动。

如果MPS确实跑不了,脚本会退化为CPU训练。CPU训练的batch size建议2-4,epochs可以适当减少,因为速度确实慢,用这份5000张图的数据跑100个epoch在M芯片的Mac上大概需要十几个小时,建议先用50个epoch验证流程能不能跑通。

4.4 训练日志解读:mAP、loss曲线和训练结果日志里的参考值

数据集提供了博主的训练结果日志,这个日志的价值在于给你一个预期基准。如果你的训练结果明显低于日志里的mAP,说明数据加载或参数设置有误。

# 训练日志关键指标示例 Epoch 50/100: 0.9823 Class Images Instances Box(P) R mAP50 mAP50-95 all 5000 28412 0.947 0.935 0.971 0.863 coca_cola 476 1123 0.953 0.941 0.978 0.874 sprite 382 856 0.938 0.927 0.962 0.851

看日志的时候重点关注两点:一是mAP50是否达到0.9以上,二是mAP50-95mAP50的差距。如果mAP50很高但mAP50-95明显偏低,说明检测框的位置精度不够,即框虽然框对了位置但和真实框的IoU不高,这种情况和标注框的紧致程度、训练时的anchor设置都有关系。

5. 避坑指南:数据、训练与跨平台脚本的五个常见问题

5.1 标签文件名与图片文件名不一致导致训练时没有目标

  • 现象:训练日志中显示total targets = 0,loss没有任何下降趋势。
  • 原因:YOLO格式要求图片和标签文件的名称完全一致,只是扩展名不同。比如img_0001.jpg对应的标签文件必须是img_0001.txt。如果从VOC格式转换时没有严格保持文件名一致,或者把文件放在错误的子目录(如images/trainlabels/train不匹配),训练时数据加载器找不到对应的标签文件,就会把这张图当作无目标图片处理。
  • 解决:写一个脚本遍历所有训练图片,逐一检查对应的txt标签是否存在。
import os def check_label_file(image_dir, label_dir): missing = [] for img_file in os.listdir(image_dir): if not img_file.endswith(('.jpg', '.jpeg', '.png')): continue stem = os.path.splitext(img_file)[0] txt_file = os.path.join(label_dir, stem + '.txt') if not os.path.exists(txt_file): missing.append(img_file) return missing missing = check_label_file('images/train', 'labels/train') print(f"缺失标签的图片数量: {len(missing)}") if missing: print(missing[:10])

这个脚本的原理很简单:遍历图片目录,将图片文件的扩展名替换为txt后检查标签目录下是否存在。如果缺失数量较多,优先检查数据集解压是否完整,或者labelimg标注时是否漏存了部分xml。如果缺失数量极少,可以直接删除这些图片,避免影响训练。

5.2 batch size设置过大导致GPU显存溢出

  • 现象:训练刚开始即报错CUDA out of memory,或者训练到一半随机中断。
  • 原因:GPU显存是硬性约束,batch size乘以单张图片的显存开销必须小于显存总量。当--imgsz设置为640时,YOLO11s在FP16混合精度下单张图的显存开销大约为1.5-2GB,16的batch size就需要24-32GB显存,8GB的卡根本扛不住。
  • 解决:按显存容量设置batch size,8GB显存用8,12GB用16,24GB用32。同时把--cache参数改为--cache ram或者不加,避免缓存图片到显存增加额外开销。另外一个有效手段是降低--imgsz到480,代价是精度略有下降,但对零售柜这种大目标的场景影响有限。

5.3 Mac M芯片训练时MPS算子不支持导致报错

  • 现象:训练脚本检测到MPS可用,但运行到某个epoch时报错,错误信息包含RuntimeError: Placeholder storage has not been allocated on MPS devicenot implemented for MPS
  • 原因:PyTorch的MPS后端还在持续完善中,YOLO11的数据增强操作和部分损失计算函数可能没有MPS实现,导致前向或反向传播时遇到不支持的算子。
  • 解决:脚本的降级逻辑应该是自动检测到MPS错误后切换到CPU。如果没有这个逻辑,手动在环境变量中禁用MPS:
# 强制使用CPU训练 export PYTORCH_ENABLE_MPS_FALLBACK=1 python train.py --data data.yaml --epochs 50 --batch-size 4 --device cpu

或者更简单直接,把启动参数--device设为cpu。注意MPS不支持时不要反复重试,直接切CPU更节省时间,训练速度慢一些但稳定。

5.4 类别不平衡导致部分商品类别的AP极低

  • 现象:训练日志显示整体mAP确实在0.9以上,但个别类别的AP只有0.5甚至更低,比如某些小众零食类别。
  • 原因:113个类别中长尾分布明显,部分冷门SKU在整个数据集中的目标数量可能只有几十个,训练样本数量不足以让模型学会判别这些类别。另外部分类别外观非常相似,比如不同口味的同品牌饮料,罐体颜色接近,模型容易混淆。
  • 解决:最直接的手段是给这些类别增加数据增强的权重,或者复制这些样本生成更多训练数据。另一个思路是调整类别损失的权重:
# data.yaml中增加类别权重配置(YOLO11部分版本支持) cls_weights: 0: 1.0 1: 1.0 # 冷门类别权重调高 45: 3.0 46: 3.0

调整权重的逻辑是让模型在计算分类损失时给冷门类别更高的惩罚,迫使模型更重视这些类别的特征学习。但权重不宜过大,否则可能干扰其他类别的正常收敛。

5.5 训练日志显示的mAP与博主日志差异过大

  • 现象:使用完全相同的参数训练,自己的结果比博主日志里的结果低了10个百分点以上。
  • 原因:最常见的原因是数据集划分不一致。博主可能对训练集、验证集做了特定划分,而你的脚本自动随机划分后,验证集里恰好含有较多困难样本。另一个可能的原因是训练时--seed参数不同,导致随机初始化权重路径不同,落在不同的局部最优解。
  • 解决:使用博主提供的划分文件,如果没有,固定--seed 42再训练一次。数据集划分对最终结果的影响比很多初学者想象的大得多,尤其是类别不平衡时。固定种子的意义是让结果有可复现性,方便排查问题。

6. 验证与进阶:用训练结果反推数据集质量,让YOLO11跑得更准

训练完成后,模型的推理精度已经基本定型,但还有三个验证维度和两个进阶技巧值得做。

第一个验证维度是混淆矩阵。YOLO11训练结束后会在runs/train/exp/confusion_matrix.png生成混淆矩阵,从对角线的亮度分布能直观看到哪些类别互相混淆。如果同类商品的不同口味之间出现系统性错分,说明特征空间重叠严重,需要在数据层面增加这些类别的差异性样本。第二个维度是绘制PR曲线,重点关注尾部——那些AP低的类别在PR曲线上通常表现为召回率上不去,说明模型对这些类别的特征提取能力不足,可以考虑增加这类商品的增强样本,比如模拟不同的摆放角度和遮挡程度。第三个维度是直接做一次推理可视化:

from ultralytics import YOLO # 加载训练好的权重并进行推理验证 model = YOLO('runs/train/exp/weights/best.pt') results = model.predict( source='dataset/images/val/', conf=0.25, save=True, save_txt=True, imgsz=640 )

重点关注labels文件夹下生成的txt预测结果,与真实标签逐类对比,找出系统性的漏检或误检位置。如果漏检集中在层板边缘的商品,可以考虑在训练时增加Mosaic增强的概率;如果误检集中在玻璃反光区域,可以引入随机亮度和对比度扰动来增强模型的鲁棒性。

进阶技巧方面,第一个值得做的是在现有113类基础上增量训练。零售柜场景的SKU是动态变化的,新品上架后不可能重新标注5000张图。正确做法是冻结前80%的层,只微调最后的检测头,用几百张新品的标注图做增量训练。这种方式既保留了原有类别的识别能力,又能快速适配新商品,训练时间只需要几十分钟。第二个技巧是知识蒸馏。如果你拥有一张性能好的大模型作为教师模型,把它的输出作为软标签来训练一个小模型,可以在几乎不掉精度的情况下把模型体积压缩到原来的四分之一,这对部署到嵌入式设备很有价值。

最后说一个我自己的习惯:每次训练结束,我都会保存模型在验证集上的失败case,按类别归类放到一个文件夹里,持续积累。这些失败case在后续迭代中是最有价值的调试素材,比盲目调参要高效得多。这个习惯坚持下来已经成了我项目流程的一部分,希望也能帮到你。

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

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

STM32H750VBT6+LAN8720A+LWIP以太网开发实战与避坑指南

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

作者头像 李华
网站建设 2026/9/24 11:58:50

Google Workspace 区域定价购域与 Cloudflare 托管配置记录

本文记录通过 Google Workspace 开通流程获取自定义域名&#xff0c;并将 DNS 解析托管至 Cloudflare 的完整技术实践。涉及区域结算差异、订阅生命周期管理、DNS 迁移与 DNSSEC 配置等环节。一、背景与问题常规域名注册商普遍采用“首年低价、续费高价”策略&#xff0c;对需要…

作者头像 李华
网站建设 2026/9/24 11:56:45

QGC连接PX4飞控保姆级教程:USB、数传、WiFi三方式全流程

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

作者头像 李华
网站建设 2026/9/24 11:52:21

ESP32-CAM网络摄像头实战:Arduino IDE配置与烧录全攻略

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

作者头像 李华
网站建设 2026/9/24 11:52:15

ST7701S屏幕不亮?从SPI到MIPI DSI的完整排查思路与实战复盘

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

作者头像 李华