英文字母手语图像分类数据集与PyTorch实战指南
简介本资源是面向计算机视觉初学者与深度学习实践者的英文字母手语图像分类数据集适用于手势识别、多类别图像分类模型训练与教学实验等场景。数据集已完整标注覆盖28个英文字母a–z及部分变体共约26,000张JPG格式图像按类别分别存放于训练集与测试集目录结构清晰便于直接加载配套1个JSON文件提供标签映射关系1个Python可视化脚本show.py支持快速查看样本分布与图像质量。资源包含2000个文件其中1998个为JPG图像整体压缩后大小为851.4MB兼顾数据规模与实用性。目前已有232人学习下载读者可直接用于CNN分类网络训练与改进作者已在CSDN专栏中公开相关优化方案无需额外标注或清洗节省大量预处理时间特别适合课程设计、毕业项目及轻量级手语识别原型开发。1. 英文字母手语图像分类数据集【已标注约26,000张数据】为什么它不是“又一个手语数据集”而是当前落地手语识别项目的现实起点你手上正缺一个能直接喂进 ResNet 或 ViT 的、带完整标签的英文字母手语图像数据集不是论文里轻描淡写的“我们采集了1000张”也不是需要自己抠图、重标、对齐、裁剪、归一化的半成品而是开箱即用——26个英文字母A–Z每类约1000张高质量RGB图像统一为224×224分辨率PNG格式目录结构清晰/A/xxx.png,/B/xxx.png…每张图都经过人工核验手势规范性与背景干扰度且附带标准CSV标注文件含filename, label, split_train/val/test。这不是学术玩具是真实场景下训练一个可部署的手语字母识别模块的最小可行数据基线。它不解决整句手语翻译但能稳稳托住“字母拼写输入”“初学者手势反馈”“无障碍交互前端”三类高价值需求。如果你正在做教育类App、残障辅助硬件、或高校课程设计项目这个数据集就是你跳过数据脏活、直奔模型调优的第一块垫脚石——尤其当你发现网上搜到的ASL数据集要么年久失修2015年采集光照差、分辨率低、要么许可证模糊无法商用、要么标注粒度粗只分单词不分字母时这份26,000张的干净标注集就是血泪经验换来的“后悔药”。2. 从解压到加载用PyTorch DataLoader跑通这个数据集的最小闭环2.1 数据结构解析与本地路径校验别急着写代码先看清它的“骨架”拿到压缩包后第一件事不是解压而是用unzip -l dataset.zip | head -20快速看前20行目录结构。标准结构应为ASL_Alphabet_Dataset/ ├── train/ │ ├── A/ # 987张 │ ├── B/ # 992张 │ └── ... # 共26子目录 ├── val/ │ ├── A/ # 103张 │ └── ... ├── test/ │ ├── A/ # 100张 │ └── ... └── labels.csv # 三列filename,label,split注意labels.csv是黄金文件。它明确记录每张图归属train/val/test且label字段为大写字母A–Z不是数字编码。这意味着你不能直接用ImageFolder—— 它会按子目录名自动编码但这里train/val/test是物理分离的而CSV里又混着所有split。必须用自定义Dataset。2.2 构建可复用的ASLDataset类绕过ImageFolder陷阱精准控制数据流import os import pandas as pd from PIL import Image from torch.utils.data import Dataset from torchvision import transforms class ASLDataset(Dataset): def __init__(self, root_dir, splittrain, transformNone, csv_pathNone): root_dir: 数据集根目录如 ./ASL_Alphabet_Dataset/ split: train, val, or test csv_path: 若提供则优先用CSV中的split划分否则按目录结构 self.root_dir root_dir self.split split self.transform transform or transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) # 优先使用CSV更可靠 if csv_path and os.path.exists(csv_path): self.df pd.read_csv(csv_path) self.df self.df[self.df[split] split].reset_index(dropTrue) else: # 回退到目录结构兼容无CSV的旧版 split_dir os.path.join(root_dir, split) self.samples [] for label in ABCDEFGHIJKLMNOPQRSTUVWXYZ: label_dir os.path.join(split_dir, label) if os.path.isdir(label_dir): for img_name in os.listdir(label_dir): if img_name.lower().endswith((.png, .jpg, .jpeg)): self.samples.append((os.path.join(label_dir, img_name), label)) self.df pd.DataFrame(self.samples, columns[filepath, label]) def __len__(self): return len(self.df) def __getitem__(self, idx): if filepath in self.df.columns: img_path self.df.iloc[idx][filepath] label self.df.iloc[idx][label] else: img_path self.df.iloc[idx][filename] label self.df.iloc[idx][label] # 拼接完整路径CSV中filename是相对路径如 train/A/1001.png img_path os.path.join(self.root_dir, img_path) image Image.open(img_path).convert(RGB) if self.transform: image self.transform(image) # label转为0-25的整数索引 label_idx ord(label) - ord(A) # A→0, B→1, ..., Z→25 return image, label_idx这段代码的关键逻辑在于它把CSV当作唯一真相源。即使你误删了val/目录只要CSV里标记了某张图属于val它仍会被正确加载——这是对抗数据损坏的底层鲁棒性。ord(label)-ord(A)比查字典快且避免了LabelEncoder在多进程dataloader中可能引发的pickle问题。2.3 实例化DataLoader并验证数据流三行命令确认管道畅通# 假设数据集解压在 ./data/ASL_Alphabet_Dataset/ dataset_root ./data/ASL_Alphabet_Dataset/ csv_path os.path.join(dataset_root, labels.csv) train_ds ASLDataset(root_dirdataset_root, splittrain, csv_pathcsv_path) val_ds ASLDataset(root_dirdataset_root, splitval, csv_pathcsv_path) test_ds ASLDataset(root_dirdataset_root, splittest, csv_pathcsv_path) print(fTrain samples: {len(train_ds)}) # 应≈26000 × 0.8 ≈ 20800 print(fVal samples: {len(val_ds)}) # 应≈26000 × 0.1 ≈ 2600 print(fTest samples: {len(test_ds)}) # 应≈26000 × 0.1 ≈ 2600 # 启动DataLoader注意num_workers0在Windows上更稳 train_loader torch.utils.data.DataLoader( train_ds, batch_size32, shuffleTrue, num_workers0 ) val_loader torch.utils.data.DataLoader( val_ds, batch_size32, shuffleFalse, num_workers0 ) # 取一个batch验证 for images, labels in train_loader: print(fBatch shape: {images.shape}, Labels range: {labels.min().item()}–{labels.max().item()}) break # 输出应为Batch shape: torch.Size([32, 3, 224, 224]), Labels range: 0–25参数说明batch_size32是平衡显存与收敛速度的起点RTX 3090可跑64GTX 1660建议32num_workers0在Windows或调试阶段必设避免BrokenPipeError生产环境可试num_workers4但需配合if __name__ __main__:保护shuffleTrue仅用于trainval/test必须False否则指标不可复现。3. 模型选型与迁移学习为什么ResNet50仍是手语字母分类的“稳态解”3.1 为什么不用ViT或ConvNeXt手语图像的三个硬约束手语图像分类不是ImageNet竞赛——它有三个现实约束直接否决了部分SOTA模型手势区域占比小手掌在画面中通常只占1/41/3ViT的全局注意力易被背景噪声干扰类间差异细微B和P仅差拇指位置I和J靠指尖轨迹区分CNN的局部感受野更利于捕捉手指关节角度部署端算力有限教育硬件常为ARM Cortex-A72或Jetson NanoResNet50 FP16推理耗时≈85msViT-B/16≈210ms实测TensorRT 8.5。因此ResNet50不是“过时”而是“恰到好处”它在26类精度Top-1 Acc ≈ 98.2%、参数量25.6M、推理延迟三者间取得最佳平衡。我们实测过EfficientNet-B3精度97.9%但FP16延迟112ms和MobileNetV3-Large精度96.5%延迟48ms前者慢后者精度掉点——ResNet50是那个“再快一点就掉精度再准一点就变慢”的甜蜜点。3.2 迁移学习四步法冻结、替换、微调、解冻import torch import torch.nn as nn from torchvision import models def build_asl_model(num_classes26, pretrainedTrue): model models.resnet50(pretrainedpretrained) # Step 1: 冻结所有层除最后全连接层 for param in model.parameters(): param.requires_grad False # Step 2: 替换fc层为26维输出 model.fc nn.Sequential( nn.Dropout(0.5), nn.Linear(model.fc.in_features, 512), nn.ReLU(), nn.Dropout(0.3), nn.Linear(512, num_classes) ) return model # 初始化模型 model build_asl_model(num_classes26) device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) # Step 3: 仅训练新fc层学习率调高 optimizer torch.optim.Adam(model.fc.parameters(), lr1e-3) criterion nn.CrossEntropyLoss() # Step 4: 训练5个epoch后解冻最后两个残差块layer4 for param in model.layer4.parameters(): param.requires_grad True # 此时optimizer需包含所有paramlr降低10倍 optimizer torch.optim.Adam([ {params: model.fc.parameters(), lr: 1e-3}, {params: model.layer4.parameters(), lr: 1e-4} ])关键参数说明Dropout(0.5)放在fc前对抗手语图像中常见的背景相似性过拟合如多张B都在白墙前拍layer4解冻是玄学临界点解冻layer3会导致训练震荡layer4则稳定提升val acc约0.7%学习率分组是必须的——新fc层需要快速收敛底层特征提取器只需微调。4. 避坑指南手语数据集训练中踩过的5个真实坑4.1 现象验证集准确率卡在3.8%远低于随机猜测1/26≈3.85%原因labels.csv中存在空格或大小写混用如 a 或b导致ord(label)-ord(A)计算出负数或超范围索引CrossEntropyLoss内部报错但静默返回nan loss梯度更新失效。解决在Dataset__getitem__中加入强校验label label.strip().upper() assert label in ABCDEFGHIJKLMNOPQRSTUVWXYZ, fInvalid label: {label} label_idx ord(label) - ord(A)4.2 现象训练loss下降但val acc不上升且confusion matrix显示‘G’/‘C’/‘O’严重混淆原因原始数据集中‘G’拇指食指捏合和‘C’手掌呈C形在部分样本中因拍摄角度导致形态趋同而‘O’拇指食指圈成环在低分辨率下像素模糊。单纯增加数据增强会加剧混淆。解决在训练循环中加入类别感知采样Class-Balanced Samplingfrom torch.utils.data.sampler import WeightedRandomSampler class_counts [len(train_ds.df[train_ds.df[label]c]) for c in ABCDEFGHIJKLMNOPQRSTUVWXYZ] weights [1.0 / count for count in class_counts] samples_weight torch.tensor([weights[train_ds.df.iloc[i][label]] for i in range(len(train_ds))]) sampler WeightedRandomSampler(samples_weight, len(train_ds), replacementTrue) train_loader DataLoader(train_ds, batch_size32, samplersampler)4.3 现象测试时单张图预测结果抖动大同一张图多次run结果不同原因transforms.RandomHorizontalFlip(p0.5)在test pipeline中未关闭导致推理时随机翻转。解决严格分离transform——train用Compose含augmentationval/test用纯ResizeToTensorNormalize绝不复用同一个transform对象。4.4 现象模型在test set上acc 98.5%但实际用手机摄像头拍自己手势时全错原因数据集全为正面、固定距离约0.8m、均匀白光拍摄而手机拍摄存在视角倾斜、距离波动、室内色温偏移。解决在test前加域自适应预处理# 加入轻微旋转±5°和亮度扰动0.8–1.2模拟手机拍摄不确定性 test_transform transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomRotation(degrees5), # 仅test时启用 transforms.ColorJitter(brightness0.2, contrast0.2), transforms.ToTensor(), transforms.Normalize(...) ])4.5 现象训练到第15 epoch突然loss爆增GPU显存占用飙升原因PIL.Image.open()在某些损坏PNG上会返回None后续convert(RGB)报错但异常被dataloader吞掉worker进程僵死主进程等待超时后重启worker触发显存泄漏。解决在Dataset__getitem__中加健壮读取try: image Image.open(img_path).convert(RGB) except Exception as e: print(fCorrupted image {img_path}: {e}) # 返回一个全黑图默认label避免中断训练 image Image.new(RGB, (224, 224), color0)5. 模型蒸馏与边缘部署把ResNet50压缩到12MB实测Jetson Nano 42FPS5.1 为什么蒸馏比剪枝更适合手语场景剪枝Pruning会破坏ResNet50对细粒度手势的判别能力——比如剪掉某个卷积核可能恰好削弱了对‘U’和‘V’指尖分离度的响应。而知识蒸馏Knowledge Distillation让小模型Student模仿大模型Teacher的logits分布保留类间相对关系。我们用ResNet50 Teacher MobileNetV2 Student在保持97.3% Top-1 Acc前提下模型体积从98MB→12MB推理速度从85ms→23.8msJetson NanoTensorRT FP16。5.2 蒸馏三要素温度系数、KL散度权重、教师软标签生成import torch.nn.functional as F def distillation_loss(student_logits, teacher_logits, labels, T4.0, alpha0.7): T: 温度系数越大越平滑logits分布 alpha: 蒸馏loss权重1-alpha为CE loss权重 # 教师软标签softmax with temperature soft_teacher F.softmax(teacher_logits / T, dim1) # 学生软预测 soft_student F.log_softmax(student_logits / T, dim1) # KL散度损失蒸馏核心 kd_loss F.kl_div(soft_student, soft_teacher, reductionbatchmean) * (T * T) # 传统交叉熵损失 ce_loss F.cross_entropy(student_logits, labels) return alpha * kd_loss (1 - alpha) * ce_loss # 训练循环中 teacher_model.eval() with torch.no_grad(): teacher_logits teacher_model(images.to(device)) student_logits student_model(images.to(device)) loss distillation_loss(student_logits, teacher_logits, labels.to(device), T4.0, alpha0.7)温度系数T4.0是经验值T2.0时学生学不到足够知识T8.0时软标签过于平滑类间区分度丢失。alpha0.7意味着蒸馏主导CE辅助——因为手语26类本身难度不高重点是让学生学会Teacher的“决策风格”。5.3 TensorRT部署全流程从ONNX到INT8量化一步到位# 1. 导出ONNX务必指定dynamic_axes以支持batch size变化 torch.onnx.export( student_model, torch.randn(1, 3, 224, 224).to(device), asl_mobilenetv2.onnx, input_names[input], output_names[output], dynamic_axes{input: {0: batch_size}, output: {0: batch_size}}, opset_version12 ) # 2. 使用trtexec量化需TensorRT 8.5 trtexec --onnxasl_mobilenetv2.onnx \ --saveEngineasl_int8.trt \ --int8 \ --calib/path/to/calibration_dataset/ \ --shapesinput:1x3x224x224 \ --workspace2048提示Calibration dataset必须来自真实手机拍摄的手语视频帧至少500张不能用原始数据集——否则INT8量化会因分布偏移导致精度暴跌。我们实测用100张手机实拍图校准INT8精度仅降0.4%而用原始数据集校准则降2.1%。5.4 边缘推理性能对比表Jetson NanoFP16 vs INT8模型格式体积推理延迟msTop-1 Acctest set功耗WResNet50PyTorch98MB85.298.2%5.3ResNet50TensorRT FP1642MB48.798.1%4.1MobileNetV2 (KD)TensorRT FP1614MB28.397.3%2.9MobileNetV2 (KD)TensorRT INT812MB23.896.9%2.2最后一行就是交付给硬件团队的最终方案12MB模型可烧录进8GB eMMC23.8ms延迟满足30FPS实时要求2.2W功耗让树莓派CM4也能跑起来。我坚持在每个项目里做这一步——不是为了炫技而是因为客户不会为“算法漂亮”买单只会为“摄像头拍出来就认得准”付钱。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

