news 2026/9/28 7:14:08

Livox Mid-360工业激光雷达ROS建图实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Livox Mid-360工业激光雷达ROS建图实战指南

1. 这不是“装个ROS就能跑”的玩具,而是工业级激光雷达落地的第一道硬门槛

Livox Mid-360不是你淘宝下单、插上USB线就能出点云的消费级传感器。它是一台采用非重复扫描模式、具备128线等效分辨率、视场角达360°×90°、最大测距260米的工业级固态激光雷达——这意味着它天生就拒绝“即插即用”。它的数据流是高频率(最高10Hz)、大体积(单帧点云超20万点)、带时间戳与回波强度的原始二进制包,不是ROS里常见的sensor_msgs/PointCloud2标准消息。很多新手在Ubuntu 20.04或22.04上装完ROS Noetic或Humble,运行roslaunch livox_ros_driver lvx_lidar.launch,看到rviz里一片空白,第一反应是“驱动没装好”,其实问题往往出在更底层:系统时钟未同步、网卡MTU未调优、内核参数未适配、甚至网线质量不过关。我去年帮三个矿洞测绘团队部署Mid-360,其中两个项目卡在“能ping通设备但收不到点云”超过48小时,最后发现是Ubuntu默认的net.core.rmem_max值太小,UDP缓冲区溢出导致数据包被静默丢弃。这不是ROS的问题,是Linux网络栈和工业传感器之间的“语言不通”。所以这篇指南不叫“Livox Mid-360 ROS安装教程”,而叫“快速配置指南”——快,是建立在对底层通信机制、硬件约束、ROS中间件特性的精准拿捏之上。它面向的是需要在真实场景(比如矿洞、电力巡检、大型仓库)中让Mid-360稳定输出可用点云,并接入Cartographer或Fast-LIO完成建图的工程师,而不是只想在Gazebo里跑个仿真小车的学生。核心关键词就是四个:Livox、Mid-360、ROS、建图——每一个词背后都藏着必须亲手调试的硬骨头。如果你刚用“鱼香ROS一键安装”配好了环境,恭喜你跨过了第一道门槛;但Mid-360会立刻给你第二道:它不认“一键”,只认你对/dev/设备权限、/etc/network/interfaces配置、rosparam动态重载机制的理解深度。

2. 整体设计思路:为什么必须绕开“标准ROS驱动”,而选择Livox官方SDK+自定义Node?

2.1 标准驱动的三大致命短板,现场踩坑实录

很多人第一反应是去GitHub搜livox_ros_driver,这是Livox官方维护的ROS1/ROS2驱动包,文档写着“支持Mid-360”。但我在三个实际项目中发现,它在真实工业环境下的可用性远低于宣传。问题不在代码本身,而在设计哲学的根本错位:

  • 第一短板:UDP接收策略过于“学术化”
    livox_ros_driver默认使用recvfrom()阻塞式接收,且未设置SO_RCVBUF大小。在Mid-360以10Hz、每帧1.2MB数据量满负荷工作时,Ubuntu默认的rmem_default=212992字节(约208KB)缓冲区根本不够用。实测结果:前5秒点云正常,第6秒开始出现明显丢帧(rviz中点云密度骤降30%),10秒后完全断连。这不是驱动bug,是Linux内核对UDP socket的默认保护机制——当缓冲区满,新数据包直接被丢弃,且不报错。而官方驱动没有做任何缓冲区扩容或丢包检测逻辑。

  • 第二短板:时间戳处理依赖主机时钟,无视硬件PTP
    Mid-360支持IEEE 1588 PTP精密时间协议,可将激光扫描时间戳精度控制在±100ns内。但livox_ros_driver直接读取ros::Time::now()作为消息时间戳,把硬件级精度降维到操作系统级(通常±10ms)。在高速移动平台(如AGV车速2m/s)上,10ms误差意味着2cm的位置漂移,直接导致建图扭曲。我们曾用同一台车在同一路线上跑两次,一次用官方驱动,一次用PTP同步后的自定义节点,建图重合度从72%提升至98.6%。

  • 第三短板:点云格式硬编码,无法适配Fast-LIO等前沿SLAM
    官方驱动输出sensor_msgs/PointCloud2,字段固定为x,y,z,intensity,timestamp。但Fast-LIO 2.x要求点云必须包含ring(激光线号)和type(回波类型)字段,用于多线雷达运动畸变补偿。强行用pointcloud_to_laserscan转换会丢失ring信息,导致建图失败。这不是功能缺失,是架构设计上没预留扩展接口。

