news 2026/10/6 1:04:17

IMU校准实战:加速度计六面法与陀螺仪零偏标定全流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
IMU校准实战:加速度计六面法与陀螺仪零偏标定全流程

我们做机器人、无人机或者无人车的时候,最绕不开的一个传感器就是IMU(惯性测量单元)。很多朋友拿到MPU6050或者更高端的工业级IMU,第一件事就是往例程里一塞,读出来的数据看着好像挺正常,结果一跑姿态解算,不是漂得离谱就是角度乱跳。我早期调无人机的时候也在IMU参数调试上栽过不少跟头,后来才明白一个道理:不校准的IMU,数据只能算“参考”,校准之后才配叫“测量”。这篇文章我把自己实际调过的加速度计和陀螺仪校准流程、参数计算方法以及踩过的坑全部整理出来,手把手带你走一遍,文末附上高频问题排查表,希望能帮你少走几个月的弯路。

这篇内容适合刚接触IMU的朋友,也适合那些已经跑通例程但总觉得数据不对劲的开发者。我会从原理讲起,再给完整实操步骤,最后讲问题排查,全程以常见的六轴IMU(比如MPU6050、ICM20602)为例,但流程和方法对所有IMU通用。

1. 校准之前,先把IMU的“脾气”摸清楚

1.1 加速度计和陀螺仪到底在测什么

想调好IMU,先得知道手里这个传感器的“底层逻辑”。加速度计测的是比力(specific force),换句话说它测量的不是单纯的重力加速度,而是物体自身的加速度减去重力加速度的负值。我们平时静态放平的时候,加速度计读到的数值是1g(约9.8 m/s²),方向指向天顶,这个“1g”就是校准多个参数的基准。

陀螺仪测的是角速度,单位一般是度每秒(dps)或者弧度每秒(rad/s)。它不像加速度计那样有天然的静态参照——静止时陀螺仪理论输出应该是0,但实际总有零偏,而且这个零偏还会随温度漂移。也就是说,陀螺仪校准的核心任务就是想办法让“静止输出”无限接近0,同时让“转动输入”和“输出读数”成严格的比例关系。

有个很关键的认知要先建立:加速度计擅长长期稳定,陀螺仪擅长短时响应。加速度计受振动影响很大,急加速时读数会被“污染”,但长期看不会漂;陀螺仪响应快,不受平动加速度影响,但积分之后漂移会越来越大。IMU数据处理的所有算法,本质上都是在两种传感器之间取长补短。

1.2 误差来源比你想的多

很多朋友以为IMU不准主要怪“噪声”,我认为这是最大的误解。实际上IMU误差可以拆成好几类:

  • 确定性误差:零偏(bias)、比例因子误差(scale factor error)、轴间非正交误差(misalignment)。这类误差是“有规律”的,校准主要就是干掉它们。
  • 随机误差:角度随机游走(angle random walk)、速率随机游走(rate random walk)、偏置稳定性(bias instability)。这类误差用艾伦方差(Allan variance)分析,主要靠滤波和算法融合去压制。
  • 环境相关误差:温度漂移、振动、电磁干扰等。

真正让姿态解算“跑飞”的,往往不是随机噪声,而是那些确定性的零偏和高斯误差。你想想,如果陀螺仪零偏是0.5°/s,积分10秒就是5°的误差,这比任何滤波算法都致命。所以校准第一步,是消除这些“有规律”的误差,把传感器的原始数据修正到“理想传感器”的水平。

1.3 校准时需要准备什么

我的建议是准备下面几样东西,缺了会影响效率:

  • 一块稳定的安装板(比如亚克力板、3D打印的夹具),IMU固定要牢靠
  • 一个相对水平的桌面,最好再用水平尺确认
  • 一个可以缓慢旋转的转台(如果实在没有,手动转动也行,但精度会打折)
  • 数据采集工具:串口助手或者自己写的Python脚本,能记录IMU原始数据
  • 一个可靠的参考角度源,比如高精度转台或者光学水平仪