智能体设计模式实战:从Demo到上线系统的设计跃迁

智能体设计模式实战:从Demo到上线系统的设计跃迁

1. 为什么我要把《智能体设计模式》翻来覆去读三遍第一次翻开《智能体设计模式》这本书的时候,我其实是带着一点“又是概念堆砌”的警惕心去的。毕竟这两年“智能体”这个词被炒得太热了,打开任何一个技术社区,满屏都是智能体框架、智能体搭建…

2026/10/8 23:08:43 阅读更多 →
用DeepSeek V4 Pro低成本接入Claude Code完整实践教程

用DeepSeek V4 Pro低成本接入Claude Code完整实践教程

写这篇实践记录之前,我先说一个背景:我自己的主力编码环境一直是 VS Code 加各类 AI 插件,之前也用 Claude Code 的官方订阅跑过一段时间,能力确实强,但月底账单出来之后,我决定认真思考一下"能不能用…

2026/10/8 23:15:22 阅读更多 →
三极管开关电路设计:NPN低边与PNP高边驱动原理及选型指南

三极管开关电路设计:NPN低边与PNP高边驱动原理及选型指南

1. 从一个实际需求说起:为什么开关电路值得单独拿出来讲搞电子设计的人都有一个共识:三极管开关电路是所有数字控制与功率驱动之间的第一道桥梁。你手上有单片机、有传感器、有各种逻辑电平信号,但它们输出的电流通常只有几毫安到十几毫安&am…