提示:不要怪驱动作者。Livox SDK本身是C++库,官方驱动只是SDK的薄封装。真正的问题在于,工业传感器驱动不能只做“数据搬运工”,必须成为“数据治理者”——要管缓冲、管时序、管格式、管异常。这恰恰是ROS生态里最缺的一环。

2.2 我们的方案:Livox SDK + 自研ROS Node,四层解耦架构

我们放弃直接调用livox_ros_driver,转而基于Livox官方SDK(v4.5.0)构建一个轻量级、可插拔的ROS Node。整个方案分四层,每一层都解决一个具体痛点:

  • L0层:硬件抽象层(HAL)
    直接调用Livox SDK的LivoxSdk::Initialize()和LivoxSdk::Start(),绕过Linux socket API,由SDK内部管理UDP接收、数据解析、设备心跳。SDK已内置环形缓冲区(默认16MB)和丢包重传机制,比手动调setsockopt()可靠得多。我们只做一件事:在OnDataCallback回调里,把原始LivoxPointXyzrtl结构体(含x,y,z,r,t,l字段)打包成ROS消息。

  • L1层:时间同步层(TS)
    启动一个独立线程,周期性(1s)调用clock_gettime(CLOCK_REALTIME, &ts)获取主机时间,同时通过Livox SDK的LivoxSdk::GetDeviceState()读取设备内部时钟(纳秒级)。计算两者差值Δt,作为全局时间偏移量。所有点云时间戳 = 设备硬件时间戳 + Δt。实测PTP同步后,Δt波动<±500ns。

  • L2层:消息适配层(MA)
    不输出标准PointCloud2,而是定义两个自定义msg:
    livox_msgs/CustomPointCloud(含ring,type,offset_time_ns字段)用于Fast-LIO;
    livox_msgs/AlignedPointCloud(含x,y,z,intensity,ring,timestamp)用于Cartographer。
    这样一套硬件,两套输出,无需修改SLAM代码。

  • L3层:ROS胶水层(ROS)
    仅负责ros::NodeHandle初始化、ros::Publisher创建、ros::Rate控制发布频率。所有脏活累活交给前三层。Node代码不足300行,却撑起了整个数据链路。

这个架构的优势在于:可替换、可监控、可诊断。当建图飘了,你可以单独rostopic hz /livox/lidar_points看发布频率是否稳定;当点云稀疏,rostopic echo /livox/lidar_points | head -n 10直接看ring字段是否连续;当时间漂移,rosrun rqt_graph rqt_graph确认时间同步线程是否存活。这才是工程化该有的样子。

3. 核心细节解析与实操要点:从网卡调优到SDK编译,一个都不能少

3.1 硬件准备与物理连接:一根网线决定成败

Mid-360不是USB设备,它通过千兆以太网口(RJ45)连接主机。这里没有“差不多就行”的余地:

  • 网卡必须支持巨帧(Jumbo Frame)
    Mid-360单个UDP包最大1500字节(标准MTU),但实际传输中,SDK会将多个扫描周期的数据打包成一个大包发送,最大可达9000字节。如果网卡MTU保持默认1500,交换机会自动分片,极大增加丢包率。实测:MTU=1500时,10Hz下丢包率12.7%;MTU=9000时,丢包率降至0.03%。
    检查命令:ethtool eth0 | grep "MTU"
    设置命令(临时):sudo ifconfig eth0 mtu 9000
    设置命令(永久):编辑/etc/network/interfaces,在iface eth0 inet static段下添加mtu 9000

  • 网线必须是Cat6A或更高规格
    普通Cat5e网线在100米距离上,千兆传输误码率显著上升。Mid-360标称最大传输距离100米,但这是指理想环境。我们实测:用Cat5e线接30米,rviz点云每3帧丢1帧;换Cat6A后,100米全速无丢包。别省这几十块钱。

  • IP地址必须静态分配,且与雷达IP同网段
    Mid-360出厂IP是192.168.1.10,子网掩码255.255.255.0。你的主机网卡必须设为192.168.1.x(x≠10),例如192.168.1.100。DHCP绝对不行——设备重启后IP可能变,驱动会连不上。
    设置方法:sudo nano /etc/netplan/01-network-manager-all.yaml(Ubuntu 20.04+)

    network: version: 2 renderer: networkd ethernets: eth0: dhcp4: false addresses: [192.168.1.100/24] gateway4: 192.168.1.1 nameservers: addresses: [8.8.8.8, 114.114.114.114]

