Skip to content

Commit 258bbe9

Browse files
authored
Merge pull request #721 from JdeRobot/issue_720
Solves Issue 720, added functionality to test models in CARLA using only the CARLA API instead of ROS/ROS2
2 parents 4a69b94 + dab4535 commit 258bbe9

28 files changed

Lines changed: 4259 additions & 886 deletions

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ share/python-wheels/
101101
*.egg
102102
MANIFEST
103103

104+
#Bird-eye-view for CARLA
105+
carla-birdeye-view/*
106+
104107
# PyInstaller
105108
# Usually these files are written by a python script from a template
106109
# before PyInstaller builds the exe, so as to inject date/other infos into it.
@@ -195,3 +198,4 @@ Gemfile.lock
195198

196199
.idea
197200
.BehaviorMetrics
201+
*.onnx
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
#!/usr/bin/python
2+
# -*- coding: utf-8 -*-
3+
import numpy as np
4+
import threading
5+
import time
6+
from utils.constants import DATASETS_DIR, ROOT_PATH
7+
import cv2 as cv
8+
import torch
9+
import torch.nn as nn
10+
import torchvision.models as models
11+
from torchvision import transforms
12+
from brains.CARLA.utils.pilotnet import PilotNet
13+
import timm
14+
GENERATED_DATASETS_DIR = ROOT_PATH + '/' + DATASETS_DIR
15+
16+
17+
class Brain:
18+
19+
def __init__(self, sensors, actuators, handler, config=None):
20+
21+
# Obtain sensors
22+
self.camera = sensors.get_camera('camera_0')
23+
self.pose = sensors.get_pose3d('pose3d_0')
24+
# Get actuators
25+
self.motors = actuators.get_motor('motors_0')
26+
27+
self.handler = handler
28+
self.config = config
29+
30+
self.cont = 0
31+
self.iteration = 0
32+
33+
# Create model object
34+
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
35+
self.preprocess = transforms.Compose([
36+
transforms.ToTensor()
37+
])
38+
39+
self.input_size = config['ImageSize']
40+
41+
42+
if config['ModelName'] == 'mobilenet_large':
43+
self.model = models.mobilenet_v3_large()
44+
num_ftrs = self.model.classifier[-1].in_features
45+
self.model.classifier[-1] = nn.Linear(num_ftrs, 2)
46+
elif config['ModelName'] == 'mobilenet_small':
47+
self.model = models.mobilenet_v3_small()
48+
num_ftrs = self.model.classifier[-1].in_features
49+
self.model.classifier[-1] = nn.Linear(num_ftrs, 2)
50+
elif config['ModelName'] == 'resnet':
51+
self.model = models.resnet18()
52+
num_ftrs = self.model.fc.in_features
53+
self.model.fc = nn.Linear(num_ftrs, 2)
54+
elif config['ModelName'] == 'efficientnet_v2':
55+
self.model = models.efficientnet_v2_s(weights=None)
56+
num_ftrs = self.model.classifier[-1].in_features
57+
self.model.classifier[-1] = torch.nn.Linear(num_ftrs, 2)
58+
elif config['ModelName'] == 'efficientvit':
59+
model = timm.create_model('efficientvit_b0', pretrained=False)
60+
num_ftrs = model.head.classifier[-1].in_features
61+
model.head.classifier[-1] = nn.Linear(num_ftrs, 2)
62+
elif config['ModelName'] == 'fastvit':
63+
model = timm.create_model('fastvit_mci0', pretrained=False)
64+
num_ftrs = model.head.classifier[-1].in_features
65+
model.head.classifier[-1] = nn.Linear(num_ftrs, 2)
66+
else:
67+
self.model = PilotNet(self.input_size, 2)
68+
# Load the state dictionary from the local .pth file
69+
state_dict = torch.load(config['SavedModelPath'], weights_only=True)
70+
# Load the state dictionary into the model
71+
self.model.load_state_dict(state_dict)
72+
73+
# Move the model to the selected device (cpu or gpu)
74+
self.model.to(self.device)
75+
76+
# Set the model to evaluation mode
77+
self.model.eval()
78+
79+
time.sleep(2)
80+
81+
def update_frame(self, frame_id, data):
82+
"""Update the information to be shown in one of the GUI's frames.
83+
84+
Arguments:
85+
frame_id {str} -- Id of the frame that will represent the data
86+
data {*} -- Data to be shown in the frame. Depending on the type of frame (rgbimage, laser, pose3d, etc)
87+
"""
88+
self.handler.update_frame(frame_id, data)
89+
90+
def update_pose(self, pose_data):
91+
self.handler.update_pose3d(pose_data)
92+
93+
def execute(self):
94+
image = self.camera.getImage()
95+
if image is not None:
96+
#image = cv.cvtColor(image, cv.COLOR_BGR2RGB)
97+
cropped_image = image[240:480, 0:640]
98+
resized_image = cv.resize(cropped_image, (int(self.input_size[1]), int(self.input_size[0])))
99+
input_tensor = self.preprocess(resized_image).to(self.device)
100+
input_batch = input_tensor.unsqueeze(0)
101+
output = self.model(input_batch)
102+
if self.device == "cpu":
103+
net_throttle = output[0].detach().numpy()[0].item()
104+
net_steer = output[0].detach().numpy()[1].item()
105+
else:
106+
net_throttle = output.data.cpu().numpy()[0][0].item()
107+
net_steer = output.data.cpu().numpy()[0][1].item()
108+
109+
self.motors.sendThrottle(net_throttle)
110+
print(net_steer)
111+
self.motors.sendSteer(net_steer)
112+
self.update_frame('frame_0', image)
113+
self.update_pose(self.pose.getPose3d())
114+
#print(self.pose.getPose3d())
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#!/usr/bin/python
2+
# -*- coding: utf-8 -*-
3+
import numpy as np
4+
import threading
5+
import time
6+
from utils.constants import DATASETS_DIR, ROOT_PATH
7+
8+
9+
GENERATED_DATASETS_DIR = ROOT_PATH + '/' + DATASETS_DIR
10+
11+
12+
class Brain:
13+
14+
def __init__(self, sensors, actuators, handler, config=None):
15+
self.camera = sensors.get_camera('camera_0')
16+
self.camera_1 = sensors.get_camera('camera_1')
17+
self.camera_2 = sensors.get_camera('camera_2')
18+
19+
self.pose = sensors.get_pose3d('pose3d_0')
20+
21+
self.handler = handler
22+
self.config = config
23+
self.motors = actuators.get_motor('motors_0')
24+
self.threshold_image = np.zeros((640, 360, 3), np.uint8)
25+
self.color_image = np.zeros((640, 360, 3), np.uint8)
26+
self.lock = threading.Lock()
27+
self.threshold_image_lock = threading.Lock()
28+
self.color_image_lock = threading.Lock()
29+
self.cont = 0
30+
self.iteration = 0
31+
32+
# self.previous_timestamp = 0
33+
# self.previous_image = 0
34+
35+
self.previous_v = None
36+
self.previous_w = None
37+
self.previous_w_normalized = None
38+
39+
time.sleep(2)
40+
41+
def update_frame(self, frame_id, data):
42+
"""Update the information to be shown in one of the GUI's frames.
43+
44+
Arguments:
45+
frame_id {str} -- Id of the frame that will represent the data
46+
data {*} -- Data to be shown in the frame. Depending on the type of frame (rgbimage, laser, pose3d, etc)
47+
"""
48+
self.handler.update_frame(frame_id, data)
49+
50+
def update_pose(self, pose_data):
51+
self.handler.update_pose3d(pose_data)
52+
53+
def execute(self):
54+
image = self.camera.getImage()
55+
image_1 = self.camera_1.getImage()
56+
image_2 = self.camera_2.getImage()
57+
58+
self.motors.sendThrottle(0.5)
59+
self.motors.sendSteer(0.05)
60+
61+
self.update_frame('frame_0', image)
62+
self.update_frame('frame_1', image_1)
63+
self.update_frame('frame_2', image_2)
64+
self.update_pose(self.pose.getPose3d())
65+
#print(self.pose.getPose3d())

behavior_metrics/brains/CARLA/brain_modifiedDeepestLSTM.py

Lines changed: 37 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -82,19 +82,6 @@ def __init__(self, sensors, actuators, model=None, handler=None, config=None):
8282
monolithic_model_path = PRETRAINED_MODELS + model
8383
print("Loading model from: ", monolithic_model_path)
8484

85-
# self.monolithic_model = ModifiedDeepestLSTM(image_shape=(66, 200, 3), num_labels=2)
86-
# self.monolithic_model = resnet18(weights=ResNet18_Weights.DEFAULT)
87-
# self.monolithic_model.fc = nn.Linear(self.monolithic_model.fc.in_features, 2)
88-
# self.monolithic_model.load_state_dict(torch.load(monolithic_model_path, map_location=self.device))
89-
# self.monolithic_model.to(self.device)
90-
# self.monolithic_model.eval()
91-
92-
# self.monolithic_model = efficientnet_v2_s(weights=None)
93-
# self.monolithic_model.classifier[-1] = nn.Linear(self.monolithic_model.classifier[-1].in_features, 2)
94-
# self.monolithic_model.load_state_dict(torch.load(monolithic_model_path, map_location=self.device))
95-
# self.monolithic_model.to(self.device)
96-
# self.monolithic_model.eval()
97-
9885
# onnx model
9986
providers = [('CUDAExecutionProvider', {})] if ort.get_available_providers().__contains__('CUDAExecutionProvider') else ['CPUExecutionProvider']
10087
self.ort_session = ort.InferenceSession(str(monolithic_model_path), providers=providers)
@@ -116,7 +103,7 @@ def __init__(self, sensors, actuators, model=None, handler=None, config=None):
116103
self.prev_yaw = None
117104
self.delta_yaw = 0
118105

119-
self.target_point = [160.0,-105.3,0.42,0.00,0.00,180.00]
106+
self.target_point = [160.0,108.3,0.42,0.00,0.00,180.00]
120107
self.termination_code = 0 # 0: not terminated; 1: arrived at target; 2: wrong turn
121108

122109
self._last_tick = None # para calcular el tiempo entre ticks
@@ -158,8 +145,15 @@ def update_frame(self, frame_id, data):
158145

159146
def predict_controls(self, image_seg): # se saco model como argumento
160147
# Asegurarse de que sea una imagen BGR como la cargada por cv2.imread
161-
calzada_color = [128, 64, 128]
162-
mask = cv2.inRange(image_seg, np.array(calzada_color), np.array(calzada_color))
148+
if image_seg is None:
149+
raise ValueError("Segmented image is None (sensor may not be ready).")
150+
151+
if not isinstance(image_seg, np.ndarray):
152+
print(f"[DEBUG] Segmentation image type: {type(image_seg)}")
153+
154+
155+
calzada_color =np.array([128, 64, 128], dtype=np.uint8)
156+
mask = cv2.inRange(image_seg, calzada_color, calzada_color)
163157

164158
masked_image = np.zeros_like(image_seg)
165159
masked_image[mask > 0] = [255, 255, 255]
@@ -176,16 +170,10 @@ def predict_controls(self, image_seg): # se saco model como argumento
176170

177171
gray = cv2.cvtColor(resized, cv2.COLOR_BGR2GRAY)
178172
rgb_like = cv2.merge([gray, gray, gray])
179-
173+
174+
# rgb_like /= 255.0
175+
180176
input_tensor = torch.tensor(rgb_like, dtype=torch.float32).permute(2, 0, 1)
181-
# input_tensor = input_tensor.unsqueeze(0).to(self.device)
182-
183-
# with torch.no_grad():
184-
# prediction = model(input_tensor)
185-
186-
# # steer, throttle = prediction[0].tolist()
187-
# steer = prediction[0][0].item()
188-
# throttle = prediction[0][1].item()
189177

190178
input_np = input_tensor.unsqueeze(0).cpu().numpy() # [1,3,66,200] float32
191179

@@ -206,22 +194,12 @@ def predict_controls(self, image_seg): # se saco model como argumento
206194

207195
def execute(self):
208196
"""Main loop of the brain. This will be called iteratively each TIME_CYCLE (see pilot.py)"""
209-
rgb_image = self.camera_rgb.getImage().data # rgb_image shape: (768, 1024, 3)
210-
seg_image = self.camera_seg.getImage().data # seg_image shape: (80, 400, 3)
211-
197+
rgb_image = self.camera_rgb.getImage() #.data # rgb_image shape: (768, 1024, 3)
198+
seg_image = self.camera_seg.getImage() #.data # seg_image shape: (80, 400, 3)
199+
212200
self.update_frame("frame_0", rgb_image)
213201
self.update_frame("frame_1", seg_image)
214-
215-
# if hasattr(self, 'pilot'):
216-
# current = self.pilot.tick_counter
217-
# if self._last_tick is None:
218-
# delta = 0
219-
# else:
220-
# delta = current - self._last_tick
221-
# logger.info(f"{delta} ticks -> inferencia")
222-
# self._last_tick = current
223-
224-
# print("Inferencia ejecutandose") # (600, 800, 3)
202+
225203

226204
steer, throttle = self.predict_controls(seg_image) # se saco self.monolithic_model como argumento
227205
# print(f"Predicted - Steer: {steer:.3f}, Throttle: {throttle:.3f}")
@@ -236,18 +214,29 @@ def execute(self):
236214
# f"target: ({self.target_point[0]:.2f}, {self.target_point[1]:.2f})")
237215

238216
if self.target_point is not None:
239-
distance_to_target = np.sqrt(
240-
(self.target_point.x - vehicle_location.x) ** 2 +
241-
(self.target_point.y - (-vehicle_location.y)) ** 2)
242-
243-
# print(f'Euclidean distance to target: {distance_to_target}')
217+
# Aceptar lista o Location
218+
if isinstance(self.target_point, (list, tuple, np.ndarray)):
219+
tx, ty = self.target_point[0], self.target_point[1]
220+
elif hasattr(self.target_point, "x"):
221+
tx, ty = self.target_point.x, self.target_point.y
222+
else:
223+
raise ValueError(f"Unsupported type for target_point: {type(self.target_point)}")
224+
225+
# Coordenadas actuales
226+
vx, vy = vehicle_location.x, vehicle_location.y
227+
# print(f"x: {vx} -> {tx}, y: {vy} -> {ty}")
228+
229+
# Calcular distancia euclidiana 2D
230+
distance_to_target = math.hypot(tx - vx, ty - vy)
231+
244232
if distance_to_target < 1.5:
245233
self.termination_code = 1
246-
arrived = True
247-
print(f"======== Arrived at target point {distance_to_target} m away============.")
248-
234+
print(f"======== Arrived at target point ({tx:.1f}, {ty:.1f}) | {distance_to_target:.2f} m away ==========")
235+
249236

250237
self.motors.sendThrottle(throttle)
251238
self.motors.sendSteer(steer)
252239
self.motors.sendBrake(0.0)
253-
240+
241+
242+
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import torch
2+
import torch.nn as nn
3+
4+
5+
class PilotNet(nn.Module):
6+
def __init__(self,
7+
image_shape,
8+
num_labels):
9+
super().__init__()
10+
11+
self.img_height = image_shape[0]
12+
self.img_width = image_shape[1]
13+
self.num_channels = 3
14+
15+
self.output_size = num_labels
16+
17+
self.ln_1 = nn.BatchNorm2d(self.num_channels, eps=1e-03)
18+
19+
self.cn_1 = nn.Conv2d(self.num_channels, 24, kernel_size=5, stride=2)
20+
self.relu1 = nn.ReLU()
21+
self.cn_2 = nn.Conv2d(24, 36, kernel_size=5, stride=2)
22+
self.relu2 = nn.ReLU()
23+
self.cn_3 = nn.Conv2d(36, 48, kernel_size=5, stride=2)
24+
self.relu3 = nn.ReLU()
25+
self.cn_4 = nn.Conv2d(48, 64, kernel_size=3, stride=1)
26+
self.relu4 = nn.ReLU()
27+
self.cn_5 = nn.Conv2d(64, 64, kernel_size=3, stride=1)
28+
self.relu5 = nn.ReLU()
29+
self.flatten = nn.Flatten()
30+
self.fc_1 = nn.Linear(1 * 18 * 64, 1164)
31+
self.relu_fc1 = nn.ReLU()
32+
self.fc_2 = nn.Linear(1164, 100)
33+
self.relu_fc2 = nn.ReLU()
34+
self.fc_3 = nn.Linear(100, 50)
35+
self.relu_fc3 = nn.ReLU()
36+
self.fc_4 = nn.Linear(50, 10)
37+
self.relu_fc4 = nn.ReLU()
38+
self.fc_5 = nn.Linear(10, self.output_size)
39+
40+
def forward(self, img):
41+
42+
out = self.ln_1(img)
43+
44+
out = self.cn_1(out)
45+
out = self.relu1(out)
46+
out = self.cn_2(out)
47+
out = self.relu2(out)
48+
out = self.cn_3(out)
49+
out = self.relu3(out)
50+
out = self.cn_4(out)
51+
out = self.relu4(out)
52+
out = self.cn_5(out)
53+
out = self.relu5(out)
54+
55+
out = self.flatten(out)
56+
57+
out = self.fc_1(out)
58+
out = self.relu_fc1(out)
59+
out = self.fc_2(out)
60+
out = self.relu_fc2(out)
61+
out = self.fc_3(out)
62+
out = self.relu_fc3(out)
63+
out = self.fc_4(out)
64+
out = self.relu_fc4(out)
65+
out = self.fc_5(out)
66+
67+
return out

0 commit comments

Comments
 (0)