Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added logger to Object Detection node and Detector Plugins #72

Merged
merged 6 commits into from
Aug 29, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docker_scripts/run_devel.sh
Original file line number Diff line number Diff line change
@@ -63,9 +63,9 @@ fi
docker run --gpus all --shm-size=1g --ulimit memlock=-1 --ulimit stack=67108864 \
-it --rm --privileged --net=host --ipc=host \
--name $CONTAINER_NAME \
-v $PERCEP_WS_PATH/src/:/root/percep_ws/src \
-v $PERCEP_WS_PATH/models/:/root/percep_ws/models/ \
-v $PERCEP_WS_PATH/:/root/percep_ws/ \
-v ddsconfig.xml:/ddsconfig.xml \
--env ROS_LOG_DIR=/root/percep_ws/src/logs \
--env CYCLONEDDS_URI=/ddsconfig.xml \
--env="QT_X11_NO_MITSHM=1" \
--env="DISPLAY" \
7 changes: 3 additions & 4 deletions object_detection/config/params.yaml
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
object_detection:
ros__parameters:
input_img_topic: /kitti/camera/color/left/image_raw
input_img_topic: color/image_raw
output_bb_topic: object_detection/img_bb
output_img_topic: object_detection/img
publish_output_img: 1
model_params:
detector_type: YOLOv5
model_dir_path: /root/percep_ws/models/yolov5
weight_file_name: yolov5.onnx
confidence_threshold : 0.5
show_fps : 1
confidence_threshold: 0.5
show_fps: 1
3 changes: 3 additions & 0 deletions object_detection/object_detection/DetectorBase.py
Original file line number Diff line number Diff line change
@@ -22,6 +22,9 @@ class DetectorBase(ABC):
def __init__(self) -> None:
self.predictions = []

def set_logger(self, logger) -> None:
self.logger = logger

def create_predictions_list(self, class_ids, confidences, boxes):
self.predictions = []
for i in range(len(class_ids)):
23 changes: 17 additions & 6 deletions object_detection/object_detection/Detectors/RetinaNet.py
Original file line number Diff line number Diff line change
@@ -24,29 +24,39 @@
class RetinaNet(DetectorBase):

def __init__(self):
super.__init__()
super().__init__()

def build_model(self, model_dir_path, weight_file_name):
model_path = os.path.join(model_dir_path, weight_file_name)

try:
self.model = models.load_model(model_path, backbone_name='resnet50')
self.logger.info("[RetinaNet] Model successfully loaded from: {}".format(model_path))
except Exception as e:
print("Loading the model failed with exception {}".format(e))
self.logger.error("[RetinaNet] Loading model failed with exception: {}".format(e))
raise Exception("Error loading given model from path: {}.".format(model_path) +
"Maybe the file doesn't exist?")
" Maybe the file doesn't exist?")

def load_classes(self, model_dir_path):
self.class_list = []
fpath = os.path.join(model_dir_path, 'classes.txt')

with open(model_dir_path + "/classes.txt", "r") as f:
self.class_list = [cname.strip() for cname in f.readlines()]
try:
with open(os.path.join(model_dir_path, "classes.txt"), "r") as f:
self.class_list = [cname.strip() for cname in f.readlines()]
self.logger.info("[RetinaNet] Loaded classes from {}".format(fpath))
except FileNotFoundError:
self.logger.error("[RetinaNet] Classes file not found at path: {}".format(fpath))
raise FileNotFoundError("Classes file not found. Make sure the file exists at the specified path.")
except Exception as e:
self.logger.error("[RetinaNet] Error loading classes with exception: {}".format(e))
raise Exception("Error loading classes from file: {}".format(fpath))

return self.class_list

def get_predictions(self, cv_image):
if cv_image is None:
# TODO: show warning message (different color, maybe)
self.logger.warning("[RetinaNet] Input image is None. No predictions will be generated.")
return None
else:
# copy to draw on
@@ -71,5 +81,6 @@ def get_predictions(self, cv_image):
boxes = [[int(coord/scale) for coord in box] for box in boxes]

super().create_predictions_list(class_ids, confidences, boxes)
self.logger.debug("[RetinaNet] Object detection successfully performed on the input image.")

return self.predictions
20 changes: 15 additions & 5 deletions object_detection/object_detection/Detectors/YOLOv5.py
Original file line number Diff line number Diff line change
@@ -30,22 +30,31 @@ def build_model(self, model_dir_path, weight_file_name):
model_path = os.path.join(model_dir_path, weight_file_name)
self.model = torch.hub.load('ultralytics/yolov5:v6.0', 'custom', path=model_path,
force_reload=True)
self.logger.info("[YOLOv5] Model successfully loaded from: {}".format(model_path))
except Exception as e:
print("Loading model failed with exception: {}".format(e))
self.logger.error("[YOLOv5] Loading model failed with exception: {}".format(e))
raise Exception("Error loading given model from path: {}.".format(model_path) +
" Maybe the file doesn't exist?")

