尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
BTCV 14器官切片数据实战:医学图像分割预处理与训练避坑
简介一套基于BTCV公开数据集的腹部14器官医学图像分割2D切片数据已整理完成面向医学影像算法开发者、科研人员及相关专业学生提供可直接用于模型训练与验证的规范化数据。数据按轴位面、冠状面、矢状面三个方向组织每个方向均包含images原始切片与masks标签掩膜目录制作时已剔除ROI区域占比不足3%的切片对CT图像采用窗宽窗位对比度拉伸并统一缩放为512×512像素。标签定义清晰0为背景113分别对应脾脏、双肾、胆囊、食道、肝脏、胃、主动脉、下腔静脉、门静脉和脾静脉、胰腺及左右肾上腺可直接适配多器官分割任务。资源包为7z格式共2000个文件其中1998个PNG构成图像与掩膜数据另含show.py可视化脚本和classes.json类别配置文件包体约854.92MB目录结构便于按方向检索。目前已有1290人学习下载适合需要标准医疗分割切片数据的初学者和研究者快速上手。1. 医学图像分割数据集怎么选BTCV 14 器官切片数据的落地价值真正做过腹部多器官分割的人很少上来就拿 3D 体数据直接训练。显存不够、标注颗粒度不统一、切面朝向混乱任何一个都够折腾一周。我拿到这套 BTCV 14 器官切片数据时第一反应是检查它对切面的处理方式轴位面、冠状面、矢状面各生成一组 2D 图像和对应 mask去掉了 ROI 区域占比小于 3% 的无效切片CT 已做 windowing 对比度拉伸全部缩放到 512×512。换句话说绕开 3D 显存瓶颈的预处理环节已经替你完成。适合刚起步做医学图像分割的工程师或者想在 2D 网络里快速验证腹部多器官方案的从业者把精力放到模型训练而不是数据清洗上。2. 数据组织与标签体系三个切面 1141/5691/6811 张切片的构成2.1 轴位、冠状、矢状三个切面的解剖含义与目录组织把 BTCV 的 3D CT 体数据想象成一个长方体的体素块x 轴对应病人左右方向y 轴对应前后方向z 轴对应头脚方向。沿不同方向抽片得到的 2D 视图完全不同。沿 z 轴逐层抽片得到轴位面也就是从上往下看身体临床读片最常用沿 y 轴逐层抽片得到冠状面相当于从前往后看沿 x 轴逐层抽片得到矢状面能清楚看到脊柱两侧的对称关系。这份资源就是按这个思路组织的。每个切面一个目录里面分成 images 和 masks 两个文件夹images 存放预处理后的 2D CT 切片masks 存放同名同尺寸的标签图。文件名完全对应像 img0009_315.png 这种命名前半段是卷号后半段是切片序号在 masks 目录里能找到同名文件。验证两个目录是否配套只需要比对文件名集合。3D 体数据切成 2D 图后每个器官在单张切片上只表现为截面。肝脏、脾脏这类大器官在轴位面上截面大肾上腺这种小结构只在少数切片上出现。这也就是为什么资源在切完之后又做了一道 ROI 过滤——把标注区域占整张图比例不足 3% 的切片丢弃滤掉绝大多数“几乎看不见器官”的无效层。源 CT 本身一般是 512×512 的重建矩阵但不同厂商略有差异统一缩放到 512×512 后网络输入尺寸不需要再猜训练时能省掉不少 batch padding 的麻烦。2.2 classes.json 与 0-13 标签映射标签体系是这套资源最核心的部分。背景是 0其余 13 个类按解剖位置依次排开对应关系如下表标签值含义标签值含义0背景7胃1脾脏8主动脉2右肾9下腔静脉3左肾10门静脉和脾静脉4胆囊11胰腺5食道12右肾上腺6肝脏13左肾上腺这个顺序不是随意编排的直接沿用 BTCV 官方的 label 顺序。classes.json 里保存的就是这份映射表可能是数组也可能是字典。我一般先打开看一眼结构再决定用classes[1]还是classes[1]取名字几秒就能确认。要特别提醒的是mask 图里的像素值就是这些整数不是 one-hot 编码后的多通道图所以加载时要用cv2.IMREAD_UNCHANGED不能用默认三通道方式否则会把 label 读成 RGB 再转灰度值域全乱。2.3 从文件数量反推切片分布三个切面的图片数量差异很大x 轴 1141 张y 轴 5691 张z 轴 6811 张。这个差异本身就说明问题——矢状面经过 ROI 过滤后保留的切片最少轴位面最多。腹部 CT 的 z 方向覆盖范围最大所以轴位切片数量合理而矢状面方向身体左右宽度最小器官截面占比更容易低于 3%被滤掉的切片比例自然最高。可以写一个几行的脚本验证这个假设同时检查数据配对是否完整。遍历三组目录后把两边的图片名集合做差任何一边多出来的文件就是配对问题from pathlib import Path for axis in [x, y, z]: img_dir Path(f{axis}/images) mask_dir Path(f{axis}/masks) img_names {p.name for p in img_dir.glob(*.png)} mask_names {p.name for p in mask_dir.glob(*.png)} print(f{axis} axis: images{len(img_names)}, masks{len(mask_names)}) print(only in images:, sorted(img_names - mask_names)[:5]) print(only in masks:, sorted(mask_names - img_names)[:5])逻辑说明脚本把两个目录的文件名转成集合集合差运算能一次性找出不配对文件。前三行用于确认数量与资源描述一致后两行定位具体是无 mask 的图还是无图的 mask。我第一次核对 z 轴时就靠这个脚本抓到一批后缀大小写不一致的文件PNG与pngWindows 上不敏感、Linux 上却会直接报文件找不到训练循环里才爆出来。建议拿到数据先跑一遍几秒钟的事。参数说明文件都假设是 PNG 格式如果资源里存在.jpg或.npy把glob(*.png)改成对应后缀[:5]只是输出前 5 个防止刷屏全量名单可以去掉切片。脚本不要写死绝对路径用相对路径组织三个切面目录后续换机器不用改代码。2.4 切片数量差异对训练采样策略的影响1141 张矢状面与 6811 张轴位面如果直接混合训练模型会被动把“见过更多的轴位面”当作先验导致矢状面切片上的分割精度明显偏低。常见做法是每个 batch 里按轴均匀采样或者对矢状面做短边重采样增强。这个问题等模型跑起来再发现就晚了最好在网络结构设计阶段就决定要不要做轴向对齐。我一般会为每个轴单独维护一个采样权重让三个轴在一个 epoch 内被采到的次数接近而不是按文件数量自然分布。这样做的代价是每个 epoch 实际看到的样本数变少但对小切面方向的性能提升非常直接。如果你的目标只是轴位面那把 x、y 轴当作辅助训练集也是一种可选方案辅助集不参与最终评估只用来做预训练。3. 预处理链路windowing 对比度拉伸与 3% ROI 过滤的实现3.1 windowing从 CT 值到可视灰度CT 图像里每个像素是组织对 X 线衰减的量化值单位是 HU范围通常在 -1024 到 3000 以上。如果把这个宽范围直接线性压到 0-255内脏对比度会非常差因为软组织只集中在很窄的一段 HU 区间里。windowing 做的事是只保留某个感兴趣区间把区间外的值截断区间内映射到可视灰度import numpy as np def apply_windowing(volume, window_center50, window_width400): lower window_center - window_width / 2.0 upper window_center window_width / 2.0 out (volume - lower) / (upper - lower) out np.clip(out, 0.0, 1.0) return (out * 255).astype(np.uint8)逻辑说明分母window_width控制对比度分子volume - lower先把区间下界平移到 0再除以窗宽归一化到 0-1最后乘 255 变成 8bit 灰度。低于下界的值全部变成 0高于上界的全部变成 255腹部软组织的层次就这样展开。参数说明腹部平扫常用的窗位在 30-60 HU、窗宽在 300-500 HU。如果是门静脉期增强扫描窗位固定 50、窗宽放到 400 附近肝脏和门静脉的灰度层次都会比较清楚。但这份资源里已经做过了这一步你拿到的是处理后的结果不要重复再做一次 windowing否则灰度被二次压缩对比度反而劣化。我自己踩过一次把两套灰度分布不一致的数据混进训练集测试集精度掉了两个点才排查到根因。3.2 ROI 占比不足 3% 的切片过滤这一步发生在切完切片之后。每张切片对应一张 mask统计 mask 中所有非零像素占整张图的比例低于阈值就丢弃import cv2 import numpy as np def keep_slice(mask_path, threshold0.03): mask cv2.imread(mask_path, cv2.IMREAD_UNCHANGED) if mask is None: return False non_zero np.count_nonzero(mask) total mask.shape[0] * mask.shape[1] ratio non_zero / total return ratio threshold逻辑说明np.count_nonzero统计像素值不为 0 的数量除以总像素数得到 ROI 面积占比函数返回 True 表示保留这张切片。判别阈值为 3%低于它时切片里最大器官的截面积也只有一两百个像素网络很难从中学到特征还会作为难例拖慢收敛。参数说明threshold是 float0.03 对应 3%。如果做小器官专项比如只分左右肾上腺可以调到 0.01否则过少的肾上腺切片会被全部滤掉反过来做脾脏或肝脏0.03 就够了。建议先用这个脚本跑一遍三组目录记录每张切片的 ratio 分布再决定阈值是否需要调整而不是直接信任一个固定数字。3.3 统一缩放到 512×512 时 mask 的插值策略缩放图像和 mask 是两条路CT 灰度图用双线性插值保平滑mask 用最近邻插值避免引入非法标签。如果给 mask 也做双线性边缘像素会插出 0.5、2.4 这样的中间值后续交叉熵计算时这些非法类别会让 loss 直接报错或者悄悄改变类别权重。import cv2 def resize_pair(img, mask, size(512, 512)): img_resized cv2.resize(img, size, interpolationcv2.INTER_LINEAR) mask_resized cv2.resize(mask, size, interpolationcv2.INTER_NEAREST) return img_resized, mask_resized逻辑说明cv2.resize的size参数是目标尺寸顺序是 (宽, 高)也就是 (512, 512)。图像用线性插值保留器官边缘的灰度过渡mask 用最近邻保证输出像素值只来自原图的整数标签不会产生新类别。参数说明如果后面要做随机裁剪增强建议先缩放到比 512 更大的尺寸比如 560训练时再随机裁剪 512给模型一点平移不变性如果资源已经固定 512×512跳过这一步改用在线水平翻转即可。这是一个取舍在线旋转通常会给 mask 引入伪影所以 2D 切片场景我一般只用翻转和轻微亮度扰动不用旋转。4. 加载与可视化show.py 与 img0009_315.png 里藏着什么4.1 文件名的语义与同步关系文件名 img0009_315.png 包含两段信息img0009 是卷号315 是切片序号。同一卷下切片序号应当是连续的这种命名其实帮了大忙后续做 3D 重建时只要按序号排序就能恢复空间顺序。正因为如此我拿到数据的第一件事就是确认每个卷号的切片数连续、没有中间缺号。我在另一份数据上吃过亏切片序号从 315 直接跳到 318缺的两层实际上是空层模型在训练时没见过这种空洞推理阶段碰到类似分布输出就抖得厉害。查这个问题不复杂按卷号分组、对切片序号排序然后检查相邻差值是否都为 1几行脚本就能完成。可视化之前先做这一步能避免后面拿着一套断裂的数据反复调模型。4.2 show.py一份合格的可视化代码该做三件事资源里自带的 show.py是打包者为了让刚下载的人快速验证数据内容而写的。一份在分割任务里真正好用的可视化脚本至少应该做三件事显示图像、显示 mask、叠加显示。单独看图像只能确认切片内容单独看 mask 只能确认标签涂得对不对只有把半透明 mask 叠在 CT 灰度图上才能判断器官边界是否与图像解剖结构对齐。show.py 的调用方式通常就是python show.py --img x/images/img0009_315.png --mask x/masks/img0009_315.png这类命令行图像以灰度显示mask 以彩色标签叠加。常见做法是给每类分配固定颜色肝脏用暗红、脾脏用紫、肾脏用黄绿人眼核对边界时效率高不少。如果没有 show.py用下面的代码也能实现同样的叠加效果import cv2 import numpy as np img cv2.imread(x/images/img0009_315.png, cv2.IMREAD_GRAYSCALE) mask cv2.imread(x/masks/img0009_315.png, cv2.IMREAD_UNCHANGED) color_mask np.zeros((*mask.shape, 3), dtypenp.uint8) color_mask[mask 6] [60, 20, 220] # 肝脏 color_mask[mask 1] [220, 20, 60] # 脾脏 overlay cv2.addWeighted(cv2.cvtColor(img, cv2.COLOR_GRAY2BGR), 0.7, color_mask, 0.3, 0) cv2.imwrite(overlay.png, overlay)逻辑说明读取灰度图后转成三通道 BGR掩码按标签值刷成不同 RGB 颜色再用addWeighted做加权叠加权重分别是 0.7 和 0.3。这样背景保持原灰度器官区域透出颜色边界一眼就能看出有没有错位。mask 6就是肝脏如果要叠加全部标签可以遍历 1 到 13 给每类配一个颜色。参数说明cv2.IMREAD_UNCHANGED是这里的关键参数不加时 OpenCV 会按 8 位三通道读取本资源的 label 是 0-13 的单通道 PNG用IMREAD_UNCHANGED才能保持原值。addWeighted的权重可以反过来调想更清楚看器官就把 0.3 调成 0.5但太透明时边界不容易分辨。4.3 用像素统计验证 mask 是否“干净”可视化只是肉眼层面我还会用脚本统计 mask 的离散值域确认没有越界标签。这个验证在分割项目里的重要性不亚于数据本身import cv2 import numpy as np from pathlib import Path mask_dir Path(z/masks) unique_tags set() for mask_path in mask_dir.glob(*.png): mask cv2.imread(str(mask_path), cv2.IMREAD_UNCHANGED) if mask is None: continue unique_tags.update(np.unique(mask).tolist()) print(所有出现的标签值:, sorted(unique_tags))逻辑说明遍历一个切面下的全部 mask把每张图里出现的像素值归并到集合最后打印全集。理论上这个集合应当是 0 到 13 的子集而且对于腹部多器官数据集应出现几个主要器官而不是只剩背景。如果输出只有 0说明加载方式错了多半是读成三通道再转灰度如果出现大于 13 的值说明某张 mask 存在非法标注或混入其他数据源。参数说明update和tolist保证 Python 集合操作正常如果数据量太大可以抽样前 200 张但抽样只能证明样本干净不能替代全量扫描。这个脚本建议在第一次跑训练前执行也是一线团队常说的“数据准入”环节。5. 避坑与排查5 个高频问题的现象、原因与解决办法5.1 现象mask 加载后全黑或类别数异常现象用 show.py 显示 mask大部分区域是黑的或者某些器官边界出现黑白噪声。原因最常见是用了cv2.imread(mask.png)默认三通道读取随后又转灰度。单通道 PNG 被强制按三通道读后每个通道值仍是原值但转灰度时会按 BGR 权重换算0-13 的小整数会被映射到 0-10 左右的灰阶看起来就是接近全黑。解决统一用cv2.IMREAD_UNCHANGED读 mask并且打印np.unique(mask)核对值域。这类问题在网络输入阶段很难发现因为归一化到 0-1 后 0 和 255 的差异被缩小真正爆出来是在 loss 计算时类别数对不上。5.2 现象三个切面里同一器官的形状对不上现象轴位面上肝脏轮廓很大矢状面上同一个病例看不到肝脏甚至左右肾在两个方向上的形状差异让人觉得标注错了。原因这不是资源错误而是同一 3D 体积在不同方向切片上的截面天然不同。肝脏在轴位面是最大截面在矢状面就窄得多左肾和右肾在冠状面和矢状面上的投影也完全不一样。腹部多器官分割用 2D 网络做时最容易在这里产生“标注不对”的误判。解决按轴位、冠状、矢状分开建模或者用多轴输入的网络结构而不是把三个轴混进一个模型当作同一分布。常见做法是三个切面各训一个编码器下游融合成 3D 输出比单轴向模型更可靠。如果资源已经固定 512×512先把 z 轴一个轴跑通再考虑融合 x、y 轴。5.3 现象mask 边界像锯齿比 CT 图像边缘更难看现象某器官边缘在 CT 灰度图上还算平滑在 mask 里却是一格一格的甚至出现 1 像素宽的缺口。原因如果 mask 缩放到 512×512 时用了线性插值边缘像素会被插成小数再取整产生明显阶梯此外原 3D 标注本身可能就是在低分辨率体素上画的转到 2D 后边缘天然粗糙。解决缩放 mask 一律用最近邻插值代码见 3.3 节的resize_pair。如果锯齿来自原始标注不要用形态学开闭运算去后处理那会改变器官面积影响 Dice 评估的公平性。训练时用在线数据增强比事后平滑更合适。5.4 现象胰腺和肾上腺在预测结果里几乎总是缺失现象训练后模型对肝脏、肾脏分得很准但胰腺、肾上腺的输出几乎是一团零。原因ROI 3% 过滤后胰腺和肾上腺切片占比远低于肝脏类别极度不平衡再加上这两类体积小、边界模糊模型在交叉熵主导下很容易把它们当成背景。解决给 loss 加类别权重或改用 Dice 类 loss。对这类小器官权重按训练集里像素占比的倒数来设置是个可操作起点import numpy as np counts np.bincount(mask.ravel(), minlength14) epsilon 1e-6 weights 1.0 / (counts epsilon) weights weights / weights.sum()逻辑说明np.bincount统计 0-13 每个类别的像素数取倒数后小器官获得大权重再归一化避免数值过大。传入损失函数时交叉熵的weight参数直接填这份数组即可。参数说明minlength14保证缺失的类也占一个位置否则数组长度会随数据变化。epsilon防止除零。训练时还要观察验证集上的小器官 Dice否则训练损失降得漂亮胰腺和肾上腺却可能完全没有被学习到。5.5 现象训练损失忽高忽低同一个 case 不同 epoch 的表现抖动大现象loss 曲线不是平滑下降而是周期性波动切到批次里某个特定卷时损失明显变大。原因文件名里卷号相同的切片在随机 batch 里被分到不同迭代不同卷的灰度分布和器官形态差异又大模型每遇到一个没见过的卷就重新适应一遍。解决按卷号拆分验证集别让同一个卷的切片同时出现在训练和验证里。这比调参更根本也直接影响测试指标的可信度。周期抖动如果还伴随显存溢出可以把 batch size 降到 8 并开启梯度累积但首先要保证数据切分是按卷进行的。图像分割里随机打乱图片按行拆分是最常见的翻车点尤其是医学序列数据同一个患者的相邻切片高度相似不按卷拆分会造成严重的信息泄漏。6. 进阶技巧2D 切片的批处理、3D 重建与验证闭环常见的坑是拿到 2D 切片后就把 3D 空间信息全丢掉。我会把切片序号和卷号保存成索引表训练完成后用同一套索引把 2D 预测卷回 3D 体积这样既能做体积计算又能回到原始空间里定量验证。做法分两步。第一步在数据加载器里维护一张表记录每张 PNG 对应的 (卷号, 轴向, 切片序号)比如文件名 img0009_315.png 对应(img0009, z, 315)第二步推理时按卷号收集所有切片预测按序号排序后沿深度方向拼接得到 512×512×N 的三维概率图。下面这段代码演示从文件名恢复顺序并堆叠import numpy as np import cv2 from pathlib import Path def parse_name(name): parts name.stem.split(_) return parts[0], int(parts[1]) # 卷号, 切片序号 def stack_slices(pred_dir): items [parse_name(p) for p in Path(pred_dir).glob(*.png)] items.sort(keylambda x: x[1]) slices [ cv2.imread(str(Path(pred_dir) / f{vid}_{idx}.png), cv2.IMREAD_UNCHANGED) for vid, idx in items ] return np.stack(slices, axis-1), items逻辑说明parse_name返回卷号和整数序号排序用整数而不是字符串避免315排在99前面的字典序错误。np.stack沿最后一个维度叠加得到的三维数组最后一维对应切片方向。返回的items是排序后的索引表方便后续与病例信息对齐。参数说明如果三个切面混在一起重建parse_name还需要返回轴向字段因为不同轴向的序号不能互相排序例如x/img0009_200.png和z/img0009_200.png序号相同但物理位置完全不同。我只在 z 轴做 3D 重建时直接按序号排序x、y 轴的重建需要配合原始 spacing 重采样建议先把 z 轴的闭环跑通再扩展另外两轴。这套验证闭环我建立得很晚。最早用 2D 切片训练时只看切片上的 Dice模型整体分数不低但医生说体积计算差得离谱我才意识到是轴向坐标混淆的问题。从那以后我每拿到一个切片数据集都会先跑三件事文件名解析脚本、索引连续性检查、mask 值域全量扫描全部通过再进训练管线。三维重建的闭环每个项目强制走一遍多花半天时间也不愿再靠视觉直觉猜坐标。希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

