Skip to content

Commit c2f2b2b

Browse files
yolo_node.py update transform matrices + clean up
1 parent f95cc0c commit c2f2b2b

1 file changed

Lines changed: 53 additions & 37 deletions

File tree

GEMstack/onboard/perception/yolo_node.py

Lines changed: 53 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,21 @@ class YoloNode():
2424
and return only detections from the current frame.
2525
"""
2626

27-
def __init__(
28-
self,
29-
):
30-
self.latest_image = None
31-
self.latest_lidar = None
32-
self.bridge = CvBridge()
27+
def __init__(self):
28+
self.latest_image = None
29+
self.latest_lidar = None
30+
self.bridge = CvBridge()
3331
self.camera_name = 'front'
34-
self.camera_front = (self.camera_name=='front')
32+
self.camera_front = (self.camera_name == 'front')
3533
self.score_threshold = 0.4
3634
self.debug = True
35+
# self.undistort_map1 = None
36+
# self.undistort_map2 = None
3737
self.initialize()
3838

3939
def initialize(self):
40-
# --- Determine the correct RGB topic for this camera ---
40+
"""Initialize the YOLO node with camera calibration and ROS connections."""
41+
# # --- Determine the correct RGB topic for this camera ---
4142
rgb_topic_map = {
4243
'front': '/oak/rgb/image_raw',
4344
'front_right': '/camera_fr/arena_camera_node/image_raw',
@@ -50,29 +51,31 @@ def initialize(self):
5051

5152
# Initialize YOLO node
5253
rospy.init_node('yolo_box_publisher')
54+
5355
# Create bounding box publisher
5456
self.pub = rospy.Publisher('/yolo_boxes', BoundingBoxArray, queue_size=1)
5557
rospy.loginfo("YOLO node initialized and waiting for messages.")
5658

59+
# Set camera intrinsic parameters based on camera
5760
if self.camera_front:
5861
self.K = np.array([[684.83331299, 0., 573.37109375],
5962
[0., 684.60968018, 363.70092773],
6063
[0., 0., 1.]])
64+
self.D = np.array([0.0, 0.0, 0.0, 0.0, 0.0])
6165
else:
6266
self.K = np.array([[1.17625545e+03, 0.00000000e+00, 9.66432645e+02],
6367
[0.00000000e+00, 1.17514569e+03, 6.08580326e+02],
6468
[0.00000000e+00, 0.00000000e+00, 1.00000000e+00]])
69+
self.D = np.array([-2.70136325e-01, 1.64393255e-01, -1.60720782e-03,
70+
-7.41246708e-05, -6.19939758e-02])
6571

66-
if self.camera_front:
67-
self.D = np.array([0.0, 0.0, 0.0, 0.0, 0.0])
68-
else:
69-
self.D = np.array([-2.70136325e-01, 1.64393255e-01, -1.60720782e-03, -7.41246708e-05,
70-
-6.19939758e-02])
71-
72+
# Transformation matrix from LiDAR to vehicle frame
7273
self.T_l2v = np.array([[0.99939639, 0.02547917, 0.023615, 1.1],
7374
[-0.02530848, 0.99965156, -0.00749882, 0.03773583],
7475
[-0.02379784, 0.00689664, 0.999693, 1.95320223],
7576
[0., 0., 0., 1.]])
77+
78+
# Transformation matrix from LiDAR to camera frame
7679
if self.camera_front:
7780
self.T_l2c = np.array([
7881
[0.001090, -0.999489, -0.031941, 0.149698],
@@ -86,12 +89,14 @@ def initialize(self):
8689
[0.68884317, -0.7061996, -0.16363744, -1.04767285],
8790
[0., 0., 0., 1.]]
8891
)
92+
93+
# Compute inverse transformation (camera to LiDAR)
8994
self.T_c2l = np.linalg.inv(self.T_l2c)
9095
self.R_c2l = self.T_c2l[:3, :3]
9196
self.camera_origin_in_lidar = self.T_c2l[:3, 3]
9297

93-
# Initialize the YOLO detector
94-
self.detector = YOLO('yolov8n.pt') # 'GEMstack/knowledge/detection/cone.pt')
98+
# Initialize the YOLO detector and move to GPU if available
99+
self.detector = YOLO('yolov8n.pt')
95100
self.detector.to('cuda')
96101

97102
# Subscribe to the RGB and LiDAR streams
@@ -103,28 +108,31 @@ def initialize(self):
103108
self.sync.registerCallback(self.synchronized_callback)
104109

105110
def synchronized_callback(self, image_msg, lidar_msg):
111+
"""Process synchronized RGB and LiDAR messages to detect pedestrians."""
106112
rospy.loginfo("Received synchronized RGB and LiDAR messages")
113+
114+
# Convert image message to OpenCV format
107115
try:
108116
self.latest_image = self.bridge.imgmsg_to_cv2(image_msg, "bgr8")
109117
except Exception as e:
110-
rospy.logerr("Failed to convert image: {}".format(e))
118+
rospy.logerr(f"Failed to convert image: {e}")
111119
self.latest_image = None
120+
121+
# Convert LiDAR message to numpy array
112122
self.latest_lidar = pc2_to_numpy(lidar_msg, want_rgb=False)
113123

114124
# Gate guards against data not being present for both sensors:
115125
if self.latest_image is None or self.latest_lidar is None:
116-
return {}
117-
lastest_image = self.latest_image.copy()
126+
return {} # Skip
127+
128+
latest_image = self.latest_image.copy()
118129

130+
# Optionally downsample LiDAR points
119131
downsample = False
120132
if downsample:
121133
lidar_down = downsample_points(self.latest_lidar, voxel_size=0.1)
122134
else:
123135
lidar_down = self.latest_lidar.copy()
124-
125-
# if self.start_time is None:
126-
# self.start_time = current_time
127-
# time_elapsed = current_time - self.start_time
128136

129137
if self.camera_front == False:
130138
start = time.time()
@@ -137,10 +145,12 @@ def synchronized_callback(self, image_msg, lidar_msg):
137145
# --- Begin modifications for three-angle detection ---
138146
img_normal = undistorted_img
139147
else:
140-
img_normal = lastest_image.copy()
141-
undistorted_img = lastest_image.copy()
142-
orig_H, orig_W = lastest_image.shape[:2]
148+
img_normal = latest_image.copy()
149+
undistorted_img = latest_image.copy()
150+
orig_H, orig_W = latest_image.shape[:2]
143151
self.current_K = self.K
152+
153+
# Run YOLO detection on the image
144154
results_normal = self.detector(img_normal, conf=0.4, classes=[0])
145155
combined_boxes = []
146156

@@ -159,6 +169,7 @@ def synchronized_callback(self, image_msg, lidar_msg):
159169
# projected_pts[:, 1]: v-coordinate in the image (vertical pixel position)
160170
# projected_pts[:, 2:5]: original X, Y, Z coordinates in the LiDAR frame
161171

172+
162173
# Create empty list of bounding boxes to fill and publish later
163174
boxes = BoundingBoxArray()
164175
boxes.header.frame_id = 'currentVehicleFrame'
@@ -181,8 +192,8 @@ def synchronized_callback(self, image_msg, lidar_msg):
181192
bottom = int(cy + h / 2)
182193

183194
# Find LiDAR points that project to this box
184-
mask = (projected_pts[:, 0] >= left) & (projected_pts[:, 0] <= right) & \
185-
(projected_pts[:, 1] >= top) & (projected_pts[:, 1] <= bottom)
195+
mask = ((projected_pts[:, 0] >= left) & (projected_pts[:, 0] <= right) &
196+
(projected_pts[:, 1] >= top) & (projected_pts[:, 1] <= bottom))
186197
roi_pts = projected_pts[mask]
187198

188199
# Ignore regions with too few points
@@ -220,31 +231,36 @@ def synchronized_callback(self, image_msg, lidar_msg):
220231
# yaw, pitch, roll = R.from_matrix(R_vehicle).as_euler('zyx', degrees=False)
221232
yaw = np.arctan2(R_vehicle[1, 0], R_vehicle[0, 0])
222233

223-
refined_center = refined_center_vehicle
224-
225-
boxes = add_bounding_box(boxes=boxes,
234+
# Add the bounding box
235+
boxes = add_bounding_box(
236+
boxes=boxes,
226237
frame_id='currentVehicleFrame',
227238
stamp=lidar_msg.header.stamp,
228239
x=refined_center_vehicle[0],
229240
y=refined_center_vehicle[1],
230241
z=refined_center_vehicle[2],
231-
l=dims[2], # length
232-
w=dims[1], # width
233-
h=dims[0], # height
242+
l=dims[2], # length
243+
w=dims[1], # width
244+
h=dims[0], # height
234245
yaw=yaw,
235246
conf_score=float(conf_scores[i]),
236-
label=0 # person/pedestrian class
247+
label=0 # person/pedestrian class
237248
)
238249

239-
rospy.loginfo(f"Person detected at ({refined_center_vehicle[0]:.2f}, {refined_center_vehicle[1]:.2f}, {refined_center_vehicle[2]:.2f}) with score {conf_scores[i]:.2f}")
250+
rospy.loginfo(f"Person detected at ({refined_center_vehicle[0]:.2f}, "
251+
f"{refined_center_vehicle[1]:.2f}, {refined_center_vehicle[2]:.2f}) "
252+
f"with score {conf_scores[i]:.2f}")
240253

241254
# Publish the bounding boxes
242255
rospy.loginfo(f"Publishing {len(boxes.boxes)} person bounding boxes")
243256
self.pub.publish(boxes)
244257

245258
def undistort_image(self, image, K, D):
259+
"""Undistort an image using the camera calibration parameters."""
246260
h, w = image.shape[:2]
247261
newK, _ = cv2.getOptimalNewCameraMatrix(K, D, (w, h), 1, (w, h))
262+
263+
# Initialize undistortion maps if not already done
248264
if self.undistort_map1 is None or self.undistort_map2 is None:
249265
self.undistort_map1, self.undistort_map2 = cv2.initUndistortRectifyMap(K, D, R=None,
250266
newCameraMatrix=newK, size=(w, h),
@@ -262,4 +278,4 @@ def undistort_image(self, image, K, D):
262278
node = YoloNode()
263279
rospy.spin()
264280
except rospy.ROSInterruptException:
265-
pass
281+
pass

0 commit comments

Comments
 (0)