news 2026/9/7 15:30:57

MODIS MOD13Q1质量波段解析:Python掩膜生成与时序应用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MODIS MOD13Q1质量波段解析:Python掩膜生成与时序应用

做遥感时间序列分析的同学,基本都绕不开MODIS的MOD13Q1产品。这份数据是250米分辨率、16天合成的植被指数产品,里面有NDVI和EVI,直接拿来就能做长时序的植被变化分析,看起来特别“友好”。但用着用着你就会发现,产品里的像元质量参差不齐,有些NDVI高达0.9的像元,实际上是云污染或气溶胶干扰的产物,直接拿去做趋势分析,结果能偏差到让你怀疑人生。所以,MOD13Q1里那个“250m 16 days VI Quality”质量波段,就是决定你后面分析是否靠谱的关键关卡。

这篇文章我会从数据格式讲起,把质量波段里的每一位拆开解释,再给出一套完整的Python掩膜生成方案,包括质量筛选、NDVI/EVI清洗、时序分析时的掩膜一致性处理,以及我在实际处理中踩过的各种坑。适合刚接触MODIS数据、或者是用Python做过一些遥感数据但没深入研究质量控制的读者。就算你不做植被指数,只看MODIS的QA处理思路,这套方法也能直接迁移到MOD11A1、MOD09GA等产品的质量波段处理上。

1. 先搞清楚数据里都有什么:MOD13Q1的波段结构与质量信息分布

1.1 为什么MOD13Q1是“植被指数全家桶”

MOD13Q1看起来是一个文件,实际内部是一大袋子科学数据集(SDS),每个SDS都是一个栅格图层。以Collection 6.1版本为例,MOD13Q1内部包含了16个左右的SDS,核心的几个包括:

科学数据集名称含义分辨率数值类型
250m 16 days NDVI归一化植被指数250mint16,-2000~10000,乘0.0001为真实值
250m 16 days EVI增强型植被指数250mint16,-2000~10000,乘0.0001为真实值
250m 16 days VI Quality详细质量波段250muint16,位编码
250m 16 days pixel reliability简化质量波段250mint8,0~3
250m 16 days composite day of the year合成日序250mint16
250m 16 days reflectance波段若干红、近红外、蓝、中红外反射率250mint16,乘0.0001为真实反射率

这个结构决定了取数据的方式:你不可能用普通看图软件打开HDF文件就看到植被指数,必须通过GDAL、rasterio或pyhdf按SDS名字去提取。而“VI Quality”和“pixel reliability”这两个波段,前者是详细的质量位编码,后者是官方已经替你简化好的等级标签,两者配合使用,基本能覆盖绝大多数质量筛选需求。

1.2 质量波段的两种打开方式

MOD13Q1给出了两套质量信息,很多人第一次接触会懵:既然有pixel reliability这种简单明了的图层,为什么还要设计一个几百位编码的VI Quality?

先说pixel reliability,这个波段取值只有0到3:

pixel reliability值含义建议处理方式
0理想质量,可直接使用保留
1可用,但存在一定噪声按需求决定是否保留
2降级,可能受云/阴影/雪影响一般剔除
3不可用,填充值剔除

这套标签好用是好用,但它是个“一刀切”的结果,你看不到像元为什么被降级。而VI Quality波段是uint16整数,每个bit位都记录了不同的质量判断信息,比如云状态、气溶胶水平、邻近云情况、冰雪覆盖、阴影、混合像元等。搞明白这套位编码,你才能按自己的研究需求做更精细的筛选,比如某些研究不惧气溶胶,就可以放宽气溶胶位;但冰雪像元绝对不能留,那就在掩膜里单独扣掉。

实际项目中,我通常先看pixel reliability做快速判断,再用VI Quality做精细掩膜。两套信息组合处理,既保留了效率,又不牺牲准确性。

1.3 瓦片边界与填充值的坑

MOD13Q1是按正弦投影的瓦片(tile)来分发的,每个瓦片是10度乘10度的范围,数据网格大小为4800乘4800。因为瓦片范围与实际地理范围的边界不完全对齐,在瓦片边缘一定会出现大量填充值。这些填充值在质量波段里通常表现为特殊编码,比如255或-1,而在NDVI波段里则是-3000。