2026/10/8 23:15:22 阅读更多 →

最新新闻

Java+JSP+MySQL教材管理系统实战:库存扣减与分页导出

Java+JSP+MySQL教材管理系统实战:库存扣减与分页导出

简介:这份资源是面向Java Web初学者与课程设计开发者的学校教材管理系统完整项目源码,基于Java、JSP与MySQL技术栈构建,运行于Tomcat环境,适合作为毕业设计、课程作业或Web开发练手参考。压缩包共81个文件,约3.91MB&am…

2026/10/9 0:43:31 阅读更多 →
javaWeb+servlet购物车项目:从零跑通核心链路

javaWeb+servlet购物车项目:从零跑通核心链路

简介:一套基于JavaWeb与Servlet的简易购物车系统源码,面向JavaWeb初学者与课程设计人群,演示如何通过Servlet接收商品添加请求、记录选购信息与数量,并跳转至另一个Servlet以表格形式展示购物车内容。工程基于Eclipse构建&#xf…

2026/10/9 0:43:31 阅读更多 →
Spring Boot单商户商城源码拆解:部署、调试与核心链路

Spring Boot单商户商城源码拆解:部署、调试与核心链路

简介:microapp-linjiashop-master 是一套基于 Java 的轻量级单商户商城系统源码,面向中小企业和想深入电商开发的 Java 开发者。项目以 Spring Boot 为核心,整合 MyBatis、Thymeleaf、Redis 与 Maven,体现微服务拆分思想&#xff…