有的朋友问:能不能直接用开发板的例程读数据然后手工记录?可以,但效率太低。我建议写一个简单的Python脚本,用pyserial读取串口数据,直接存成CSV文件,校准计算全都用脚本处理。工具这东西,花15分钟准备,能省后面2小时。

2. 加速度计校准实战:六面法详解

2.1 六面法的原理和计算模型

加速度计校准最常用的方法就是六面静态校准法(six-position static test)。原理很简单:把IMU分别朝六个方向静止放置,让重力加速度分别作用在X、Y、Z轴的正负方向上,然后利用已知的1g基准求解每个轴的零偏和比例因子。

常见的数学模型是这样的,以X轴为例,加速度计实际输出与真实加速度之间的关系可以简化为:

A_meas_x = S_x * A_true_x + B_x

其中A_meas_x是加速度计输出的原始值(换算到g单位),A_true_x是真实加速度,S_x是比例因子,B_x是零偏。理想情况下S_x=1,B_x=0。

实际操作时用六面法,分别让X轴朝上、X轴朝下、Y轴朝上、Y轴朝下、Z轴朝上、Z轴朝下,记录每个方向下三个轴的输出平均值。然后对每个轴单独计算:

S_x = (A_x_up - A_x_down) / 2 B_x = (A_x_up + A_x_down) / 2

这里A_x_up表示X轴朝上时X轴的输出,A_x_down表示X轴朝下时X轴的输出。推导逻辑很简单:X轴朝上时真实加速度是+1g,朝下时真实加速度是-1g,两者相减消掉了零偏,直接得到2倍的比例因子;两者相加消掉了真实加速度,直接得到2倍的零偏。

2.2 完整操作步骤(MPU6050为例)

先说数据采集的配置。我通常把MPU6050的加速度计量程设为±2g(对应16384 LSB/g分辨率),量程越小分辨率越高,静态校准时用±2g最合适。采集频率设到100Hz,每个方向静止采样5到10秒,取平均值。

具体操作步骤:

  1. 将IMU平放,Z轴朝上,确保静止,记录5秒数据
  2. 将IMU翻转,Z轴朝下,记录5秒数据
  3. 将IMU立放,X轴朝上(Y轴水平),记录5秒数据
  4. 翻转,X轴朝下,记录5秒数据
  5. 将IMU立放,Y轴朝上(X轴水平),记录5秒数据
  6. 翻转,Y轴朝下,记录5秒数据

这里有个小技巧:每个方向静止后,不要急着采数,等1到2秒让传感器稳定下来,然后去掉前几帧数据,再取平均值。另外,采样时手不要扶着板子,人走动都会影响读数。

数据记录完之后,用Python脚本处理。每组的平均值算出来后,按上面公式分别计算S_x/B_x、S_y/B_y、S_z/B_z。打个比方,我实测过一块MPU6050,X轴朝上时输出是0.987g,X轴朝下时是-1.032g,那么:

S_x = (0.987 - (-1.032)) / 2 = 1.0095 B_x = (0.987 + (-1.032)) / 2 = -0.0225

也就是说这个传感器X轴的刻度比理想值偏大0.95%,同时有-22.5mg的零偏。别看这个数值好像不大,在姿态解算里,加速度计零偏会造成姿态角恒定偏差,尤其是横滚角和俯仰角,一两度的误差就够你头疼了。

2.3 六面法常见错误和注意事项

我见过很多朋友在校准时犯同样的错误,这里集中提醒一下。

第一,六个方向必须尽量精准。手动摆放时角度偏差超过5度,计算出的比例因子误差就会明显变大。有个变通办法:先用加速度计的原始读数辅助“找平”,比如让X轴朝上时,观察Y和Z轴的输出尽量接近0,这样摆位就准了。

第二,采样时长要够。每个方向至少采5秒,如果现场有低频振动,建议采10秒以上,取均值能有效压制随机噪声。