这些边缘填充值如果不提前剔除,只要混进后续统计,就会造成很诡异的现象:比如明明覆盖的是山地区域,突然在瓦片边缘出现一堆NDVI等于0的像元;时序曲线一到边缘位置就往下掉。所以后面生成掩膜的时候,填充值剔除必须放在第一步,任何分析步骤都不能绕过它。

2. 环境准备与读取姿势:让Python顺利打开HDF4

2.1 工具选型:为什么最终选了xarray + rioxarray

处理MOD13Q1的Python工具有几套,底层都是GDAL。老一代的写法是用gdal.Open打开子数据集,再手动读数组,代码繁琐但通用。新一代我推荐用rioxarray,它本质还是调用rasterio,但好处在于直接返回带坐标、投影属性的xarray DataArray,后续做时间序列堆叠、按像元统计、可视化,都能跟xarray生态无缝衔接。

我的常用组合是:GDAL做底层驱动检查、rasterio做子数据集读取、xarray做时序堆叠和掩膜运算、numpy处理位运算、matplotlib或cartopy出图。这5个库分工明确,基本不会碰到“某个库大而全但bug多”的窘境。

2.2 安装与驱动配置的几个细节

环境问题永远是MODIS新手的第一道坎。MOD13Q1本身是HDF4格式,PyPI上直接用pip install gdal装的GDAL经常不带HDF4驱动,导致你读取时直接报错。最稳妥的还是用conda安装GIS全家桶:

conda create -n modis python=3.10 conda activate modis conda install -c conda-forge gdal rasterio rioxarray xarray numpy matplotlib

安装完先做一个驱动检查,确认当前GDAL支持HDF4:

from osgeo import gdal driver = gdal.GetDriverByName("HDF4") print(driver)

如果输出None,说明这个GDAL没编译HDF4支持。这时候要么换conda-forge的gdal,要么退一步用pyhdf库读取。pyhdf是专门的HDF4解析库,不依赖GDAL,缺点是接口不如rasterio顺手,而且Windows下安装容易出幺蛾子。我的建议是优先解决GDAL驱动问题,实在解决不了再用pyhdf。

2.3 读取MOD13Q1并快速查看质量波段

用rasterio读取HDF4子数据集,路径格式比较特殊,需要走HDF4_EOS:EOS_GRID这种子数据集语法。一个完整的读取示例是这样:

import rasterio import numpy as np modis_file = "MOD13Q1.A2021185.h25v05.061.2021204153722.hdf" def open_modis_sds(sds_name): """通过GDAL子数据集路径读取MOD13Q1内部SDS""" sds_path = f'HDF4_EOS:EOS_GRID:"{modis_file}":MODIS_Grid_250m_2D:{sds_name}' with rasterio.open(sds_path) as src: data = src.read(1) transform = src.transform crs = src.crs return data, transform, crs qa, transform, crs = open_modis_sds("250m 16 days VI Quality") reliability, _, _ = open_modis_sds("250m 16 days pixel reliability") ndvi_raw, _, _ = open_modis_sds("250m 16 days NDVI") print("VI Quality shape:", qa.shape, "dtype:", qa.dtype) print("unique QA values:", np.unique(qa)[:20])

注意路径里的MODIS_Grid_250m_2D是MOD13Q1对应的内部网格名称,如果去读MOD11A1这种1公里产品,这个名称要换成MODIS_Grid_1km,拼错了直接找不到子数据集。

读到qa之后,先不要急着算,先看它的数据类型和取值分布。正常质量波段的数值是多种多样的,因为每个像元都按bit位编码成整数,如果一整个瓦片的unique(qa)只有一两个值,多半是瓦片大量落在海洋或极地,这种数据不适合直接做时序。

3. 质量波段逐位拆解:从整数编码到可用信息

3.1 质量波段到底存了什么

MOD13Q1的VI Quality是uint16类型,也就是16个bit位。每个bit位或每几个bit位组合在一起,记录一种质量判断。常用位段整理如下:

