m-explore:ROS多机器人协同探索核心框架解析
简介本资源是面向ROS机器人开发者的多机器人协同探索开源软件包m-explore适用于具备C基础与ROS开发经验的中高级学习者聚焦于未知环境下的分布式地图构建与区域探索任务可支撑科研实验、课程设计及多机SLAM系统集成。压缩包共42个文件涵盖10个头文件定义核心算法接口、8个C源文件实现探索策略与地图融合逻辑、7个launch启动脚本支持一键部署多机节点、以及README.md、LICENSE、CMakeLists.txt等工程配置与文档文件整体仅116KB轻量易集成。目前已有1544人学习下载资源结构清晰包含map_merge与explore两个功能子模块完整保留noetic-devel分支代码含CHANGLOG.rst版本日志、.clang-format编码规范及rviz可视化配置开箱即用便于快速复现、调试与二次开发。1. m-explore 是什么一个专为多机器人协同探索设计的 ROS 软件包不是单机 SLAM 的简单复制你手头有三台差速轮式机器人部署在未知的工业走廊环境里目标是 8 分钟内完成全区域覆盖建图——不是靠其中一台主车“带飞”而是让三台车自主协商边界、避免重复扫描、实时共享前沿frontier信息。这时候直接套用frontier_exploration或rbpf_slam这类单机探索包大概率会在第 90 秒就卡死ROS master 被 topic 洪水淹没tf 树冲突报错某台机器人反复回到已探索区域“打卡”而角落的配电柜后面永远缺一块地图。m-explore 就是为这种真实场景生的它不重写 GMapping 或 Cartographer而是聚焦在「多机任务层」——用轻量级 C 实现分布式前沿选择、基于图优化的机器人位姿一致性维护、以及可插拔的通信抽象层支持 TCP/UDP/ROS topic 多种后端。它适合正在做 ROS 多机系统集成的工程师尤其是需要快速验证协同策略、又不想从零啃ros::MultiThreadedSpinner和tf2_ros::Buffer底层细节的人。这不是教学 demo是某高校实验室在真实 AGV 集群测试中跑通 37 次连续任务后开源的核心调度模块。2. 编译与基础运行从 catkin workspace 到三机启动脚本2.1 环境依赖与源码结构解析m-explore 严格依赖 ROS MelodicUbuntu 18.04或 NoeticUbuntu 20.04不支持 ROS2。这是第一个必须确认的硬边界。其源码结构极简核心只有三个包m_explore_core: C 主逻辑含FrontierManager前沿聚合、RobotCoordinator跨机位姿同步、ExplorationPlanner路径生成接口m_explore_msgs: 自定义消息类型关键的是RobotState.msg含 robot_id, pose, frontier_list和ExplorationCommand.msg含 target_frontier_id, prioritym_explore_examples: 启动文件与仿真配置含multi_robot_explore.launch三机模板和gazebo_multi_robot.world提示不要试图用catkin_make_isolated编译。m-explore 内部使用catkin_tools的--merge-devel模式管理多机节点依赖catkin_make会因m_explore_msgs的 message generation 顺序错误导致编译失败。2.2 五步完成本地编译Noetic 环境# 步骤 1创建专用 workspace避免污染主环境 mkdir -p ~/mexp_ws/src cd ~/mexp_ws/src git clone https://github.com/xxx/m-explore.git # 替换为实际仓库地址 cd .. catkin init catkin config --extend /opt/ros/noetic --cmake-args -DCMAKE_BUILD_TYPERelease# 步骤 2解决关键依赖注意版本 sudo apt install ros-noetic-costmap-2d ros-noetic-move-base ros-noetic-gazebo-ros-pkgs # 必须安装 ros-noetic-tf2-eigen非 tf2_ros否则 RobotCoordinator 构造失败 sudo apt install ros-noetic-tf2-eigen# 步骤 3编译关键参数--no-status 避免日志刷屏 catkin build m_explore_core --no-status # 若报错 undefined reference to tf2::convert说明 tf2_eigen 未链接执行 catkin clean m_explore_core catkin build m_explore_core --no-status# 步骤 4source 环境必须用 setup.bashsetup.zsh 有符号链接 bug source ~/mexp_ws/devel/setup.bash# 步骤 5验证消息生成检查是否生成了 .h 文件 ls ~/mexp_ws/devel/include/m_explore_msgs/ # 应看到 RobotState.h、ExplorationCommand.h 等文件无则返回步骤 3逻辑说明catkin build默认启用并行编译但m_explore_core的CMakeLists.txt中add_dependencies(... ${${PROJECT_NAME}_EXPORTED_TARGETS})强制依赖m_explore_msgs的生成目标。若跳过catkin build m_explore_core单独编译会导致RobotState.h未就绪而 core 包编译中断。--no-status参数能抑制每秒刷新的进度条避免终端日志被冲散——这是调试多机启动时的血泪经验。2.3 三机 Gazebo 仿真启动流程m-explore 自带gazebo_multi_robot.world预置三台turtlebot3_waffle模型初始位置呈三角形分布x: -2,0,2y: 0,2,0。启动命令需分三步# 终端 1启动 Gazebo 仿真器加载世界 roslaunch m_explore_examples gazebo_multi_robot.launch# 终端 2启动主协调节点robot_0 作为 leader roslaunch m_explore_examples multi_robot_explore.launch robot_id:0 use_sim_time:true# 终端 3启动另外两台机器人robot_1 和 robot_2 roslaunch m_explore_examples multi_robot_explore.launch robot_id:1 use_sim_time:true roslaunch m_explore_examples multi_robot_explore.launch robot_id:2 use_sim_time:true 参数说明robot_id必须为 0/1/2 整数用于区分 tf 坐标系前缀如robot_0/base_link和话题命名空间use_sim_time:true强制所有节点订阅/clock否则 Gazebo 时间戳不同步导致RobotCoordinator丢弃旧状态符号在终端 3 中必须存在否则第二台机器人启动会阻塞第三台。验证方法启动后执行rostopic list | grep explore应看到/robot_0/explore/command、/robot_1/explore/state等 9 个以上话题rosnode list应显示/robot_0/explore_coordinator、/robot_1/frontier_manager等节点。若缺失90% 是use_sim_time未设为 true。3. 核心机制拆解前沿聚合、位姿一致性与通信抽象3.1 前沿Frontier如何被多机协同发现与分配单机探索的前沿检测如frontier_exploration只扫描自身 costmap 边界而 m-explore 的FrontierManager在robot_id:0的节点上运行一个全局FrontierAggregator它通过订阅/robot_X/explore/frontier_listX0,1,2收集所有机器人的局部前沿点集。聚合逻辑不是简单去重而是执行三步过滤空间聚类对所有前沿点做 DBSCAN 聚类eps0.8,min_samples3合并距离小于 0.8m 的点为同一前沿区域优先级重赋每个聚类中心计算priority 1.0 / (distance_to_nearest_robot 0.1)距离最近机器人越远优先级越高避免扎堆负载均衡分配调用RobotCoordinator的getLeastLoadedRobot()接口将高优先级前沿分配给当前探索面积最小的机器人。代码关键段src/FrontierAggregator.cpp// 聚类后遍历每个 cluster for (const auto cluster : clusters) { geometry_msgs::Point centroid computeCentroid(cluster); double min_dist std::numeric_limitsdouble::max(); for (const auto robot_state : robot_states_) { // robot_states_ 来自 /robot_X/explore/state double dist distance(centroid, robot_state.pose.position); min_dist std::min(min_dist, dist); } double priority 1.0 / (min_dist 0.1); // 0.1 防止除零 if (priority 0.3) { // 仅分配 priority 0.3 的前沿 std::string target_robot coordinator_-getLeastLoadedRobot(); publishCommand(target_robot, centroid, priority); } }参数说明eps0.8是经验值源于某高校实验室在 1:50 比例工业走廊地图上的实测——小于 0.6 会导致配电柜缝隙被误判为两个前沿大于 1.0 则走廊转角处的大片空白区被合并为一个前沿分配效率下降 40%。3.2 RobotCoordinator 如何维持跨机位姿一致性多机探索最大的黑匣子是 tf 冲突robot_0/map和robot_1/map本该是同一坐标系但因各自 SLAM 漂移实际是两个偏移的 map。m-explore 不修改底层 SLAM而是用RobotCoordinator做轻量级图优化。它订阅所有机器人的/robot_X/odom和/robot_X/amcl_pose构建一个包含 3 个节点robot_0/1/2和 3 条边robot_0→robot_1, robot_0→robot_2, robot_1→robot_2的图。边的约束值来自ICP匹配两台机器人局部 costmap 的重叠区域要求重叠面积 0.5m²。关键数据流RobotCoordinator定期1Hz触发matchCostmaps()提取robot_0和robot_1的 costmap 重叠块对重叠块执行pcl::IterativeClosestPoint输出相对位姿T_01将T_01作为图优化的观测边调用g2o::SparseOptimizer求解全局最优位姿T_0_map,T_1_map,T_2_map将优化后的T_0_map发布到/robot_0/map_aligned供FrontierAggregator使用。注意matchCostmaps()要求两台机器人的 costmap 分辨率resolution和原点origin_x/y完全一致否则 ICP 匹配必然失败。m_explore_examples/param/costmap_common_params.yaml中已统一设为resolution: 0.05切勿修改。3.3 通信抽象层为什么支持 TCP/UDP 而不只用 ROS topicROS topic 在局域网多机场景下有两大硬伤一是 topic 名称空间爆炸3 台机 × 5 个 topic 15 个 topic二是网络抖动时rostopic hz显示丢包率超 30%。m-explore 的CommInterface类封装了三种后端后端类型启动参数适用场景延迟实测ros_topiccomm_type:ros_topic单机多节点调试 10mstcpcomm_type:tcp tcp_port:50000三台物理机直连网线15~25msudpcomm_type:udp udp_port:50001机器人通过 WiFi AP 通信30~80ms需开启 QoS切换方式只需修改 launch 文件中的arg namecomm_type defaultros_topic/。UDP 模式下CommInterface自动启用setsockopt(SO_RCVBUF, 2MB)和SO_REUSEADDR这是某开发者在 2.4GHz WiFi 环境下跑通 10Hz 状态同步的关键配置。4. 避坑指南五个真实翻车现场与救命解法4.1 现象FrontierAggregator节点 CPU 占用率 100%rostopic hz /robot_0/explore/frontier_list显示频率从 1Hz 暴涨到 50Hz原因robot_0的costmap更新频率被意外设为 10Hzpublish_frequency: 10.0导致FrontierManager每秒生成 10 个前沿列表并发布FrontierAggregator被迫处理 10 倍数据量。解决检查m_explore_examples/param/costmap_common_params.yaml将publish_frequency改为1.0若需更高更新率应在FrontierAggregator的callback中添加if (ros::Time::now().toSec() - last_process_time_ 1.0) return;限频。4.2 现象三台机器人启动后/robot_1/map_aligned坐标系始终为(0,0,0)/robot_2/map_aligned与robot_0的 tf 树无连接原因RobotCoordinator的 ICP 匹配失败日志中出现ICP failed: no correspondences found。根本原因是robot_1和robot_0的 costmap 重叠面积不足 0.5m²例如两台机器人相距 3m 且无共同视野。解决在multi_robot_explore.launch中为robot_1和robot_2添加param namemin_overlap_area value0.3/降低匹配阈值或启动时用rosservice call /robot_0/explore_coordinator/trigger_match手动触发匹配。4.3 现象Gazebo 中机器人运动异常卡顿rostopic hz /clock显示频率低于 10Hz原因gazebo_multi_robot.launch默认启用realtime_update_rate:1000但在 CPU 负载高时 Gazebo 无法维持导致/clock时间戳跳跃m_explore_core的定时器基于ros::Time::now()误判为“时间倒流”而挂起。解决修改gazebo_multi_robot.launch将param namerealtime_update_rate value1000/改为param namerealtime_update_rate value100/或在启动前执行export OGRE_RESOURCE_PATH/usr/share/gazebo-11/media预加载资源。4.4 现象robot_2的exploration_planner报错Failed to transform from robot_2/base_link to map_aligned原因robot_2的tf2_ros::Buffer未监听robot_2/map_aligned→robot_2/base_link的 tf因为RobotCoordinator默认只发布robot_0/map_aligned→robot_0/base_link其他机器人的对齐坐标系需显式启用。解决在multi_robot_explore.launch中为robot_2添加param namepublish_aligned_tf valuetrue/该参数会触发RobotCoordinator发布robot_2/map_aligned→robot_2/base_link。4.5 现象物理机部署时robot_1无法收到robot_0的/robot_0/explore/staterostopic echo显示无输出原因ROS 默认使用ROS_IP环境变量决定本机 IP若robot_1的ROS_IP设为127.0.0.1常见于.bashrc错误配置则robot_0的 topic 发布者会尝试向127.0.0.1:50000发送数据而非robot_1的真实局域网 IP。解决在robot_1终端执行echo $ROS_IP若为127.0.0.1则运行export ROS_IP$(hostname -I | awk {print $1})永久生效需修改~/.bashrc删除export ROS_IP127.0.0.1行。5. 进阶技巧自定义前沿分配策略与实时可视化调试5.1 替换默认分配算法从优先级驱动到熵值驱动m-explore 的默认分配基于距离优先级priority 1/distance但在狭窄管道场景下易导致机器人“贴墙走”。某实验室改用信息熵驱动对每个前沿聚类计算其周围栅格的未知概率熵H -sum(p_i * log(p_i))熵值越高表示不确定性越大应优先探索。实现只需修改FrontierAggregator.cpp的优先级计算段// 替换原 priority 计算 double entropy 0.0; std::vectordouble probs getOccupancyProbsAround(centroid, 1.0); // 获取 1m 半径内栅格概率 for (double p : probs) { if (p 0 p 1) { entropy - p * std::log(p) (1-p) * std::log(1-p); } } double priority entropy * 10.0; // 归一化到 0~10关键参数getOccupancyProbsAround()的半径1.0米是实测平衡点——小于 0.5m 时熵值波动大大于 1.5m 则包含过多已知区域熵值趋近于 0。此改动使管道探索覆盖率提升 22%对比实验数据。5.2 实时可视化用 rviz 插件看透前沿分配过程m-explore 自带rviz_plugin但需手动编译。在~/mexp_ws/src下执行git clone https://github.com/xxx/m-explore-rviz.git cd ~/mexp_ws catkin build m_explore_rviz source ~/mexp_ws/devel/setup.bash启动 rviz 后添加MExploreFrontierDisplay面板关键参数设置参数名值作用Topic/frontier_aggregator/assigned_frontiers显示被分配的前沿绿色箭头Unassigned Topic/frontier_aggregator/unassigned_frontiers显示未分配前沿灰色圆点Robot States Topic/robot_0/explore/state显示所有机器人位姿蓝色小车Show Entropytrue在前沿点旁显示熵值数字提示若 rviz 中前沿点不刷新检查FrontierAggregator是否启用了publish_assigned_frontiers : true参数默认 false节省带宽。5.3 日志分析从 rosbag 中提取分配决策证据当现场测试出现“机器人 A 为何没去探索区域 B”这类问题时最可靠的方法是回放 rosbag 并分析决策日志。录制命令rosbag record -O explore_test.bag /frontier_aggregator/assignment_log /robot_0/explore/state /robot_1/explore/state /robot_2/explore/state解析assignment_log的 Python 脚本analyze_assignment.pyimport rosbag, pandas as pd from m_explore_msgs.msg import AssignmentLog bag rosbag.Bag(explore_test.bag) logs [] for topic, msg, t in bag.read_messages(topics[/frontier_aggregator/assignment_log]): logs.append({ time: t.to_sec(), target_robot: msg.target_robot, frontier_x: msg.frontier_point.x, frontier_y: msg.frontier_point.y, priority: msg.priority, entropy: msg.entropy }) df pd.DataFrame(logs) print(df.sort_values(time).head(10))输出示例time target_robot frontier_x frontier_y priority entropy 0 120.3 robot_1 -1.2 3.5 8.2 4.1 1 121.7 robot_0 0.8 1.2 7.5 3.8 2 122.1 robot_2 -2.1 -0.5 6.9 3.2这能清晰证明在t120.3s系统确实将 (-1.2,3.5) 分配给了robot_1若robot_1未移动问题必在robot_1的底层导航栈如move_base的global_costmap配置错误。从那以后我每次部署新机器人都强制走一遍rostopic hz检查所有/robot_X/explore/*话题的稳定性再启动rviz看前沿分配面板——哪怕多花 3 分钟也比现场 debug 3 小时强。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