第三,记得把数据换算成g。很多库直接输出的是LSB原始值,用的时候要除以灵敏度。MPU6050在±2g量程下灵敏度是16384 LSB/g,换算公式:

acc_g = raw_acc / 16384.0

如果这一步忘了,后面所有参数算出来都是错的。

第四,别忘了交叉轴影响。上面简化模型忽略了一个事实:当X轴朝上时,Y和Z轴并非严格输出0,而是可能有几十毫g的读数。这说明轴间存在非正交或者交叉耦合。想彻底校掉这部分,需要用六面法加上最小二乘法来求解完整的9参数模型(3个比例因子+3个零偏+3个交叉轴耦合系数)。不过对大多数应用来说,简化的6参数模型已经够用,交叉轴误差通常小于1%,后面融合算法能容忍。

3. 陀螺仪校准实战:零偏与比例因子

3.1 零偏校准:最简单但也最容易被忽略

陀螺仪的零偏校准(bias calibration)看起来太简单了,以至于很多人压根不做,或者随便记了一个数就完事。但我要说的是:零偏校准是IMU参数里投入产出比最高的一项。一个0.5°/s的零偏,纯积分10秒就有5°的姿态误差,这种误差是任何滤波算法都救不回来的。

零偏校准的正确做法是:让IMU完全静止,采集至少100秒的陀螺仪数据,然后取平均值。注意,这里的“静止”必须是真正的静止,不是用手拿着,而是放在桌面上固定住。手抖、地面振动都会污染数据。

采集时间为什么建议100秒?因为陀螺仪的随机噪声虽然零均值,但短期内的平均并不稳定,采样时间越长,平均值越接近真实零偏。我实测过,采样10秒和采样100秒算出的零偏能差出0.02°/s,这对高精度应用来说不可忽略。

具体操作:

  1. 传感器上电后,不要立即采集,等2分钟甚至更久,让内部温度稳定
  2. 放置静止,用和加速度计一样的采样频率(1kHz原始数据或者100Hz滤波后数据都可以)采集100秒
  3. 计算出每个轴的均值,这就是该温度下的零偏
  4. 在使用时直接将原始读数减去零偏

还有一点容易踩坑:陀螺仪的零偏是温度敏感参数。我早期在一台无人机上校准完零偏后,把设备从室内拿到室外,起飞前又测了一遍,发现零偏已经变了0.1°/s。所以如果你的设备工作环境温差大,建议做一下温补,或者至少在设备达到工作温度后再校准。

3.2 比例因子标定:转台不是必需,但要有方法

陀螺仪比例因子校准不像加速度计那样有“1g”天然基准。理想情况下,陀螺仪输出的角速度积分应该等于真实转过的角度,所以我们可以通过“转一个已知角度”来反推比例因子。

标准做法是用转台:让IMU绕某个轴精确旋转一定角度(比如90度、180度、360度),对比陀螺仪积分值和真实角度的比值,就能得到比例因子。

没有转台怎么办?我教大家一个土办法:找一个直角或者平整的桌面边缘,把IMU固定在一个平板上,让平板的一边紧贴桌子边缘,然后缓慢转动平板,让另一边靠到另一条参考边上,转完正好90度。这个过程要缓慢匀速,避免激起加速度计的振动干扰,然后对陀螺仪输出做积分,和90度比较。

计算公式:

S_g = I_measured / I_true

其中I_measured是陀螺仪输出的积分值(按当前比例因子折算成角度),I_true是真实转动角度。如果I_measured是92度,说明当前比例因子偏大,修正系数为92/90=1.022,修正后的角速度 = 原始角速度 / 1.022。

这里有个操作细节:转动过程最好不要直接用未校准的加速度计去辅助判断起始和结束的时刻,因为转动过程中加速度计读数受切向加速度和向心加速度污染严重。正确做法是给陀螺仪数据加一个静止检测阈值,角速度绝对值连续50个采样点低于某个值(比如0.1°/s)才认为“停稳了”,然后计算起止时间戳。

