1818import math
1919import ros_numpy
2020import os
21+ import yaml
2122
2223
2324# ----- Helper Functions -----
@@ -269,30 +270,79 @@ class ConeDetector3D(Component):
269270
270271 Tracking is optional: set `enable_tracking=False` to disable persistent tracking
271272 and return only detections from the current frame.
273+
274+ Supports multiple cameras; each camera’s intrinsics and extrinsics are
275+ loaded from a single YAML calibration file via plain PyYAML.
272276 """
273277
274- def __init__ (self , vehicle_interface : GEMInterface ):
275- self .vehicle_interface = vehicle_interface
276- self .enable_tracking = False
277- self .current_agents = {}
278- self .tracked_agents = {}
279- self .cone_counter = 0
280- self .latest_image = None
281- self .latest_lidar = None
282- self .bridge = CvBridge ()
283- self .start_pose_abs = None
284- self .camera_front = False
285- self .visualize_2d = False
286- self .use_cyl_roi = False
287- self .start_time = None
288- self .use_start_frame = False
289- self .save_data = False
290- self .orientation = False
278+ def __init__ (
279+ self ,
280+ vehicle_interface : GEMInterface ,
281+ camera_name : str ,
282+ camera_calib_file : str ,
283+ enable_tracking : bool = True ,
284+ visualize_2d : bool = False ,
285+ use_cyl_roi : bool = False ,
286+ T_l2v : list = None ,
287+ save_data : bool = True ,
288+ orientation : bool = True ,
289+ use_start_frame : bool = True ,
290+ ** kwargs
291+ ):
292+ # Core interfaces and state
293+ self .vehicle_interface = vehicle_interface
294+ self .current_agents = {}
295+ self .tracked_agents = {}
296+ self .cone_counter = 0
297+ self .latest_image = None
298+ self .latest_lidar = None
299+ self .bridge = CvBridge ()
300+ self .start_pose_abs = None
301+ self .start_time = None
302+
303+ # Config flags
304+ self .camera_name = camera_name
305+ self .enable_tracking = enable_tracking
306+ self .visualize_2d = visualize_2d
307+ self .use_cyl_roi = use_cyl_roi
308+ self .save_data = save_data
309+ self .orientation = orientation
310+ self .use_start_frame = use_start_frame
311+
312+ # 1) Load lidar→vehicle transform
313+ if T_l2v is not None :
314+ self .T_l2v = np .array (T_l2v )
315+ else :
316+ self .T_l2v = np .array ([
317+ [0.99939639 , 0.02547917 , 0.023615 , 1.1 ],
318+ [- 0.02530848 , 0.99965156 , - 0.00749882 , 0.03773583 ],
319+ [- 0.02379784 , 0.00689664 , 0.999693 , 1.95320223 ],
320+ [0.0 , 0.0 , 0.0 , 1.0 ]
321+ ])
322+
323+ # 2) Load camera intrinsics/extrinsics from the supplied YAML
324+ with open (camera_calib_file , 'r' ) as f :
325+ calib = yaml .safe_load (f )
326+
327+ # Expect structure:
328+ # cameras:
329+ # front:
330+ # K: [[...], [...], [...]]
331+ # D: [...]
332+ # T_l2c: [[...], ..., [...]]
333+ cam_cfg = calib ['cameras' ][camera_name ]
334+ self .K = np .array (cam_cfg ['K' ])
335+ self .D = np .array (cam_cfg ['D' ])
336+ self .T_l2c = np .array (cam_cfg ['T_l2c' ])
337+
338+ # Derived transforms
339+
291340 self .undistort_map1 = None
292341 self .undistort_map2 = None
342+ self .camera_front = (camera_name == 'front' )
293343
294344 def rate (self ) -> float :
295- return 10.0
345+ return 8
296346
297347 def state_inputs (self ) -> list :
298348 return ['vehicle' ]
@@ -301,48 +351,30 @@ def state_outputs(self) -> list:
301351 return ['agents' ]
302352
303353 def initialize (self ):
304- self .rgb_sub = Subscriber ('/camera_fr/arena_camera_node/image_raw' , Image )
354+ # --- Determine the correct RGB topic for this camera ---
355+ rgb_topic_map = {
356+ 'front' : '/oak/rgb/image_raw' ,
357+ 'front_right' : '/camera_fr/arena_camera_node/image_raw' ,
358+ # add additional camera mappings here if needed
359+ }
360+ rgb_topic = rgb_topic_map .get (
361+ self .camera_name ,
362+ f'/{ self .camera_name } /rgb/image_raw'
363+ )
364+
365+ # Subscribe to the RGB and LiDAR streams
366+ self .rgb_sub = Subscriber (rgb_topic , Image )
305367 self .lidar_sub = Subscriber ('/ouster/points' , PointCloud2 )
306- self .sync = ApproximateTimeSynchronizer ([self .rgb_sub , self .lidar_sub ],
307- queue_size = 200 , slop = 0.1 )
368+ self .sync = ApproximateTimeSynchronizer ([
369+ self .rgb_sub , self .lidar_sub
370+ ], queue_size = 200 , slop = 0.1 )
308371 self .sync .registerCallback (self .synchronized_callback )
372+
373+ # Initialize the YOLO detector
309374 self .detector = YOLO ('GEMstack/knowledge/detection/cone.pt' )
310375 self .detector .to ('cuda' )
311-
312- if self .camera_front :
313- self .K = np .array ([[684.83331299 , 0. , 573.37109375 ],
314- [0. , 684.60968018 , 363.70092773 ],
315- [0. , 0. , 1. ]])
316- else :
317- self .K = np .array ([[1.17625545e+03 , 0.00000000e+00 , 9.66432645e+02 ],
318- [0.00000000e+00 , 1.17514569e+03 , 6.08580326e+02 ],
319- [0.00000000e+00 , 0.00000000e+00 , 1.00000000e+00 ]])
320-
321- if self .camera_front :
322- self .D = np .array ([0.0 , 0.0 , 0.0 , 0.0 , 0.0 ])
323- else :
324- self .D = np .array ([- 2.70136325e-01 , 1.64393255e-01 , - 1.60720782e-03 , - 7.41246708e-05 ,
325- - 6.19939758e-02 ])
326-
327- self .T_l2v = np .array ([[0.99939639 , 0.02547917 , 0.023615 , 1.1 ],
328- [- 0.02530848 , 0.99965156 , - 0.00749882 , 0.03773583 ],
329- [- 0.02379784 , 0.00689664 , 0.999693 , 1.95320223 ],
330- [0. , 0. , 0. , 1. ]])
331- if self .camera_front :
332- self .T_l2c = np .array ([
333- [2.89748006e-02 , - 9.99580136e-01 , 3.68439439e-05 , - 3.07300513e-02 ],
334- [- 9.49930618e-03 , - 3.12215512e-04 , - 9.99954834e-01 , - 3.86689354e-01 ],
335- [9.99534999e-01 , 2.89731321e-02 , - 9.50437214e-03 , - 6.71425124e-01 ],
336- [0.00000000e+00 , 0.00000000e+00 , 0.00000000e+00 , 1.00000000e+00 ]
337- ])
338- else :
339- self .T_l2c = np .array ([[- 0.71836368 , - 0.69527204 , - 0.02346088 , 0.05718003 ],
340- [- 0.09720448 , 0.13371206 , - 0.98624154 , - 0.1598301 ],
341- [0.68884317 , - 0.7061996 , - 0.16363744 , - 1.04767285 ],
342- [0. , 0. , 0. , 1. ]]
343- )
344- self .T_c2l = np .linalg .inv (self .T_l2c )
345- self .R_c2l = self .T_c2l [:3 , :3 ]
376+ self .T_c2l = np .linalg .inv (self .T_l2c )
377+ self .R_c2l = self .T_c2l [:3 , :3 ]
346378 self .camera_origin_in_lidar = self .T_c2l [:3 , 3 ]
347379
348380 def synchronized_callback (self , image_msg , lidar_msg ):
@@ -439,15 +471,18 @@ def update(self, vehicle: VehicleState) -> Dict[str, AgentState]:
439471 img_normal = undistorted_img
440472 else :
441473 img_normal = lastest_image .copy ()
442- results_normal = self .detector (img_normal , conf = 0.3 , classes = [0 ])
474+ undistorted_img = lastest_image .copy ()
475+ orig_H , orig_W = lastest_image .shape [:2 ]
476+ self .current_K = self .K
477+ results_normal = self .detector (img_normal , conf = 0.25 , classes = [0 ])
443478 combined_boxes = []
444479 if not self .enable_tracking :
445480 self .cone_counter = 0
446481 if self .orientation :
447482 img_left = cv2 .rotate (undistorted_img .copy (), cv2 .ROTATE_90_COUNTERCLOCKWISE )
448483 img_right = cv2 .rotate (undistorted_img .copy (), cv2 .ROTATE_90_CLOCKWISE )
449- results_left = self .detector (img_left , conf = 0.75 , classes = [0 ])
450- results_right = self .detector (img_right , conf = 0.75 , classes = [0 ])
484+ results_left = self .detector (img_left , conf = 0.05 , classes = [0 ])
485+ results_right = self .detector (img_right , conf = 0.05 , classes = [0 ])
451486 boxes_left = np .array (results_left [0 ].boxes .xywh .cpu ()) if len (results_left ) > 0 else []
452487 boxes_right = np .array (results_right [0 ].boxes .xywh .cpu ()) if len (results_right ) > 0 else []
453488 for box in boxes_left :
@@ -489,7 +524,6 @@ def update(self, vehicle: VehicleState) -> Dict[str, AgentState]:
489524 cv2 .FONT_HERSHEY_SIMPLEX , 0.6 , color , 2 )
490525 cv2 .imshow ("Detection - Cone 2D" , undistorted_img )
491526
492-
493527 start = time .time ()
494528 pts_cam = transform_points_l2c (lidar_down , self .T_l2c )
495529 projected_pts = project_points (pts_cam , self .current_K , lidar_down )
0 commit comments