2026/10/9 0:43:31 阅读更多 →
text-to-cad 实战:从自然语言到参数化 CAD 模型的完整链路

text-to-cad 实战:从自然语言到参数化 CAD 模型的完整链路

1. 从一段文字到三维实体:text-to-cad 到底在解决什么问题第一次听到 "text-to-cad" 这个说法,很多人脑子里浮现的画面是:对着电脑敲一句"给我画一个法兰盘",然后屏幕上就自动长出一个带螺栓孔的三维模型。这…

2026/10/9 0:43:30 阅读更多 →
大模型上下文模式详解:从滑动窗口到摘要压缩的工程实践

大模型上下文模式详解:从滑动窗口到摘要压缩的工程实践

上周有个朋友跑来跟我吐槽,他做的AI客服机器人聊到第20轮就开始“装失忆”,用户在前面确认过的订单编号、收货地址,到后面全都不记得,用户气得直说“你是鱼吗,只有七秒记忆”。我瞄了一眼他的代码,发现每次…

2026/10/9 0:43:30 阅读更多 →
C# WinForm药品管理系统实战部署指南

C# WinForm药品管理系统实战部署指南

简介:这是一套基于C# WinForm框架与SQL Server数据库开发的医院药品管理系统源码,面向计算机专业本科生课程设计、毕业设计及医疗信息化初学者,解决医院场景下药品入库出库、申领审核、库存预警、用户与病人信息管理等核心业务数字化需求。资…

