Initial Commit

This commit is contained in:
2026-02-09 03:39:49 +00:00
commit a756be4bf7
71 changed files with 6705 additions and 0 deletions

View File

@@ -0,0 +1,188 @@
#!/usr/bin/env python3
"""Position Estimator - Fuses sensors for local position estimate."""
import rclpy
from rclpy.node import Node
from geometry_msgs.msg import PoseStamped, TwistStamped, PoseWithCovarianceStamped
from sensor_msgs.msg import Imu
from nav_msgs.msg import Odometry
import numpy as np
from scipy.spatial.transform import Rotation
class PositionEstimator(Node):
def __init__(self):
super().__init__('position_estimator')
self.declare_parameter('fusion_method', 'weighted_average')
self.declare_parameter('vo_weight', 0.6)
self.declare_parameter('optical_flow_weight', 0.3)
self.declare_parameter('imu_weight', 0.1)
self.declare_parameter('update_rate', 50.0)
self.fusion_method = self.get_parameter('fusion_method').value
self.vo_weight = self.get_parameter('vo_weight').value
self.of_weight = self.get_parameter('optical_flow_weight').value
self.imu_weight = self.get_parameter('imu_weight').value
self.position = np.zeros(3)
self.velocity = np.zeros(3)
self.orientation = np.array([0.0, 0.0, 0.0, 1.0])
self.vo_pose = None
self.of_velocity = None
self.imu_data = None
self.prev_time = None
self.vo_sub = self.create_subscription(
PoseStamped, '/uav/visual_odometry/pose', self.vo_callback, 10)
self.of_sub = self.create_subscription(
TwistStamped, '/uav/optical_flow/velocity', self.of_callback, 10)
self.imu_sub = self.create_subscription(
Imu, '/uav/imu/data', self.imu_callback, 10)
self.pose_pub = self.create_publisher(
PoseWithCovarianceStamped, '/uav/local_position/pose', 10)
self.odom_pub = self.create_publisher(
Odometry, '/uav/local_position/odom', 10)
update_rate = self.get_parameter('update_rate').value
self.timer = self.create_timer(1.0 / update_rate, self.update_estimate)
self.get_logger().info(f'Position Estimator Started - {self.fusion_method}')
def vo_callback(self, msg):
self.vo_pose = msg
def of_callback(self, msg):
self.of_velocity = msg
def imu_callback(self, msg):
self.imu_data = msg
def update_estimate(self):
current_time = self.get_clock().now()
if self.prev_time is not None:
dt = (current_time.nanoseconds - self.prev_time.nanoseconds) / 1e9
if dt <= 0:
dt = 0.02
else:
dt = 0.02
self.prev_time = current_time
if self.fusion_method == 'weighted_average':
self.weighted_average_fusion(dt)
else:
self.simple_fusion(dt)
self.publish_estimate()
def weighted_average_fusion(self, dt):
total_weight = 0.0
weighted_pos = np.zeros(3)
weighted_vel = np.zeros(3)
if self.vo_pose is not None:
vo_pos = np.array([
self.vo_pose.pose.position.x,
self.vo_pose.pose.position.y,
self.vo_pose.pose.position.z
])
weighted_pos += self.vo_weight * vo_pos
total_weight += self.vo_weight
self.orientation = np.array([
self.vo_pose.pose.orientation.x,
self.vo_pose.pose.orientation.y,
self.vo_pose.pose.orientation.z,
self.vo_pose.pose.orientation.w
])
if self.of_velocity is not None:
of_vel = np.array([
self.of_velocity.twist.linear.x,
self.of_velocity.twist.linear.y,
0.0
])
weighted_vel += self.of_weight * of_vel
if self.imu_data is not None:
imu_accel = np.array([
self.imu_data.linear_acceleration.x,
self.imu_data.linear_acceleration.y,
self.imu_data.linear_acceleration.z - 9.81
])
self.velocity += imu_accel * dt * self.imu_weight
if total_weight > 0:
self.position = weighted_pos / total_weight
if self.of_velocity is not None:
self.velocity = weighted_vel
def simple_fusion(self, dt):
if self.vo_pose is not None:
self.position = np.array([
self.vo_pose.pose.position.x,
self.vo_pose.pose.position.y,
self.vo_pose.pose.position.z
])
def publish_estimate(self):
pose_msg = PoseWithCovarianceStamped()
pose_msg.header.stamp = self.get_clock().now().to_msg()
pose_msg.header.frame_id = 'odom'
pose_msg.pose.pose.position.x = float(self.position[0])
pose_msg.pose.pose.position.y = float(self.position[1])
pose_msg.pose.pose.position.z = float(self.position[2])
pose_msg.pose.pose.orientation.x = float(self.orientation[0])
pose_msg.pose.pose.orientation.y = float(self.orientation[1])
pose_msg.pose.pose.orientation.z = float(self.orientation[2])
pose_msg.pose.pose.orientation.w = float(self.orientation[3])
pose_msg.pose.covariance = [
0.1, 0, 0, 0, 0, 0,
0, 0.1, 0, 0, 0, 0,
0, 0, 0.1, 0, 0, 0,
0, 0, 0, 0.05, 0, 0,
0, 0, 0, 0, 0.05, 0,
0, 0, 0, 0, 0, 0.05
]
self.pose_pub.publish(pose_msg)
odom_msg = Odometry()
odom_msg.header = pose_msg.header
odom_msg.child_frame_id = 'base_link'
odom_msg.pose = pose_msg.pose
odom_msg.twist.twist.linear.x = float(self.velocity[0])
odom_msg.twist.twist.linear.y = float(self.velocity[1])
odom_msg.twist.twist.linear.z = float(self.velocity[2])
self.odom_pub.publish(odom_msg)
def main(args=None):
rclpy.init(args=args)
node = PositionEstimator()
try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
finally:
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()