位段位数含义可选值
bit 0-12bitMODLAND_QA整体质量等级0=理想质量,1=可用,2=降级,3=不可用
bit 2-54bitVI usefulness指数0-15,数值越大质量越差
bit 6-72bit气溶胶含量0=气候态,1=低,2=中,3=高
bit 8-92bit邻近云检测0=无,1=低概率,2=中概率,3=高概率
bit 101bit大气校正状态0=未校正,1=已校正
bit 12-132bit混合像元/陆地水标记需查具体说明
bit 141bit雪/冰覆盖0=无,1=有
bit 151bit阴影0=无,1=有

很多教程只告诉你bit 0-1,但实际使用中,bit 2-5的usefulness信息往往更关键。一个像元的MODLAND_QA如果显示“理想质量”,但usefulness却到了10以上,说明这个像元虽然在产品内部被标记为“可发布”,但实际反射率信号已经很差了,做定量分析必须剔除。

3.2 用位运算提取MODLAND_QA和VI有用性

Python位运算天然适合解析这种位编码。核心就是“按位与”和“右移”。以提取bit 0-1和bit 2-5为例:

modland_qa = qa & 0b11 vi_usefulness = (qa >> 2) & 0b1111 aerosol = (qa >> 6) & 0b11 adjacent_cloud = (qa >> 8) & 0b11 snow_ice = (qa >> 14) & 0b1 shadow = (qa >> 15) & 0b1

解释一下两个操作。qa & 0b11是保留最低两位,其余位全部清零,因为最低两位就是MODLAND_QA。(qa >> 2) & 0b1111是先右移两位,让原来bit 2-5变成新的最低四位,再用0b1111清零高位的干扰,得到usefulness值。

这样得到的数组都是uint8或uint16整数,取值范围清晰,后面你可以直接用它们来做条件筛选。建议保留这些中间数组,后面做质量统计或制图都用得上,没必要一上来就合并成一个掩膜。

3.3 质量等级映射与快速可视化

光看数值不直观,我一般会把质量等级映射成几个大类,然后直接出图。这样可以快速发现整个瓦片哪些区域云多、哪些区域质量好:

import matplotlib.pyplot as plt quality_class = np.zeros_like(modland_qa, dtype=np.uint8) quality_class[(modland_qa == 0) & (vi_usefulness <= 7)] = 1 # 优质 quality_class[(modland_qa == 1) & (vi_usefulness <= 10)] = 2 # 中等 quality_class[modland_qa >= 2] = 3 # 差 quality_class[(snow_ice == 1) | (shadow == 1)] = 4 # 积雪或阴影 plt.figure(figsize=(8, 8)) plt.imshow(quality_class, cmap="RdYlGn", vmin=1, vmax=4) plt.colorbar(ticks=[1, 2, 3, 4]) plt.show()

这里我还是建议不要直接只显示一个va值,而是把不同质量原因分开映射。因为从图上你才能看出,一个NDVI高值区既可能是茂密植被,也可能是雪覆盖的假高值。质量波段的价值就在这里——它告诉你数据到底是“真的”还是“装的”。

4. 掩膜生成全流程:从像元级质量到“干净”NDVI/EVI

4.1 掩膜标准怎么定:既要严格又不能太严

掩膜本质上就是把质量不好的像元置为无效,这个“不好”的标准,与你的研究目标强相关。如果你做的是大尺度多年份的森林趋势分析,用严格标准没问题;如果你研究的是半干旱区植被动态,区域本身植被稀疏,再用“MODLAND_QA必须为0”这么严格的标准,最后有效像元可能不到三成,样本量不足,时序根本投不出来。

我习惯的默认标准是这样组合:MODLAND_QA不大于1,VI usefulness不大于7,无雪/冰,无阴影,且基础填充值已经被剔除。解释一下为什么是7:usefulness区间0到15,其中0到7是可选用的范围,8以上意味着像元质量已经退化到不建议使用了,所以直接用阈值7圈定。

气溶胶位的处理要看场景。做大气污染相关主题时可能还需要把气溶胶考虑进去,但普通植被分析里,VI usefulness已经在很大程度上吸收了高气溶胶的影响,一般不用再单独加一条气溶胶过滤条件。加了反而过度剔除,有效样本大幅减少。

4.2 完整掩膜生成代码