3.3 艾伦方差:要不要做随机误差分析

如果你只是做做demo,随机误差分析可以跳过;但如果你做的产品对姿态精度要求高(比如工业级云台、辅助驾驶),那我建议还是做一次艾伦方差分析,至少知道自己传感器的“天花板”在哪里。

艾伦方差是分析陀螺仪随机误差的经典工具,它能把角度随机游走、速率随机游走、偏置稳定性这些误差源从一段静止数据中分离出来。具体做法是采集数小时的静止数据,然后用艾伦方差公式计算不同积分时间下的方差曲线,根据曲线各段的斜率判断主要误差源。

实际做起来有个痛点:需要连续长时间静止数据,几个小时不能有人碰传感器。而且计算代码虽然不复杂,但要理解艾伦方差原理需要一些信号处理的底子。我的建议是:初期可以直接用现成的艾伦方差Python库来跑,比如allan_variance这个包,跑出结果后重点看偏置稳定性这个指标,它决定了你的IMU在长时间积分后姿态漂移的下限。

4. 轴间对齐与联合标定:从“单轴准”到“整体准”

4.1 轴正交性校准:为什么三个轴不是完美90度

理想情况下,IMU的X、Y、Z轴是相互正交的,加速度计的三轴和陀螺仪的三轴也应该是完全对齐的。但半导体工艺决定了芯片内部MEMS结构不可能完美正交,一般会有0.1度到几度的误差。这个误差对低成本IMU来说尤其明显。

轴间非正交带来的问题很隐蔽:当IMU绕X轴旋转时,Y轴和Z轴的陀螺仪会读到微小的角速度;当IMU承受Z方向的加速度时,X轴和Y轴的加速度计也会产生微弱输出。对于这些“串扰”,最明显的影响就是姿态解算时横滚和俯仰会耦合,比如水平状态下绕Z轴转,Y轴会出现一个假的俯仰角输出。

校准轴间非正交最简单的方法是借助转台精确转动,然后观察非旋转轴的输出,把交叉耦合系数求出来。以陀螺仪为例,当绕X轴精确旋转一定角度时,Y轴和Z轴的积分值除以X轴的积分值,就是Y轴和Z轴对X轴的交叉耦合系数。

对于大多数入门级应用,我建议先不折腾这个参数。因为非正交误差带来的影响通常在1度以内,而零偏和比例因子校准不当带来的误差可能是好几度。先把大头(零偏、比例因子)校好,后面如果发现姿态还有固定的横滚-俯仰耦合误差,再回头做轴间校准。

4.2 加速度计与陀螺仪的联合对齐(含Lidar-IMU标定的思路)

如果你的项目里除了IMU还有其他传感器(比如相机、激光雷达),那你还需要考虑传感器之间的位姿对齐。这已经超出了“IMU内部校准”的范畴,属于多传感器标定。

这几年做Lidar-IMU标定、Camera-IMU标定的需求越来越多,最常用的开源方案包括lidar_camera_calibration、Kalibr和livox_camera_lidar_calibration这些。核心思路都是:让传感器一起运动,采集数据,然后通过优化算法求解两个传感器之间的外参(旋转矩阵和平移向量)。

具体到IMU和Lidar的外参标定,有个关键点:IMU内参要校好,外参标定才有意义。如果IMU的零偏没校准,运动过程中的角速度积分误差会让外参优化的残差变大,标出来的外参精度也会受影响。所以我的流程一直是:先做好IMU内参校准,再做Lidar-IMU联合标定。

如果你是做视觉融合,Kalibr工具可以同时标定Camera内参、Camera-IMU外参和IMU自身的零偏/比例因子,非常强大。但有句话我得说:工具越强大,使用门槛越高。Kalibr需要录制rosbag,需要充分的激励运动(六个自由度都要动),还需要良好的光照环境。新手第一次跑容易在数据采集环节出问题,建议严格按照官方文档的激励要求来,不要随意缩减运动幅度和时长。

