Skip to content

Commit 950447a

Browse files
committed
integrate spot detection
1 parent e941b30 commit 950447a

3 files changed

Lines changed: 199 additions & 21 deletions

File tree

GEMstack/onboard/perception/cone_detection_parking.py

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from message_filters import Subscriber, ApproximateTimeSynchronizer
1616
from cv_bridge import CvBridge
1717
from .visualization_utils import *
18+
from .parking_utils import *
1819
import time
1920
import math
2021
import ros_numpy
@@ -322,8 +323,8 @@ def initialize(self):
322323
self.vis_3d_cones_bboxes = False
323324

324325
# Subscribers
325-
self.rgb_sub = Subscriber('/camera_fl/arena_camera_node/image_raw', Image)
326-
self.lidar_sub = Subscriber('/ouster/points', PointCloud2)
326+
self.rgb_sub = Subscriber('/camera/fl/image_raw', Image)
327+
self.lidar_sub = Subscriber('/lidar/top/points', PointCloud2)
327328
self.sync = ApproximateTimeSynchronizer([self.rgb_sub, self.lidar_sub],
328329
queue_size=10, slop=0.1)
329330
self.sync.registerCallback(self.synchronized_callback)
@@ -334,6 +335,7 @@ def initialize(self):
334335
self.pub_cones_image_detection = rospy.Publisher("cones_detection/annotated_image", Image, queue_size=1)
335336
self.pub_cones_bboxes_markers = rospy.Publisher("cones_detection/bboxes/markers", MarkerArray, queue_size=10)
336337
self.pub_cones_centers_pc2 = rospy.Publisher("cones_detection/centers/point_cloud", PointCloud2, queue_size=10)
338+
self.pub_parking_spot_marker = rospy.Publisher("parking_spot_detection/marker", MarkerArray, queue_size=10)
337339

338340
# Detection model
339341
self.model_path = os.getcwd() + '/GEMstack/knowledge/detection/cone.pt'
@@ -357,16 +359,7 @@ def initialize(self):
357359
self.R_c2l = self.T_c2l[:3, :3]
358360
self.camera_origin_in_lidar = self.T_c2l[:3, 3]
359361

360-
def viz_object_states(self, cv_image, boxes):
361-
cone_3d_centers = list()
362-
cone_3d_dims = list()
363-
364-
for track_id, agent in self.current_agents.items():
365-
if agent.pose.x != None and agent.pose.y != None and agent.pose.z != None:
366-
cone_3d_centers.append((agent.pose.x, agent.pose.y, agent.pose.z))
367-
if agent.dimensions != None and agent.dimensions[0] != None and agent.dimensions[1] != None and agent.dimensions[2] != None:
368-
cone_3d_dims.append(agent.dimensions)
369-
362+
def viz_object_states(self, cone_3d_centers, cone_3d_dims, cv_image, boxes):
370363
# Transform top lidar pointclouds to vehicle frame for visualization
371364
if self.vis_lidar_pc:
372365
latest_lidar_vehicle = transform_lidar_points(self.latest_lidar_unfiltered, self.T_l2v)
@@ -383,8 +376,11 @@ def viz_object_states(self, cv_image, boxes):
383376
self.pub_cones_image_detection.publish(ros_img)
384377

385378
# Create vehicle marker
386-
ros_vehicle_marker = create_bbox_marker([[0.0, 0.0, 0.0]], [[0.8, 0.5, 0.3]], (0.0, 1.0, 0.0, 1), "vehicle")
379+
ros_vehicle_marker = create_bbox_marker([[0.0, 0.0, 0.0]], [[0.8, 0.5, 0.3]], (0.0, 0.0, 1.0, 1), "vehicle")
387380
self.pub_vehicle_marker.publish(ros_vehicle_marker)
381+
# Delete previous parking spot markers
382+
ros_delete_parking_spot_markers = delete_markers("parking_spot", 1)
383+
self.pub_parking_spot_marker.publish(ros_delete_parking_spot_markers)
388384
# Draw 3D cone centers and dimensions
389385
if len(cone_3d_centers) > 0 and len(cone_3d_dims) > 0:
390386
if self.vis_3d_cones_centers:
@@ -397,11 +393,25 @@ def viz_object_states(self, cv_image, boxes):
397393