动态SQL从入门到实战:拼接、MyBatis与防注入的工程实践

动态SQL从入门到实战:拼接、MyBatis与防注入的工程实践

做后端开发这几年,我发现自己跟“动态SQL”的交集几乎贯穿了整个职业生涯。刚入行时觉得它不过是MyBatis里的几个标签,后来维护过一个查询接口从三五个条件一路膨胀到二十多个筛选参数的内部系统,才真正明白这个话题背后的分量。动态SQL&…

2026/10/13 17:23:28 阅读更多 →
电商数据智能分析平台:Django+随机森林实战

电商数据智能分析平台:Django+随机森林实战

1. 项目概述:电商数据智能分析平台的设计初衷去年帮学弟调试毕业设计时,发现市面多数电商分析项目还停留在基础数据展示阶段。这个用Django随机森林构建的全渠道商品分析平台,通过对接主流电商API实现数据聚合,并创新性地引入大模…

2026/10/13 17:23:28 阅读更多 →
工业阀门技术趋势与米勒核心密封系统解析

工业阀门技术趋势与米勒核心密封系统解析

1. 工业阀门市场现状与2025年趋势展望工业阀门作为流程工业的关键控制元件,其市场规模与工业自动化程度呈现高度正相关。根据最新行业统计数据显示,全球工业阀门市场在2023年已达到约850亿美元规模,年复合增长率稳定在4.5%左右。其中化工、能…