4.3 手动验证联合效果的方法

不管用什么工具标定,最后一定要做一次交叉验证。我常用的验证方法是“一致性检查”:让设备静止,分别用加速度计计算的初始姿态和陀螺仪积分后的姿态对比;然后手动转动设备到几个已知角度,观察解算姿态和真实角度的偏差。

还有一个小技巧:把IMU固定在一个水平转盘上,绕Z轴缓慢旋转360度,同时记录姿态解算的航向角(yaw)。由于yaw只能靠陀螺仪积分(加速度计无法修正yaw),这个实验能直接看出陀螺仪零偏和比例因子校准得怎么样。如果转了一圈结束,yaw偏差超过2度,说明陀螺仪的零偏或者比例因子还有问题,需要重新校准。

5. 常见问题排查与调试技巧实录

5.1 现象-原因-解法速查表

以下是这几年我帮别人调试IMU时最常遇到的几类问题,整理成速查表,建议收藏。

现象可能原因排查与解决方法
静止时姿态缓慢漂移陀螺仪零偏未校准或校准不准确重新做100秒静止零偏校准,检查温度变化
静止时姿态快速漂移且无规律陀螺仪噪声太大,或数据出现“跳变”检查供电稳定性、I2C/SPI通信干扰,增大滤波
静止时姿态有恒定角度偏差加速度计零偏或比例因子未校准好六面法重新校准加速度计
转动后停止,姿态回不到原角度陀螺仪比例因子误差或轴间非正交做转台/手动转动的比例因子校准,必要时做轴间校准
加速度计读数整体偏小或偏大量程配置错误,或比例因子错误检查灵敏度配置,用1g基准验证
数据噪声大,姿态抖动电源纹波、电磁干扰、采样率设置不对加RC滤波,检查电源,调整低通滤波频率
温度变化后漂移加剧零偏随温度漂移做温度补偿或在工作温度下校准
转台旋转时横滚和俯仰耦合轴间非正交误差做轴间校准或算法端补偿

5.2 噪声处理:低通滤波参数选择

IMU原始数据总是带噪声的,关键是不要“一刀切”地乱加滤波。加速度计和陀螺仪对滤波的需求不一样。

加速度计对高频噪声敏感(振动、冲击),但姿态解算需要它响应真实运动,所以低通滤波截止频率不能太低。一般静态应用用5Hz到10Hz的低通滤波足够,动态应用(比如无人机)可以用20Hz到50Hz。MPU6050内部自带数字低通滤波器(DLPF),可配置范围在5Hz到260Hz之间,我常用的配置是20Hz或42Hz。

陀螺仪的随机噪声处理方式不同。除了低通滤波之外,更重要的是在姿态解算里用互补滤波或者卡尔曼滤波的协方差参数去权衡信任度。简单来说:静止时多信加速度计,运动时多信陀螺仪。很多库的参数(比如Mahony滤波器的kp、ki)就是在调这个权衡比例。

我见过有人上来就搞扩展卡尔曼滤波(EKF)调参数,调了几天姿态还是乱跳。我的建议是先用互补滤波(Mahony/Madgwick)把系统跑通,如果发现噪声确实影响效果,再上卡尔曼。原因很简单:互补滤波就两个参数(kp、ki),而卡尔曼滤波的Q和R矩阵动辄几个维度,新手调参根本没有方向感。

5.3 数据通信和采样时间的坑

这个问题不在算法层,但在实际项目中极其常见,值得单独说一说。

最常见的是I2C通信速度问题。MPU6050默认I2C速率是100kHz,如果你用400kHz快速模式,有些非原装模块可能不稳定,导致偶发的读取失败。我的排查经验是:如果数据流里出现周期性的“野值”(比如加速度突然变成几百g),大概率是通信错误,而不是传感器本身的问题。解决方法是降低I2C速率、检查上拉电阻(一般4.7kΩ比较合适)、或者改用SPI接口。

