公司动态

四足机器人跨楼层轨迹规划:SCAN-Planner-Pure-ROS2开源项目实践指南

📅 2026/8/7 12:30:33
四足机器人跨楼层轨迹规划:SCAN-Planner-Pure-ROS2开源项目实践指南
这次我们来看一个专门为四足机器人设计的跨楼层轨迹规划开源项目SCAN-Planner-Pure-ROS2。这个项目的核心目标很直接让四足机器人在复杂的多层楼宇环境中比如楼梯、斜坡、不平整地面能够自主规划出安全、平滑且高效的移动路径。它不是另一个庞大的、难以集成的仿真框架而是一个精简、纯粹的ROS2功能包旨在让你能快速集成到现有的四足机器人系统中并立即开始测试跨楼层导航能力。对于从事机器人开发、SLAM导航或移动机器人研究的工程师和学生来说这个项目提供了一个宝贵的参考实现。它剥离了复杂的UI和外围工具专注于核心的轨迹规划算法在ROS2环境下的运行。本文将带你从零开始理解其核心能力、完成环境搭建、启动规划节点、进行仿真测试并分析其在实际部署中可能遇到的资源占用与常见问题。如果你正在寻找一个能跑在真实机器人或Gazebo仿真中解决“上下楼”难题的规划器那么这个开源实现值得你花时间深入了解一下。1. 核心能力速览在深入代码之前我们先通过一个表格快速把握SCAN-Planner-Pure-ROS2的核心特性和使用门槛这有助于你判断它是否适合你的项目。能力项说明项目类型四足机器人跨楼层轨迹规划算法ROS2功能包核心功能在已知或实时构建的3D多层地图如OctoMap中为四足机器人规划从起点到终点的可行轨迹特别优化了楼梯、斜坡等跨楼层场景。依赖框架ROS 2 (Humble 或更高版本推荐) 依赖nav2、octomap等导航相关包。硬件门槛无强制GPU要求。算法运行主要依赖CPU算力。实际部署到机器人时需要机器人具备足式运动控制器和状态反馈IMU、关节编码器。仿真测试对硬件无特殊要求。显存/内存占用规划算法本身不占用显存。内存占用取决于地图分辨率与大小处理大型多层建筑地图时需关注内存使用。启动方式通过ROS2 launch文件启动规划节点可与SLAM节点、地图服务器、机器人状态发布节点协同工作。接口能力标准ROS2话题与服务接口。接收目标点geometry_msgs/PoseStamped发布路径nav_msgs/Path和用于可视化的标记visualization_msgs/MarkerArray。批量/连续任务支持连续的导航目标请求。可作为nav2规划器插件集成实现与行为树配合的自动任务序列。适合场景1. 学术研究四足机器人复杂环境路径规划算法学习与改进。2. 原型开发为自研四足机器人平台快速集成跨楼层导航功能。3. Gazebo仿真在仿真环境中验证多层楼宇的导航逻辑与算法性能。2. 适用场景与使用边界SCAN-Planner-Pure-ROS2 解决的是一个非常具体且具有挑战性的问题足式机器人在非结构化、多楼层环境中的自主路径规划。这与轮式机器人在平坦地面的规划有本质区别。它非常适合以下场景楼宇巡检与探索让四足机器人自主上下楼梯巡查不同楼层。灾难救援模拟在模拟坍塌、崎岖的多层建筑环境中测试机器人的通过性。算法研究与对比作为一个基线规划器Baseline与你改进的或其他规划算法如RRT*, A*的3D变种进行对比实验。教学与演示在机器人学课程中展示3D路径规划与足式运动控制的结合。它的能力边界和注意事项也很明确非“即插即用”的完整解决方案它提供的是规划模块。你需要自行提供或搭建完整的机器人系统包括机器人URDF模型、状态估计SLAM/定位、底层步态控制器、感知用于建图或避障。规划器输出的是空间路径点如何控制腿足跟踪这些点是底层控制器的工作。依赖高质量的环境表征规划器的输入通常是3D占据地图如OctoMap。地图的准确性、分辨率、噪声水平直接影响规划结果的质量和安全性。如果地图错误地将可通过区域标记为障碍机器人将无法规划路径。计算实时性约束在大型复杂环境中3D搜索的计算量可能较大。虽然项目进行了精简优化但在资源受限的嵌入式机载计算机上仍需评估规划耗时是否满足机器人实时运动的要求。安全与伦理边界在真实物理世界测试时必须在安全可控的环境如实验室、测试场中进行并配备急停装置。避免在人员密集或存在高空跌落风险的场景直接测试。所有测试应遵循实验室安全规范。3. 环境准备与前置条件要运行SCAN-Planner你需要一个安装了ROS2的工作环境。以下是一个通用的环境准备清单假设你使用Ubuntu 22.04 LTS和ROS 2 Humble。基础系统与ROS2操作系统Ubuntu 22.04 LTS (Jammy Jellyfish) 是ROS2 Humble的推荐系统。ROS 2 发行版Humble Hawksbill。建议安装桌面完整版desktop以包含Gazebo、RViz等仿真和可视化工具。# 设置ROS2 apt仓库 sudo apt update sudo apt install curl gnupg lsb-release sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg echo deb [arch$(dpkg --print-architecture) signed-by/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(source /etc/os-release echo $UBUNTU_CODENAME) main | sudo tee /etc/apt/sources.list.d/ros2.list /dev/null # 安装ROS2 Humble桌面版 sudo apt update sudo apt install ros-humble-desktop工作空间创建一个ROS2工作空间用于存放本项目。mkdir -p ~/scan_planner_ws/src cd ~/scan_planner_ws/src核心依赖包在编译本项目前需要安装一些必要的ROS2包和系统库。以下命令提供了关键依赖具体依赖项请以项目package.xml为准。# 更新源并安装基础编译工具 sudo apt update sudo apt install build-essential cmake python3-colcon-common-extensions python3-rosdep2 # 初始化rosdep并安装依赖在workspace根目录外执行 sudo rosdep init rosdep update # 安装导航、可视化、仿真相关包部分可能已随桌面版安装 sudo apt install ros-humble-nav2-* ros-humble-octomap-* ros-humble-gazebo-ros-pkgs ros-humble-rviz2 ros-humble-robot-state-publisher ros-humble-xacro机器人模型用于仿真你需要一个四足机器人的URDF模型文件。可以使用开源模型如spot_description但注意许可协议或使用你自己的模型。将模型包放在src目录下。4. 安装部署与启动方式假设你已经准备好了ROS2 Humble环境和基础依赖接下来是获取和编译SCAN-Planner-Pure-ROS2项目。步骤1克隆项目代码进入你的ROS2工作空间的src目录克隆仓库。请将[GitHub仓库地址]替换为实际的项目URL。cd ~/scan_planner_ws/src git clone [GitHub仓库地址] scan_planner步骤2解决项目依赖在工作空间根目录下使用rosdep自动安装项目声明的依赖。cd ~/scan_planner_ws rosdep install -i --from-path src --rosdistro humble -y如果rosdep无法找到某些依赖可能需要根据编译错误信息手动安装对应的系统包或ROS包。步骤3编译工作空间使用colcon工具进行编译。--symlink-install参数在开发时很有用它创建符号链接而非复制文件方便修改代码后无需重新编译。colcon build --symlink-install步骤4激活工作空间编译成功后需要source安装脚本来使环境生效。source ~/scan_planner_ws/install/setup.bash # 可以将此命令加入 ~/.bashrc 以便每次打开终端自动生效 # echo source ~/scan_planner_ws/install/setup.bash ~/.bashrc步骤5启动规划节点概念性流程SCAN-Planner作为一个规划器节点通常不会单独启动而是作为一个组件集成到完整的导航系统中。一个典型的启动流程可能包含以下节点启动Gazebo仿真环境加载带有楼梯等结构的建筑世界和机器人模型。启动机器人状态发布运行robot_state_publisher发布机器人关节状态到TF。启动SLAM或地图服务器如果使用已知地图启动nav2_map_server如果在线建图启动SLAM工具箱如slam_toolbox。启动导航2Nav2系统Nav2是ROS2的导航框架。SCAN-Planner需要被配置为Nav2的全局规划器插件。这通常在Nav2的配置文件中完成。通过RViz发送目标点在RViz中使用“2D Pose Estimate”初始化机器人位置然后用“Nav2 Goal”指定目标点。由于具体启动文件(.launch.py)和参数配置(.yaml)需要根据项目实际结构来编写这里给出一个概念性的Nav2参数配置片段展示如何将规划器设置为插件# 假设的 nav2_params.yaml 部分内容 planner_server: ros__parameters: expected_planner_frequency: 20.0 planner_plugins: [GridBased] GridBased: plugin: scan_planner/ScanPlanner # 插件类型名需与实际C类对应 # 以下是规划器可能需要的参数 resolution: 0.05 search_radius: 5.0 min_climb_height: 0.15 max_climb_height: 0.5 # ... 其他参数实际的启动命令可能类似于# 在四个不同的终端中分别执行 # 终端1: 启动Gazebo世界和机器人 ros2 launch your_robot_gazebo your_robot_world.launch.py # 终端2: 启动Nav2及相关节点并加载上述参数文件 ros2 launch nav2_bringup bringup_launch.py params_file:/path/to/your_nav2_params.yaml # 终端3: 启动RViz进行可视化 ros2 launch nav2_bringup rviz_launch.py请注意以上启动流程是通用导航栈的集成思路。具体到SCAN-Planner项目你需要查阅其README或源码中的launch/和config/目录找到项目提供的专用启动和配置文件。5. 功能测试与效果验证在成功启动系统后我们需要验证SCAN-Planner的核心功能跨楼层路径规划。由于无法进行真实的物理测试我们将在Gazebo仿真环境中进行验证。5.1 测试环境搭建选择或构建测试世界在Gazebo中加载一个包含多层结构如带楼梯的两层楼的仿真环境。你可以使用Gazebo自带的模型库或从网上下载.world文件。加载四足机器人模型确保你的机器人URDF模型已正确导入Gazebo并且关节控制器、IMU、激光雷达如果用于SLAM等传感器插件工作正常。确认TF树正确在RViz中查看TF确保地图(map)、里程计(odom)、机器人基座(base_link)等坐标系关系正确建立。5.2 基础导航测试单楼层首先在平坦的同一楼层进行测试确保基础导航功能正常。测试目的验证规划器能接收目标并输出可行路径机器人能通过底层控制器粗略跟随。操作步骤在RViz中使用“2D Pose Estimate”工具点击地图给出机器人初始位置需大致对准。使用“Nav2 Goal”工具在同一楼层点击一个目标位置和朝向。预期结果RViz中应立即显示一条从当前位置到目标点的路径通常是nav_msgs/Path消息显示为绿色线条。机器人开始尝试移动。注意SCAN-Planner只规划路径不控制腿足运动。机器人的实际运动取决于你集成的底层控制器如MPC、WBC对路径的跟踪能力。成功判断路径能避开地图中的静态障碍物并且路径是连续的、平滑的没有剧烈转折。机器人有向目标移动的趋势。5.3 跨楼层路径规划测试这是SCAN-Planner的核心测试。测试目的验证规划器能在3D空间中找到连接不同楼层的可行路径如上下楼梯的路径。操作步骤确保你的3D地图OctoMap已经包含了楼梯的完整几何信息。这可能需要机器人先在环境中跑一遍SLAM或者直接加载一个预先制作好的包含楼梯的3D地图。在RViz中将目标点设置到另一个楼层例如从一楼地面点到二楼地面点。预期结果RViz中显示的路径不应是直线穿透楼板而应该沿着楼梯的斜坡或台阶蜿蜒而上/下。路径在3D空间中应该是连续的。你可以通过RViz的“Interact”模式从侧面视角观察路径的高度变化。成功判断规划出的路径符合人类对“上下楼梯”路径的认知且路径点的高度Z坐标是渐进变化的。路径没有穿过被标记为障碍物的墙体或楼板。5.4 复杂地形适应性测试测试规划器对非标准跨楼层地形如斜坡、废墟堆的处理能力。测试目的验证算法对不规则地形和机器人运动能力约束如最大爬坡角的考虑。操作步骤在世界中放置一个倾斜的坡道或一堆不规则形状的障碍物模拟废墟。将目标点设置在坡道或废墟的另一侧。预期结果规划器应能利用坡道规划路径或在不规则障碍物中找到可通过的缝隙。如果坡度超过机器人参数中设置的最大爬坡角规划器应认为该区域不可通过寻找其他路径或规划失败。成功判断路径符合地形特征和机器人运动学约束。5.5 规划效率与稳定性观察观察规划时间通过查看规划器节点发布的/planning_time话题如果提供或使用ros2 topic echo监听路径话题并手动计时了解单次规划耗时。在复杂环境中耗时应在可接受范围内例如1-3秒内。观察CPU占用在系统运行的同时使用htop或top命令观察运行规划器节点的进程的CPU使用率。持续的过高占用如80%可能影响系统其他部分。测试连续重规划在机器人移动过程中动态地在RViz中设置新的目标点观察规划器是否能快速中断当前规划并生成新路径。6. 接口API与集成开发SCAN-Planner作为ROS2节点其“接口”就是标准的ROS2话题和服务。理解这些接口是将其集成到你自己的机器人系统中的关键。6.1 核心话题接口规划器节点通常会订阅和发布以下话题具体话题名称可能因配置而异订阅 (Subscriptions):/goal_pose(geometry_msgs/PoseStamped): 导航目标位置和姿态。通常由Nav2的bt_navigator或RViz的“Nav2 Goal”工具发布。/tf/tf_static: 获取坐标系变换信息用于将目标点转换到规划器使用的坐标系通常是map。/octomap_full或/map(octomap_msgs/Octomap或nav_msgs/OccupancyGrid): 3D或2.5D环境地图输入。这是规划的基础。/robot_pose或通过TF获取当前机器人位置。发布 (Publications):/global_plan或/plan(nav_msgs/Path): 规划出的全局路径。这是最重要的输出会被后续的局部规划器或控制器使用。/planning_markers(visualization_msgs/MarkerArray): 用于在RViz中可视化规划过程的可视化标记如搜索树、候选路径、关键点等便于调试。/planning_time(std_msgs/Float32): 单次规划耗时用于性能监控。6.2 服务接口如果提供一些规划器可能提供重配置参数的服务例如/scan_planner/update_parameters(rcl_interfaces/srv/SetParameters): 动态更新规划器参数如改变分辨率、搜索半径等。6.3 编程集成示例如果你想在自己的C节点中调用该规划器通常不是直接调用函数而是通过ROS2话题/服务与其交互。以下是一个概念性的Python示例展示如何向规划器发送目标并接收路径#!/usr/bin/env python3 import rclpy from rclpy.node import Node from geometry_msgs.msg import PoseStamped from nav_msgs.msg import Path import time class PlannerClient(Node): def __init__(self): super().__init__(planner_client) # 创建发布者用于发送目标点 self.goal_publisher self.create_publisher(PoseStamped, /goal_pose, 10) # 创建订阅者用于接收规划出的路径 self.plan_subscriber self.create_subscription( Path, /global_plan, self.plan_callback, 10 ) self.received_plan None def plan_callback(self, msg): self.get_logger().info(fReceived path with {len(msg.poses)} poses.) self.received_plan msg # 这里可以处理路径例如发送给控制器 def send_goal(self, x, y, z, qx, qy, qz, qw): goal PoseStamped() goal.header.stamp self.get_clock().now().to_msg() goal.header.frame_id map # 确保坐标系与地图一致 goal.pose.position.x x goal.pose.position.y y goal.pose.position.z z goal.pose.orientation.x qx goal.pose.orientation.y qy goal.pose.orientation.z qz goal.pose.orientation.w qw self.get_logger().info(fPublishing goal: ({x}, {y}, {z})) self.goal_publisher.publish(goal) def main(argsNone): rclpy.init(argsargs) client PlannerClient() # 等待一下确保连接建立 time.sleep(2) # 发送一个目标点例如二楼某点 client.send_goal(x5.0, y2.0, z3.0, qx0.0, qy0.0, qz0.0, qw1.0) # 保持节点运行等待回调 try: rclpy.spin(client) except KeyboardInterrupt: pass finally: client.destroy_node() rclpy.shutdown() if __name__ __main__: main()7. 资源占用与性能观察对于在嵌入式平台如机器人搭载的Jetson、Upboard上部署资源占用至关重要。CPU占用观察工具使用top、htop或ros2 top。观察点启动规划节点后在机器人静止和规划进行时分别观察CPU使用率。规划瞬间通常会出现一个峰值。连续发送目标请求观察CPU占用是否持续高位。优化方向如果CPU占用过高可以尝试在规划器参数中调低搜索分辨率(resolution)、减小搜索半径(search_radius)或使用更高效的地图表示如降低OctoMap分辨率。内存占用观察工具使用top(查看RES列) 或htop。主要消耗源内存占用主要来自加载的3D地图。一个高分辨率、大范围的OctoMap可能占用数百MB内存。规划算法本身的数据结构如搜索图也会占用一部分。优化方向使用剪枝后的地图或采用局部地图滚动窗口的方式进行规划而不是始终加载全局地图。规划耗时分析测量方法话题时间戳记录发送目标消息的时间戳和收到路径消息的时间戳计算差值。节点内计时如果规划器发布了/planning_time话题直接订阅它。使用rqt_graph和console通过rqt_console查看节点输出的INFO日志看是否打印了规划时间。性能因素规划时间与地图复杂度障碍物数量、搜索空间大小起点到终点的3D空间范围、规划器参数如步长、启发式函数权重强相关。网络带宽话题通信规划器节点与系统其他部分如地图服务器、控制器通过ROS2话题通信。路径消息(nav_msgs/Path)可能包含大量路径点在高速控制回路中频繁发布可能占用一定网络带宽。如果使用DDS中间件注意配置合适的QoS策略例如使用BestEffort而非Reliable来降低延迟但可能丢失消息。8. 常见问题与排查方法在部署和测试SCAN-Planner过程中你可能会遇到以下典型问题。这里提供排查思路。问题现象可能原因排查方式解决方案编译失败1. 缺少ROS2或系统依赖包。2. CMakeLists.txt或package.xml配置错误。3. C代码语法或库兼容性问题。1. 仔细阅读colcon build的错误信息。2. 检查rosdep install是否成功安装了所有依赖。3. 确认ROS2版本与项目要求一致。1. 根据错误信息安装缺失的包 (sudo apt install ...)。2. 检查项目README确认支持的ROS2版本。3. 在项目Issue中搜索类似错误。节点启动后立即退出1. 启动文件或参数文件路径错误。2. 节点所需的某个话题或服务不存在依赖缺失。3. 参数配置错误导致初始化失败。1. 使用ros2 run scan_planner scan_planner_node --ros-args直接运行节点查看终端输出。2. 检查启动文件日志看是否有“找不到...”、“初始化失败”等提示。3. 使用rqt_graph查看节点间连接是否正常。1. 确保所有参数文件路径正确且YAML格式无误。2. 按正确顺序启动所有依赖节点地图服务器、TF广播等。3. 逐项检查节点参数特别是地图话题名称、坐标系名称。RViz中不显示路径1. 规划器节点未成功发布路径话题。2. RViz中未正确添加Path显示插件或话题名称不匹配。3. 规划失败未生成有效路径。1.ros2 topic list查看是否存在/global_plan等路径话题。2.ros2 topic echo /global_plan查看是否有数据。3. 查看规划器节点的日志输出 (ros2 topic echo /rosout)。1. 在RViz中添加Path显示并将Topic设置为正确的路径话题名。2. 检查目标点是否在地图坐标系下且位置有效。3. 检查地图数据是否正常发布机器人位置TF是否准确。规划出的路径穿透障碍物1. 地图数据不准确或未更新动态障碍物。2. 规划器使用的碰撞检测模型与机器人实际尺寸不匹配。3. 规划器参数中膨胀半径设置过小。1. 在RViz中可视化地图确认障碍物位置正确。2. 检查规划器参数中机器人半径或边界框设置。3. 尝试增大地图的膨胀层inflation layer半径。1. 确保SLAM建图质量或使用准确的事先地图。2. 根据机器人URDF模型正确设置规划器的机器人外形参数。3. 在参数中增加安全距离。跨楼层规划失败路径卡在楼梯口1. 3D地图中楼梯区域未被正确表示为可通过区域可能是悬空或缺失。2. 规划器的“可攀爬高度”参数(min_climb_height,max_climb_height)设置不合理。3. 搜索算法在3D空间中的启发式函数或代价函数不利于垂直方向探索。1. 在RViz中从侧面观察OctoMap检查楼梯结构是否连续、完整。2. 检查规划器关于楼梯/斜坡处理的专用参数。3. 尝试简化环境先在单个小斜坡上测试规划。1. 改进建图过程确保楼梯被完整扫描。2. 根据机器人腿足的运动能力仔细调整可攀爬高度、坡度等参数。3. 查阅项目文档或代码看是否有针对楼梯的特定处理逻辑需要启用。规划速度非常慢1. 地图分辨率过高搜索空间巨大。2. 起点与终点距离过远或环境极其复杂。3. 算法未使用有效的启发式函数或剪枝策略。1. 观察规划时CPU占用率是否持续100%。2. 尝试在更小、更简单的环境中测试。3. 降低规划器参数中的地图分辨率或搜索步长。1. 在不影响导航安全的前提下使用更低分辨率的地图进行规划。2. 考虑分层规划先进行粗粒度2.5D规划再在局部进行精细3D规划。3. 检查代码是否启用了优化编译如Release模式。9. 最佳实践与使用建议为了更高效、安全地使用SCAN-Planner进行开发和实验遵循以下实践建议从仿真开始逐步过渡到实物永远先在Gazebo等仿真环境中充分测试算法逻辑、参数和系统集成确保基本功能稳定再在实物机器人上以低速、低高度进行谨慎测试。建立参数调试工作流规划器性能高度依赖参数。建议创建一个参数配置文件.yaml并编写一个简单的脚本可以批量测试不同参数组合在标准测试场景如特定楼梯下的表现规划成功率、耗时、路径长度用数据指导调参。实现规划结果可视化与记录除了RViz可以将每次规划的关键信息如起点、终点、路径点、规划时间、地图快照保存下来例如用rosbag2录包或写日志文件。这对于分析规划失败案例和算法改进至关重要。与底层控制器解耦测试初期可以将规划器与底层控制器解耦测试。即只验证规划器输出的路径在几何上是否合理在RViz中观察而不要求机器人立即跟踪。这有助于隔离问题。关注坐标系一致性ROS导航中坐标系错误是常见问题。务必明确规划器使用的全局坐标系通常是map、机器人基坐标系(base_link)、传感器坐标系之间的关系并使用tf2_tools如view_frames定期检查TF树是否正确。版本管理与备份对规划器的参数配置、启动文件和你的测试世界文件进行版本管理如Git。在做出重大参数更改前进行备份。理解算法原理SCAN-Planner作为开源实现是学习的好材料。花时间阅读其核心规划算法的论文或代码注释理解其搜索策略、代价函数设计。这能让你在遇到问题时更有方向地进行调试和修改。10. 总结与下一步SCAN-Planner-Pure-ROS2 这个项目为四足机器人的跨楼层导航提供了一个可直接在ROS2生态中使用的规划模块。它的价值在于其“纯粹性”——专注于解决3D空间中的路径搜索问题并与ROS2导航栈(nav2)的标准接口对齐降低了集成难度。对于想要快速上手的开发者最应该优先验证的是在已知的、结构良好的多层地图中规划器能否生成一条符合物理直觉的、连接不同楼层的3D路径。通过本文提供的Gazebo仿真测试流程你可以在几小时内完成从环境搭建到功能验证的全过程。最容易踩的坑通常不在算法本身而在系统集成环节TF坐标系错误、地图话题名称不匹配、参数文件路径错误、机器人模型与规划参数不匹配等。按照第8节的排查方法大部分问题都能快速定位。在成功运行基础功能后你可以从以下几个方向进行深入性能优化针对你的特定机器人硬件调整规划器参数在规划质量和实时性之间找到平衡点。算法改进以此项目为基础尝试集成更高效的3D搜索算法如Anytime D*或改进启发式函数以加快搜索速度。动态障碍物处理将本规划器作为全局规划器与一个能够处理动态障碍物的局部规划器如TEB结合实现完整的动态环境导航。真实机器人部署将经过充分仿真测试的系统部署到真实的四足机器人平台上在安全环境下进行实地验证。这个开源项目是一个起点而非终点。它为你提供了解决复杂导航问题的工具和思路真正的挑战和乐趣在于如何让它适配你的机器人并在实际场景中可靠工作。建议将本文作为操作指南收藏在部署过程中逐步探索和解决遇到的具体问题。