398394
if self.vis_3d_cones_bboxes:
399395
# Delete previous markers
400-
ros_delete_bboxes_markers = delete_bbox_marker()
396+
ros_delete_bboxes_markers = delete_markers("markers", 15)
401397
self.pub_cones_bboxes_markers.publish(ros_delete_bboxes_markers)
402398
# Create bbox markers from cone dimensions
403399
ros_cones_bboxes_markers = create_bbox_marker(cone_3d_centers, cone_3d_dims, (1.0, 0.0, 0.0, 0.4), "vehicle")
404400
self.pub_cones_bboxes_markers.publish(ros_cones_bboxes_markers)
401+
402+
def detect_parking_spot(self, cone_3d_centers):
403+
cone_ground_centers = np.array(cone_3d_centers)
404+
cone_ground_centers_2D = cone_ground_centers[:, :2]
405+
# print(f"-----cone_ground_centers_2D: {cone_ground_centers_2D}")
406+
candidates = findAllCandidateParkingLot(cone_ground_centers_2D)
407+
# print(f"-----candidates: {candidates}")
408+
if len(candidates) > 0:
409+
closest_spot = candidates[0]
410+
# print(f"-----closest_spot: {closest_spot}")
411+
# Create parking spot marker
412+
ros_parking_spot_marker = create_parking_spot_marker(closest_spot, ref_frame="vehicle")
413+
self.pub_parking_spot_marker.publish(ros_parking_spot_marker)
414+
return
405415

406416

407417
def synchronized_callback(self, image_msg, lidar_msg):
@@ -573,9 +583,23 @@ def update(self, vehicle: VehicleState) -> Dict[str, AgentState]:
573583
f"Velocity: (vx: {agent.velocity[0]:.3f}, vy: {agent.velocity[1]:.3f}, vz: {agent.velocity[2]:.3f})\n"
574584
)
575585

586+
# Now retrieve all cone agents from current agent items
587+
cone_3d_centers = list()
588+
cone_3d_dims = list()
589+
590+
for track_id, agent in self.current_agents.items():
591+
if agent.pose.x != None and agent.pose.y != None and agent.pose.z != None:
592+
cone_3d_centers.append((agent.pose.x, agent.pose.y, agent.pose.z))
593+
if agent.dimensions != None and agent.dimensions[0] != None and agent.dimensions[1] != None and agent.dimensions[2] != None:
594+
cone_3d_dims.append(agent.dimensions)
595+
596+
# Detect parking spot if 4 or more cones are detected
597+
if len(cone_3d_centers) >= 4:
598+
self.detect_parking_spot(cone_3d_centers)
599+
576600
# Add Visualization
577601
cv_image = self.latest_image.copy()
578-
self.viz_object_states(cv_image, bboxes)
602+
self.viz_object_states(cone_3d_centers, cone_3d_dims, cv_image, bboxes)
579603

580604
return agents
581605

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import cv2
2+
import numpy as np
3+
4+
5+
GEM_E4_LENGTH = 3.6 # m
6+
GEM_E4_WIDTH = 1.5 # m
7+
8+
9+
def judgeRectInPolygon(rectPts:np.ndarray, polygonPts:np.ndarray):
10+
polygon = polygonPts.reshape((-1, 1, 2)).astype(np.float32)
11+
for pt in rectPts:
12+
inside = cv2.pointPolygonTest(polygon, tuple(pt), measureDist=False)
13+
if inside < 0:
14+
return False
15+
return True
16+
17+
18+
def findAllCandidateParkingLot(cornerPts, angleStepDegree=5, positionStrideMeter=0.5):
19+
20+
cornerPts = np.array(cornerPts, dtype=np.float32)
21+
22+
min_x = np.min(cornerPts[:, 0]) + 0.5
23+
max_x = np.max(cornerPts[:, 0]) - 0.5
24+
min_y = np.min(cornerPts[:, 1]) + 0.5
25+
max_y = np.max(cornerPts[:, 1]) - 0.5
26+
27+
candidates = []
28+
29+
for angleDegree in np.arange(-90, 90, angleStepDegree):
30+
rect = ((0, 0), (GEM_E4_LENGTH, GEM_E4_WIDTH), float(angleDegree))
31+
carBox = cv2.boxPoints(rect)
32+
33+
for cx in np.arange(min_x, max_x, positionStrideMeter):
34+
for cy in np.arange(min_y, max_y, positionStrideMeter):
35+
car_box_shifted = carBox + np.array([cx, cy])
36+
37+
if judgeRectInPolygon(car_box_shifted, cornerPts):
38+
candidates.append((cx, cy, angleDegree))
39+
40+
return candidates
41+
42+
43+
def drawCarPose(img, center, angleDegree, color=(0, 0, 255), scale=100):
44+
rect = (center, (GEM_E4_LENGTH, GEM_E4_WIDTH), float(angleDegree))
45+
box = cv2.boxPoints(rect) # 四个角点
46+
box_scaled = np.int32(box * scale)
47+
48+
cv2.polylines(img, [box_scaled], isClosed=True, color=color, thickness=2)
49+
50+
51+
def visualizeCandidateCarPoses(cornerPts, candidates, scale=100):
52+
img = np.zeros((1000, 1000, 3), dtype=np.uint8)
53+
54+
parking_polygon = np.int32(cornerPts * scale)
55+
cv2.polylines(img, [parking_polygon], isClosed=True, color=(0, 255, 0), thickness=2)
56+
57+
for (x, y, angle) in candidates:
58+
drawCarPose(img, (x, y), angle, color=(100, 100, 255), scale=scale)
59+
60+
cv2.imshow("Parking Candidates", img)
61+
cv2.waitKey(0)
62+
cv2.destroyAllWindows()
63+
64+
65+
if __name__ == "__main__":
66+
cornerPts = np.array([
67+
[8,9],
68+
[4,5],
69+
[1,15],
70+
[5,9],
71+
]).astype(np.float32) # any order
72+
73+
74+
candidates = findAllCandidateParkingLot(cornerPts)
75+
76+
print(f"find {len(candidates)} candidates")
77+
print(f"candidates: {candidates[::10]}")
78+
for idx, pose in enumerate(candidates[::10]):
79+
thetaDegree = -(90 - (-pose[2])) if abs(- (90 - (-pose[2]))) < 90 else 180 + (-(90 - (-pose[2])))
80+
print(f"Candidate {idx}, Pose: centerXY:({pose[0]}, {pose[1]}), thetaDegree:{thetaDegree}")
81+
# OpenCV (rotation is from the lowest point, range [-90, 0])
82+
# counter-clock-wise is negative
83+
# clock-wise is positive
84+
85+
86+
visualizeCandidateCarPoses(cornerPts, candidates[::10])
87+
88+
89+
90+
91+
92+
93+
94+
95+
96+
97+
98+
99+
100+
101+
102+
103+
104+
105+
106+
107+

