Skip to content

Commit b3eea3e

Browse files
author
TheNoobInventor
committed
Complete aruco trajectory visualizer node, pending tests
1 parent 0fde086 commit b3eea3e

File tree

2 files changed

+41
-81
lines changed

2 files changed

+41
-81
lines changed

lidarbot_aruco/README.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,18 @@ First install the [ROS usb camera driver](https://index.ros.org/r/usb_cam/#humbl
4949
sudo apt install ros-humble-usb-cam
5050
```
5151

52-
Camera calibration was done following the steps outlined this [guide](https://navigation.ros.org/tutorials/docs/camera_calibration.html).
53-
54-
TODO: Note about 'dt' field in calibration file. Reference Addison's calibration guide.
52+
Camera calibration was done following the steps outlined this [guide](https://automaticaddison.com/how-to-perform-pose-estimation-using-an-aruco-marker/)
5553

5654
Execute the command below to run the usb-cam driver node:
5755

5856
```bash
5957
ros2 run usb_cam usb_cam_node_exe --ros-args --params-file ~/dev_ws/src/lidarbot_aruco/config/params_1.yaml
6058
```
6159

60+
## Aruco trajectory visualizer node
61+
62+
```bash
63+
ros2 run lidarbot_aruco aruco_trajectory_visualizer_node
64+
```
65+
66+
TODO: add gif showing node in action

lidarbot_aruco/lidarbot_aruco/aruco_trajectory_visualizer.py

Lines changed: 33 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,21 @@
11
#!/usr/bin/env python3
22

33
# This node is used to visualize the trajectory of lidarbot by tracking the ArUco marker
4-
# on it
4+
# on it and drawing lines through the center of the marker in subsequent image frames
55

66
# Adapted from: https://automaticaddison.com/estimate-aruco-marker-pose-using-opencv-gazebo-and-ros-2/
77

88
# Import Python libraries
99
import cv2
1010
import numpy as np
1111
import argparse
12-
from scipy.spatial.transform import Rotation as R
1312

1413
# Import ROS2 libraries
1514
import rclpy
1615
from rclpy.node import Node
1716
from rclpy.qos import qos_profile_sensor_data # Uses Best Effort reliability for camera
1817
from cv_bridge import CvBridge # Package to convert between ROS and OpenCV Images
19-
from geometry_msgs.msg import TransformStamped # Handles TransformStamped message
2018
from sensor_msgs.msg import Image # Image is the message type
21-
from tf2_ros import TransformBroadcaster
2219

2320
# Construct argument parser and parse the arguments
2421
ap = argparse.ArgumentParser()
@@ -62,7 +59,6 @@ def __init__(self):
6259
"/home/noobinventor/lidarbot_ws/src/lidarbot_aruco/config/chessboard_calibration.yaml",
6360
)
6461
self.declare_parameter("image_topic", "/image_raw")
65-
self.declare_parameter("aruco_marker_name", "aruco_marker_frame")
6662

6763
# Read parameters
6864
aruco_dictionary_name = (
@@ -83,9 +79,6 @@ def __init__(self):
8379
image_topic = (
8480
self.get_parameter("image_topic").get_parameter_value().string_value
8581
)
86-
self.aruco_marker_name = (
87-
self.get_parameter("aruco_marker_name").get_parameter_value().string_value
88-
)
8982

9083
# Check that we have a valid ArUco marker
9184
if ARUCO_DICT.get(aruco_dictionary_name, None) is None:
@@ -118,18 +111,18 @@ def __init__(self):
118111
)
119112
self.subscription # prevent unused variable warning
120113

121-
# Initialize the transform broadcaster
122-
self.tfbroadcaster = TransformBroadcaster(self)
123-
124114
# Used to convert between ROS and OpenCV images
125115
self.bridge = CvBridge()
126116

117+
# List of points to draw curves through
118+
self.points = []
119+
127120
# Callback function
128121
def listener_callback(self, data):
129122
# Display the message on the console
130123
self.get_logger().info("Receiving video frame")
131124

132-
# Convert ROS Image message to OpenCV image
125+
# Convert ROS image message to OpenCV image
133126
current_frame = self.bridge.imgmsg_to_cv2(data, desired_encoding="bgr8")
134127

135128
# Detect ArUco markers in the video frame
@@ -142,9 +135,6 @@ def listener_callback(self, data):
142135
# Check that at least one ArUco marker was detected
143136
if marker_ids is not None:
144137

145-
# Draw a square around detected markers in the video frame
146-
cv2.aruco.drawDetectedMarkers(current_frame, corners, marker_ids)
147-
148138
# Get the rotation and translation vectors
149139
rvecs, tvecs, obj_points = cv2.aruco.estimatePoseSingleMarkers(
150140
corners,
@@ -153,71 +143,36 @@ def listener_callback(self, data):
153143
self.dist_coeffs,
154144
)
155145

156-
# The pose of the marker is with respect to the camera lens frame.
157-
# Imagine you are looking through the camera viewfinder,
158-
# the camera lens frame's:
159-
# x-axis points to the right
160-
# y-axis points straight down towards your toes
161-
# z-axis points straight ahead away from your eye, out of the camera
162-
for i, marker_id in enumerate(marker_ids):
163-
164-
# Create the coordinate transform
165-
t = TransformStamped()
166-
t.header.stamp = self.get_clock().now().to_msg()
167-
t.header.frame_id = "webcam_frame"
168-
t.child_frame_id = self.aruco_marker_name
169-
170-
# Store the translation (i.e. position) information
171-
t.transform.translation.x = tvecs[i][0][0]
172-
t.transform.translation.y = tvecs[i][0][1]
173-
t.transform.translation.z = tvecs[i][0][2]
174-
175-
# Store the rotation information
176-
rotation_matrix = np.eye(4)
177-
rotation_matrix[0:3, 0:3] = cv2.Rodrigues(np.array(rvecs[i][0]))[0]
178-
r = R.from_matrix(rotation_matrix[0:3, 0:3])
179-
quat = r.as_quat()
180-
181-
# Quaternion format
182-
t.transform.rotation.x = quat[0]
183-
t.transform.rotation.y = quat[1]
184-
t.transform.rotation.z = quat[2]
185-
t.transform.rotation.w = quat[3]
186-
187-
# Send the transform
188-
self.tfbroadcaster.sendTransform(t)
189-
190-
# Draw the axes on the marker
191-
cv2.drawFrameAxes(
192-
current_frame,
193-
self.camera_matrix,
194-
self.dist_coeffs,
195-
rvecs[i],
196-
tvecs[i],
197-
0.05,
146+
# Loop over the detected ArUCo corners
147+
for markerCorner, markerID in zip(corners, marker_ids):
148+
# Extract the marker corners (which are always returned in
149+
# top-left, top-right, bottom-right, and bottom-left order)
150+
corners = markerCorner.reshape((4, 2))
151+
(topLeft, topRight, bottomRight, bottomLeft) = corners
152+
153+
# Convert the (x,y) coordinates pairs to integers
154+
topRight = (int(topRight[0]), int(topRight[1]))
155+
bottomRight = (int(bottomRight[0]), int(bottomRight[1]))
156+
bottomLeft = (int(bottomLeft[0]), int(bottomLeft[1]))
157+
topLeft = (int(topLeft[0]), int(topLeft[1]))
158+
159+
# Compute and draw the center (x, y)-coordinates of the ArUco
160+
# marker
161+
cX = int((topLeft[0] + bottomRight[0]) / 2.0)
162+
cY = int((topLeft[1] + bottomRight[1]) / 2.0)
163+
self.points.append([cX, cY])
164+
cv2.circle(current_frame, (cX, cY), 4, (0, 0, 255), -1)
165+
166+
# Convert points list to numpy array
167+
points_array = np.array(self.points)
168+
points_array = points_array.reshape((-1, 1, 2))
169+
170+
# Connect center coordinates of detected marker in respective frames
171+
# NOTE: this section works for only one marker
172+
cv2.polylines(
173+
current_frame, [points_array], False, (0, 255, 0), thickness=5
198174
)
199175

200-
# Draw circle at the center of ArUco tag
201-
# x_sum = (
202-
# corners[0][0][0][0]
203-
# + corners[0][0][1][0]
204-
# + corners[0][0][2][0]
205-
# + corners[0][0][3][0]
206-
# )
207-
# y_sum = (
208-
# corners[0][0][0][1]
209-
# + corners[0][0][1][1]
210-
# + corners[0][0][2][1]
211-
# + corners[0][0][3][1]
212-
# )
213-
#
214-
# x_centerPixel = x_sum * 0.25
215-
# y_centerPixel = y_sum * 0.25
216-
#
217-
# cv2.circle(
218-
# current_frame, (x_centerPixel, y_centerPixel), 4, (0, 0, 255), -1
219-
# )
220-
221176
# Display image for testing
222177
cv2.imshow("camera", current_frame)
223178
cv2.waitKey(1)

0 commit comments

Comments
 (0)