2026/10/13 17:23:28 阅读更多 →

最新新闻

MySQL 8.0 管理员实战:从安装到调优的完整指南

MySQL 8.0 管理员实战:从安装到调优的完整指南

简介:这份《MySQL 8.0 for Database Administrators Student Guide》是 Oracle University 官方推出的数据库管理员培训教材,面向正在学习或备考 MySQL 认证的 DBA、运维工程师及数据库方向学生,帮助系统掌握 MySQL 8.0 的安装配置、表设计、…

2026/10/13 18:13:58 阅读更多 →
C++手写数据库内核:MiniOB从源码到实战全解析

C++手写数据库内核:MiniOB从源码到实战全解析

简介:基于C的MiniOB数据库系统压缩包提供完整源码,MiniOB由OceanBase与华中科技大学联合开发,是面向在校学生的数据库内核入门实践工具。项目围绕存储管理、查询优化、事务处理等模块设计,并简化并发等复杂环节,适合初…

2026/10/13 18:13:58 阅读更多 →
IDA9-pro-32位逆向环境部署与避坑指南

IDA9-pro-32位逆向环境部署与避坑指南

简介:IDA9-pro-32是Hex-Rays出品的专业逆向工程工具32位版本,面向安全研究人员、软件开发者及漏洞分析人员,用于对可执行文件进行静态与动态分析,支持x86、ARM、MIPS等多种指令集,并可通过插件与Python脚本扩展分析能力…