把前面所有操作合到一起,一个可以直接对照参考的完整流程如下:

import numpy as np import xarray as xr import rioxarray import rasterio from osgeo import gdal modis_file = "MOD13Q1.A2021185.h25v05.061.2021204153722.hdf" def read_sds(sds_name): sds_path = f'HDF4_EOS:EOS_GRID:"{modis_file}":MODIS_Grid_250m_2D:{sds_name}' with rasterio.open(sds_path) as src: data = src.read(1) profile = src.profile return data, profile qa, profile = read_sds("250m 16 days VI Quality") reliability, _ = read_sds("250m 16 days pixel reliability") ndvi_raw, _ = read_sds("250m 16 days NDVI") # 第一步:剔除填充值 valid_base = (ndvi_raw != -3000) & (qa != 255) & (reliability != 255) # 第二步:提取QA位信息 modland_qa = qa & 0b11 usefulness = (qa >> 2) & 0b1111 snow_ice = (qa >> 14) & 0b1 shadow = (qa >> 15) & 0b1 # 第三步:按研究需求组合掩膜条件 good_quality = (modland_qa <= 1) & (usefulness <= 7) good_environment = (snow_ice == 0) & (shadow == 0) reliability_ok = (reliability <= 1) valid_mask = valid_base & good_quality & good_environment & reliability_ok # 第四步:计算真实NDVI,并对无效像元赋NaN ndvi = ndvi_raw.astype(np.float32) * 0.0001 ndvi_clean = np.where(valid_mask, ndvi, np.nan) # 第五步:保存结果 with rasterio.open("NDVI_clean.tif", "w", driver="GTiff", height=ndvi_clean.shape[0], width=ndvi_clean.shape[1], count=1, dtype=np.float32, crs=profile["crs"], transform=profile["transform"], nodata=np.nan) as dst: dst.write(ndvi_clean, 1) dst.write_mask(valid_mask) # 同时把掩膜单独存一份,后续统计有效像元数会用到 with rasterio.open("valid_mask.tif", "w", driver="GTiff", height=valid_mask.shape[0], width=valid_mask.shape[1], count=1, dtype=np.uint8, crs=profile["crs"], transform=profile["transform"], nodata=2) as dst: dst.write(valid_mask.astype(np.uint8), 1)

这段代码保存了两个文件:一个是清洗后的NDVI栅格,另一个是布尔掩膜栅格。在时序分析中,掩膜栅格的价值甚至超过NDVI本身——因为它记录了每个像元每年有多少次有效观测,没有统计这个,你做出来的趋势可能会被观测次数差异严重误导。

4.3 时序数据中的掩膜一致性处理

单景影像的掩膜很简单,难的是时间序列。MOD13Q1每16天一景,一年大概23期,10年就是230期影像。这里有一个经常被忽略的问题:每期影像的有效像元范围不同,有的年份云多,有效观测就少。如果你直接把所有年份的NDVI堆叠起来做趋势回归,有些像元可能只有两年有效数据,另一些像元有20年有效数据,这两类像元计算出的趋势置信度完全不同。

我的做法是分三步来处理。第一步,对每期影像独立生成掩膜并计算清洁NDVI;第二步,统计每个像元在时间维度上的有效观测次数;第三步,设定最小有效观测阈值,比如至少需要总期数的60%才纳入最终趋势分析。这样能在空间完整性和时间置信度之间取得一个平衡。

时间序列堆叠的示意代码如下:

import glob ndvi_list = [] mask_list = [] for f in sorted(glob.glob("MOD13Q1*.hdf")): ndvi_clean, valid_mask = process_modis_file(f) ndvi_list.append(ndvi_clean) mask_list.append(valid_mask.astype(np.float32)) # 时间维堆叠 ndvi_stack = np.stack(ndvi_list, axis=0) mask_stack = np.stack(mask_list, axis=0) # 有效观测次数统计 obs_count = np.sum(mask_stack, axis=0) # 设定阈值:至少60%的有效观测 min_obs = int(ndvi_stack.shape[0] * 0.6) mask_final = obs_count >= min_obs