另一个坑是采样时间不固定。很多单片机例程用delay函数控制采样周期,但前后两次读取之间的时间间隔可能因为其他任务而抖动。姿态解算对时间戳非常敏感——互补滤波里的dt(积分步长)如果按标称值填,但实际采样间隔忽大忽小,积分误差就会累积。解决办法是每次读取数据时记录当前时间(毫秒级),用实测时间差作为dt,而不是用固定值。

5.4 校准效果验证:别急着上真机

校准完参数之后,我强烈建议先在桌面上做一次完整的开环验证,而不是直接装到设备上。验证步骤我固定为以下三步:

  1. 静止验证:把校准后的IMU静止放置在桌面上,观察姿态解算输出的横滚、俯仰、航向。横滚和俯仰应该在0度附近波动,波动范围小于0.5度;航向漂移速率应该小于0.05°/s。
  2. 已知角度验证:用直角工具辅助,分别让IMU旋转90度、180度、270度,检查解算角度和实际角度的差值,误差应在1度以内。
  3. 往返验证:绕某一轴正转360度再反转360度,回到原点后,三个姿态角都应该回到初始值附近,偏差越小说明校准越靠谱。

如果这三步验证都过了,恭喜你,IMU参数调试基本过关了。如果没过,回到上面速查表里对应排查。

6. 一些我个人的调试心得

最后聊点书本上看不到的经验。

第一,校准数据比算法更重要。很多人在姿态解算算法上较劲,调了一周参数还是漂,最后发现根本原因是陀螺仪零偏没校好。我现在的流程是:花两个小时把校准做扎实,再花半小时调融合算法参数,效果往往比反过来强十倍。

第二,温度是IMU的隐形敌人。如果你的设备使用环境是从室内到室外,或者设备本身有发热源(比如电机、主控芯片),一定要警惕温度对零偏的影响。做产品的话,建议加上温补模型;做原型验证的话,至少保证校准时的工作温度和实际使用温度接近。

第三,每次上电都应该重新校准零偏。加速度计零偏可以复用存好的参数,但陀螺仪零偏我建议每次上电都重新测一遍。很多飞控(比如PX4)的校准流程里就包含这个动作,因为MEMS陀螺仪的零偏随机性比较强,哪怕同一颗芯片,每次上电可能都不一样。

第四,录数据的时候养成写日志的习惯。采集时间、温度、量程配置、滤波配置、固件版本,这些信息全部记录下来。我曾经校准完一批数据,过了一周回来想重新处理,结果忘了当时用的量程和滤波参数,只能重新采数,浪费了不少时间。

调试IMU确实是一件需要耐心的事,但一旦把校准流程跑顺了,后面所有基于姿态数据的开发都会顺畅很多。希望这篇实战总结能帮你在IMU参数调试这条路上少踩几个坑。如果实测中遇到我没有提到的问题,欢迎在评论区一起交流。

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

Mininet实验报告写作指南:拓扑搭建、OpenFlow协商与iperf测量

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

作者头像 李华
网站建设 2026/10/6 1:03:18

纯硬件搭建循迹小车:从红外传感器到MOS管驱动的完整信号链

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

作者头像 李华
网站建设 2026/10/6 1:03:02

Cadence Allegro差分等长布线实战:5分钟搞定蛇形绕线

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

作者头像 李华
网站建设 2026/10/6 1:02:34

政务内网部署DeepSeek大模型:从选型到RAG落地的完整方案

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

作者头像 李华
网站建设 2026/10/6 1:02:33

如何高效啃透排序算法英文课件?一份PDF顶半轮复习

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

作者头像 李华
网站建设 2026/10/6 1:01:40

中断上下文禁止malloc:嵌入式死机的根源与规避方案

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

作者头像 李华