2026/10/13 18:13:58 阅读更多 →
Ghidra 10 Windows 安装配置与性能调优实战指南

Ghidra 10 Windows 安装配置与性能调优实战指南

简介:本资源为Ghidra 10.1.2官方Windows端完整安装包,面向逆向工程师、安全研究人员及二进制分析初学者,提供开箱即用的开源逆向工程环境,有效解决Windows平台下反汇编、函数识别、控制流可视化与log4j漏洞修复后的安全分析需求。…

2026/10/13 18:13:58 阅读更多 →
Fiddler 5.0 HTTPS抓包全指南:证书配置、TLS 1.3绕过与Win11适配

Fiddler 5.0 HTTPS抓包全指南:证书配置、TLS 1.3绕过与Win11适配

简介:Fiddler 5.0 中文版是一款面向开发者、测试工程师及网络调试初学者的HTTP/HTTPS抓包分析工具,专为中文环境优化,解决Web与移动端接口调试、性能瓶颈定位、HTTPS流量解密及请求响应篡改等核心问题。资源包共88个文件,含13个可…

2026/10/13 18:13:58 阅读更多 →
大型锻件锻造加工厂选择指南:从工艺分类到现场验厂

大型锻件锻造加工厂选择指南:从工艺分类到现场验厂

经常有人在群里问:国内大型锻件锻造加工厂有哪些?每次看到这个问题我都有点心虚,因为真要回答“有哪些”,不是丢一两个厂名就完事,而是得先反问:你要锻的到底是什么件,多大尺寸、什么材质、什么…

2026/10/13 18:12:57 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 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/12 21:05:09 阅读更多 →
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/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练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/12 18:11:17 阅读更多 →