这里的obs_count图层特别有用,我建议最后输出趋势结果时,同时输出一张有效观测次数图。审稿人如果没有这张图,很可能会质疑你的趋势分析“样本量不平衡”,有了它,一图胜千言。

4.4 多瓦片拼接时的掩膜注意事项

处理大区域研究时,一个瓦片往往不够。比如覆盖中国东部,可能需要h25v05、h26v05、h27v05、h25v06等多个瓦片。多瓦片拼接时最容易犯的错误是:先拼接原始NDVI,再统一做掩膜。这是错的。

正确的做法是每个瓦片先做掩膜,再拼接。为什么?因为不同瓦片的产品质量状态不同,先拼接再掩膜,边缘瓦片的质量信息已经被混合到相邻瓦片中,掩膜条件无法再对瓦片边界单独处理。尤其在山地、海岸线附近,瓦片之间的有效观测次数会明显不同,处理顺序不对,边界线就会“发黑”或出现明显的接缝块状异常。

另外,正弦投影的瓦片之间是有重叠的。相邻瓦片的重叠区域不能简单取一个舍一个,最好是取两景掩膜结果中质量更优的,或者先栅格到统一的网格坐标系再取平均。我一般用rasterio的merge工具统一投影,必要时加上resolution参数强制一致的分辨率,避免拼接出来的网格位置错位。

5. 实战问题排查:我在处理MOD13Q1时踩过的坑

5.1 HDF4驱动不在,读文件直接报错

这是最多人问的问题。现象是rasterio.open报“Unsupported driver”或GdalError,检查gdal.GetDriverByName("HDF4")返回None。原因基本就是GDAL编译时没有把HDF4编辑器打包进来,多见于用pip直接安装gdal的情况。

解决办法有两个。第一个是用conda重装GDAL,大概率能解决:

conda install -c conda-forge gdal

第二个是用pyhdf做备选读取方案:

from pyhdf.SD import SD, SDC hdf = SD(modis_file, SDC.READ) qa = hdf.select("250m 16 days VI Quality").get() ndvi = hdf.select("250m 16 days NDVI").get()

pyhdf读取的数据是numpy数组,但没有地理坐标信息,需要你自己从HDF文件的元数据里读投影参数再手动构建。这个方案比较原始,适合紧急情况下使用,长期处理还是建议把GDAL环境一次配置到位。

5.2 坐标信息丢失或投影不对

HDF4的子数据集在读取时,rasterio偶尔会识别不出CRS,导致输出的GeoTIFF没有空间参考。这时候写出的文件在其他GIS软件里打开是对不齐的。

检查方法很简单:

print(profile["crs"])

如果返回None,手动指定MOD13Q1的正弦投影。MOD13Q1使用MODIS Sinusoidal投影,EPSG代码是6842:

profile["crs"] = "EPSG:6842"

如果不确定原投影是否正确,用gdalinfo看原始文件信息:

gdalinfo MOD13Q1.A2021185.h25v05.061.2021204153722.hdf

输出里会显示子数据集列表、尺寸、投影和GeoTransform,这是排查坐标问题最直接的方式。注意有的版本GDAL把Sinusoidal识别成ESRI:54008,两种情况对应同一套投影,但输出到底用哪种要看你对齐的目标投影,建议直接在读取时就统一到EPSG:6842,后期再用rio.reproject做转换,避免后续麻烦。

5.3 忘乘scale factor导致植被指数离谱

MOD13Q1里NDVI/EVI的真实值都是缩放存储的,整数范围是-2000到10000,对应实际NDVI范围是-0.2到1.0。很多人在代码里直接拿ndvi_raw去做计算,然后发现NDVI最小值是-3000,最大值是9000多,一条曲线下来全是毛刺。这就是没乘0.0001。

还有另一个容易出错的点是统计前没剔除-3000。即使你乘了0.0001,-3000乘完等于-0.3,依然会混在NDVI区间里。正确的顺序一定是:先剔除填充值,再乘缩放因子,最后应用质量掩膜。

5.4 质量码“0”反而是最好的,这很反直觉

pixel reliability里0是最好,3最差。VI Quality里MODLAND_QA位的0是最好,3最差。很多新手写条件时把它当成了“大于0不要”,结果把所有有效像元全删了。我甚至见过有人把条件写成qa > 0作为“有质量”的判断,导致整个分析结果全反了。

