当前位置: 首页 > news >正文

单目避障实战(1):自动回正功能实现

前言

欢迎大家来到这篇文章,其实这篇文章也是按之前一样的。这是我在备赛期间对技术的积累和记录,之所以想要分享出来是因为自己确实在备赛期间的迷茫和不解,希望能够给这样的同学一些帮助!本人在技术上肯定有改进空间,所以如果本文有遗漏之处欢迎与我讨论。

问题分析

其实所谓的避障在我看来就是在原有轨道上识别到障碍物,然后躲避障碍之后在回到轨道上。那么第一步我们首先需要实现的小功能如何把车头回正?其实这更多是一个闭环控制过程,具体的理论支撑后续可以给大家补充PID部分的知识,但是在这就不再赘述。

如何知道自己的朝向?

在ros中呢,其实本身就有里程计记录机器自身的速度和方向的,如/odom,/odom_combined.所以我们可以使用这样的话题对原有的方向进行记录和计算误差。下图是我echo了该话题的图片,下面我们更直观一点,打印出来!(下面我将会使用更为精确的/odom_combined)

/odom_combined 里面有机器人姿态
姿态是四元数
我们把四元数转成 yaw
打印 yaw,看车头方向变化

你可以先在终端确认话题类型:

ros2 topic info /odom_combined

我们在这可以看到/odom_combined的包名和消息类型,这也是我们待会要导入的。大家可以自行查阅或者使用AI看看odom_callback中的数学原理,会让大家明白四元数到朝向的关系。(不知道也没关系,可以只是看成一个转化而且是固定的。)

import math import rclpy from rclpy.node import Node from nav_msgs.msg import Odometry class YawReader(Node): def __init__(self): super().__init__("yaw_reader") self.sub = self.create_subscription( Odometry, "/odom_combined", self.odom_callback, 10, ) def odom_callback(self, msg): q = msg.pose.pose.orientation siny_cosp = 2.0 * (q.w * q.z + q.x * q.y) cosy_cosp = 1.0 - 2.0 * (q.y * q.y + q.z * q.z) yaw = math.atan2(siny_cosp, cosy_cosp) yaw_deg = math.degrees(yaw) self.get_logger().info(f"yaw = {yaw:.3f} rad, {yaw_deg:.1f} deg") def main(args=None): rclpy.init(args=args) node = YawReader() rclpy.spin(node) node.destroy_node() rclpy.shutdown() if __name__ == "__main__": main()

然后再理解一下Odometry结构

nav_msgs/msg/Odometry header child_frame_id pose pose position x y z orientation x y z w twist twist linear angular

所以:

msg.pose.pose.position.x msg.pose.pose.position.y msg.pose.pose.orientation

分别是融合后的x/y/方向

计算偏差

针对于回正操作,什么是偏差呢?其实就是目标角度和当前角度的差值(current_angle-target_angle).那我们现在就开始实现这一个小步骤

角度不能直接减,因为有+pi-pi跳变问题,所以要归一化:

def normalize_angle(a): return math.atan2(math.sin(a), math.cos(a))

完整一点就是:

self.target_yaw = None def odom_callback(self, msg): current_yaw = self.quaternion_to_yaw(msg.pose.pose.orientation) if self.target_yaw is None: self.target_yaw = current_yaw self.get_logger().info( f"target yaw set: {math.degrees(self.target_yaw):.1f} deg" ) error = self.normalize_angle(self.target_yaw - current_yaw) self.get_logger().info( f"current={math.degrees(current_yaw):.1f}, " f"target={math.degrees(self.target_yaw):.1f}, " f"error={math.degrees(error):.1f} deg" )

辅助函数:

def quaternion_to_yaw(self, q): siny_cosp = 2.0 * (q.w * q.z + q.x * q.y) cosy_cosp = 1.0 - 2.0 * (q.y * q.y + q.z * q.z) return math.atan2(siny_cosp, cosy_cosp)#这个就是前面odom_callback中的转换 def normalize_angle(self, a): return math.atan2(math.sin(a), math.cos(a))

于是,我们的文件变成了,之后再次运行本文件并转动机器就可以看到error啦

import math import rclpy from rclpy.node import Node from nav_msgs.msg import Odometry from geometry_msgs.msg import Twist class YawCorrect(Node): def __init__(self): super().__init__("yaw_reader") self.sub = self.create_subscription( Odometry, "/odom_combined", self.odom_callback, 10, ) self.target_yaw = None def quaternion_to_yaw(self, q): siny_cosp = 2.0 * (q.w * q.z + q.x * q.y) cosy_cosp = 1.0 - 2.0 * (q.y * q.y + q.z * q.z) return math.atan2(siny_cosp, cosy_cosp) def normalize_angle(self, a): return math.atan2(math.sin(a), math.cos(a)) def odom_callback(self, msg): twist=Twist() current_yaw = self.quaternion_to_yaw(msg.pose.pose.orientation) if self.target_yaw is None: self.target_yaw = current_yaw error = self.normalize_angle(current_yaw - self.target_yaw) self.get_logger().info( f"yaw = {current_yaw:.3f} rad, {math.degrees(current_yaw):.1f} deg,{error:.1f}error" ) def main(args=None): rclpy.init(args=args) node = YawCorrect() rclpy.spin(node) node.destroy_node() rclpy.shutdown() if __name__ == "__main__": main()

控制器编写

下一步就是把error变成转向控制,也就是最基础的P 控制

公式很简单:

angular_z = kp * error

含义是:

偏差越大,转得越快 偏差越小,转得越慢 偏差接近 0,就不转

但是要限制最大角速度,防止车突然猛打方向:

def clamp(value, low, high): return max(low, min(high, value))

回正控制可以这样理解:

error = normalize_angle(target_yaw - current_yaw) angular_z = kp * error angular_z = clamp(angular_z, -max_angular_speed, max_angular_speed)

比如参数:

kp = 1.5 max_angular_speed = 0.8 yaw_tolerance = math.radians(5)

逻辑:

if abs(error) > yaw_tolerance: twist.linear.x = 0.05 twist.angular.z = angular_z else: twist.linear.x = 0.0 twist.angular.z = 0.0 print("回正完成")

这里有个非常重要的方向问题:
如果你发现小车越修越偏,说明符号反了,把它改成:

angular_z = -kp * error

这很正常,因为不同底盘对angular.z正方向的定义可能和你的 yaw 正方向不一致。

所以学习时一定要先低速测试:

linear.x = 0.0 或 0.03 max_angular_speed = 0.3

最后就变成了

import math import rclpy from rclpy.node import Node from nav_msgs.msg import Odometry from geometry_msgs.msg import Twist class YawCorrect(Node): def __init__(self): super().__init__("yaw_reader") self.sub = self.create_subscription( Odometry, "/odom_combined", self.odom_callback, 10, ) self.publisher = self.create_publisher(Twist, '/cmd_vel', 10) self.target_yaw = None self.kp=0.5 self.max_speed_a=0.8 def quaternion_to_yaw(self, q): siny_cosp = 2.0 * (q.w * q.z + q.x * q.y) cosy_cosp = 1.0 - 2.0 * (q.y * q.y + q.z * q.z) return math.atan2(siny_cosp, cosy_cosp) def normalize_angle(self, a): return math.atan2(math.sin(a), math.cos(a)) def clamp(self,speed_r,min_speed,max_speed): return max(min_speed,min(speed_r,max_speed)) def odom_callback(self, msg): twist=Twist() current_yaw = self.quaternion_to_yaw(msg.pose.pose.orientation) if self.target_yaw is None: self.target_yaw = current_yaw error = self.normalize_angle(current_yaw - self.target_yaw) angle_speed=self.kp*error angle_speed_1=self.clamp(angle_speed,-self.max_speed_a,self.max_speed_a) if abs(error)>math.radians(5): twist.linear.x=0.1 twist.angular.z=-angle_speed_1 else: twist.linear.x=0.0 twist.angular.z=0.0 self.publisher.publish(twist) self.get_logger().info( f"yaw = {current_yaw:.3f} rad, {math.degrees(current_yaw):.1f} deg" ) def main(args=None): rclpy.init(args=args) node = YawCorrect() rclpy.spin(node) node.destroy_node() rclpy.shutdown() if __name__ == "__main__": main()

这样就可以实现一辆小车的自动回正功能了,这也是我们自动避障的第一步。恭喜你!!!!我也会尽快更新接下来的文章。

http://www.jsqmd.com/news/1097502/

相关文章:

  • 用STM32F103和OpenMV做个快递小车:从硬件选型到PID调参的避坑实录
  • AI驱动数据库查询助手WorkBuddy:自然语言生成SQL,业务人员自助取数实践
  • 告别手动开终端!用Python写ROS2 Launch文件一键启动C++/Python节点(附避坑指南)
  • Playwright与GitHub Actions集成:构建稳定高效的UI自动化CI/CD流水线
  • 性能测试工具选型指南:LoadRunner、JMeter与Locust深度对比
  • KMS_VL_ALL_AIO:终极Windows与Office激活解决方案,3分钟搞定系统授权!
  • Dism++:Windows系统维护的创新方案与高效实践
  • awesome-cli-apps:近两万 Star 的命令行应用精选
  • 首批_国家级_时序数据库诞生:DolphinDB 走过的那道门槛
  • SpringBoot+Vue3 仓储管理系统(WMS)设计:商品·SKU·出入库·移库·盘点全流程拆解
  • STM32新手避坑指南:用江科大模板+MPU6050 DMP库,5分钟搞定欧拉角读取
  • 零风险Cookie导出神器:Get cookies.txt LOCALLY完全本地化方案深度解析 [特殊字符]
  • 3分钟搞定:Postman便携版,让API测试摆脱安装束缚
  • 踩遍布局所有弯路,我整理这份Flex全套实战笔记
  • JMeter+Ant+Jenkins自动化测试流水线搭建与实战指南
  • 每周AI新动态:GLM 5.2、gpt-oss与Qwen-AgentWorld发布
  • 红外热成像仪详细功能解析,测温成像测距一机搞定
  • 如何快速上手openYuanrong agent runtime?5分钟入门教程
  • 公文管理别再用 Word 传来传去:套红模板、发文自动拆收文、归档台账的闭环设计
  • BK 2713 功率放大器介绍:为什么它适合驱动水声换能器和容性负载?
  • 现代工业传动系统中盖茨皮带的适配方案
  • 如何在Photoshop中直接使用AI绘图?SD-PPP插件终极指南
  • SQL注入攻击原理与防范:从数据混淆到参数化查询实战
  • 深入解析Grafana k6性能测试中的Stage负载模型设计与实战应用
  • OpenCV 核心算法大全、解决问题 + 落地应用完整详解
  • Codex++ 安装与 Codex 环境配置指南
  • 免费解锁iPhone 6s-X激活锁:applera1n完整指南与安全操作
  • 10个openeuler/ssh-utils使用技巧,让远程运维更高效
  • DCMTK医疗影像处理开源工具包:5大核心模块深度解析与实战应用
  • sysmaster特权容器部署教程:突破传统容器限制的终极方案