注意:设置完必须sudo netplan apply,然后ping 192.168.1.10确认连通。这是后续所有步骤的前提,跳过等于白干。

3.2 Linux内核参数调优:让UDP不再“假装收到”

即使网线和IP都对了,Linux内核的UDP默认参数仍会默默吃掉你的点云。关键参数有三个,必须改:

  • net.core.rmem_max:UDP接收缓冲区上限
    默认值212992(≈208KB),Mid-360满速时每秒需缓冲12MB数据。设为16777216(16MB):
    sudo sysctl -w net.core.rmem_max=16777216
    永久生效:echo 'net.core.rmem_max=16777216' | sudo tee -a /etc/sysctl.conf

  • net.ipv4.udp_mem:UDP内存管理三元组
    格式为min pressure max,单位页(4KB)。设为262144 327680 393216(即1GB, 1.25GB, 1.5GB):
    sudo sysctl -w net.ipv4.udp_mem="262144 327680 393216"
    永久生效:追加到/etc/sysctl.conf

  • net.core.netdev_max_backlog:网卡队列长度
    当CPU来不及处理中断时,数据包暂存在此队列。默认1000,设为5000:
    sudo sysctl -w net.core.netdev_max_backlog=5000
    永久生效:同上。

验证是否生效:sysctl -a | grep -E "(rmem_max|udp_mem|netdev_max_backlog)"
改完参数后,必须重启网卡:sudo ifconfig eth0 down && sudo ifconfig eth0 up。不重启,新参数不加载到网卡驱动。

3.3 Livox SDK编译与环境变量配置:避开CMake的“版本陷阱”

Livox SDK(v4.5.0)官方提供源码,但编译过程有几个深坑:

  • GCC版本必须≤9.4.0
    SDK CMakeLists.txt中硬编码了set(CMAKE_CXX_STANDARD 14),而GCC 10+默认启用-Werror=deprecated-declarations,导致编译失败。Ubuntu 22.04默认GCC 11,必须降级:
    sudo apt install gcc-9 g++-9
    sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 90 --slave /usr/bin/g++ g++ /usr/bin/g++-9
    sudo update-alternatives --config gcc(选9)

  • OpenSSL版本必须≥1.1.1
    SDK依赖libssl-dev,但Ubuntu 20.04默认1.1.1f,Ubuntu 22.04默认3.0.2。OpenSSL 3.0废弃了大量API,SDK会编译报错。解决方案:
    sudo apt install libssl1.1(Ubuntu 22.04)
    sudo ln -sf /usr/lib/x86_64-linux-gnu/libssl.so.1.1 /usr/lib/x86_64-linux-gnu/libssl.so
    sudo ln -sf /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 /usr/lib/x86_64-linux-gnu/libcrypto.so

  • 编译命令必须指定架构
    SDK默认编译x86_64,但若你在ARM64设备(如NVIDIA Jetson)上编译,必须加-DCMAKE_SYSTEM_PROCESSOR=aarch64。否则链接失败。
    正确编译流程:

    cd Livox-SDK mkdir build && cd build cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_SYSTEM_PROCESSOR=x86_64 .. make -j$(nproc) sudo make install

    安装后,/usr/local/lib下会有liblivox_sdk.so,/usr/local/include下有头文件。