建议在处理前先做一次统计,看一眼qareliability的取值分布。正常的MOD13Q1瓦片,VI Quality里应该大量出现0、1、2、3等低值,如果全是大于100的数值,要么是瓦片质量太差,要么是你读取的顺序错了,提前检查永远比事后排查省时间。

5.5 时序曲线突然跳变,先查掩膜

做时序NDVI时,如果画出的像元曲线在某一年突然跳到0.8以上,且旁边几年都在0.3左右,大概率不是植被真的发生了什么。先看那一年该像元的掩膜和雪/阴影标志。MOD13Q1是16天合成产品,单期中混入一个云污染像元或冰雪像元,就是从根源反映在NDVI上。所以做跳变检查时,不要只看NDVI,一定要把同步保存的掩膜或pixel reliability拉出来对照。

我是习惯在时序堆叠之后,再额外生成一个“无效观测次数”图层,每个像元累计有多少期被掩膜剔除。如果一个像元有超过一半时间是被剔除的,那它的趋势结果可信度很低,这类像元在最终的显著性检验里应该被单独剔除。

6. 掩膜成果的扩展应用:不只是植被指数

说到最后一步,其实质量掩膜和清洁后的NDVI/EVI,不只是能拿来出图做趋势。你可以把掩膜作为后续所有植被指数产品处理的标准前置流程,比如计算物候时,只有在有效像元范围内做阈值提取,物候参数才比较稳定;计算土地利用变化时,只有质量可靠的变化才能说明问题。

我建议所有做MODIS时序的人,在第一次处理MOD13Q1时就把“掩膜”当成一个正式产品来输出,而不是临时用一下。有了固定的掩膜输出,后续做聚合、统计、建模都不用再重头处理。哪怕是换一台机器、换一个同事来接手,只要原始数据和处理脚本在,结果依然可复现。这比你辛辛苦苦调参调出来的某个年份NDVI趋势图要值钱得多。

还有一个小细节,输出GeoTIFF时建议把掩膜单独保存成布尔型的tif,而不是mask到NaN里就不再管。因为很多时候你后续需要用掩膜做面积统计、叠加分析或训练样本筛选,这时候一张独立的掩膜tif要比你从NDTI里再反推有效像元方便得多。

我个人的习惯是,每一个时相的MOD13Q1处理,都固定输出三个文件:原始NDVI转成float并乘scale factor后的文件、应用掩膜后的NDVI_clean文件、以及valid_mask布尔掩膜文件。后期不管是做趋势分析、物候提取还是机器学习分类,这三个文件基本就是万能前置。这套流程稳定跑了一年多,几乎没有返工过。如果你也打算开始用MOD13Q1做长时序分析,建议先把这套掩膜流程固定下来,后面会省去大量重新处理数据的痛苦。

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

OpenClaw+Skills+星辰大模型:企业级AI代理安全落地实践

最近圈子里聊OpenClaw的人肉眼可见地多起来了&#xff0c;身边做运维和自动化的朋友也开始打听这玩意到底能不能在企业里正经用。我花了小半年时间&#xff0c;基于OpenClawSkills星辰大模型这条主线&#xff0c;从零搭了一套企业级AI代理能力平台&#xff0c;重点是围绕“安全…

作者头像 李华
网站建设 2026/9/7 15:26:05

GenOffice、Motrix Next、Qx:三款提升效率的开源项目实测

这次一次聊三个 GitHub 开源项目&#xff0c;方向完全不同&#xff0c;但都属于“装上就能提升效率”的类型&#xff1a;GenOffice、Motrix Next 和 Qx 效率启动器。三者的 Star 数分别是 54.9K、4.1K 和 15&#xff0c;量级差很远&#xff0c;但各自解决的问题都很明确——办公…

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

秋叶ComfyUI-V35整合包:AI绘画节点式工作流一键部署指南

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

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

并行处理与批量任务调度:从原理到稳定落地的工程实践

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

作者头像 李华
网站建设 2026/9/7 15:21:42

ComfyUI零基础入门:节点式工作流、自定义节点与报错排查指南

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

作者头像 李华