人脸检测+表情识别实战:从Haar Cascade到ResNet18的完整流水线

人脸检测+表情识别实战:从Haar Cascade到ResNet18的完整流水线

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

📅 2026/9/28 2:05:46
Python+深度学习人脸识别签到系统:从技术选型到工程闭环的毕设实战

Python+深度学习人脸识别签到系统:从技术选型到工程闭环的毕设实战

简介:这是一套面向计算机相关专业毕业设计场景的深度学习人脸识别签到系统完整源码包,适合正在准备毕设、课程设计或期末大作业的学生,以及需要项目实战练习的开发者。项目经导师指导并获评审99分,代码完整可运行,对新…

📅 2026/9/28 2:05:46
Arduino四足蜘蛛机器人运动学与步态实现全解析

Arduino四足蜘蛛机器人运动学与步态实现全解析

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

📅 2026/9/28 2:05:46
MORE NEWS

更多资讯

📰

【日本 6G 三线并进·第 5 篇】无线接入(下):太赫兹与 7GHz 的 6G 双路线

【日本 6G 三线并进第 5 篇】无线接入(下):太赫兹与 7GHz 的 6G 双路线摘要:这是系列第 5 篇,继续无线接入线。上一篇讲了 AI-RAN 如何让基站变“聪明”,这一篇看 6G 的“频率选择”。德岛大学用微梳技术在…

📰

你以为 Claude / Cursor 是被「越狱」封的,其实本机环境早就替你签了风控样本:TaoToken 统一 Key 通道下的 settings.json 与 config.toml 骨架

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

📰

AI Agent 开发与落地指南:用 TaoToken 统一 Key 打通 Cline 与 CC Switch 配置

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

📰

如何关闭端口被占用的进程

文章目录👉 作者:辣椒 📌 个人网站|项目资源|技术分享 👉 点击访问 [https://qiyex.com](https://qiyex.com)背景操作步骤一、打开命令提示符(Command Prompt)二、查看占用端口的进程…

📰

解决wordpress设置网址错:3步搞定域名服务器配置与免费工具排查

解决wordpress设置网址错:3步搞定域名服务器配置与免费工具排查 域名解析和服务器环境配置,是新手做 WordPress 网站时最容易“翻车”的两个坑。很多人改了后台 URL 却打不开,或者 SSL…

📰

Codex++安全边界探秘:从提示词注入到SecDevOps的配置实践

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

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