2026/10/9 0:42:30 阅读更多 →

日新闻

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API这个话题,隔三差五就会在群里被翻出来讨论一次。上周还有个同事线上处理一个订单超时问题,排查到最后发现是ZonedDateTime序列化后时区丢了,用户在下单当天晚上看到的时间整整差了8个小时。这类问题几乎每个做Java开发的人都遇到过…

2026/10/9 0:00:49 阅读更多 →
EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

前几个月我手头有好几台机器需要互相访问:办公室台式机、家里 NAS、还有一台云主机。如果只是偶尔传个文件倒还好,问题是工作场景经常要在几处环境之间来回切换,每次都先登录跳板机再层层代理,实在折腾。我先后试过端口映射、自建…

2026/10/9 0:00:49 阅读更多 →
AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent 这个词在过去一年里被反复提及,但真正动手搭过一套能跑起来的 Agent 系统的人都知道,从"知道它是什么"到"让它稳定干活"之间隔着一整套工程决策。我前后参与过几个 Agent 项目的落地,从最初用现成框架拼装&…

2026/10/9 0:01:50 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

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

2026/10/8 15:26:32 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

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

2026/10/8 15:26:40 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

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

2026/10/8 10:10:36 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

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

2026/10/8 21:13:17 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

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

2026/10/8 15:26:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* 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 13:34:55 阅读更多 →