def load_classes(self, model_dir_path):
self.class_list = []

with open(os.path.join(model_dir_path, 'classes.txt')) as f:
self.class_list = [cname.strip() for cname in f.readlines()]
fpath = os.path.join(model_dir_path, 'classes.txt')
try:
with open(fpath) as f:
self.class_list = [cname.strip() for cname in f.readlines()]
self.logger.info("[YOLOv5] Loaded classes from {}".format(fpath))
except FileNotFoundError:
self.logger.error("[YOLOv5] Classes file not found at path: {}".format(fpath))
raise FileNotFoundError("Classes file not found. Make sure the file exists at the specified path.")
except Exception as e:
self.logger.error("[YOLOv5] Error loading classes with exception: {}".format(e))
raise Exception("Error loading classes from file: {}".format(fpath))

return self.class_list

def get_predictions(self, cv_image):
if cv_image is None:
# TODO: show warning message (different color, maybe)
self.logger.warning("[YOLOv5] Input image is None. No predictions will be generated.")
return None, None
else:
self.frame = cv_image
@@ -61,5 +70,6 @@ def get_predictions(self, cv_image):
boxes.append([int(xy) for xy in xyxy])

super().create_predictions_list(class_id, confidence, boxes)
self.logger.debug("[YOLOv5] Object detection successfully performed on the input image.")

return self.predictions
45 changes: 30 additions & 15 deletions object_detection/object_detection/Detectors/YOLOv8.py
Original file line number Diff line number Diff line change
@@ -29,22 +29,32 @@ def build_model(self, model_dir_path, weight_file_name):
try:
model_path = os.path.join(model_dir_path, weight_file_name)
self.model = YOLO(model_path)
self.logger.info("[YOLOv8] Model successfully loaded from: {}".format(model_path))
except Exception as e:
print("Loading model failed with exception: {}".format(e))
raise Exception("Error loading given model from path: {}.".format(model_path) +
" Maybe the file doesn't exist?")
self.logger.error("[YOLOv8] Failed to load model with exception: {}".format(e))
raise Exception("Error loading the given model from path: {}.".format(model_path) +
" Make sure the file exists and the format is correct.")

def load_classes(self, model_dir_path):
self.class_list = []
fpath = os.path.join(model_dir_path, "classes.txt")

with open(model_dir_path + "/classes.txt", "r") as f:
self.class_list = [cname.strip() for cname in f.readlines()]
try:
with open(fpath, "r") as f:
self.class_list = [cname.strip() for cname in f.readlines()]
self.logger.info("[YOLOv8] Loaded classes from {}".format(fpath))
except FileNotFoundError:
self.logger.error("[YOLOv8] Classes file not found at path: {}".format(fpath))
raise FileNotFoundError("Classes file not found. Make sure the file exists at the specified path.")
except Exception as e:
self.logger.error("[YOLOv8] Error loading classes with exception: {}".format(e))
raise Exception("Error loading classes from file: {}".format(fpath))

return self.class_list

def get_predictions(self, cv_image):
if cv_image is None:
# TODO: show warning message (different color, maybe)
self.logger.warning("[YOLOv8] Input image is None. No predictions will be generated.")
return None, None
else:
self.frame = cv_image
@@ -53,14 +63,19 @@ def get_predictions(self, cv_image):
boxes = []

# Perform object detection on image
result = self.model.predict(self.frame, verbose=False) # Perform object detection on image
row = result[0].boxes.cpu()

for box in row:
class_id.append(box.cls.numpy().tolist()[0])
confidence.append(box.conf.numpy().tolist()[0])
boxes.append(box.xyxy.numpy().tolist()[0])

super().create_predictions_list(class_id, confidence, boxes)
try:
result = self.model.predict(self.frame, verbose=False)
row = result[0].boxes.cpu()

for box in row:
class_id.append(box.cls.numpy().tolist()[0])
confidence.append(box.conf.numpy().tolist()[0])
boxes.append(box.xyxy.numpy().tolist()[0])

super().create_predictions_list(class_id, confidence, boxes)
self.logger.debug("[YOLOv8] Object detection successfully performed on the input image.")
except Exception as e:
self.logger.error("[YOLOv8] Object detection failed with exception: {}".format(e))
raise Exception("Error performing object detection on the input image.")

return self.predictions
89 changes: 57 additions & 32 deletions object_detection/object_detection/ObjectDetection.py
Original file line number Diff line number Diff line change
@@ -32,50 +32,62 @@ class ObjectDetection(Node):
def __init__(self):
super().__init__('object_detection')

# create an empty list that will hold the names of all available detector
# Create an empty list that will hold the names of all available detector
self.available_detectors = []

# fill available_detectors with the detectors from Detectors dir
self.discover_detectors()
# Create a logger instance
self.logger = self.get_logger()

