当前位置: 首页 > 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.gsyq.cn/news/1609464.html

相关文章:

  • Playwright与GitHub Actions集成:构建稳定高效的UI自动化CI/CD流水线
  • awesome-cli-apps:近两万 Star 的命令行应用精选
  • Dism++:Windows系统维护的创新方案与高效实践
  • JMeter+Ant+Jenkins自动化测试流水线搭建与实战指南
  • 如何快速上手openYuanrong agent runtime?5分钟入门教程
  • 深入解析Grafana k6性能测试中的Stage负载模型设计与实战应用
  • 如何在Photoshop中直接使用AI绘图?SD-PPP插件终极指南
  • DCMTK医疗影像处理开源工具包:5大核心模块深度解析与实战应用
  • 2026 海外移动广告归因工具横向对比|适配日本・北美・南美专属场景
  • OpenBoardView:解决专业PCB分析的5大痛点与完整工作流指南
  • 华为USG5500防火墙新手避坑指南:从Trust、DMZ到Untrust,一次搞懂安全域与策略配置
  • YOLOv8 安装与实战指南:从环境配置到模型训练全解析
  • 深入理解QEMU架构:模拟器与虚拟化器的完美结合
  • 别再傻傻分不清了!PyTorch中torch.matmul()与@、mm、bmm的保姆级区别指南
  • 三阶段 DEA Performance 完整实操教程|剔除环境与随机干扰、效率校正全过程操作与论文分析思路
  • OpenEuler Infrastructure核心功能揭秘:从Ansible到CI/CD的完整工具链
  • openEuler高可用与集群部署终极指南:构建企业级HA架构与Kubernetes集群管理
  • 元容沙箱SDK开发者指南:贡献代码与扩展自定义隔离策略的最佳实践
  • QEMU性能优化:5个关键技巧提升虚拟机运行效率
  • 别再写 @CustomDialog 了,我把它从雷达鸭代码里全删了重写
  • sysSentry系统巡检框架:10分钟快速搭建企业级硬件故障监控平台
  • 终极指南:iTrustee_tzdriver与iTrustee OS通信机制详解
  • Autodesk Inventor 2027 下载安装教程 专业三维机械设计工程仿真软件下载安装步骤
  • DXVK:让Linux游戏体验媲美Windows的Vulkan转换层技术
  • 如何快速部署safeguard?5分钟入门Linux内核安全监控工具
  • UEFI安全启动签名全攻略:使用Signatrust保护你的固件
  • AI 面谈助手自动沉淀绩效改进行动项,形成 KPI 追踪落地闭环
  • DeepInsight RAG技术深度解析:构建智能检索增强生成系统
  • safeguard挂载限制实战:防止未授权文件系统挂载的终极方案
  • 别再手动装OpenOffice了!用Docker容器化部署Apache OpenOffice 4.1.13,5分钟搞定Linux服务器环境