GEMstack/onboard/perception/visualization_utils.py

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
from sensor_msgs.msg import PointField
22
from visualization_msgs.msg import Marker, MarkerArray
3+
from geometry_msgs.msg import Point
34
import sensor_msgs.point_cloud2 as pc2
45
import cv2
56
import rospy
67
import struct
8+
import math
9+
10+
GEM_E4_LENGTH = 3.6 # m
11+
GEM_E4_WIDTH = 1.5 # m
712

813
def vis_2d_bbox(image, xywh, box):
914
# Setup
@@ -119,14 +124,56 @@ def create_bbox_marker(centroids, dimensions, color = (0.0, 1.0, 1.5, 0.2), ref_
119124
return marker_array
120125

121126

122-
def delete_bbox_marker():
123-
"""
124-
Delete 3D bbox markers given ID ranges
125-
"""
127+
def create_parking_spot_marker(closest_spot, length = GEM_E4_LENGTH, width = GEM_E4_WIDTH, ref_frame="map"):
126128
marker_array = MarkerArray()
127-
for i in range(15):
129+
130+
marker = Marker()
131+
marker.header.frame_id = ref_frame
132+
marker.header.stamp = rospy.Time.now()
133+
marker.ns = "parking_spot"
134+
marker.id = 0
135+
marker.type = Marker.LINE_STRIP
136+
marker.action = Marker.ADD
137+
marker.scale.x = 0.05 # Line width
138+
139+
marker.color.r = 0.0
140+
marker.color.g = 1.0
141+
marker.color.b = 0.0
142+
marker.color.a = 1.0
143+
144+
x, y, theta_deg = closest_spot
145+
146+
# Convert theta to radians
147+
theta = math.radians(theta_deg)
148+
149+
# Half-dimensions
150+
dx = length / 2.0
151+
dy = width / 2.0
152+
153+
# Define corner offsets in the local frame
154+
local_corners = [
155+
(-dx, -dy),
156+
(-dx, dy),
157+
(dx, dy),
158+
(dx, -dy),
159+
(-dx, -dy) # close the loop
160+
]
161+
162+
# Rotate and translate corners to global frame
163+
for lx, ly in local_corners:
164+
gx = x + lx * math.cos(theta) - ly * math.sin(theta)
165+
gy = y + lx * math.sin(theta) + ly * math.cos(theta)
166+
marker.points.append(Point(x=gx, y=gy, z=0.0))
167+
168+
marker_array.markers.append(marker)
169+
return marker_array
170+
171+
172+
def delete_markers(ns="markers", max_markers=15):
173+
marker_array = MarkerArray()
174+
for i in range(max_markers):
128175
marker = Marker()
129-
marker.ns = "markers"
176+
marker.ns = ns
130177
marker.id = i
131178
marker.action = Marker.DELETE
132179
marker_array.markers.append(marker)

0 commit comments

Comments
 (0)