# Declare parameters with default values
self.declare_parameters(
namespace='',
parameters=[
('input_img_topic', ''),
('output_bb_topic', ''),
('output_img_topic', ''),
('model_params.detector_type', ''),
('model_params.model_dir_path', ''),
('model_params.weight_file_name', ''),
('model_params.confidence_threshold', 0.7),
('input_img_topic', 'color/image_raw'),
('output_bb_topic', 'object_detection/img_bb'),
('output_img_topic', 'object_detection/img'),
('model_params.detector_type', 'YOLOv5'),
('model_params.model_dir_path', '/root/percep_ws/models/yolov5'),
('model_params.weight_file_name', 'yolov5.onnx'),
('model_params.confidence_threshold', 0.5),
('model_params.show_fps', 1),
]
)
# Load parameters set by user
self.load_parameters()

# Fill available_detectors with the detectors from Detectors directory
self.discover_detectors()
# Load the detector specified through the parameters
self.load_detector()

self.img_pub = self.create_publisher(Image, self.output_img_topic, 10)
self.bb_pub = None
self.img_sub = self.create_subscription(Image, self.input_img_topic, self.detection_cb, 10)

# node params
self.bridge = CvBridge()

self.logger.info("[OBJECT DETECTION] Initialized Object Detection Node")

def load_parameters(self):
# Node params
self.input_img_topic = self.get_parameter('input_img_topic').value
self.output_bb_topic = self.get_parameter('output_bb_topic').value
self.output_img_topic = self.get_parameter('output_img_topic').value

# model params
self.logger.info("[OBJECT DETECTION] Input image topic set to {}".format(self.input_img_topic))
self.logger.info("[OBJECT DETECTION] Publishig output image on topic {}".format(self.output_img_topic) +
" and bounding box data on topic {}".format(self.output_bb_topic))

# Model params
self.detector_type = self.get_parameter('model_params.detector_type').value
self.model_dir_path = self.get_parameter('model_params.model_dir_path').value
self.weight_file_name = self.get_parameter('model_params.weight_file_name').value
self.confidence_threshold = self.get_parameter('model_params.confidence_threshold').value
self.show_fps = self.get_parameter('model_params.show_fps').value

# raise an exception if specified detector was not found
if self.detector_type not in self.available_detectors:
raise ModuleNotFoundError(self.detector_type + " Detector specified in config was not found. " +
"Check the Detectors dir for available detectors.")
else:
self.load_detector()

self.img_pub = self.create_publisher(Image, self.output_img_topic, 10)
self.bb_pub = None
self.img_sub = self.create_subscription(Image, self.input_img_topic, self.detection_cb, 10)

self.bridge = CvBridge()
self.logger.info("[OBJECT DETECTION] Detector type set to {} and".format(self.detector_type) +
" using weight file from {}".format(self.model_dir_path + self.weight_file_name))
self.logger.info("[OBJECT DETECTION] Detection confidence threshold set to: {}".format(self.confidence_threshold))

def discover_detectors(self):
curr_dir = os.path.dirname(__file__)
@@ -88,23 +100,36 @@ def discover_detectors(self):
self.available_detectors.remove('__init__')

def load_detector(self):
detector_mod = importlib.import_module(".Detectors." + self.detector_type,
"object_detection")
detector_class = getattr(detector_mod, self.detector_type)
self.detector = detector_class()
# Raise an exception if specified detector was not found
if self.detector_type not in self.available_detectors:
self.logger.error("[OBJECT DETECTION]" +
" {} Detector was set in parameters but was not found".format(self.detector_type) +
"Check the Detectors directory for list of available detectors")
raise ModuleNotFoundError(self.detector_type + " Detector specified in config was not found. " +
"Check the Detectors dir for available detectors.")
else:
detector_mod = importlib.import_module(".Detectors." + self.detector_type,
"object_detection")
detector_class = getattr(detector_mod, self.detector_type)
self.detector = detector_class()

# Set the logger for the detector plugins
self.detector.set_logger(self.logger)

self.detector.build_model(self.model_dir_path, self.weight_file_name)
self.detector.load_classes(self.model_dir_path)
# Load the model and the classes file for the detector plugin
self.detector.build_model(self.model_dir_path, self.weight_file_name)
self.detector.load_classes(self.model_dir_path)

print("Your detector: {} has been loaded !".format(self.detector_type))
self.logger.info("[OBJECT DETECTION] Your detector: {} has been loaded!".format(self.detector_type))

def detection_cb(self, img_msg):
cv_image = self.bridge.imgmsg_to_cv2(img_msg, "bgr8")

predictions = self.detector.get_predictions(cv_image=cv_image)

if predictions is None:
print("Image input from topic: {} is empty".format(self.input_img_topic))
self.logger.warning("[OBJECT DETECTION] " +
"Image input from topic: {} is empty".format(self.input_img_topic), throttle_duration_sec=1)
else:
for prediction in predictions:
confidence = prediction['confidence']