1
1
import carla
2
2
import logging
3
3
import numpy as np
4
+ import cv2 .cv2 as cv
4
5
5
6
from enum import IntEnum , auto , Enum
6
7
from pathlib import Path
21
22
Dimensions ,
22
23
)
23
24
24
- __all__ = ["BirdViewProducer" , "DEFAULT_HEIGHT" , "DEFAULT_WIDTH" ]
25
-
26
25
LOGGER = logging .getLogger (__name__ )
27
26
28
- DEFAULT_HEIGHT = 336 # its 84m when density is 4px/m
29
- DEFAULT_WIDTH = 150 # its 37.5m when density is 4px/m
30
27
31
- BirdView = np .ndarray # [np.uint8] with shape (level, y, x )
32
- RgbCanvas = np .ndarray # [np.uint8] with shape (y, x , 3)
28
+ BirdView = np .ndarray # [np.uint8] with shape (height, width, channel )
29
+ RgbCanvas = np .ndarray # [np.uint8] with shape (height, width , 3)
33
30
34
31
35
32
class BirdViewCropType (Enum ):
36
33
FRONT_AND_REAR_AREA = auto () # Freeway mode
37
34
FRONT_AREA_ONLY = auto () # Like in "Learning by Cheating"
38
35
39
36
37
+ DEFAULT_HEIGHT = 336 # its 84m when density is 4px/m
38
+ DEFAULT_WIDTH = 150 # its 37.5m when density is 4px/m
39
+ DEFAULT_CROP_TYPE = BirdViewCropType .FRONT_AND_REAR_AREA
40
+
41
+
40
42
class BirdViewMasks (IntEnum ):
41
43
PEDESTRIANS = 8
42
44
RED_LIGHTS = 7
@@ -69,11 +71,6 @@ def bottom_to_top() -> List[int]:
69
71
BirdViewMasks .ROAD : RGB .DIM_GRAY ,
70
72
}
71
73
72
- BIRDVIEW_SHAPE_CHW = (len (RGB_BY_MASK ), DEFAULT_HEIGHT , DEFAULT_WIDTH )
73
- BIRDVIEW_SHAPE_HWC = (DEFAULT_HEIGHT , DEFAULT_WIDTH , len (RGB_BY_MASK ))
74
-
75
- import cv2 .cv2 as cv2
76
-
77
74
78
75
def rotate (image , angle , center = None , scale = 1.0 ):
79
76
assert image .dtype == np .uint8
@@ -88,13 +85,13 @@ def rotate(image, angle, center=None, scale=1.0):
88
85
center = (w // 2 , h // 2 )
89
86
90
87
# perform the rotation
91
- M = cv2 .getRotationMatrix2D (center , angle , scale )
92
- rotated = cv2 .warpAffine (
88
+ M = cv .getRotationMatrix2D (center , angle , scale )
89
+ rotated = cv .warpAffine (
93
90
image ,
94
91
M ,
95
92
(w , h ),
96
- flags = cv2 .INTER_NEAREST ,
97
- borderMode = cv2 .BORDER_CONSTANT ,
93
+ flags = cv .INTER_NEAREST ,
94
+ borderMode = cv .BORDER_CONSTANT ,
98
95
borderValue = 0 ,
99
96
)
100
97
@@ -131,20 +128,27 @@ def __init__(
131
128
self ,
132
129
client : carla .Client ,
133
130
target_size : PixelDimensions ,
131
+ render_lanes_on_junctions : bool ,
134
132
pixels_per_meter : int = 4 ,
135
- crop_type : BirdViewCropType = BirdViewCropType .FRONT_AND_REAR_AREA
133
+ crop_type : BirdViewCropType = BirdViewCropType .FRONT_AND_REAR_AREA ,
136
134
) -> None :
137
135
self .client = client
138
136
self .target_size = target_size
139
- self ._pixels_per_meter = pixels_per_meter
137
+ self .pixels_per_meter = pixels_per_meter
140
138
self ._crop_type = crop_type
141
139
142
140
if crop_type is BirdViewCropType .FRONT_AND_REAR_AREA :
143
- rendering_square_size = round (square_fitting_rect_at_any_rotation (self .target_size ))
141
+ rendering_square_size = round (
142
+ square_fitting_rect_at_any_rotation (self .target_size )
143
+ )
144
144
elif crop_type is BirdViewCropType .FRONT_AREA_ONLY :
145
145
# We must keep rendering size from FRONT_AND_REAR_AREA (in order to avoid rotation issues)
146
- enlarged_size = PixelDimensions (width = target_size .width , height = target_size .height * 2 )
147
- rendering_square_size = round (square_fitting_rect_at_any_rotation (enlarged_size ))
146
+ enlarged_size = PixelDimensions (
147
+ width = target_size .width , height = target_size .height * 2
148
+ )
149
+ rendering_square_size = round (
150
+ square_fitting_rect_at_any_rotation (enlarged_size )
151
+ )
148
152
else :
149
153
raise NotImplementedError
150
154
self .rendering_area = PixelDimensions (
@@ -153,46 +157,50 @@ def __init__(
153
157
self ._world = client .get_world ()
154
158
self ._map = self ._world .get_map ()
155
159
self .masks_generator = MapMaskGenerator (
156
- client , pixels_per_meter = pixels_per_meter
160
+ client ,
161
+ pixels_per_meter = pixels_per_meter ,
162
+ render_lanes_on_junctions = render_lanes_on_junctions ,
157
163
)
158
164
159
165
cache_path = self .parametrized_cache_path ()
160
- if Path ( cache_path ). is_file ( ):
161
- LOGGER . info ( f"Loading cache from { cache_path } " )
162
- with FileLock (f"{ cache_path } .lock" ):
166
+ with FileLock ( f" { cache_path } .lock" ):
167
+ if Path ( cache_path ). is_file ():
168
+ LOGGER . info (f"Loading cache from { cache_path } " )
163
169
static_cache = np .load (cache_path )
164
170
self .full_road_cache = static_cache [0 ]
165
171
self .full_lanes_cache = static_cache [1 ]
166
172
self .full_centerlines_cache = static_cache [2 ]
167
- LOGGER .info (f"Loaded static layers from cache file: { cache_path } " )
168
- else :
169
- LOGGER .warning (
170
- f"Cache file does not exist, generating cache at { cache_path } "
171
- )
172
- self .full_road_cache = self .masks_generator .road_mask ()
173
- self .full_lanes_cache = self .masks_generator .lanes_mask ()
174
- self .full_centerlines_cache = self .masks_generator .centerlines_mask ()
175
- static_cache = np .stack ([self .full_road_cache , self .full_lanes_cache , self .full_centerlines_cache ])
176
- with FileLock (f"{ cache_path } .lock" ):
173
+ LOGGER .info (f"Loaded static layers from cache file: { cache_path } " )
174
+ else :
175
+ LOGGER .warning (
176
+ f"Cache file does not exist, generating cache at { cache_path } "
177
+ )
178
+ self .full_road_cache = self .masks_generator .road_mask ()
179
+ self .full_lanes_cache = self .masks_generator .lanes_mask ()
180
+ self .full_centerlines_cache = self .masks_generator .centerlines_mask ()
181
+ static_cache = np .stack (
182
+ [
183
+ self .full_road_cache ,
184
+ self .full_lanes_cache ,
185
+ self .full_centerlines_cache ,
186
+ ]
187
+ )
177
188
np .save (cache_path , static_cache , allow_pickle = False )
178
- LOGGER .info (f"Saved static layers to cache file: { cache_path } " )
189
+ LOGGER .info (f"Saved static layers to cache file: { cache_path } " )
179
190
180
191
def parametrized_cache_path (self ) -> str :
181
- cache_dir = Path ("birdview_v2_cache " )
192
+ cache_dir = Path ("birdview_v3_cache " )
182
193
cache_dir .mkdir (parents = True , exist_ok = True )
183
194
opendrive_content_hash = cache .generate_opendrive_content_hash (self ._map )
184
195
cache_filename = (
185
196
f"{ self ._map .name } __"
186
- f"px_per_meter={ self ._pixels_per_meter } __"
197
+ f"px_per_meter={ self .pixels_per_meter } __"
187
198
f"opendrive_hash={ opendrive_content_hash } __"
188
199
f"margin={ mask .MAP_BOUNDARY_MARGIN } .npy"
189
200
)
190
201
return str (cache_dir / cache_filename )
191
202
192
- def produce (
193
- self ,
194
- agent_vehicle : carla .Actor ,
195
- ) -> BirdView :
203
+ def produce (self , agent_vehicle : carla .Actor ) -> BirdView :
196
204
all_actors = actors .query_all (world = self ._world )
197
205
segregated_actors = actors .segregate_by_type (actors = all_actors )
198
206
agent_vehicle_loc = agent_vehicle .get_location ()
@@ -233,20 +241,21 @@ def produce(
233
241
self .masks_generator .enable_local_rendering_mode (rendering_window )
234
242
masks = self ._render_actors_masks (agent_vehicle , segregated_actors , masks )
235
243
cropped_masks = self .apply_agent_following_transformation_to_masks (
236
- agent_vehicle , masks ,
244
+ agent_vehicle , masks
237
245
)
238
246
ordered_indices = [mask .value for mask in BirdViewMasks .bottom_to_top ()]
239
- return cropped_masks [ordered_indices ]
247
+ return cropped_masks [:, :, ordered_indices ]
240
248
241
249
@staticmethod
242
250
def as_rgb (birdview : BirdView ) -> RgbCanvas :
243
- _ , h , w = birdview .shape
251
+ h , w , d = birdview .shape
252
+ assert d == len (BirdViewMasks )
244
253
rgb_canvas = np .zeros (shape = (h , w , 3 ), dtype = np .uint8 )
245
254
nonzero_indices = lambda arr : arr == COLOR_ON
246
255
247
256
for mask_type in BirdViewMasks .bottom_to_top ():
248
257
rgb_color = RGB_BY_MASK [mask_type ]
249
- mask = birdview [mask_type ]
258
+ mask = birdview [:, :, mask_type ]
250
259
# If mask above contains 0, don't overwrite content of canvas (0 indicates transparency)
251
260
rgb_canvas [nonzero_indices (mask )] = rgb_color
252
261
return rgb_canvas
@@ -279,8 +288,9 @@ def _render_actors_masks(
279
288
return masks
280
289
281
290
def apply_agent_following_transformation_to_masks (
282
- self , agent_vehicle : carla .Actor , masks : np .ndarray ,
291
+ self , agent_vehicle : carla .Actor , masks : np .ndarray
283
292
) -> np .ndarray :
293
+ """Returns image of shape: height, width, channels"""
284
294
agent_transform = agent_vehicle .get_transform ()
285
295
angle = (
286
296
agent_transform .rotation .yaw + 90
@@ -296,13 +306,14 @@ def apply_agent_following_transformation_to_masks(
296
306
crop_with_car_in_the_center , axes = (1 , 2 , 0 )
297
307
)
298
308
rotated = rotate (crop_with_centered_car , angle , center = rotation_center )
299
- rotated = np .transpose (rotated , axes = (2 , 0 , 1 ))
300
309
301
310
half_width = self .target_size .width // 2
302
311
hslice = slice (rotation_center .x - half_width , rotation_center .x + half_width )
303
312
304
313
if self ._crop_type is BirdViewCropType .FRONT_AREA_ONLY :
305
- vslice = slice (rotation_center .y - self .target_size .height , rotation_center .y )
314
+ vslice = slice (
315
+ rotation_center .y - self .target_size .height , rotation_center .y
316
+ )
306
317
elif self ._crop_type is BirdViewCropType .FRONT_AND_REAR_AREA :
307
318
half_height = self .target_size .height // 2
308
319
vslice = slice (
@@ -313,5 +324,5 @@ def apply_agent_following_transformation_to_masks(
313
324
assert (
314
325
vslice .start > 0 and hslice .start > 0
315
326
), "Trying to access negative indexes is not allowed, check for calculation errors!"
316
- car_on_the_bottom = rotated [:, vslice , hslice ]
327
+ car_on_the_bottom = rotated [vslice , hslice ]
317
328
return car_on_the_bottom
0 commit comments