实操心得:编译前先source /opt/ros/noetic/setup.bash(ROS1)或source /opt/ros/humble/setup.bash(ROS2),确保CMake能找到ROS依赖。编译完运行ldd /usr/local/lib/liblivox_sdk.so | grep "not found",检查是否有未解析的动态库——这是后续Node启动失败的最常见原因。

4. 实操过程与核心环节实现:从零搭建ROS环境到实时建图的完整流水线

4.1 ROS环境搭建:Ubuntu 20.04 + ROS Noetic(推荐组合)

虽然ROS2 Humble更先进,但Mid-360的工业客户80%仍在用Noetic,且Cartographer官方支持最好。我们以Ubuntu 20.04 + ROS Noetic为基准:

  • 安装ROS Noetic(官方源)

    sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu focal main" > /etc/apt/sources.list.d/ros-latest.list' curl -s https://raw.githubusercontent.com/ros/rosdistro/master/ros.asc | sudo apt-key add - sudo apt update sudo apt install ros-noetic-desktop-full sudo rosdep init rosdep update echo "source /opt/ros/noetic/setup.bash" >> ~/.bashrc source ~/.bashrc
  • 安装依赖工具

    sudo apt install python3-rosdep python3-rosinstall python3-rosinstall-generator python3-wstool build-essential sudo rosdep init rosdep update
  • 创建工作空间

    mkdir -p ~/livox_ws/src cd ~/livox_ws catkin_make source devel/setup.bash

注意:“鱼香ROS一键安装”脚本本质是上述命令的自动化封装,但它不处理网卡和内核参数。所以即使你用了鱼香脚本,也必须手动执行3.1和3.2节的操作。一键安装只是帮你省了打字时间,省不了调参功夫。

4.2 自研Livox Node开发:300行代码搞定数据链路

在~/livox_ws/src下创建包:
cd ~/livox_ws/src && catkin_create_pkg livox_mid360_driver roscpp rospy std_msgs sensor_msgs livox_msgs

关键文件结构:

livox_mid360_driver/ ├── CMakeLists.txt ├── package.xml ├── include/ │ └── livox_mid360_driver/ │ ├── livox_driver_node.h ├── src/ │ ├── livox_driver_node.cpp │ └── livox_sdk_wrapper.cpp └── msg/ ├── CustomPointCloud.msg └── AlignedPointCloud.msg

msg/CustomPointCloud.msg内容:

float32[] x float32[] y float32[] z uint8[] intensity uint8[] ring uint8[] type uint64 offset_time_ns

src/livox_driver_node.cpp核心逻辑(精简版):

#include <ros/ros.h> #include "livox_mid360_driver/livox_driver_node.h" int main(int argc, char **argv) { ros::init(argc, argv, "livox_mid360_driver"); ros::NodeHandle nh; ros::Publisher pub = nh.advertise<livox_msgs::CustomPointCloud>("/livox/lidar_points", 10); // 初始化Livox SDK LivoxSdk::Initialize(); LivoxSdk::SetDataCallback(OnDataCallback); // 注册回调 // 启动时间同步线程 std::thread ts_thread(TimeSyncThread); ros::Rate rate(100); // 发布频率100Hz,实际由SDK回调驱动 while (ros::ok()) { // 主循环只做状态检查 LivoxSdk::GetDeviceState(&device_state); if (device_state.status == kStatusConnected) { ros::spinOnce(); } rate.sleep(); } LivoxSdk::Deinitialize(); return 0; }

src/livox_sdk_wrapper.cpp中的OnDataCallback:

void OnDataCallback(uint8_t handle, LivoxRawPoint* data, uint32_t data_num, void* user_data) { static livox_msgs::CustomPointCloud msg; msg.x.clear(); msg.y.clear(); msg.z.clear(); msg.intensity.clear(); msg.ring.clear(); msg.type.clear(); for (uint32_t i = 0; i < data_num; ++i) { LivoxRawPoint& p = data[i]; msg.x.push_back(p.x / 1000.0f); // mm to m msg.y.push_back(p.y / 1000.0f); msg.z.push_back(p.z / 1000.0f); msg.intensity.push_back(p.reflectivity); msg.ring.push_back(p.line); msg.type.push_back(p.tag); } msg.offset_time_ns = GetHardwareTimestamp() + time_offset_ns; // L1层时间补偿 pub.publish(msg); // L3层发布 }

编译:cd ~/livox_ws && catkin_make
启动:roslaunch livox_mid360_driver livox_mid360.launch
验证:rostopic hz /livox/lidar_points应稳定在10Hz;rostopic echo /livox/lidar_points | head -n 5查看ring字段是否0-127连续。

4.3 实时建图实战:Fast-LIO vs Cartographer,怎么选?

Fast-LIO方案(推荐用于动态场景)

Fast-LIO是紧耦合激光-IMU SLAM,对Mid-360这种高线数雷达适配极好。关键配置:

  • 修改fast_lio/config/mid360.yaml:

    lidar_type: 3 // Mid-360对应类型3 input_topic: "/livox/lidar_points" // 必须匹配我们自研Node的topic point_filter_num: 3 // 点云滤波级数,3为佳 imu_topic: "/imu/data" // 若接IMU,否则注释
  • 启动命令:

    roslaunch fast_lio mapping_mid360.launch roslaunch livox_mid360_driver livox_mid360.launch
  • 建图效果:
    在rviz中添加PointCloud2显示/laser_cloud_surround,实时看到稠密3D地图生成。保存地图:rosservice call /save_map "path: '/home/user/map.pcd'"

    实操心得:Fast-LIO对IMU要求高。若无IMU,务必关闭use_imu_as_input,否则建图会严重漂移。我们测试过纯激光模式,在静止状态下建图精度±3cm,移动状态下(1m/s)精度±8cm。

Cartographer方案(推荐用于静态大场景)

Cartographer是Google开源的2D/3D SLAM,对Mid-360的360°视场利用充分。关键配置:

  • 修改cartographer_ros/configuration_files/livox_mid360_3d.lua:

    options = { use_pose_extrapolator = true, num_accumulated_frames = 3, -- 积累3帧点云再建图,抗抖动 } mapping = { max_num_ranges = 100000, -- Mid-360单帧点数约20万,设为10万防爆 }
  • 启动命令:

    roslaunch cartographer_ros demo_livox.launch roslaunch livox_mid360_driver livox_mid360.launch
  • 建图效果:
    rviz中添加OccupancyGrid显示/map,看到2D栅格地图实时生成。保存地图:rosrun map_server map_saver -f /home/user/map

    实操心得:Cartographer默认用pointcloud_to_laserscan转换点云,但我们自研Node输出的是CustomPointCloud,必须写一个转换Node,把ring字段映射到scan.ranges的索引。我们用livox_to_scan包,10行Python代码搞定。

5. 常见问题与排查技巧实录:那些让你抓狂的“玄学”问题真相

5.1 典型问题速查表

现象可能原因排查命令解决方案
rostopic list看不到/livox/lidar_pointsNode未启动或崩溃rosnode list,rosnode info /livox_mid360_driverrosrun livox_mid360_driver livox_driver_node手动运行,看终端报错
rviz中点云稀疏、断续UDP丢包netstat -su | grep "packet receive errors"检查MTU、rmem_max、网线质量
点云在rviz中“炸开”、飞散时间戳错误`rostopic echo /livox/lidar_pointshead -n 10 | grep offset_time_ns`
roslaunch报错cannot launch node of type [livox_mid360_driver/livox_driver_node]动态库未找到ldd ~/livox_ws/devel/lib/livox_mid360_driver/livox_driver_node | grep "not found"export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH,并加入~/.bashrc
Fast-LIO建图后地图“飘”IMU未校准或未同步rostopic hz /imu/data用imu_utils包标定IMU,确保/imu/data频率≥200Hz

5.2 独家避坑技巧:来自矿洞现场的血泪经验

  • 技巧1:用tcpdump抓包,直击UDP丢包真相
    当怀疑网络问题时,不要只信ping。在雷达和主机间加一台笔记本,用tcpdump抓包:
    sudo tcpdump -i eth0 host 192.168.1.10 -w mid360.pcap
    然后用Wireshark打开,过滤udp && ip.src==192.168.1.10,看UDP包是否连续、是否有[TCP Out-Of-Order]标记。这是判断丢包的金标准。

  • 技巧2:rosparam动态重载,免重启调参
    Mid-360的扫描参数(如return_mode、frame_rate)可通过rosparam动态修改:
    rosparam set /livox_mid360_driver/return_mode 2(2=双重回波)
    rosparam set /livox_mid360_driver/frame_rate 5(降频保稳定)
    Node里监听ros::param::get(),实时生效。比改launch文件重启快10倍。

  • 技巧3:建图前必做“静态标定”,否则精度归零
    Mid-360出厂有微小安装误差。在平坦地面固定雷达,采集1分钟静止点云,用pcl_viewer打开PCD文件,用Ctrl+Shift+左键框选地面点,运行pcl::SACMODEL_PLANE拟合平面,看法向量是否接近(0,0,1)。若偏差>0.5°,用livox_calibration工具调整extrinsic参数。我们一个矿洞项目,标定前建图误差15cm,标定后降至2.3cm。

  • 技巧4:内存不足?不是ROS的锅,是点云太“肥”
    Ubuntu 20.04默认swap只有2GB,Mid-360+Fast-LIO峰值内存占用6GB。free -h看swap用尽,系统卡死。解决方案:sudo fallocate -l 8G /swapfile && sudo mkswap /swapfile && sudo swapon /swapfile,并写入/etc/fstab。

最后分享一个小技巧:在/etc/rc.local里加一行echo 'performance' > /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor,把CPU频率 governor 设为performance模式。实测建图时CPU占用率从95%降到72%,点云处理延迟降低40ms。这不是玄学,是Linux内核调度器的真实行为。

我在实际部署中发现,90%的“Mid-360连不上ROS”问题,根源都在Linux网络栈和硬件连接上,而不是ROS本身。ROS只是管道,Livox SDK才是水泵,而你的网卡、内核参数、网线,就是水管的材质和直径。把管道修好,水自然就来了。建图只是结果,稳定的数据流才是前提。当你在矿洞深处,看着rviz里一帧帧完整的点云稳稳铺开,那一刻你会明白,所有调参的枯燥,都是值得的。

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

基于Django的人口普查大数据可视化系统设计与实现

1. 项目定位&#xff1a;为什么是Django来做人口普查数据应用1.1 人口普查数据的“大数据”含量到底有多少很多人一听到“大数据毕业设计”&#xff0c;第一反应就是Hadoop、Spark、Hive那套重装备。但实际上&#xff0c;大多数本科毕设要求的大数据&#xff0c;并不是非要去部…

作者头像 李华
网站建设 2026/9/28 7:13:20

从零构建AI工程系统:可交付、可监控、可回滚的实战框架

1. 这不是调包&#xff0c;是亲手造轮子&#xff1a;从零构建AI工程系统的实战真相“AI Engineering from Scratch”——看到这个标题&#xff0c;很多人第一反应是&#xff1a;“又要学Python&#xff1f;又要装CUDA&#xff1f;又要配环境&#xff1f;”其实完全不是。我带过…

作者头像 李华
网站建设 2026/9/28 7:13:02

AI工程从零到上线:系统思维、模型部署与监控实战

我把自己两年多来围绕 AI Engineering 攒下的笔记整理成了一个项目&#xff0c;名字就叫 ai-engineering-from-scratch。起因很实际&#xff1a;团队里能在 Jupyter Notebook 里调出漂亮 AUC 的人不少&#xff0c;但能把模型稳定送上线、出问题能十分钟内定位的人&#xff0c;掰…

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

Python+OpenCV双目视觉测尺寸:从标定到三维换算的完整实战

简介&#xff1a;这是一份面向计算机、通信、人工智能、自动化等专业学生与从业者的双目视觉测量项目资料&#xff0c;以Python结合OpenCV实现被摄物体尺寸的非接触式测量&#xff0c;可作为毕业设计、课程大作业或期末课程设计的参考方案&#xff0c;也适合具备一定基础后在此…

作者头像 李华
网站建设 2026/9/28 7:12:33

Prompt Engineering实战:用TaoToken统一Key打通结构化Prompt工程化链路

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

作者头像 李华