From 71eb12b535de46fe7be41f4ec834b349826d57de Mon Sep 17 00:00:00 2001
From: Alok Sharma <84761994+NOEMOJI041@users.noreply.github.com>
Date: Mon, 18 Sep 2023 18:44:35 +0530
Subject: [PATCH 01/17] Added RetinaNet Plugin for `object_detection` package
(#37)
* Added RetinaNet plugin
---
object_detection/config/params.yaml | 5 +-
.../object_detection/Detectors/RetinaNet.py | 129 +++++-------------
2 files changed, 39 insertions(+), 95 deletions(-)
diff --git a/object_detection/config/params.yaml b/object_detection/config/params.yaml
index 909a042..94d7f1a 100644
--- a/object_detection/config/params.yaml
+++ b/object_detection/config/params.yaml
@@ -3,9 +3,10 @@ object_detection:
input_img_topic: color_camera/image_raw
output_bb_topic: object_detection/img_bb
output_img_topic: object_detection/img
+ publish_output_img: 1
model_params:
- detector_type: YOLOv5
+ detector_type: RetinaNet
model_dir_path: models/
- weight_file_name: auto_final.onnx
+ weight_file_name: resnet50_coco_best_v2.1.0.h5
confidence_threshold : 0.7
show_fps : 1
\ No newline at end of file
diff --git a/object_detection/object_detection/Detectors/RetinaNet.py b/object_detection/object_detection/Detectors/RetinaNet.py
index 8a54122..eb04a4c 100755
--- a/object_detection/object_detection/Detectors/RetinaNet.py
+++ b/object_detection/object_detection/Detectors/RetinaNet.py
@@ -1,75 +1,39 @@
-#!/usr/bin/env python3
+import os
-from tensorflow import keras
from keras_retinanet import models
-from keras_retinanet.utils.image import read_image_bgr, preprocess_image, resize_image
-from keras_retinanet.utils.visualization import draw_box, draw_caption
-from keras_retinanet.utils.colors import label_color
-import matplotlib.pyplot as plt
-import cv2
-import os
+from keras_retinanet.utils.image import preprocess_image, resize_image
import numpy as np
-import time
-import matplotlib.pyplot as plt
+from ..DetectorBase import DetectorBase
-class RetinaNet:
- def __init__(self, model_dir_path, weight_file_name, conf_threshold = 0.7,
- score_threshold = 0.4, nms_threshold = 0.25, is_cuda = 0, show_fps = 1):
- self.model_dir_path = model_dir_path
- self.weight_file_name = weight_file_name
+class RetinaNet(DetectorBase) :
+ def __init(self) :
- self.predictions = []
- self.conf_threshold = conf_threshold
- self.show_fps = show_fps
- self.is_cuda = is_cuda
+ super.__init__()
- if self.show_fps :
- self.frame_count = 0
- self.total_frames = 0
- self.fps = -1
- self.start = time.time_ns()
+ def build_model(self, model_dir_path, weight_file_name) :
+ model_path = os.path.join(model_dir_path, weight_file_name)
- self.labels_to_names = self.load_classes()
- self.build_model()
-
- def build_model(self) :
-
- try :
- self.model_path = os.path.join(self.model_dir_path, self.weight_file_name)
- self.model = models.load_model(self.model_path, backbone_name='resnet50')
-
- except :
- raise Exception("Error loading given model from path: {}. Maybe the file doesn't exist?".format(self.model_path))
-
+ try:
+ self.model = models.load_model(model_path, backbone_name='resnet50')
+ except:
+ raise Exception("Error loading given model from path: {}. Maybe the file doesn't exist?".format(model_path))
- def load_classes(self):
+ def load_classes(self, model_dir_path) :
self.class_list = []
- with open(self.model_dir_path + "/classes.txt", "r") as f:
+ with open(model_dir_path + "/classes.txt", "r") as f:
self.class_list = [cname.strip() for cname in f.readlines()]
return self.class_list
-
- def create_predictions_list(self, class_ids, confidences, boxes):
- for i in range(len(class_ids)):
- obj_dict = {
- "class_id": class_ids[i],
- "confidence": confidences[i],
- "box": boxes[i]
- }
-
- self.predictions.append(obj_dict)
-
-
- def get_predictions(self, cv_image):
+ def get_predictions(self, cv_image) :
if cv_image is None:
# TODO: show warning message (different color, maybe)
- return None,None
-
- else :
+ return None
+
+ else :
# copy to draw on
self.frame = cv_image.copy()
@@ -77,46 +41,25 @@ def get_predictions(self, cv_image):
input = preprocess_image(self.frame)
input, scale = resize_image(input)
- self.frame_count += 1
- self.total_frames += 1
-
# process image
- start = time.time()
- boxes, scores, labels = self.model.predict_on_batch(np.expand_dims(input, axis=0))
- #print("processing time: ", time.time() - start)
+ boxes_all, confidences_all, class_ids_all = self.model.predict_on_batch(np.expand_dims(input, axis=0))
+
+ boxes, confidences, class_ids = [], [], []
+
+ for index in range(len(confidences_all[0])) :
+ if confidences_all[0][index]!=-1 :
+ confidences.append(confidences_all[0][index])
+ boxes.append(boxes_all[0][index])
+ class_ids.append(class_ids_all[0][index])
+
# correct for image scale
- boxes /= scale
-
- self.create_predictions_list(labels, scores, boxes)
-
- # visualize detections
- for box, score, label in zip(boxes[0], scores[0], labels[0]):
- # scores are sorted so we can break
- if score < self.conf_threshold:
- break
-
- color = label_color(label)
-
- b = box.astype(int)
- draw_box(self.frame, b, color=color)
-
- caption = "{} {:.3f}".format(self.labels_to_names[label], score)
- #print(self.labels_to_names[label])
- draw_caption(self.frame, b, caption)
-
- if self.show_fps :
- if self.frame_count >= 30:
- self.end = time.time_ns()
- self.fps = 1000000000 * self.frame_count / (self.end - self.start)
- self.frame_count = 0
- self.start = time.time_ns()
-
- if self.fps > 0:
- self.fps_label = "FPS: %.2f" % self.fps
- cv2.putText(self.frame, self.fps_label, (10, 25), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
-
- return (self.predictions, self.frame)
-
+ #boxes = [x/scale for x in boxes]
+ boxes = [[int(coord/scale) for coord in box] for box in boxes]
+
+ super().create_predictions_list(class_ids, confidences, boxes)
-
\ No newline at end of file
+ return self.predictions
+
+
+
From 8c9515b4a581b1daf94038d7154a3bfb6fccd316 Mon Sep 17 00:00:00 2001
From: Alok Sharma <84761994+NOEMOJI041@users.noreply.github.com>
Date: Fri, 29 Sep 2023 16:45:05 +0530
Subject: [PATCH 02/17] Added package dependencies for `rosdep` (#36)
* added package depend
* added gz_sim depend
* updated readme(rosdep)
* added bash commands for percep_ws
* added python path note
---
README.md | 37 ++++++++++++++++++++++++++++++----
object_detection/package.xml | 4 +++-
perception_bringup/package.xml | 3 ++-
3 files changed, 38 insertions(+), 6 deletions(-)
diff --git a/README.md b/README.md
index 49abc22..3f1fea6 100644
--- a/README.md
+++ b/README.md
@@ -126,11 +126,22 @@ Refer to the official [ROS 2 installation guide](https://docs.ros.org/en/humble/
Now go ahead and clone this repository inside the "src" folder of the workspace you just created.
```bash
- cd percep_ws/src
- git clone git@github.com:atom-robotics-lab/ros-perception-pipeline.git
+ cd percep_ws && git clone git@github.com:atom-robotics-lab/ros-perception-pipeline.git src/
```
+3. Install dependencies using rosdep
-3. Compile the package
+ Update Your rosdep before installation.
+
+ ```bash
+ rosdep update
+ ```
+
+ This command installs all the packages that the packages in your catkin workspace depend upon but are missing on your computer.
+ ```bash
+ rosdep install --from-paths src --ignore-src -r -y
+ ```
+
+4. Compile the package
Follow this execution to compile your ROS 2 package
@@ -138,7 +149,8 @@ Refer to the official [ROS 2 installation guide](https://docs.ros.org/en/humble/
colcon build --symlink-install
```
-4. Source your workspace
+5. Source your workspace
+
```bash
source install/local_setup.bash
```
@@ -181,8 +193,25 @@ file according to your present working directory
ros2 run object_detection ObjectDetection --ros-args --params-file src/ros-perception-pipeline/object_detection/config/object_detection.yaml
```
+**Note :** If your imports are not working while using a virtual environment, you'll need to manually set your `PYTHONPATH` environment variable.
+Follow these steps to do this :
+
+1. Activate your virtual environment
+
+2. Find out the path of your virtual environment's Python installation
+ ```bash
+ which Python
+ ```
+
+3. Set your `PYTHONPATH`
+ ```bash
+ export PYTHONPATH = {insert_your_python_path_here}
+ ```
+
+
### 3. Changing the Detector
+
To change the object detector being used, you can change the parameters inside the object_detection.yaml file location inside
the **config** folder.
diff --git a/object_detection/package.xml b/object_detection/package.xml
index 068e66a..8d10362 100644
--- a/object_detection/package.xml
+++ b/object_detection/package.xml
@@ -11,7 +11,9 @@
ament_flake8
ament_pep257
python3-pytest
-
+ vision_msgs
+ cv_bridge
+
ament_python
diff --git a/perception_bringup/package.xml b/perception_bringup/package.xml
index fafefbc..3f1d1cd 100644
--- a/perception_bringup/package.xml
+++ b/perception_bringup/package.xml
@@ -11,7 +11,8 @@
ament_lint_auto
ament_lint_common
-
+ ros_gz
+ ros_gz_bridge
ament_cmake
From 8a4340d869d67c62072e7e9894985d2e8ddd6801 Mon Sep 17 00:00:00 2001
From: Arjun K Haridas <51917087+topguns837@users.noreply.github.com>
Date: Tue, 10 Oct 2023 22:43:36 +0530
Subject: [PATCH 03/17] Added .github folder with templates for feature
request, bug report and pull requests (#42)
* Added .github folder with templates for feature request and bug report
* Added template for pull requests
---
.github/issues_and_bugs/bug_report.md | 43 ++++++++++++++++++++++
.github/issues_and_bugs/feature_request.md | 23 ++++++++++++
.github/pull_request/pull_request.md | 30 +++++++++++++++
3 files changed, 96 insertions(+)
create mode 100644 .github/issues_and_bugs/bug_report.md
create mode 100644 .github/issues_and_bugs/feature_request.md
create mode 100644 .github/pull_request/pull_request.md
diff --git a/.github/issues_and_bugs/bug_report.md b/.github/issues_and_bugs/bug_report.md
new file mode 100644
index 0000000..9a7ddda
--- /dev/null
+++ b/.github/issues_and_bugs/bug_report.md
@@ -0,0 +1,43 @@
+---
+name: Bug report
+about: Report a bug
+labels: bug
+---
+
+## Environment 🖥️
+* OS Version:
+
+* Python Version:
+
+* Do you have a dedicated GPU ?
+
+* NVIDIA Driver Version (if applicable):
+
+* CUDA Version (if applicable):
+
+* Are you running this project natively or using Docker ?
+
+* Is the issue present in the main branch or any of the development branch ?
+
+
+
+```
+# paste log here
+```
+
+
+
+## Description 📖
+* Expected behavior:
+* Actual behavior:
+
+## Steps to reproduce 👀
+
+
+1.
+2.
+3.
+
+## Output 💥
+
\ No newline at end of file
diff --git a/.github/issues_and_bugs/feature_request.md b/.github/issues_and_bugs/feature_request.md
new file mode 100644
index 0000000..80fb539
--- /dev/null
+++ b/.github/issues_and_bugs/feature_request.md
@@ -0,0 +1,23 @@
+---
+name: Feature request
+about: Request a new feature
+labels: enhancement
+---
+
+## Desired behavior
+
+
+## Alternatives considered
+
+
+## Implementation suggestion
+
+
+## Examples of this feature in some other project (if applicable)
+
+
+## Additional context
+
\ No newline at end of file
diff --git a/.github/pull_request/pull_request.md b/.github/pull_request/pull_request.md
new file mode 100644
index 0000000..ac31a7e
--- /dev/null
+++ b/.github/pull_request/pull_request.md
@@ -0,0 +1,30 @@
+# Description 📖
+
+Please include a summary of the changes and the related issue. Please also include relevant motivation and context. List any dependencies that are required for this change.
+
+Fixes # (issue)
+
+## Type of change 📜
+
+Please delete options that are not relevant.
+
+- [ ] Bug fix (non-breaking change which fixes an issue)
+- [ ] New feature (non-breaking change which adds functionality)
+- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
+- [ ] This change requires a documentation update
+- [ ] This change requires testing before it can be merged into the main/development branch
+
+# How Has This Been Tested? 👀
+
+Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration
+
+- [ ] Test A
+- [ ] Test B
+
+**Test Configuration** 🖥️
+* OS version:
+* Hardware:
+* NVIDIA Driver:
+* CUDA Version:
+
+
From 9f0e1056429a2d54251040b993b1c7441bfd0981 Mon Sep 17 00:00:00 2001
From: Abhishek Garg <132362014+abhishekgarg2511@users.noreply.github.com>
Date: Tue, 10 Oct 2023 22:56:00 +0530
Subject: [PATCH 04/17] Added Apache 2.0 License (#41)
* Added LICENSE
* minor changes
* license name added
* copyright(c) added
---
LICENSE | 195 +++++++++++++++++++++++++++++++++
object_detection/package.xml | 2 +-
perception_bringup/package.xml | 2 +-
3 files changed, 197 insertions(+), 2 deletions(-)
create mode 100644 LICENSE
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..919de01
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,195 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ Copyright (c)2023 A.T.O.M ROBOTICS
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+
+
+
\ No newline at end of file
diff --git a/object_detection/package.xml b/object_detection/package.xml
index 8d10362..482516c 100644
--- a/object_detection/package.xml
+++ b/object_detection/package.xml
@@ -5,7 +5,7 @@
0.0.0
TODO: Package description
singh
- TODO: License declaration
+ Apache 2.0
ament_copyright
ament_flake8
diff --git a/perception_bringup/package.xml b/perception_bringup/package.xml
index 3f1d1cd..a4894b2 100644
--- a/perception_bringup/package.xml
+++ b/perception_bringup/package.xml
@@ -5,7 +5,7 @@
0.0.0
TODO: Package description
singh
- TODO: License declaration
+ Apache 2.0
ament_cmake
From a790df1a473fbbba94133d939340e9e81ef940b8 Mon Sep 17 00:00:00 2001
From: Arjun K Haridas <51917087+topguns837@users.noreply.github.com>
Date: Tue, 10 Oct 2023 23:20:26 +0530
Subject: [PATCH 05/17] Added Templates for P.R., Issues and bug reports (#43)
* Added .github folder with templates for feature request and bug report
* Added template for pull requests
* Re-arranged .github template files
---
.github/ISSUE_TEMPLATE/bug_report.md | 43 +++++++++++++++++++++++
.github/ISSUE_TEMPLATE/feature_request.md | 23 ++++++++++++
.github/PULL_REQUEST_TEMPLATE.md | 30 ++++++++++++++++
3 files changed, 96 insertions(+)
create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md
create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md
create mode 100644 .github/PULL_REQUEST_TEMPLATE.md
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..9a7ddda
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,43 @@
+---
+name: Bug report
+about: Report a bug
+labels: bug
+---
+
+## Environment 🖥️
+* OS Version:
+
+* Python Version:
+
+* Do you have a dedicated GPU ?
+
+* NVIDIA Driver Version (if applicable):
+
+* CUDA Version (if applicable):
+
+* Are you running this project natively or using Docker ?
+
+* Is the issue present in the main branch or any of the development branch ?
+
+
+
+```
+# paste log here
+```
+
+
+
+## Description 📖
+* Expected behavior:
+* Actual behavior:
+
+## Steps to reproduce 👀
+
+
+1.
+2.
+3.
+
+## Output 💥
+
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000..80fb539
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,23 @@
+---
+name: Feature request
+about: Request a new feature
+labels: enhancement
+---
+
+## Desired behavior
+
+
+## Alternatives considered
+
+
+## Implementation suggestion
+
+
+## Examples of this feature in some other project (if applicable)
+
+
+## Additional context
+
\ No newline at end of file
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..ac31a7e
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,30 @@
+# Description 📖
+
+Please include a summary of the changes and the related issue. Please also include relevant motivation and context. List any dependencies that are required for this change.
+
+Fixes # (issue)
+
+## Type of change 📜
+
+Please delete options that are not relevant.
+
+- [ ] Bug fix (non-breaking change which fixes an issue)
+- [ ] New feature (non-breaking change which adds functionality)
+- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
+- [ ] This change requires a documentation update
+- [ ] This change requires testing before it can be merged into the main/development branch
+
+# How Has This Been Tested? 👀
+
+Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration
+
+- [ ] Test A
+- [ ] Test B
+
+**Test Configuration** 🖥️
+* OS version:
+* Hardware:
+* NVIDIA Driver:
+* CUDA Version:
+
+
From ea73ffd503a3ac8372b815c9b3dd61a1f4875627 Mon Sep 17 00:00:00 2001
From: topguns837
Date: Tue, 10 Oct 2023 23:23:32 +0530
Subject: [PATCH 06/17] Removed unecessary folders in .github
---
.github/issues_and_bugs/bug_report.md | 43 ----------------------
.github/issues_and_bugs/feature_request.md | 23 ------------
.github/pull_request/pull_request.md | 30 ---------------
3 files changed, 96 deletions(-)
delete mode 100644 .github/issues_and_bugs/bug_report.md
delete mode 100644 .github/issues_and_bugs/feature_request.md
delete mode 100644 .github/pull_request/pull_request.md
diff --git a/.github/issues_and_bugs/bug_report.md b/.github/issues_and_bugs/bug_report.md
deleted file mode 100644
index 9a7ddda..0000000
--- a/.github/issues_and_bugs/bug_report.md
+++ /dev/null
@@ -1,43 +0,0 @@
----
-name: Bug report
-about: Report a bug
-labels: bug
----
-
-## Environment 🖥️
-* OS Version:
-
-* Python Version:
-
-* Do you have a dedicated GPU ?
-
-* NVIDIA Driver Version (if applicable):
-
-* CUDA Version (if applicable):
-
-* Are you running this project natively or using Docker ?
-
-* Is the issue present in the main branch or any of the development branch ?
-
-
-
-```
-# paste log here
-```
-
-
-
-## Description 📖
-* Expected behavior:
-* Actual behavior:
-
-## Steps to reproduce 👀
-
-
-1.
-2.
-3.
-
-## Output 💥
-
\ No newline at end of file
diff --git a/.github/issues_and_bugs/feature_request.md b/.github/issues_and_bugs/feature_request.md
deleted file mode 100644
index 80fb539..0000000
--- a/.github/issues_and_bugs/feature_request.md
+++ /dev/null
@@ -1,23 +0,0 @@
----
-name: Feature request
-about: Request a new feature
-labels: enhancement
----
-
-## Desired behavior
-
-
-## Alternatives considered
-
-
-## Implementation suggestion
-
-
-## Examples of this feature in some other project (if applicable)
-
-
-## Additional context
-
\ No newline at end of file
diff --git a/.github/pull_request/pull_request.md b/.github/pull_request/pull_request.md
deleted file mode 100644
index ac31a7e..0000000
--- a/.github/pull_request/pull_request.md
+++ /dev/null
@@ -1,30 +0,0 @@
-# Description 📖
-
-Please include a summary of the changes and the related issue. Please also include relevant motivation and context. List any dependencies that are required for this change.
-
-Fixes # (issue)
-
-## Type of change 📜
-
-Please delete options that are not relevant.
-
-- [ ] Bug fix (non-breaking change which fixes an issue)
-- [ ] New feature (non-breaking change which adds functionality)
-- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
-- [ ] This change requires a documentation update
-- [ ] This change requires testing before it can be merged into the main/development branch
-
-# How Has This Been Tested? 👀
-
-Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration
-
-- [ ] Test A
-- [ ] Test B
-
-**Test Configuration** 🖥️
-* OS version:
-* Hardware:
-* NVIDIA Driver:
-* CUDA Version:
-
-
From b1816ad2119f8f8eeb76e6fabd2bb2a4a0c3dab1 Mon Sep 17 00:00:00 2001
From: Krachitkumar
Date: Thu, 14 Dec 2023 22:29:21 +0530
Subject: [PATCH 07/17] added image preprocessing node
---
image_preprocessing/CMakeLists.txt | 36 +++++++++++++++++++++++
image_preprocessing/package.xml | 18 ++++++++++++
image_preprocessing/src/test_node.cpp | 42 +++++++++++++++++++++++++++
3 files changed, 96 insertions(+)
create mode 100644 image_preprocessing/CMakeLists.txt
create mode 100644 image_preprocessing/package.xml
create mode 100644 image_preprocessing/src/test_node.cpp
diff --git a/image_preprocessing/CMakeLists.txt b/image_preprocessing/CMakeLists.txt
new file mode 100644
index 0000000..fe95cae
--- /dev/null
+++ b/image_preprocessing/CMakeLists.txt
@@ -0,0 +1,36 @@
+cmake_minimum_required(VERSION 3.5)
+#project(image_publisher)
+project(image_preprocessing)
+# Find dependencies
+find_package(ament_cmake REQUIRED)
+find_package(rclcpp REQUIRED)
+find_package(sensor_msgs REQUIRED)
+
+# Add executable
+add_executable(test_node src/test_node.cpp)
+
+# Include directories for the executable
+target_include_directories(test_node
+ PRIVATE
+ ${sensor_msgs_INCLUDE_DIRS}
+)
+
+# Link the executable to the required libraries
+ament_target_dependencies(test_node
+ rclcpp
+ sensor_msgs
+)
+
+# Install the executable
+install(TARGETS
+ test_node
+ DESTINATION lib/${PROJECT_NAME}
+)
+
+# Install launch files, config files, and other directories if necessary
+# install(DIRECTORY launch
+# DESTINATION share/${PROJECT_NAME}
+# )
+
+# Install the CMake package file
+ament_package()
diff --git a/image_preprocessing/package.xml b/image_preprocessing/package.xml
new file mode 100644
index 0000000..0ad68f3
--- /dev/null
+++ b/image_preprocessing/package.xml
@@ -0,0 +1,18 @@
+
+
+
+ image_preprocessing
+ 0.0.0
+ TODO: Package description
+ rachit
+ TODO: License declaration
+
+ ament_cmake
+
+ ament_lint_auto
+ ament_lint_common
+
+
+ ament_cmake
+
+
diff --git a/image_preprocessing/src/test_node.cpp b/image_preprocessing/src/test_node.cpp
new file mode 100644
index 0000000..dfc7566
--- /dev/null
+++ b/image_preprocessing/src/test_node.cpp
@@ -0,0 +1,42 @@
+#include "rclcpp/rclcpp.hpp"
+#include "sensor_msgs/msg/image.hpp"
+
+class ImagePublisherNode : public rclcpp::Node {
+public:
+ ImagePublisherNode() : Node("image_publisher_node") {
+ // Create a subscription to the "/color_camera/image_raw" topic
+ imagesubscription = create_subscription(
+ "/color_camera/image_raw", 10, [this](const sensor_msgs::msg::Image::SharedPtr msg) {
+ // Callback function for the subscription
+ publishImage(msg);
+ // std::cout<<"Publishing Image"<("img_pub", 10);
+
+ // Set the publishing rate to 10 Hz
+ publishtimer = create_wall_timer(std::chrono::milliseconds(100), [this]() {
+ // Timer callback for publishing at 10 Hz
+ // You can perform any additional processing here if needed
+ });
+ }
+
+private:
+ void publishImage(const sensor_msgs::msg::Image::SharedPtr msg) {
+ auto loaned_msg = std::make_unique(*msg);
+ imagepublisher->publish(std::move(loaned_msg));
+
+ }
+
+ rclcpp::Subscription::SharedPtr imagesubscription;
+ rclcpp::Publisher::SharedPtr imagepublisher;
+ rclcpp::TimerBase::SharedPtr publishtimer;
+};
+
+int main(int argc, char** argv) {
+ rclcpp::init(argc, argv);
+ rclcpp::spin(std::make_shared());
+ rclcpp::shutdown();
+ return 0;
+}
\ No newline at end of file
From ae51a4e50b5c862b2262cf7bb4ab045742d98c53 Mon Sep 17 00:00:00 2001
From: Krachitkumar
Date: Sat, 16 Dec 2023 13:21:35 +0530
Subject: [PATCH 08/17] resolved cv_bridge error
---
image_preprocessing/CMakeLists.txt | 5 ++++-
image_preprocessing/src/test_node.cpp | 22 ++++++++++++++++++----
2 files changed, 22 insertions(+), 5 deletions(-)
diff --git a/image_preprocessing/CMakeLists.txt b/image_preprocessing/CMakeLists.txt
index fe95cae..f8170f6 100644
--- a/image_preprocessing/CMakeLists.txt
+++ b/image_preprocessing/CMakeLists.txt
@@ -5,7 +5,8 @@ project(image_preprocessing)
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(sensor_msgs REQUIRED)
-
+find_package(cv_bridge REQUIRED)
+find_package(OpenCV REQUIRED)
# Add executable
add_executable(test_node src/test_node.cpp)
@@ -19,6 +20,8 @@ target_include_directories(test_node
ament_target_dependencies(test_node
rclcpp
sensor_msgs
+ cv_bridge
+ OpenCV
)
# Install the executable
diff --git a/image_preprocessing/src/test_node.cpp b/image_preprocessing/src/test_node.cpp
index dfc7566..3fbe9d3 100644
--- a/image_preprocessing/src/test_node.cpp
+++ b/image_preprocessing/src/test_node.cpp
@@ -1,5 +1,8 @@
#include "rclcpp/rclcpp.hpp"
#include "sensor_msgs/msg/image.hpp"
+#include "cv_bridge/cv_bridge.h"
+#include "sensor_msgs/image_encodings.hpp"
+#include
class ImagePublisherNode : public rclcpp::Node {
public:
@@ -9,7 +12,6 @@ class ImagePublisherNode : public rclcpp::Node {
"/color_camera/image_raw", 10, [this](const sensor_msgs::msg::Image::SharedPtr msg) {
// Callback function for the subscription
publishImage(msg);
- // std::cout<<"Publishing Image"<(*msg);
- imagepublisher->publish(std::move(loaned_msg));
-
+ // Convert sensor_msgs::Image to cv::Mat using cv_bridge
+ cv_bridge::CvImageConstPtr cv_ptr;
+ try {
+ cv_ptr = cv_bridge::toCvCopy(msg, sensor_msgs::image_encodings::BGR8);
+ } catch (cv_bridge::Exception& e) {
+ RCLCPP_ERROR(this->get_logger(), "cv_bridge exception: %s", e.what());
+ return;
+ }
+
+ // Add text to the image
+ cv::putText(cv_ptr->image, "Img Processed", cv::Point(10, 50), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(255, 255, 255), 2);
+
+ // Publish the modified image
+ auto modified_msg = cv_bridge::CvImage(msg->header, "bgr8", cv_ptr->image).toImageMsg();
+ imagepublisher->publish(modified_msg);
}
rclcpp::Subscription::SharedPtr imagesubscription;
From a4cbce78875b819dc5b648fda0fda9f5d34f8348 Mon Sep 17 00:00:00 2001
From: topguns837
Date: Sat, 16 Dec 2023 13:57:57 +0530
Subject: [PATCH 09/17] Resolved OpenCV issue
---
image_preprocessing/CMakeLists.txt | 6 +++-
image_preprocessing/src/test_node.cpp | 42 ++++++++++++++-------------
2 files changed, 27 insertions(+), 21 deletions(-)
diff --git a/image_preprocessing/CMakeLists.txt b/image_preprocessing/CMakeLists.txt
index f8170f6..fa3edfa 100644
--- a/image_preprocessing/CMakeLists.txt
+++ b/image_preprocessing/CMakeLists.txt
@@ -1,12 +1,15 @@
cmake_minimum_required(VERSION 3.5)
-#project(image_publisher)
+
project(image_preprocessing)
+
# Find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(sensor_msgs REQUIRED)
find_package(cv_bridge REQUIRED)
+find_package(image_transport REQUIRED)
find_package(OpenCV REQUIRED)
+
# Add executable
add_executable(test_node src/test_node.cpp)
@@ -21,6 +24,7 @@ ament_target_dependencies(test_node
rclcpp
sensor_msgs
cv_bridge
+ image_transport
OpenCV
)
diff --git a/image_preprocessing/src/test_node.cpp b/image_preprocessing/src/test_node.cpp
index 3fbe9d3..68ea62d 100644
--- a/image_preprocessing/src/test_node.cpp
+++ b/image_preprocessing/src/test_node.cpp
@@ -1,51 +1,53 @@
#include "rclcpp/rclcpp.hpp"
#include "sensor_msgs/msg/image.hpp"
#include "cv_bridge/cv_bridge.h"
-#include "sensor_msgs/image_encodings.hpp"
-#include
+#include "opencv2/opencv.hpp"
class ImagePublisherNode : public rclcpp::Node {
public:
ImagePublisherNode() : Node("image_publisher_node") {
- // Create a subscription to the "/color_camera/image_raw" topic
+
imagesubscription = create_subscription(
"/color_camera/image_raw", 10, [this](const sensor_msgs::msg::Image::SharedPtr msg) {
- // Callback function for the subscription
- publishImage(msg);
+ imageCallback(msg);
});
- // Create a publisher for the "img_pub" topic
imagepublisher = create_publisher("img_pub", 10);
- // Set the publishing rate to 10 Hz
publishtimer = create_wall_timer(std::chrono::milliseconds(100), [this]() {
- // Timer callback for publishing at 10 Hz
- // You can perform any additional processing here if needed
});
}
private:
- void publishImage(const sensor_msgs::msg::Image::SharedPtr msg) {
- // Convert sensor_msgs::Image to cv::Mat using cv_bridge
- cv_bridge::CvImageConstPtr cv_ptr;
+ void imageCallback(const sensor_msgs::msg::Image::SharedPtr msg) {
+
+ cv_bridge::CvImagePtr cv_ptr;
+
try {
cv_ptr = cv_bridge::toCvCopy(msg, sensor_msgs::image_encodings::BGR8);
- } catch (cv_bridge::Exception& e) {
- RCLCPP_ERROR(this->get_logger(), "cv_bridge exception: %s", e.what());
+ }
+ catch(cv_bridge::Exception& e) {
+ RCLCPP_ERROR(get_logger(), "cv_bridge exception: %s", e.what());
return;
}
- // Add text to the image
- cv::putText(cv_ptr->image, "Img Processed", cv::Point(10, 50), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(255, 255, 255), 2);
+ imageTranspose(cv_ptr->image);
+ }
+
+ void imageTranspose(cv::Mat& image) {
+ cv::transpose(image, image);
+ publishImage(image);
+ }
- // Publish the modified image
- auto modified_msg = cv_bridge::CvImage(msg->header, "bgr8", cv_ptr->image).toImageMsg();
- imagepublisher->publish(modified_msg);
+ void publishImage(cv::Mat& image) {
+ output_msg = cv_bridge::CvImage(std_msgs::msg::Header(), "bgr8", image).toImageMsg();
+ imagepublisher->publish(*output_msg.get());
}
rclcpp::Subscription::SharedPtr imagesubscription;
rclcpp::Publisher::SharedPtr imagepublisher;
rclcpp::TimerBase::SharedPtr publishtimer;
+ sensor_msgs::msg::Image::SharedPtr output_msg;
};
int main(int argc, char** argv) {
@@ -53,4 +55,4 @@ int main(int argc, char** argv) {
rclcpp::spin(std::make_shared());
rclcpp::shutdown();
return 0;
-}
\ No newline at end of file
+}
From deb97ebede7d68d4c9d696362dc1ebd92d13d80f Mon Sep 17 00:00:00 2001
From: deepansh
Date: Sun, 17 Dec 2023 19:10:44 +0530
Subject: [PATCH 10/17] fixed YOLOv8.py to use teh DetectorBase class
---
.../object_detection/Detectors/YOLOv8.py | 17 +++++------------
1 file changed, 5 insertions(+), 12 deletions(-)
diff --git a/object_detection/object_detection/Detectors/YOLOv8.py b/object_detection/object_detection/Detectors/YOLOv8.py
index f76c201..8f59e9c 100755
--- a/object_detection/object_detection/Detectors/YOLOv8.py
+++ b/object_detection/object_detection/Detectors/YOLOv8.py
@@ -3,7 +3,10 @@
import os
import time
-class YOLOv8:
+from ..DetectorBase import DetectorBase
+
+
+class YOLOv8(DetectorBase):
def __init__(self, model_dir_path, weight_file_name, conf_threshold = 0.7,
score_threshold = 0.4, nms_threshold = 0.25,
show_fps = 1, is_cuda = 0):
@@ -48,16 +51,6 @@ def load_classes(self):
return self.class_list
- # create list of dictionary containing predictions
- def create_predictions_list(self, class_ids, confidences, boxes):
-
- for i in range(len(class_ids)):
- obj_dict = {
- "class_id": class_ids[i],
- "confidence": confidences[i],
- "box": boxes[i]
- }
- self.predictions.append(obj_dict)
def get_predictions(self, cv_image):
@@ -81,7 +74,7 @@ def get_predictions(self, cv_image):
confidence.append(box.conf)
bb.append(box.xyxy)
- self.create_predictions_list(class_id,confidence,bb)
+ super().create_predictions_list(class_id, confidence, bb)
result = self.model.predict(self.frame, conf = self.conf_threshold)
output_frame = result[0].plot() # Frame with bounding boxes
From 6dbb0eebc9038667037d800765a303fbcb1ca61e Mon Sep 17 00:00:00 2001
From: deepansh
Date: Sun, 17 Dec 2023 19:41:05 +0530
Subject: [PATCH 11/17] changed tab size in YOLOv8.py
---
.../object_detection/Detectors/YOLOv8.py | 148 +++++++++---------
1 file changed, 75 insertions(+), 73 deletions(-)
diff --git a/object_detection/object_detection/Detectors/YOLOv8.py b/object_detection/object_detection/Detectors/YOLOv8.py
index 8f59e9c..79a6d72 100755
--- a/object_detection/object_detection/Detectors/YOLOv8.py
+++ b/object_detection/object_detection/Detectors/YOLOv8.py
@@ -7,90 +7,92 @@
class YOLOv8(DetectorBase):
- def __init__(self, model_dir_path, weight_file_name, conf_threshold = 0.7,
- score_threshold = 0.4, nms_threshold = 0.25,
- show_fps = 1, is_cuda = 0):
+ def __init__(self, model_dir_path, weight_file_name, conf_threshold = 0.7,
+ score_threshold = 0.4, nms_threshold = 0.25,
+ show_fps = 1, is_cuda = 0):
+
+ super().__init__()
+
+ self.model_dir_path = model_dir_path
+ self.weight_file_name = weight_file_name
+
+
+ self.conf_threshold = conf_threshold
+ self.show_fps = show_fps
+ self.is_cuda = is_cuda
+
+ #FPS
+ if self.show_fps :
+ self.frame_count = 0
+ self.total_frames = 0
+ self.fps = -1
+ self.start = time.time_ns()
+ self.frame = None
+
+
+ self.predictions = []
+ self.build_model()
+ self.load_classes()
+
+
+ def build_model(self) :
+
+ try :
+ model_path = os.path.join(self.model_dir_path, self.weight_file_name)
+ self.model = YOLO(model_path)
+
+ except :
+ raise Exception("Error loading given model from path: {}. Maybe the file doesn't exist?".format(model_path))
- self.model_dir_path = model_dir_path
- self.weight_file_name = weight_file_name
+ def load_classes(self):
-
- self.conf_threshold = conf_threshold
- self.show_fps = show_fps
- self.is_cuda = is_cuda
+ self.class_list = []
- #FPS
- if self.show_fps :
- self.frame_count = 0
- self.total_frames = 0
- self.fps = -1
- self.start = time.time_ns()
- self.frame = None
+ with open(self.model_dir_path + "/classes.txt", "r") as f:
+ self.class_list = [cname.strip() for cname in f.readlines()]
+ return self.class_list
- self.predictions = []
- self.build_model()
- self.load_classes()
+ def get_predictions(self, cv_image):
- def build_model(self) :
+ if cv_image is None:
+ # TODO: show warning message (different color, maybe)
+ return None,None
+
+ else :
+ self.frame = cv_image
+ self.frame_count += 1
+ self.total_frames += 1
- try :
- model_path = os.path.join(self.model_dir_path, self.weight_file_name)
- self.model = YOLO(model_path)
-
- except :
- raise Exception("Error loading given model from path: {}. Maybe the file doesn't exist?".format(model_path))
-
- def load_classes(self):
+ class_id = []
+ confidence = []
+ bb = []
+ result = self.model.predict(self.frame, conf = self.conf_threshold) # Perform object detection on image
+ row = result[0].boxes
- self.class_list = []
+ for box in row:
+ class_id.append(box.cls)
+ confidence.append(box.conf)
+ bb.append(box.xyxy)
- with open(self.model_dir_path + "/classes.txt", "r") as f:
- self.class_list = [cname.strip() for cname in f.readlines()]
+ super().create_predictions_list(class_id, confidence, bb)
+ result = self.model.predict(self.frame, conf = self.conf_threshold)
+ output_frame = result[0].plot() # Frame with bounding boxes
- return self.class_list
+ print("frame_count : ", self.frame_count)
- def get_predictions(self, cv_image):
+ if self.show_fps :
+ if self.frame_count >= 30:
+ self.end = time.time_ns()
+ self.fps = 1000000000 * self.frame_count / (self.end - self.start)
+ self.frame_count = 0
+ self.start = time.time_ns()
- if cv_image is None:
- # TODO: show warning message (different color, maybe)
- return None,None
-
- else :
- self.frame = cv_image
- self.frame_count += 1
- self.total_frames += 1
-
- class_id = []
- confidence = []
- bb = []
- result = self.model.predict(self.frame, conf = self.conf_threshold) # Perform object detection on image
- row = result[0].boxes
-
- for box in row:
- class_id.append(box.cls)
- confidence.append(box.conf)
- bb.append(box.xyxy)
-
- super().create_predictions_list(class_id, confidence, bb)
- result = self.model.predict(self.frame, conf = self.conf_threshold)
- output_frame = result[0].plot() # Frame with bounding boxes
-
- print("frame_count : ", self.frame_count)
-
-
- if self.show_fps :
- if self.frame_count >= 30:
- self.end = time.time_ns()
- self.fps = 1000000000 * self.frame_count / (self.end - self.start)
- self.frame_count = 0
- self.start = time.time_ns()
-
- if self.fps > 0:
- self.fps_label = "FPS: %.2f" % self.fps
- cv2.putText(output_frame, self.fps_label, (10, 25), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
-
- return self.predictions, output_frame
-
\ No newline at end of file
+ if self.fps > 0:
+ self.fps_label = "FPS: %.2f" % self.fps
+ cv2.putText(output_frame, self.fps_label, (10, 25), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
+
+ return self.predictions, output_frame
+
\ No newline at end of file
From ae65ab7021690bb9d8803a593b0738a62b8cf9fb Mon Sep 17 00:00:00 2001
From: deepansh
Date: Thu, 1 Feb 2024 22:43:44 +0530
Subject: [PATCH 12/17] fixed overload resolution issue
---
object_detection/object_detection/ObjectDetection.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/object_detection/object_detection/ObjectDetection.py b/object_detection/object_detection/ObjectDetection.py
index 3bbf7ca..12d20a2 100644
--- a/object_detection/object_detection/ObjectDetection.py
+++ b/object_detection/object_detection/ObjectDetection.py
@@ -96,7 +96,7 @@ def detection_cb(self, img_msg):
print("Image input from topic : {} is empty".format(self.input_img_topic))
else :
for prediction in predictions:
- left, top, width, height = prediction['box']
+ left, top, width, height = map(int, prediction['box'][0])
right = left + width
bottom = top + height
From efb16f72432fc4db908daf98b1954108122f8829 Mon Sep 17 00:00:00 2001
From: Arjun K Haridas <51917087+topguns837@users.noreply.github.com>
Date: Mon, 5 Feb 2024 01:06:39 +0530
Subject: [PATCH 13/17] Dockerized object_detection package (#32)
* initialized docker file
* git clone in dockerfile
* git checkout to topguns/dockerfile added in dockerfile
* replaced COPY with cp command
* replaced ssh with https link of the repo
* seperate RUN for each bash command
* added / after mkdir commands
* removed trailing && after RUN commands
* correction in directory structure
* cloning directly into topguns/dockerfile branch
* removed copying docker scripts to container command
* trying to install Python requirements using requirements.txt
* changed matplotlib version to 3.7.2 in requirements.txt
* conflicts in requirements.txt
* changed numpy version in requirements.txt
* enter_bash.sh working
* testing base.sh and start.sh
* added cv-bridge dependency
* working on exposing ros2 topics inside the container to host machine
* tested docker container, clip and ship
* changed model dir path for docker container
* cleaned Dockerfile
* added requirement.txt, docker instructions
* Initialized new Dockerfile with nvidia/cuda as base image
* Changes in Readme
* Minor changes to detector scripts
* Docker container working
* Added code to install nano in Dockerfile
* Added arg in dockerfile to specify CUDA Version and added docker stop in run_devel.sh
* Added feature to enter bash if container is running and remove the container after exit in run_devel. Changed tensorflow version to 2.14.0
* Added feature in run_devel to build docker image if it doesnt exist
* Updated readme
* Updated readme
* correction in run_devel.sh
* Attempting to build OpenCV from source
* Added option to select base image in run_devel.sh
* Env Variable for percep_ws
* Updated readme
* README.md changes
* additions to README.md (#50)
* fixed superimposed predictions on consecutive frames
* Resolved the error : can't convert cuda:0 device type tensor to numpy. Use Tensor.cpu() to copy the tensor to host memory
* Added output class labels and confidences to output image, fixed issue with wrong sizes of bounding boxes
* Removed code for building opencv from source from the dockerfiles
* Code cleanup and fixed minor bugs
* Code cleanup and fixed minor bugs
* Changed model weight location
---------
Co-authored-by: AaksharGarg
Co-authored-by: deepansh
Co-authored-by: Deepansh Goel <56270096+sudo-deep@users.noreply.github.com>
Co-authored-by: topguns837
Co-authored-by: Jasmeet Singh
---
.gitignore | 87 +++++++-
Dockerfile.cuda | 80 +++++++
Dockerfile.ubuntu | 77 +++++++
README.md | 109 ++++++----
docker_scripts/ddsconfig.xml | 8 +
docker_scripts/run_devel.sh | 72 +++++++
image_preprocessing/CMakeLists.txt | 43 ----
image_preprocessing/package.xml | 18 --
image_preprocessing/src/test_node.cpp | 58 ------
object_detection/config/params.yaml | 10 +-
.../launch/object_detection.launch.py | 8 +-
.../object_detection/DetectorBase.py | 1 +
.../Detectors/EfficientDet.py | 196 ------------------
.../object_detection/Detectors/RetinaNet.py | 1 -
.../object_detection/Detectors/YOLOv5.py | 7 +-
.../object_detection/Detectors/YOLOv8.py | 123 ++++-------
.../object_detection/ObjectDetection.py | 23 +-
object_detection/package.xml | 2 +-
object_detection/requirements.txt | 10 +-
19 files changed, 455 insertions(+), 478 deletions(-)
create mode 100644 Dockerfile.cuda
create mode 100644 Dockerfile.ubuntu
create mode 100644 docker_scripts/ddsconfig.xml
create mode 100755 docker_scripts/run_devel.sh
delete mode 100644 image_preprocessing/CMakeLists.txt
delete mode 100644 image_preprocessing/package.xml
delete mode 100644 image_preprocessing/src/test_node.cpp
delete mode 100644 object_detection/object_detection/Detectors/EfficientDet.py
diff --git a/.gitignore b/.gitignore
index 684bac1..9f2ae87 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,82 @@
-#Ignore the test node
-/object_detection/object_detection/test_node.py
+# Custom settings asset
+*.settings.asset*
-#Ignore pycache dirs
-object_detection/object_detection/Detectors/__pycache__/
-object_detection/object_detection/__pycache__/
+# Visual Studio 2015 cache directory
+/Project/.vs/
-#Ignore .vscode dir
-.vscode
\ No newline at end of file
+/[Ll]ibrary/
+/[Tt]emp/
+/[Oo]bj/
+/[Bb]uild/
+/[Bb]uilds/
+/[Ll]ogs/
+/[Mm]emoryCaptures/
+
+# Autogenerated VS/MD/Consulo solution and project files
+*.csproj
+*.unityproj
+*.sln
+*.suo
+*.tmp
+*.user
+*.userprefs
+*.pidb
+*.booproj
+*.svd
+*.pdb
+
+# Unity3D generated meta files
+*.pidb.meta
+
+# Builds
+*.apk
+*.unitypackage
+*.app
+*.exe
+*.x86_64
+*.x86
+
+# Generated doc folders
+/docs/html
+
+# Mac hidden files
+*.DS_Store
+*/.ipynb_checkpoints
+*/.idea
+*.pyc
+*.idea/misc.xml
+*.idea/modules.xml
+*.idea/
+*.iml
+*.cache
+*/build/
+*/dist/
+*.egg-info*
+*.eggs*
+*.gitignore.swp
+
+# VSCode hidden files
+.vscode/
+
+.DS_Store
+.ipynb_checkpoints
+
+# pytest cache
+*.pytest_cache/
+
+# Ignore PyPi build files.
+dist/
+build/
+
+# Python virtual environment
+venv/
+.mypy_cache/
+
+# Code coverage report
+.coverage
+coverage.xml
+/htmlcov/
+
+**/UserSettings/*
+
+ROSConnectionPrefab.prefab
\ No newline at end of file
diff --git a/Dockerfile.cuda b/Dockerfile.cuda
new file mode 100644
index 0000000..f784bd2
--- /dev/null
+++ b/Dockerfile.cuda
@@ -0,0 +1,80 @@
+# Use cuda_version arg to take CUDA version as input from user
+ARG cuda_version=11.8.0
+
+# Use NVIDA-CUDA's base image
+FROM nvcr.io/nvidia/cuda:${cuda_version}-devel-ubuntu22.04
+
+# Prevent console from interacting with the user
+ARG DEBIAN_FRONTEND=noninteractive
+
+# Prevent hash mismatch error for apt-get update, qqq makes the terminal quiet while downloading pkgs
+RUN apt-get clean && rm -rf /var/lib/apt/lists/* && apt-get update -yqqq
+
+# Set folder for RUNTIME_DIR. Only to prevent warnings when running RViz2 and Gz
+RUN mkdir tmp/runtime-root && chmod 0700 tmp/runtime-root
+ENV XDG_RUNTIME_DIR='/tmp/runtime-root'
+
+RUN apt-get update
+
+RUN apt-get install --no-install-recommends -yqqq \
+ apt-utils \
+ nano \
+ git
+
+# Using shell to use bash commands like 'source'
+SHELL ["/bin/bash", "-c"]
+
+# Python Dependencies
+RUN apt-get install --no-install-recommends -yqqq \
+ python3-pip
+
+# Add locale
+RUN locale && \
+ apt update && apt install --no-install-recommends -yqqq locales && \
+ locale-gen en_US en_US.UTF-8 && \
+ update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 && \
+ export LANG=en_US.UTF-8 && \
+ locale
+
+# Setup the sources
+RUN apt-get update && apt-get install --no-install-recommends -yqqq software-properties-common curl && \
+ add-apt-repository universe && \
+ curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg && \
+ echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(. /etc/os-release && echo $UBUNTU_CODENAME) main" | tee /etc/apt/sources.list.d/ros2.list > /dev/nul
+
+# Install ROS 2 Humble
+RUN apt update && apt install --no-install-recommends -yqqq \
+ ros-humble-ros-base \
+ ros-dev-tools
+
+# Install cv-bridge
+RUN apt update && apt install --no-install-recommends -yqqq \
+ ros-humble-cv-bridge
+
+# Target workspace for ROS2 packages
+ARG WORKSPACE=/root/percep_ws
+
+# Add target workspace in environment
+ENV WORKSPACE=$WORKSPACE
+
+# Creating the models folder
+RUN mkdir -p $WORKSPACE/models && \
+ mkdir -p $WORKSPACE/src
+
+# Installing Python dependencies
+COPY object_detection/requirements.txt .
+RUN pip3 install -r requirements.txt
+
+# ROS Dependencies
+RUN apt update && apt install --no-install-recommends -yqqq \
+ ros-humble-cyclonedds \
+ ros-humble-rmw-cyclonedds-cpp
+
+# Use cyclone DDS by default
+ENV RMW_IMPLEMENTATION rmw_cyclonedds_cpp
+
+WORKDIR /root/percep_ws
+
+# Update .bashrc
+RUN echo "source /opt/ros/humble/setup.bash" >> /root/.bashrc
+
diff --git a/Dockerfile.ubuntu b/Dockerfile.ubuntu
new file mode 100644
index 0000000..2a05f69
--- /dev/null
+++ b/Dockerfile.ubuntu
@@ -0,0 +1,77 @@
+# Use Ubuntu's base image
+FROM ubuntu:22.04
+
+# Prevent console from interacting with the user
+ARG DEBIAN_FRONTEND=noninteractive
+
+# Prevent hash mismatch error for apt-get update, qqq makes the terminal quiet while downloading pkgs
+RUN apt-get clean && rm -rf /var/lib/apt/lists/* && apt-get update -yqqq
+
+# Set folder for RUNTIME_DIR. Only to prevent warnings when running RViz2 and Gz
+RUN mkdir tmp/runtime-root && chmod 0700 tmp/runtime-root
+ENV XDG_RUNTIME_DIR='/tmp/runtime-root'
+
+RUN apt-get update
+
+RUN apt-get install --no-install-recommends -yqqq \
+ apt-utils \
+ nano \
+ git
+
+# Using shell to use bash commands like 'source'
+SHELL ["/bin/bash", "-c"]
+
+# Python Dependencies
+RUN apt-get install --no-install-recommends -yqqq \
+ python3-pip
+
+# Add locale
+RUN locale && \
+ apt update && apt install --no-install-recommends -yqqq locales && \
+ locale-gen en_US en_US.UTF-8 && \
+ update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 && \
+ export LANG=en_US.UTF-8 && \
+ locale
+
+# Setup the sources
+RUN apt-get update && apt-get install --no-install-recommends -yqqq software-properties-common curl && \
+ add-apt-repository universe && \
+ curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg && \
+ echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(. /etc/os-release && echo $UBUNTU_CODENAME) main" | tee /etc/apt/sources.list.d/ros2.list > /dev/nul
+
+# Install ROS 2 Humble
+RUN apt update && apt install --no-install-recommends -yqqq \
+ ros-humble-ros-base \
+ ros-dev-tools
+
+# Install cv-bridge
+RUN apt update && apt install --no-install-recommends -yqqq \
+ ros-humble-cv-bridge
+
+# Target workspace for ROS2 packages
+ARG WORKSPACE=/root/percep_ws
+
+# Add target workspace in environment
+ENV WORKSPACE=$WORKSPACE
+
+# Creating the models folder
+RUN mkdir -p $WORKSPACE/models && \
+ mkdir -p $WORKSPACE/src
+
+# Installing Python dependencies
+COPY object_detection/requirements.txt .
+RUN pip3 install -r requirements.txt
+
+# ROS Dependencies
+RUN apt update && apt install --no-install-recommends -yqqq \
+ ros-humble-cyclonedds \
+ ros-humble-rmw-cyclonedds-cpp
+
+# Use cyclone DDS by default
+ENV RMW_IMPLEMENTATION rmw_cyclonedds_cpp
+
+WORKDIR /root/percep_ws
+
+# Update .bashrc
+RUN echo "source /opt/ros/humble/setup.bash" >> /root/.bashrc
+
diff --git a/README.md b/README.md
index 3f1fea6..448d24a 100644
--- a/README.md
+++ b/README.md
@@ -104,6 +104,19 @@ These components can be stitched together to make a custom pipeline for any use-
Follow these steps to setup this project on your systm
### Prerequisites
+Install the binary Gazebo Garden/ROS 2 Humble packages:
+
+* Follow [these](https://gazebosim.org/docs/garden/install_ubuntu#binary-installation-on-ubuntu) instructions to install gz-garden from packages.osrfoundation.org repository.
+* Install ros_gz
+ * From the non official binary packages from apt:
+
+ * ```apt-get install ros-humble-ros-gzgarden```
+ * Build from source:
+ * Refer to the [official ros_gz repository](https://github.com/gazebosim/ros_gz/tree/humble#from-source)
+
+Install docker and add it to user group:
+
+* Refer to this [link](https://cloudyuga.guru/hands_on_lab/docker-as-non-root-user)
Follow these steps to install ROS Humble and OpenCV
* ROS Humble
@@ -116,45 +129,54 @@ Refer to the official [ROS 2 installation guide](https://docs.ros.org/en/humble/
### Installation
-1. Make a new workspace
+1. **Run using Docker**
+
```bash
- mkdir -p percep_ws/src
+ cd docker_scripts
+ export PERCEP_WS_PATH=
+ ./run_devel.sh
```
-2. Clone the ROS-Perception-Pipeline repository
-
- Now go ahead and clone this repository inside the "src" folder of the workspace you just created.
-
- ```bash
- cd percep_ws && git clone git@github.com:atom-robotics-lab/ros-perception-pipeline.git src/
- ```
-3. Install dependencies using rosdep
-
- Update Your rosdep before installation.
-
- ```bash
- rosdep update
- ```
+2. **Run natively**
+
+ 1. Make a new workspace
+ ```bash
+ mkdir -p percep_ws/src
+ ```
+
+ 2. Clone the ROS-Perception-Pipeline repository
+
+ Now go ahead and clone this repository inside the "src" folder of the workspace you just created.
+
+ ```bash
+ cd percep_ws/src && git clone git@github.com:atom-robotics-lab/ros-perception-pipeline.git
+ ```
+ 3. Install dependencies using rosdep
+
+ Update Your rosdep before installation.
+
+ ```bash
+ rosdep update
+ ```
+
+ This command installs all the packages that the packages in your catkin workspace depend upon but are missing on your computer.
+ ```bash
+ rosdep install --from-paths src --ignore-src -r -y
+ ```
+
+ 4. Compile the package
+
+ Follow this execution to compile your ROS 2 package
- This command installs all the packages that the packages in your catkin workspace depend upon but are missing on your computer.
- ```bash
- rosdep install --from-paths src --ignore-src -r -y
- ```
-
-4. Compile the package
-
- Follow this execution to compile your ROS 2 package
-
- ```bash
- colcon build --symlink-install
- ```
-
-5. Source your workspace
-
- ```bash
- source install/local_setup.bash
- ```
-
+ ```bash
+ colcon build --symlink-install
+ ```
+
+ 5. Source your workspace
+
+ ```bash
+ source install/local_setup.bash
+ ```
@@ -178,7 +200,7 @@ Don't forget to click on the **play** button on the bottom left corner of the Ig
-### 2. Launch the Object Detection node
+### 2.1 Launch the Object Detection node natively
Use the pip install command as shown below to install the required packages.
@@ -190,7 +212,7 @@ Use the command given below to run the ObjectDetection node. Remember to change
file according to your present working directory
```bash
-ros2 run object_detection ObjectDetection --ros-args --params-file src/ros-perception-pipeline/object_detection/config/object_detection.yaml
+ros2 launch object_detection object_detection.launch.py
```
**Note :** If your imports are not working while using a virtual environment, you'll need to manually set your `PYTHONPATH` environment variable.
@@ -208,6 +230,16 @@ Follow these steps to do this :
export PYTHONPATH = {insert_your_python_path_here}
```
+### 2.2 Launch the Object Detection node using Docker
+
+
+We can use the Docker image built previously to run the `object_detection` package
+
+ ```bash
+ colcon build --symlink-install
+ source install.setup.bash
+ ros2 launch object_detection object_detection.launch.py
+ ```
### 3. Changing the Detector
@@ -228,8 +260,7 @@ ros2 run rqt_image_view rqt_image_view
-(back to top)
-
+(back to top)
+
+
diff --git a/docker_scripts/run_devel.sh b/docker_scripts/run_devel.sh
new file mode 100755
index 0000000..95b35a8
--- /dev/null
+++ b/docker_scripts/run_devel.sh
@@ -0,0 +1,72 @@
+#!/bin/bash
+
+xhost +local:root
+
+IMAGE_NAME="object_detection"
+IMAGE_TAG="latest"
+CONTAINER_NAME="object_detection"
+
+# Build the image if it doesn't exist
+if docker inspect "$IMAGE_NAME:$IMAGE_TAG" &> /dev/null; then
+ echo "The image $IMAGE_NAME:$IMAGE_TAG exists."
+
+else
+ echo "The image $IMAGE_NAME:$IMAGE_TAG does not exist. Building the image...."
+
+ echo "Choose the base image:"
+ echo "1. NVIDIA CUDA image"
+ echo "2. Ubuntu 22.04 image"
+ read -p "Enter your choice (1 or 2): " base_image_choice
+
+ # If the user input is blank or not 1 or 2, default to NVIDIA CUDA image
+ if [ -z "$base_image_choice" ] || [ "$base_image_choice" != "1" ] && [ "$base_image_choice" != "2" ]; then
+ base_image_choice="1"
+ fi
+
+ # Choose the appropriate Dockerfile based on user input
+ if [ "$base_image_choice" == "1" ]; then
+ DOCKERFILE="Dockerfile.cuda"
+
+ echo "Enter your preferred CUDA Version (default set to 11.8.0) : "
+ read cuda_version
+
+ # If the user input is blank, use 11.8.0 as the cuda_version
+ if [ -z "$cuda_version" ]; then
+ cuda_version="11.8.0"
+ fi
+
+ cd ..
+ docker build --build-arg cuda_version="$cuda_version" -f "$DOCKERFILE" -t "$IMAGE_NAME:$IMAGE_TAG" .
+ echo "Completed building the docker image"
+ else
+ DOCKERFILE="Dockerfile.ubuntu"
+
+ cd ..
+ docker build -f "$DOCKERFILE" -t "$IMAGE_NAME:$IMAGE_TAG" .
+ fi
+fi
+
+# Enter into the container if it is already running
+if [ "$(docker ps -a --quiet --filter status=running --filter name=$CONTAINER_NAME)" ]; then
+ echo -e "\nAttaching to running container: $CONTAINER_NAME"
+ docker exec -it $CONTAINER_NAME /bin/bash $@
+ exit 0
+fi
+
+# Check if the PERCEP_WS_PATH environment variable is empty
+if [ -z "$PERCEP_WS_PATH" ]; then
+ echo -e "\nThe environment variable : PERCEP_WS_PATH is empty. Point it to the path of the ROS 2 workspace in which the ros-perception-pipeline project is kept !!"
+ exit 1
+fi
+
+# Run the docker container
+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 ddsconfig.xml:/ddsconfig.xml \
+--env CYCLONEDDS_URI=/ddsconfig.xml \
+--env="QT_X11_NO_MITSHM=1" \
+--env="DISPLAY" \
+object_detection:latest
diff --git a/image_preprocessing/CMakeLists.txt b/image_preprocessing/CMakeLists.txt
deleted file mode 100644
index fa3edfa..0000000
--- a/image_preprocessing/CMakeLists.txt
+++ /dev/null
@@ -1,43 +0,0 @@
-cmake_minimum_required(VERSION 3.5)
-
-project(image_preprocessing)
-
-# Find dependencies
-find_package(ament_cmake REQUIRED)
-find_package(rclcpp REQUIRED)
-find_package(sensor_msgs REQUIRED)
-find_package(cv_bridge REQUIRED)
-find_package(image_transport REQUIRED)
-find_package(OpenCV REQUIRED)
-
-# Add executable
-add_executable(test_node src/test_node.cpp)
-
-# Include directories for the executable
-target_include_directories(test_node
- PRIVATE
- ${sensor_msgs_INCLUDE_DIRS}
-)
-
-# Link the executable to the required libraries
-ament_target_dependencies(test_node
- rclcpp
- sensor_msgs
- cv_bridge
- image_transport
- OpenCV
-)
-
-# Install the executable
-install(TARGETS
- test_node
- DESTINATION lib/${PROJECT_NAME}
-)
-
-# Install launch files, config files, and other directories if necessary
-# install(DIRECTORY launch
-# DESTINATION share/${PROJECT_NAME}
-# )
-
-# Install the CMake package file
-ament_package()
diff --git a/image_preprocessing/package.xml b/image_preprocessing/package.xml
deleted file mode 100644
index 0ad68f3..0000000
--- a/image_preprocessing/package.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
- image_preprocessing
- 0.0.0
- TODO: Package description
- rachit
- TODO: License declaration
-
- ament_cmake
-
- ament_lint_auto
- ament_lint_common
-
-
- ament_cmake
-
-
diff --git a/image_preprocessing/src/test_node.cpp b/image_preprocessing/src/test_node.cpp
deleted file mode 100644
index 68ea62d..0000000
--- a/image_preprocessing/src/test_node.cpp
+++ /dev/null
@@ -1,58 +0,0 @@
-#include "rclcpp/rclcpp.hpp"
-#include "sensor_msgs/msg/image.hpp"
-#include "cv_bridge/cv_bridge.h"
-#include "opencv2/opencv.hpp"
-
-class ImagePublisherNode : public rclcpp::Node {
-public:
- ImagePublisherNode() : Node("image_publisher_node") {
-
- imagesubscription = create_subscription(
- "/color_camera/image_raw", 10, [this](const sensor_msgs::msg::Image::SharedPtr msg) {
- imageCallback(msg);
- });
-
- imagepublisher = create_publisher("img_pub", 10);
-
- publishtimer = create_wall_timer(std::chrono::milliseconds(100), [this]() {
- });
- }
-
-private:
- void imageCallback(const sensor_msgs::msg::Image::SharedPtr msg) {
-
- cv_bridge::CvImagePtr cv_ptr;
-
- try {
- cv_ptr = cv_bridge::toCvCopy(msg, sensor_msgs::image_encodings::BGR8);
- }
- catch(cv_bridge::Exception& e) {
- RCLCPP_ERROR(get_logger(), "cv_bridge exception: %s", e.what());
- return;
- }
-
- imageTranspose(cv_ptr->image);
- }
-
- void imageTranspose(cv::Mat& image) {
- cv::transpose(image, image);
- publishImage(image);
- }
-
- void publishImage(cv::Mat& image) {
- output_msg = cv_bridge::CvImage(std_msgs::msg::Header(), "bgr8", image).toImageMsg();
- imagepublisher->publish(*output_msg.get());
- }
-
- rclcpp::Subscription::SharedPtr imagesubscription;
- rclcpp::Publisher::SharedPtr imagepublisher;
- rclcpp::TimerBase::SharedPtr publishtimer;
- sensor_msgs::msg::Image::SharedPtr output_msg;
-};
-
-int main(int argc, char** argv) {
- rclcpp::init(argc, argv);
- rclcpp::spin(std::make_shared());
- rclcpp::shutdown();
- return 0;
-}
diff --git a/object_detection/config/params.yaml b/object_detection/config/params.yaml
index 94d7f1a..eb69547 100644
--- a/object_detection/config/params.yaml
+++ b/object_detection/config/params.yaml
@@ -1,12 +1,12 @@
object_detection:
ros__parameters:
- input_img_topic: color_camera/image_raw
+ input_img_topic: /kitti/camera/color/left/image_raw
output_bb_topic: object_detection/img_bb
output_img_topic: object_detection/img
publish_output_img: 1
model_params:
- detector_type: RetinaNet
- model_dir_path: models/
- weight_file_name: resnet50_coco_best_v2.1.0.h5
- confidence_threshold : 0.7
+ detector_type: YOLOv5
+ model_dir_path: /root/percep_ws/models/yolov5
+ weight_file_name: yolov5.onnx
+ confidence_threshold : 0.5
show_fps : 1
\ No newline at end of file
diff --git a/object_detection/launch/object_detection.launch.py b/object_detection/launch/object_detection.launch.py
index f1f1980..5d7c830 100644
--- a/object_detection/launch/object_detection.launch.py
+++ b/object_detection/launch/object_detection.launch.py
@@ -13,14 +13,9 @@
# limitations under the License.
import os
-import sys
from ament_index_python.packages import get_package_share_directory
-
from launch import LaunchDescription
-from launch.actions import IncludeLaunchDescription, DeclareLaunchArgument
-from launch.substitutions import LaunchConfiguration
-from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch_ros.actions import Node
@@ -38,7 +33,8 @@ def generate_launch_description():
name = 'object_detection',
executable = 'ObjectDetection',
parameters = [params],
- output="screen"
+ output="screen",
+ emulate_tty = True
)
diff --git a/object_detection/object_detection/DetectorBase.py b/object_detection/object_detection/DetectorBase.py
index 801b5cc..7ffe56d 100644
--- a/object_detection/object_detection/DetectorBase.py
+++ b/object_detection/object_detection/DetectorBase.py
@@ -8,6 +8,7 @@ def __init__(self) -> None:
self.predictions = []
def create_predictions_list(self, class_ids, confidences, boxes):
+ self.predictions = []
for i in range(len(class_ids)):
obj_dict = {
"class_id": class_ids[i],
diff --git a/object_detection/object_detection/Detectors/EfficientDet.py b/object_detection/object_detection/Detectors/EfficientDet.py
deleted file mode 100644
index e5a5f49..0000000
--- a/object_detection/object_detection/Detectors/EfficientDet.py
+++ /dev/null
@@ -1,196 +0,0 @@
-import tensorflow_hub as hub
-import cv2
-import numpy
-import pandas as pd
-import tensorflow as tf
-import matplotlib.pyplot as plt
-
-import tempfile
-
-
-# For drawing onto the image.
-import numpy as np
-from PIL import Image
-from PIL import ImageColor
-from PIL import ImageDraw
-from PIL import ImageFont
-from PIL import ImageOps
-
-# For measuring the inference time.
-import time
-
-class EfficientDet:
- def __init__(self, model_dir_path, weight_file_name, conf_threshold = 0.7, score_threshold = 0.25, nms_threshold = 0.4):
-
- self.frame_count = 0
- self.total_frames = 0
- self.fps = -1
- self.start = time.time_ns()
- self.frame = None
-
- self.model_dir_path = model_dir_path
- self.weight_file_name = weight_file_name
- self.conf=conf_threshold
-
- # Resizing image
- self.img_height=800
- self.img_width=800
- self.predictions=[]
-
- self.build_model()
- self.load_classes()
-
- def build_model(self) :
- module_handle="https://tfhub.dev/tensorflow/efficientdet/d0/1"
- # Loading model directly from TensorFlow Hub
- self.detector = hub.load(module_handle)
-
-
- def load_classes(self):
- self.labels = []
- with open(self.model_dir_path + "/classes.txt", "r") as f:
- self.labels = [cname.strip() for cname in f.readlines()]
- return self.labels
-
- def display_image(self,image):
- cv2.imshow("result", image)
- cv2.waitKey(0)
- cv2.destroyAllWindows()
-
- def draw_bounding_box_on_image(self,image,ymin,xmin,ymax,xmax,color,font,thickness=4,display_str_list=()):
- """Adds a bounding box to an image."""
- draw = ImageDraw.Draw(image)
- im_width, im_height = image.size
- (left, right, top, bottom) = (xmin * im_width, xmax * im_width,
- ymin * im_height, ymax * im_height)
- draw.line([(left, top), (left, bottom), (right, bottom), (right, top),
- (left, top)],
- width=thickness,
- fill=color)
- # If the total height of the display strings added to the top of the bounding
- # box exceeds the top of the image, stack the strings below the bounding box
- # instead of above.
- display_str_heights = [font.getsize(ds)[1] for ds in display_str_list]
- # Each display_str has a top and bottom margin of 0.05x.
- total_display_str_height = (1 + 2 * 0.05) * sum(display_str_heights)
-
- if top > total_display_str_height:
- text_bottom = top
- else:
- text_bottom = top + total_display_str_height
- # Reverse list and print from bottom to top.
- for display_str in display_str_list[::-1]:
- text_width, text_height = font.getsize(display_str)
- margin = np.ceil(0.05 * text_height)
- draw.rectangle([(left, text_bottom - text_height - 2 * margin),
- (left + text_width, text_bottom)],
- fill=color)
- draw.text((left + margin, text_bottom - text_height - margin),
- display_str,
- fill="black",
- font=font)
- text_bottom -= text_height - 2 * margin
-
- # create list of dictionary containing predictions
- def create_predictions_list(self, class_ids, confidences, boxes):
- for i in range(len(class_ids)):
- obj_dict = {
- "class_id": class_ids[i],
- "confidence": confidences[i],
- "box": boxes[i]
- }
- self.predictions.append(obj_dict)
-
- def draw_boxes(self,image,boxes,class_ids,confidences,max_boxes=10):
- """Overlay labeled boxes on an image with formatted scores and label names."""
- colors = list(ImageColor.colormap.values())
-
- try:
- font = ImageFont.truetype("/usr/share/fonts/truetype/liberation/LiberationSansNarrow-Regular.ttf",
- 25)
- except IOError:
- print("Font not found, using default font.")
- font = ImageFont.load_default()
-
- for i in range(min(boxes.shape[0], max_boxes)):
- if confidences[i] >= self.conf:
- ymin, xmin, ymax, xmax = tuple(boxes[i])
- display_str = "{}: {}%".format(self.labels[class_ids[i]], int(100 * confidences[i]))
- color = colors[hash(class_ids[i]) % len(colors)]
- image_pil = Image.fromarray(np.uint8(image)).convert("RGB")
- self.draw_bounding_box_on_image(image_pil,ymin,xmin,ymax,xmax,color,font,display_str_list=[display_str])
- np.copyto(image, np.array(image_pil))
- return image
-
- def load_img(self,path):
- img = tf.io.read_file(path)
- img = tf.image.decode_jpeg(img, channels=3)
- return img
-
- def get_predictions(self,cv_image):
-
- if cv_image is None:
- # TODO: show warning message (different color, maybe)
- return None,None
-
- else :
- #Convert img to RGB
- self.frame = cv_image
-
- self.frame_count += 1
- self.total_frames += 1
-
- rgb = cv2.cvtColor(self.frame, cv2.COLOR_BGR2RGB)
-
- # COnverting to uint8
- rgb_tensor = tf.convert_to_tensor(rgb, dtype=tf.uint8)
-
- #Add dims to rgb_tensor
- rgb_tensor = tf.expand_dims(rgb_tensor , 0)
-
-
- result = self.detector(rgb_tensor)
- result = {key:value.numpy() for key,value in result.items()}
-
- self.create_predictions_list(result["detection_boxes"][0],result["detection_classes"][0], result["detection_scores"][0])
- image_with_boxes = self.draw_boxes(cv_image,result["detection_boxes"][0],result["detection_classes"][0], result["detection_scores"][0])
-
- # fps
- if self.frame_count >= 30:
- self.end = time.time_ns()
- self.fps = 1000000000 * self.frame_count / (self.end - self.start)
- self.frame_count = 0
- self.start = time.time_ns()
-
- if self.fps > 0:
- self.fps_label = "FPS: %.2f" % self.fps
- cv2.putText(self.frame, self.fps_label, (10, 25), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
-
- return [self.predictions, image_with_boxes]
-
-
- def detect_img(self,image_url):
- start_time = time.time()
- self.run_detector(self.detector, image_url)#Convert img to RGB
- rgb = cv2.cvtColor(cv_image, cv2.COLOR_BGR2RGB)
- # COnverting to uint8
- rgb_tensor = tf.convert_to_tensor(rgb, dtype=tf.uint8)
- #Add dims to rgb_tensor
- rgb_tensor = tf.expand_dims(rgb_tensor , 0)
- start_time = time.time()
- result = self.detector(rgb_tensor)
- end_time = time.time()
- result = {key:value.numpy() for key,value in result.items()}
- print("Found %d objects." % len(result["detection_scores"]))
- print("Inference time: ", end_time-start_time)
- self.create_predictions_list(cv_image,result["detection_boxes"][0],result["detection_classes"][0], result["detection_scores"][0])
- image_with_boxes = self.draw_boxes(cv_image,result["detection_boxes"][0],result["detection_classes"][0], result["detection_scores"][0])
- self.display_image(self.predictions,image_with_boxes)
-
- end_time = time.time()
- print("Inference time:",end_time-start_time)
-
-if __name__=='__main__':
- # Load model
- det = EfficientDet()
- det.detect_img("/home/sanchay/yolo_catkin/src/yolov8_test/scripts/dog_cat.jpg")
\ No newline at end of file
diff --git a/object_detection/object_detection/Detectors/RetinaNet.py b/object_detection/object_detection/Detectors/RetinaNet.py
index eb04a4c..8c1ddb3 100755
--- a/object_detection/object_detection/Detectors/RetinaNet.py
+++ b/object_detection/object_detection/Detectors/RetinaNet.py
@@ -62,4 +62,3 @@ def get_predictions(self, cv_image) :
return self.predictions
-
diff --git a/object_detection/object_detection/Detectors/YOLOv5.py b/object_detection/object_detection/Detectors/YOLOv5.py
index 571f3d0..4363c33 100644
--- a/object_detection/object_detection/Detectors/YOLOv5.py
+++ b/object_detection/object_detection/Detectors/YOLOv5.py
@@ -1,4 +1,3 @@
-import time
import os
import cv2
import numpy as np
@@ -7,7 +6,7 @@
class YOLOv5(DetectorBase):
- def __init__(self, conf_threshold = 0.7, score_threshold = 0.4, nms_threshold = 0.25, is_cuda = 0):
+ def __init__(self, conf_threshold = 0.7, score_threshold = 0.4, nms_threshold = 0.25, is_cuda = 1):
super().__init__()
@@ -144,8 +143,6 @@ def get_predictions(self, cv_image):
outs = self.detect(inputImage)
class_ids, confidences, boxes = self.wrap_detection(inputImage, outs[0])
- super().create_predictions_list(class_ids, confidences, boxes)
-
- print("Detected ids: ", class_ids)
+ super().create_predictions_list(class_ids, confidences, boxes)
return self.predictions
\ No newline at end of file
diff --git a/object_detection/object_detection/Detectors/YOLOv8.py b/object_detection/object_detection/Detectors/YOLOv8.py
index 79a6d72..e1f7d59 100755
--- a/object_detection/object_detection/Detectors/YOLOv8.py
+++ b/object_detection/object_detection/Detectors/YOLOv8.py
@@ -1,98 +1,55 @@
-import cv2
from ultralytics import YOLO
import os
-import time
from ..DetectorBase import DetectorBase
class YOLOv8(DetectorBase):
- def __init__(self, model_dir_path, weight_file_name, conf_threshold = 0.7,
- score_threshold = 0.4, nms_threshold = 0.25,
- show_fps = 1, is_cuda = 0):
-
- super().__init__()
-
- self.model_dir_path = model_dir_path
- self.weight_file_name = weight_file_name
-
-
- self.conf_threshold = conf_threshold
- self.show_fps = show_fps
- self.is_cuda = is_cuda
-
- #FPS
- if self.show_fps :
- self.frame_count = 0
- self.total_frames = 0
- self.fps = -1
- self.start = time.time_ns()
- self.frame = None
-
-
- self.predictions = []
- self.build_model()
- self.load_classes()
-
-
- def build_model(self) :
-
- try :
- model_path = os.path.join(self.model_dir_path, self.weight_file_name)
- self.model = YOLO(model_path)
-
- except :
- raise Exception("Error loading given model from path: {}. Maybe the file doesn't exist?".format(model_path))
+ def __init__(self, conf_threshold = 0.7):
- def load_classes(self):
-
- self.class_list = []
-
- with open(self.model_dir_path + "/classes.txt", "r") as f:
- self.class_list = [cname.strip() for cname in f.readlines()]
-
- return self.class_list
-
-
- def get_predictions(self, cv_image):
-
- if cv_image is None:
- # TODO: show warning message (different color, maybe)
- return None,None
-
- else :
- self.frame = cv_image
- self.frame_count += 1
- self.total_frames += 1
-
- class_id = []
- confidence = []
- bb = []
- result = self.model.predict(self.frame, conf = self.conf_threshold) # Perform object detection on image
- row = result[0].boxes
+ super().__init__()
+
+ self.conf_threshold = conf_threshold
- for box in row:
- class_id.append(box.cls)
- confidence.append(box.conf)
- bb.append(box.xyxy)
+ def build_model(self, model_dir_path, weight_file_name) :
- super().create_predictions_list(class_id, confidence, bb)
- result = self.model.predict(self.frame, conf = self.conf_threshold)
- output_frame = result[0].plot() # Frame with bounding boxes
+ try :
+ model_path = os.path.join(model_dir_path, weight_file_name)
+ self.model = YOLO(model_path)
+
+ except :
+ raise Exception("Error loading given model from path: {}. Maybe the file doesn't exist?".format(model_path))
+
- print("frame_count : ", self.frame_count)
+ def load_classes(self, model_dir_path):
+ self.class_list = []
- if self.show_fps :
- if self.frame_count >= 30:
- self.end = time.time_ns()
- self.fps = 1000000000 * self.frame_count / (self.end - self.start)
- self.frame_count = 0
- self.start = time.time_ns()
+ with open(model_dir_path + "/classes.txt", "r") as f:
+ self.class_list = [cname.strip() for cname in f.readlines()]
- if self.fps > 0:
- self.fps_label = "FPS: %.2f" % self.fps
- cv2.putText(output_frame, self.fps_label, (10, 25), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
+ return self.class_list
- return self.predictions, output_frame
-
\ No newline at end of file
+ def get_predictions(self, cv_image):
+
+ if cv_image is None:
+ # TODO: show warning message (different color, maybe)
+ return None,None
+
+ else :
+ self.frame = cv_image
+ class_id = []
+ confidence = []
+ boxes = []
+
+ result = self.model.predict(self.frame, conf = self.conf_threshold, 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)
+
+ return self.predictions
\ No newline at end of file
diff --git a/object_detection/object_detection/ObjectDetection.py b/object_detection/object_detection/ObjectDetection.py
index 12d20a2..72dac99 100644
--- a/object_detection/object_detection/ObjectDetection.py
+++ b/object_detection/object_detection/ObjectDetection.py
@@ -80,7 +80,7 @@ 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()
-
+
self.detector.build_model(self.model_dir_path, self.weight_file_name)
self.detector.load_classes(self.model_dir_path)
@@ -91,21 +91,25 @@ 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 == None :
print("Image input from topic : {} is empty".format(self.input_img_topic))
else :
for prediction in predictions:
- left, top, width, height = map(int, prediction['box'][0])
- right = left + width
- bottom = top + height
+ x1, y1, x2, y2 = map(int, prediction['box'])
+
+ # Draw the bounding box
+ cv_image = cv2.rectangle(cv_image, (x1, y1), (x2, y2), (0,255,0),1)
- #Draw the bounding box
- cv_image = cv2.rectangle(cv_image,(left,top),(right, bottom),(0,255,0),1)
+ # Show names of classes on the output image
+ class_id = int(prediction['class_id'])
+ class_name = self.detector.class_list[class_id]
+ label = f"{class_name} : {prediction['confidence']:.2f}"
+
+ cv_image = cv2.putText(cv_image, label, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1)
output = self.bridge.cv2_to_imgmsg(cv_image, "bgr8")
self.img_pub.publish(output)
- print(predictions)
def main():
@@ -120,6 +124,3 @@ def main():
if __name__=="__main__" :
main()
-
-
-
diff --git a/object_detection/package.xml b/object_detection/package.xml
index 482516c..fc13de3 100644
--- a/object_detection/package.xml
+++ b/object_detection/package.xml
@@ -3,7 +3,7 @@
object_detection
0.0.0
- TODO: Package description
+ This is a ROS 2 package aimed at providing an interfacing between Deep Learning models and the ROS architecture using a plug-and-play modular arhitecture
singh
Apache 2.0
diff --git a/object_detection/requirements.txt b/object_detection/requirements.txt
index 71ac88e..06197c0 100644
--- a/object_detection/requirements.txt
+++ b/object_detection/requirements.txt
@@ -1,9 +1,9 @@
+tensorflow==2.14.0
+tensorflow-hub==0.13.0
keras-retinanet==1.0.0
-matplotlib==3.5.4
-numpy==1.25.0
+matplotlib==3.7.2
+numpy==1.26.1
opencv-python==4.7.0.72
pandas==2.0.3
pillow==9.5.0
-tensorflow==2.12.0
-tensorflow-hub==0.13.0
-ultralytics==8.0.124
\ No newline at end of file
+ultralytics==8.0.124
From 9eb121ab49979c4eab5bd2c1c03f59eda8804ca7 Mon Sep 17 00:00:00 2001
From: Jasmeet Singh
Date: Fri, 9 Feb 2024 01:36:52 +0530
Subject: [PATCH 14/17] Added Build and Test Workflows (#58)
* Added build and test workflow
* Corrected IMAGE_NAME varibles in run_devel.sh script
* Corrected pull_requests trigger name
* Removed -it option from docker run & added docker attach step
* Removed PERCEP_WS_PATH environment variable and used github.workspace instead
* Corrected container name in docker run
* Corrected volume mount path
* Removed not required arguments from docker run
* Removed docker attach & used docker exec for colcon build
* Added | character in run for multi-line command
* Added container_name environment variable
* Added jobs for lint and test
* Updated linting job, added matrix for linter
* Added package name field to lint job
* Removed pep8 and mypy linter from action workflows
* Added copyright notice, corrected according to lint
* Updated worflow to pass arguments for linter, made some corrections
* Removed arguments from workflow
* Updated flake8 test to use setup.cfg as the config
* Updated workflow to pass config argument to flake8 lint
* Fixed variable name typo
* Corrected variable used for config path
* Updated lin job to setup ros humble
* Added ros distribution field for lint steps
* Updated flake8 config from default ament_flake8 config
* Added project wide flake8 config file, some more lint corrections
---
.flake8 | 6 +
.github/workflows/build_and_test.yml | 65 +++++++++++
docker_scripts/run_devel.sh | 6 +-
.../launch/object_detection.launch.py | 19 ++-
.../object_detection/DetectorBase.py | 19 ++-
.../object_detection/Detectors/RetinaNet.py | 67 ++++++-----
.../object_detection/Detectors/YOLOv5.py | 62 ++++++----
.../object_detection/Detectors/YOLOv8.py | 109 ++++++++++--------
.../object_detection/ObjectDetection.py | 86 ++++++++------
object_detection/setup.cfg | 6 +
object_detection/setup.py | 5 +-
object_detection/test/test_flake8.py | 9 +-
.../launch/playground.launch.py | 92 +++++++--------
13 files changed, 346 insertions(+), 205 deletions(-)
create mode 100644 .flake8
create mode 100644 .github/workflows/build_and_test.yml
diff --git a/.flake8 b/.flake8
new file mode 100644
index 0000000..9b0d3a7
--- /dev/null
+++ b/.flake8
@@ -0,0 +1,6 @@
+[flake8]
+extend-ignore = B902,C816,D100,D101,D102,D103,D104,D105,D106,D107,D203,D212,D404,I202,Q000
+import-order-style = google
+max-line-length = 125
+show-source = true
+statistics = true
\ No newline at end of file
diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml
new file mode 100644
index 0000000..8bea564
--- /dev/null
+++ b/.github/workflows/build_and_test.yml
@@ -0,0 +1,65 @@
+name: Build and Test Workflows
+
+on:
+ pull_request:
+ branches:
+ - main
+ - develop
+ push:
+ branches:
+ - main
+
+jobs:
+ ament_lint:
+ name: Lint and Codecheck
+ runs-on: ubuntu-latest
+ steps:
+ - name: Code Checkout
+ uses: actions/checkout@v4
+ - name: Setup ROS
+ uses: ros-tooling/setup-ros@master
+ - name: Ament Copyright Lint
+ uses: ros-tooling/action-ros-lint@master
+ with:
+ distribution: humble
+ linter: copyright
+ package-name: "*"
+ - name: Ament Flake8 Lint
+ uses: ros-tooling/action-ros-lint@master
+ with:
+ distribution: humble
+ linter: flake8
+ package-name: "*"
+ arguments: '--config=${{ github.workspace }}/.flake8'
+ - name: Ament PEP257 Lint
+ uses: ros-tooling/action-ros-lint@master
+ with:
+ distribution: humble
+ linter: pep257
+ package-name: "*"
+ - name: Ament xmllint Lint
+ uses: ros-tooling/action-ros-lint@master
+ with:
+ distribution: humble
+ linter: xmllint
+ package-name: "*"
+
+ build_source:
+ name: Build Docker Image and ROS 2 Packages
+ runs-on: ubuntu-latest
+ env:
+ CONTAINER_NAME: perception_pipeline
+ PERCEP_WS: /root/percep_ws
+ steps:
+ - name: Code Checkout
+ uses: actions/checkout@v4
+ - name: Build Docker Image
+ run: docker build . --file Dockerfile.ubuntu -t ${{ github.repository }}:latest
+ - name: Run Docker Image
+ run: |
+ docker run -it -d --name $CONTAINER_NAME \
+ -v ${{ github.workspace }}:$PERCEP_WS/src \
+ ${{ github.repository }}:latest
+ - name: Build ROS 2 Packages in Container
+ run: docker exec $CONTAINER_NAME bash -c \
+ "source /opt/ros/humble/setup.bash && cd $PERCEP_WS && colcon build"
diff --git a/docker_scripts/run_devel.sh b/docker_scripts/run_devel.sh
index 95b35a8..a29ce24 100755
--- a/docker_scripts/run_devel.sh
+++ b/docker_scripts/run_devel.sh
@@ -2,9 +2,9 @@
xhost +local:root
-IMAGE_NAME="object_detection"
+IMAGE_NAME="perception_pipeline"
IMAGE_TAG="latest"
-CONTAINER_NAME="object_detection"
+CONTAINER_NAME="perception_pipeline"
# Build the image if it doesn't exist
if docker inspect "$IMAGE_NAME:$IMAGE_TAG" &> /dev/null; then
@@ -69,4 +69,4 @@ docker run --gpus all --shm-size=1g --ulimit memlock=-1 --ulimit stack=67108864
--env CYCLONEDDS_URI=/ddsconfig.xml \
--env="QT_X11_NO_MITSHM=1" \
--env="DISPLAY" \
-object_detection:latest
+$IMAGE_NAME:$IMAGE_TAG
diff --git a/object_detection/launch/object_detection.launch.py b/object_detection/launch/object_detection.launch.py
index 5d7c830..65804df 100644
--- a/object_detection/launch/object_detection.launch.py
+++ b/object_detection/launch/object_detection.launch.py
@@ -1,10 +1,10 @@
-# Copyright (c) 2018 Intel Corporation
+# Copyright (c) 2023 A.T.O.M ROBOTICS
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
-# http://www.apache.org/licenses/LICENSE-2.0
+# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
@@ -27,15 +27,14 @@ def generate_launch_description():
'config',
'params.yaml'
)
-
- node=Node(
- package = 'object_detection',
- name = 'object_detection',
- executable = 'ObjectDetection',
- parameters = [params],
+
+ node = Node(
+ package='object_detection',
+ name='object_detection',
+ executable='ObjectDetection',
+ parameters=[params],
output="screen",
- emulate_tty = True
+ emulate_tty=True
)
-
return LaunchDescription([node])
diff --git a/object_detection/object_detection/DetectorBase.py b/object_detection/object_detection/DetectorBase.py
index 7ffe56d..d0b11f5 100644
--- a/object_detection/object_detection/DetectorBase.py
+++ b/object_detection/object_detection/DetectorBase.py
@@ -1,4 +1,19 @@
+# Copyright (c) 2023 A.T.O.M ROBOTICS
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
from abc import ABC, abstractmethod
+
import numpy as np
@@ -17,7 +32,7 @@ def create_predictions_list(self, class_ids, confidences, boxes):
}
self.predictions.append(obj_dict)
-
+
@abstractmethod
def build_model(self, model_dir_path: str, weight_file_name: str) -> None:
pass
@@ -28,4 +43,4 @@ def load_classes(self, model_dir_path: str) -> None:
@abstractmethod
def get_predictions(self, cv_image: np.ndarray) -> list[dict]:
- pass
\ No newline at end of file
+ pass
diff --git a/object_detection/object_detection/Detectors/RetinaNet.py b/object_detection/object_detection/Detectors/RetinaNet.py
index 8c1ddb3..1005034 100755
--- a/object_detection/object_detection/Detectors/RetinaNet.py
+++ b/object_detection/object_detection/Detectors/RetinaNet.py
@@ -1,3 +1,17 @@
+# Copyright (c) 2023 A.T.O.M ROBOTICS
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
import os
from keras_retinanet import models
@@ -7,58 +21,55 @@
from ..DetectorBase import DetectorBase
-class RetinaNet(DetectorBase) :
- def __init(self) :
+class RetinaNet(DetectorBase):
+ def __init(self):
super.__init__()
- def build_model(self, model_dir_path, weight_file_name) :
+ def build_model(self, model_dir_path, weight_file_name):
model_path = os.path.join(model_dir_path, weight_file_name)
-
- try:
+
+ try:
self.model = models.load_model(model_path, backbone_name='resnet50')
- except:
- raise Exception("Error loading given model from path: {}. Maybe the file doesn't exist?".format(model_path))
+ except Exception as e:
+ print("Loading the 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) :
+ def load_classes(self, model_dir_path):
self.class_list = []
-
+
with open(model_dir_path + "/classes.txt", "r") as f:
self.class_list = [cname.strip() for cname in f.readlines()]
-
+
return self.class_list
- def get_predictions(self, cv_image) :
+ def get_predictions(self, cv_image):
if cv_image is None:
# TODO: show warning message (different color, maybe)
return None
-
- else :
-
+ else:
# copy to draw on
self.frame = cv_image.copy()
# preprocess image for network
- input = preprocess_image(self.frame)
- input, scale = resize_image(input)
-
+ processed_img = preprocess_image(self.frame)
+ processed_img, scale = resize_image(processed_img)
+
# process image
- boxes_all, confidences_all, class_ids_all = self.model.predict_on_batch(np.expand_dims(input, axis=0))
+ boxes_all, confidences_all, class_ids_all = self.model.predict_on_batch(np.expand_dims(processed_img, axis=0))
boxes, confidences, class_ids = [], [], []
-
- for index in range(len(confidences_all[0])) :
- if confidences_all[0][index]!=-1 :
+
+ for index in range(len(confidences_all[0])):
+ if confidences_all[0][index] != -1:
confidences.append(confidences_all[0][index])
boxes.append(boxes_all[0][index])
class_ids.append(class_ids_all[0][index])
-
-
+
# correct for image scale
- #boxes = [x/scale for x in boxes]
+ # boxes = [x/scale for x in boxes]
boxes = [[int(coord/scale) for coord in box] for box in boxes]
-
+
super().create_predictions_list(class_ids, confidences, boxes)
-
- return self.predictions
-
+ return self.predictions
diff --git a/object_detection/object_detection/Detectors/YOLOv5.py b/object_detection/object_detection/Detectors/YOLOv5.py
index 4363c33..3a7825d 100644
--- a/object_detection/object_detection/Detectors/YOLOv5.py
+++ b/object_detection/object_detection/Detectors/YOLOv5.py
@@ -1,4 +1,19 @@
+# Copyright (c) 2023 A.T.O.M ROBOTICS
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
import os
+
import cv2
import numpy as np
@@ -6,7 +21,9 @@
class YOLOv5(DetectorBase):
- def __init__(self, conf_threshold = 0.7, score_threshold = 0.4, nms_threshold = 0.25, is_cuda = 1):
+ def __init__(self, conf_threshold=0.7,
+ score_threshold=0.4, nms_threshold=0.25,
+ is_cuda=1):
super().__init__()
@@ -17,17 +34,19 @@ def __init__(self, conf_threshold = 0.7, score_threshold = 0.4, nms_threshold =
self.INPUT_HEIGHT = 640
self.CONFIDENCE_THRESHOLD = conf_threshold
- self.is_cuda = is_cuda
+ self.is_cuda = is_cuda
-
- # load model and prepare its backend to either run on GPU or CPU, see if it can be added in constructor
+ # load model and prepare its backend to either run on GPU or CPU,
+ # see if it can be added in constructor
def build_model(self, model_dir_path, weight_file_name):
model_path = os.path.join(model_dir_path, weight_file_name)
try:
self.net = cv2.dnn.readNet(model_path)
- except:
- raise Exception("Error loading given model from path: {}. Maybe the file doesn't exist?".format(model_path))
+ except Exception as e:
+ print("Loading the model failed with exception {}".format(e))
+ raise Exception("Error loading given model from path: {}.".format(model_path) +
+ "Maybe the file doesn't exist?")
if self.is_cuda:
print("is_cuda was set to True. Attempting to use CUDA")
@@ -38,24 +57,23 @@ def build_model(self, model_dir_path, weight_file_name):
self.net.setPreferableBackend(cv2.dnn.DNN_BACKEND_OPENCV)
self.net.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU)
-
# load classes.txt that contains mapping of model with labels
- # TODO: add try/except to raise exception that tells the use to check the name if it is classes.txt
+ # TODO: add try/except to raise exception that tells the use to
+ # check the name if it is classes.txt
def load_classes(self, model_dir_path):
self.class_list = []
with open(model_dir_path + "/classes.txt", "r") as f:
self.class_list = [cname.strip() for cname in f.readlines()]
return self.class_list
-
def detect(self, image):
- # convert image to 640x640
- blob = cv2.dnn.blobFromImage(image, 1/255.0, (self.INPUT_WIDTH, self.INPUT_HEIGHT), swapRB=True, crop=False)
+ # convert image to 640x640
+ blob = cv2.dnn.blobFromImage(image, 1/255.0, (self.INPUT_WIDTH, self.INPUT_HEIGHT),
+ swapRB=True, crop=False)
self.net.setInput(blob)
preds = self.net.forward()
return preds
-
# extract bounding box, class IDs and confidences of detected objects
# YOLOv5 returns a 3D tensor of dimension 25200*(5 + n_classes)
def wrap_detection(self, input_image, output_data):
@@ -68,7 +86,7 @@ def wrap_detection(self, input_image, output_data):
image_width, image_height, _ = input_image.shape
x_factor = image_width / self.INPUT_WIDTH
- y_factor = image_height / self.INPUT_HEIGHT
+ y_factor = image_height / self.INPUT_HEIGHT
# Iterate through all the 25200 vectors
for r in range(rows):
@@ -77,7 +95,7 @@ def wrap_detection(self, input_image, output_data):
# Continue only if Pc > conf_threshold
confidence = row[4]
if confidence >= self.CONFIDENCE_THRESHOLD:
-
+
# One-hot encoded vector representing class of object
classes_scores = row[5:]
@@ -95,7 +113,7 @@ def wrap_detection(self, input_image, output_data):
class_ids.append(class_id)
- x, y, w, h = row[0].item(), row[1].item(), row[2].item(), row[3].item()
+ x, y, w, h = row[0].item(), row[1].item(), row[2].item(), row[3].item()
left = int((x - 0.5 * w) * x_factor)
top = int((y - 0.5 * h) * y_factor)
width = int(w * x_factor)
@@ -104,7 +122,7 @@ def wrap_detection(self, input_image, output_data):
boxes.append(box)
# removing intersecting bounding boxes
- indexes = cv2.dnn.NMSBoxes(boxes, confidences, 0.25, 0.45)
+ indexes = cv2.dnn.NMSBoxes(boxes, confidences, 0.25, 0.45)
result_class_ids = []
result_confidences = []
@@ -117,7 +135,6 @@ def wrap_detection(self, input_image, output_data):
return result_class_ids, result_confidences, result_boxes
-
# makes image square with dimension max(h, w)
def format_yolov5(self):
row, col, _ = self.frame.shape
@@ -126,14 +143,13 @@ def format_yolov5(self):
result[0:row, 0:col] = self.frame
return result
-
def get_predictions(self, cv_image):
- #Clear list
+ # Clear list
self.predictions = []
if cv_image is None:
# TODO: show warning message (different color, maybe)
- return None,None
+ return None, None
else:
self.frame = cv_image
@@ -143,6 +159,6 @@ def get_predictions(self, cv_image):
outs = self.detect(inputImage)
class_ids, confidences, boxes = self.wrap_detection(inputImage, outs[0])
- super().create_predictions_list(class_ids, confidences, boxes)
-
- return self.predictions
\ No newline at end of file
+ super().create_predictions_list(class_ids, confidences, boxes)
+
+ return self.predictions
diff --git a/object_detection/object_detection/Detectors/YOLOv8.py b/object_detection/object_detection/Detectors/YOLOv8.py
index e1f7d59..9bb1918 100755
--- a/object_detection/object_detection/Detectors/YOLOv8.py
+++ b/object_detection/object_detection/Detectors/YOLOv8.py
@@ -1,55 +1,66 @@
-from ultralytics import YOLO
+# Copyright (c) 2023 A.T.O.M ROBOTICS
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
import os
+from ultralytics import YOLO
+
from ..DetectorBase import DetectorBase
class YOLOv8(DetectorBase):
- def __init__(self, conf_threshold = 0.7):
-
- super().__init__()
-
- self.conf_threshold = conf_threshold
-
- 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)
-
- except :
- raise Exception("Error loading given model from path: {}. Maybe the file doesn't exist?".format(model_path))
-
-
- def load_classes(self, model_dir_path):
-
- self.class_list = []
-
- with open(model_dir_path + "/classes.txt", "r") as f:
- self.class_list = [cname.strip() for cname in f.readlines()]
-
- return self.class_list
-
- def get_predictions(self, cv_image):
-
- if cv_image is None:
- # TODO: show warning message (different color, maybe)
- return None,None
-
- else :
- self.frame = cv_image
- class_id = []
- confidence = []
- boxes = []
-
- result = self.model.predict(self.frame, conf = self.conf_threshold, 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)
-
- return self.predictions
\ No newline at end of file
+
+ def __init__(self, conf_threshold=0.7):
+ super().__init__()
+ self.conf_threshold = conf_threshold
+
+ 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)
+ 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?")
+
+ def load_classes(self, model_dir_path):
+ self.class_list = []
+
+ with open(model_dir_path + "/classes.txt", "r") as f:
+ self.class_list = [cname.strip() for cname in f.readlines()]
+
+ return self.class_list
+
+ def get_predictions(self, cv_image):
+ if cv_image is None:
+ # TODO: show warning message (different color, maybe)
+ return None, None
+ else:
+ self.frame = cv_image
+ class_id = []
+ confidence = []
+ boxes = []
+
+ # Perform object detection on image
+ result = self.model.predict(self.frame, conf=self.conf_threshold, 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)
+
+ return self.predictions
diff --git a/object_detection/object_detection/ObjectDetection.py b/object_detection/object_detection/ObjectDetection.py
index 72dac99..b2ff1e0 100644
--- a/object_detection/object_detection/ObjectDetection.py
+++ b/object_detection/object_detection/ObjectDetection.py
@@ -1,38 +1,52 @@
#! /usr/bin/env python3
-import os
+# Copyright (c) 2023 A.T.O.M ROBOTICS
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
import importlib
+import os
+
+import cv2
+
+from cv_bridge import CvBridge
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Image
-#from vision_msgs.msg import BoundingBox2D
-
-from cv_bridge import CvBridge
-import cv2
class ObjectDetection(Node):
+
def __init__(self):
super().__init__('object_detection')
# 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()
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', ""),
+ ('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),
('model_params.show_fps', 1),
]
@@ -42,71 +56,69 @@ def __init__(self):
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.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.")
+ 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()
-
def discover_detectors(self):
curr_dir = os.path.dirname(__file__)
- dir_contents = os.listdir(curr_dir + "/Detectors")
+ dir_contents = os.listdir(curr_dir + "/Detectors")
for entity in dir_contents:
if entity.endswith('.py'):
self.available_detectors.append(entity[:-3])
self.available_detectors.remove('__init__')
-
-
+
def load_detector(self):
- detector_mod = importlib.import_module(".Detectors." + self.detector_type, "object_detection")
+ detector_mod = importlib.import_module(".Detectors." + self.detector_type,
+ "object_detection")
detector_class = getattr(detector_mod, self.detector_type)
self.detector = detector_class()
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))
-
-
+ print("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 == None :
- print("Image input from topic : {} is empty".format(self.input_img_topic))
- else :
+
+ if predictions is None:
+ print("Image input from topic: {} is empty".format(self.input_img_topic))
+ else:
for prediction in predictions:
- x1, y1, x2, y2 = map(int, prediction['box'])
+ x1, y1, x2, y2 = map(int, prediction['box'])
# Draw the bounding box
- cv_image = cv2.rectangle(cv_image, (x1, y1), (x2, y2), (0,255,0),1)
+ cv_image = cv2.rectangle(cv_image, (x1, y1), (x2, y2), (0, 255, 0), 1)
# Show names of classes on the output image
class_id = int(prediction['class_id'])
class_name = self.detector.class_list[class_id]
- label = f"{class_name} : {prediction['confidence']:.2f}"
+ label = f"{class_name}: {prediction['confidence']:.2f}"
- cv_image = cv2.putText(cv_image, label, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1)
+ cv_image = cv2.putText(cv_image, label, (x1, y1 - 5),
+ cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1)
output = self.bridge.cv2_to_imgmsg(cv_image, "bgr8")
self.img_pub.publish(output)
@@ -115,12 +127,12 @@ def detection_cb(self, img_msg):
def main():
rclpy.init()
od = ObjectDetection()
- try :
+ try:
rclpy.spin(od)
except Exception as e:
print(e)
-if __name__=="__main__" :
+if __name__ == "__main__":
main()
diff --git a/object_detection/setup.cfg b/object_detection/setup.cfg
index 90b06b2..2165e16 100644
--- a/object_detection/setup.cfg
+++ b/object_detection/setup.cfg
@@ -2,3 +2,9 @@
script_dir=$base/lib/object_detection
[install]
install_scripts=$base/lib/object_detection
+[flake8]
+extend-ignore = B902,C816,D100,D101,D102,D103,D104,D105,D106,D107,D203,D212,D404,I202,Q000
+import-order-style = google
+max-line-length = 125
+show-source = true
+statistics = true
\ No newline at end of file
diff --git a/object_detection/setup.py b/object_detection/setup.py
index 6cec5fe..c1fcbea 100644
--- a/object_detection/setup.py
+++ b/object_detection/setup.py
@@ -1,6 +1,7 @@
-from setuptools import setup
-import os
from glob import glob
+import os
+
+from setuptools import setup
package_name = 'object_detection'
diff --git a/object_detection/test/test_flake8.py b/object_detection/test/test_flake8.py
index 27ee107..7932fbd 100644
--- a/object_detection/test/test_flake8.py
+++ b/object_detection/test/test_flake8.py
@@ -12,6 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from os.path import abspath, dirname, join
+
from ament_flake8.main import main_with_errors
import pytest
@@ -19,7 +21,12 @@
@pytest.mark.flake8
@pytest.mark.linter
def test_flake8():
- rc, errors = main_with_errors(argv=[])
+ config_file = join(dirname(dirname(dirname(abspath(__file__)))), ".flake8")
+ args = [
+ "--config={}".format(config_file)
+ ]
+
+ rc, errors = main_with_errors(argv=args)
assert rc == 0, \
'Found %d code style errors / warnings:\n' % len(errors) + \
'\n'.join(errors)
diff --git a/perception_bringup/launch/playground.launch.py b/perception_bringup/launch/playground.launch.py
index 83868b0..a2bd10c 100644
--- a/perception_bringup/launch/playground.launch.py
+++ b/perception_bringup/launch/playground.launch.py
@@ -1,10 +1,10 @@
-# Copyright (c) 2018 Intel Corporation
+# Copyright (c) 2023 A.T.O.M ROBOTICS
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
-# http://www.apache.org/licenses/LICENSE-2.0
+# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
@@ -18,56 +18,48 @@
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
-from launch.actions import IncludeLaunchDescription, DeclareLaunchArgument
-from launch.substitutions import LaunchConfiguration
+from launch.actions import DeclareLaunchArgument, IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch_ros.actions import Node
def generate_launch_description():
-
- pkg_perception_bringup = get_package_share_directory("perception_bringup")
- #pkg_ros_gz_sim = get_package_share_directory("ros_gz_sim")
-
- world_name = "playground"
-
- for arg in sys.argv:
- if arg.startswith("world:="):
- world_name = arg.split(":=")[1]
-
- world_sdf = pkg_perception_bringup + "/worlds/" + world_name + ".sdf"
-
- '''gz_sim = IncludeLaunchDescription(
- PythonLaunchDescriptionSource(
- os.path.join(pkg_ros_gz_sim, 'launch', 'gz_sim.launch.py')),
- )'''
-
- gz_sim_share = get_package_share_directory("ros_gz_sim")
- gz_sim = IncludeLaunchDescription(
- PythonLaunchDescriptionSource(os.path.join(gz_sim_share, "launch", "gz_sim.launch.py")),
- launch_arguments={
- "gz_args" : world_sdf
- }.items()
- )
-
- parameter_bridge = Node(package="ros_gz_bridge", executable="parameter_bridge",
- parameters = [
- {'config_file' : os.path.join(pkg_perception_bringup, "config", "bridge.yaml")}
- ]
- )
-
- arg_gz_sim = DeclareLaunchArgument('gz_args', default_value=world_sdf)
-
- arg_world_name = DeclareLaunchArgument('world', default_value='playground_world' )
-
- launch = [
- gz_sim,
- parameter_bridge
- ]
-
- args = [
- arg_gz_sim,
- arg_world_name
- ]
-
- return LaunchDescription(args + launch)
+ pkg_perception_bringup = get_package_share_directory("perception_bringup")
+ pkg_ros_gz_sim = get_package_share_directory("ros_gz_sim")
+ ros_gz_bridge_config = os.path.join(pkg_perception_bringup, "config", "bridge.yaml")
+ world_name = "playground"
+
+ for arg in sys.argv:
+ if arg.startswith("world:="):
+ world_name = arg.split(":=")[1]
+
+ world_sdf = pkg_perception_bringup + "/worlds/" + world_name + ".sdf"
+
+ gz_sim = IncludeLaunchDescription(
+ PythonLaunchDescriptionSource(
+ os.path.join(pkg_ros_gz_sim, "launch", "gz_sim.launch.py")
+ ),
+ launch_arguments={
+ "gz_args": world_sdf
+ }.items()
+ )
+
+ parameter_bridge = Node(package="ros_gz_bridge", executable="parameter_bridge",
+ parameters=[
+ {'config_file': ros_gz_bridge_config}
+ ])
+
+ arg_gz_sim = DeclareLaunchArgument('gz_args', default_value=world_sdf)
+ arg_world_name = DeclareLaunchArgument('world', default_value='playground_world')
+
+ launch = [
+ gz_sim,
+ parameter_bridge
+ ]
+
+ args = [
+ arg_gz_sim,
+ arg_world_name
+ ]
+
+ return LaunchDescription(args + launch)
From eaadc67c8620e708b3448935b97fbde1eea7b16e Mon Sep 17 00:00:00 2001
From: Arjun K Haridas <51917087+topguns837@users.noreply.github.com>
Date: Sun, 11 Feb 2024 00:02:37 +0530
Subject: [PATCH 15/17] Re-wrote YOLOv5 Plugin using torch instead of OpenCV
(#59)
* Re-wrote YOLOv5 Plugin using torch instead of OpenCV
* Added copyright to YOLOv5 Detector
* Removing flake8 linting issues
* Removing flake8 linting issues
---------
Co-authored-by: topguns837
---
.../object_detection/Detectors/YOLOv5.py | 139 +++---------------
1 file changed, 20 insertions(+), 119 deletions(-)
mode change 100644 => 100755 object_detection/object_detection/Detectors/YOLOv5.py
diff --git a/object_detection/object_detection/Detectors/YOLOv5.py b/object_detection/object_detection/Detectors/YOLOv5.py
old mode 100644
new mode 100755
index 3a7825d..4553004
--- a/object_detection/object_detection/Detectors/YOLOv5.py
+++ b/object_detection/object_detection/Detectors/YOLOv5.py
@@ -14,151 +14,52 @@
import os
-import cv2
-import numpy as np
+import torch
from ..DetectorBase import DetectorBase
class YOLOv5(DetectorBase):
- def __init__(self, conf_threshold=0.7,
- score_threshold=0.4, nms_threshold=0.25,
- is_cuda=1):
+ def __init__(self, conf_threshold=0.7):
super().__init__()
+ self.conf_threshold = conf_threshold
- # opencv img input
- self.frame = None
- self.net = None
- self.INPUT_WIDTH = 640
- self.INPUT_HEIGHT = 640
- self.CONFIDENCE_THRESHOLD = conf_threshold
-
- self.is_cuda = is_cuda
-
- # load model and prepare its backend to either run on GPU or CPU,
- # see if it can be added in constructor
def build_model(self, model_dir_path, weight_file_name):
- model_path = os.path.join(model_dir_path, weight_file_name)
-
try:
- self.net = cv2.dnn.readNet(model_path)
+ 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)
except Exception as e:
- print("Loading the model failed with exception {}".format(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?")
+ " Maybe the file doesn't exist?")
- if self.is_cuda:
- print("is_cuda was set to True. Attempting to use CUDA")
- self.net.setPreferableBackend(cv2.dnn.DNN_BACKEND_CUDA)
- self.net.setPreferableTarget(cv2.dnn.DNN_TARGET_CUDA_FP16)
- else:
- print("is_cuda was set to False. Running on CPU")
- self.net.setPreferableBackend(cv2.dnn.DNN_BACKEND_OPENCV)
- self.net.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU)
-
- # load classes.txt that contains mapping of model with labels
- # TODO: add try/except to raise exception that tells the use to
- # check the name if it is classes.txt
def load_classes(self, model_dir_path):
self.class_list = []
- with open(model_dir_path + "/classes.txt", "r") as f:
- self.class_list = [cname.strip() for cname in f.readlines()]
- return self.class_list
-
- def detect(self, image):
- # convert image to 640x640
- blob = cv2.dnn.blobFromImage(image, 1/255.0, (self.INPUT_WIDTH, self.INPUT_HEIGHT),
- swapRB=True, crop=False)
- self.net.setInput(blob)
- preds = self.net.forward()
- return preds
-
- # extract bounding box, class IDs and confidences of detected objects
- # YOLOv5 returns a 3D tensor of dimension 25200*(5 + n_classes)
- def wrap_detection(self, input_image, output_data):
- class_ids = []
- confidences = []
- boxes = []
-
- rows = output_data.shape[0]
-
- image_width, image_height, _ = input_image.shape
-
- x_factor = image_width / self.INPUT_WIDTH
- y_factor = image_height / self.INPUT_HEIGHT
-
- # Iterate through all the 25200 vectors
- for r in range(rows):
- row = output_data[r]
-
- # Continue only if Pc > conf_threshold
- confidence = row[4]
- if confidence >= self.CONFIDENCE_THRESHOLD:
- # One-hot encoded vector representing class of object
- classes_scores = row[5:]
-
- # Returns min and max values in a array alongwith their indices
- _, _, _, max_indx = cv2.minMaxLoc(classes_scores)
-
- # Extract the column index of the maximum values in classes_scores
- class_id = max_indx[1]
-
- # Continue of the class score is greater than a threshold
- # class_score represents the probability of an object belonging to that class
- if (classes_scores[class_id] > .25):
-
- confidences.append(confidence)
-
- class_ids.append(class_id)
-
- x, y, w, h = row[0].item(), row[1].item(), row[2].item(), row[3].item()
- left = int((x - 0.5 * w) * x_factor)
- top = int((y - 0.5 * h) * y_factor)
- width = int(w * x_factor)
- height = int(h * y_factor)
- box = np.array([left, top, width, height])
- boxes.append(box)
-
- # removing intersecting bounding boxes
- indexes = cv2.dnn.NMSBoxes(boxes, confidences, 0.25, 0.45)
-
- result_class_ids = []
- result_confidences = []
- result_boxes = []
-
- for i in indexes:
- result_confidences.append(confidences[i])
- result_class_ids.append(class_ids[i])
- result_boxes.append(boxes[i])
-
- return result_class_ids, result_confidences, result_boxes
+ with open(os.path.join(model_dir_path, 'classes.txt')) as f:
+ self.class_list = [cname.strip() for cname in f.readlines()]
- # makes image square with dimension max(h, w)
- def format_yolov5(self):
- row, col, _ = self.frame.shape
- _max = max(col, row)
- result = np.zeros((_max, _max, 3), np.uint8)
- result[0:row, 0:col] = self.frame
- return result
+ return self.class_list
def get_predictions(self, cv_image):
- # Clear list
- self.predictions = []
-
if cv_image is None:
# TODO: show warning message (different color, maybe)
return None, None
else:
self.frame = cv_image
+ class_id = []
+ confidence = []
+ boxes = []
- # make image square
- inputImage = self.format_yolov5()
+ results = self.model(self.frame)
- outs = self.detect(inputImage)
- class_ids, confidences, boxes = self.wrap_detection(inputImage, outs[0])
+ for *xyxy, conf, label in results.xyxy[0]:
+ class_id.append(int(label))
+ confidence.append(conf.item())
+ boxes.append([int(xy) for xy in xyxy])
- super().create_predictions_list(class_ids, confidences, boxes)
+ super().create_predictions_list(class_id, confidence, boxes)
return self.predictions
From 640747fee2fa5ad1f850547749fb44b53a9f79ff Mon Sep 17 00:00:00 2001
From: topguns837
Date: Sun, 11 Feb 2024 15:31:23 +0530
Subject: [PATCH 16/17] Add FPS calculations and added License and description
to setup.cfg
---
object_detection/config/params.yaml | 2 +-
.../object_detection/ObjectDetection.py | 25 +++++++++++++++++++
object_detection/package.xml | 2 +-
object_detection/setup.py | 4 +--
4 files changed, 29 insertions(+), 4 deletions(-)
diff --git a/object_detection/config/params.yaml b/object_detection/config/params.yaml
index eb69547..681c2b9 100644
--- a/object_detection/config/params.yaml
+++ b/object_detection/config/params.yaml
@@ -7,6 +7,6 @@ object_detection:
model_params:
detector_type: YOLOv5
model_dir_path: /root/percep_ws/models/yolov5
- weight_file_name: yolov5.onnx
+ weight_file_name: yolov5s.pt
confidence_threshold : 0.5
show_fps : 1
\ No newline at end of file
diff --git a/object_detection/object_detection/ObjectDetection.py b/object_detection/object_detection/ObjectDetection.py
index b2ff1e0..5ac3b48 100644
--- a/object_detection/object_detection/ObjectDetection.py
+++ b/object_detection/object_detection/ObjectDetection.py
@@ -16,6 +16,7 @@
import importlib
import os
+import time
import cv2
@@ -77,6 +78,12 @@ def __init__(self):
self.bridge = CvBridge()
+ if self.show_fps:
+ self.start_time = time.time()
+ self.frame_count = 0
+ self.total_elapsed_time = 0
+ self.average_fps = 0
+
def discover_detectors(self):
curr_dir = os.path.dirname(__file__)
dir_contents = os.listdir(curr_dir + "/Detectors")
@@ -101,6 +108,8 @@ def load_detector(self):
def detection_cb(self, img_msg):
cv_image = self.bridge.imgmsg_to_cv2(img_msg, "bgr8")
+ start_time = time.time()
+
predictions = self.detector.get_predictions(cv_image=cv_image)
if predictions is None:
@@ -120,6 +129,22 @@ def detection_cb(self, img_msg):
cv_image = cv2.putText(cv_image, label, (x1, y1 - 5),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1)
+ if self.show_fps:
+ elapsed_time = time.time() - start_time
+ self.frame_count += 1
+ self.total_elapsed_time += elapsed_time
+
+ # Write FPS on the frame
+ cv_image = cv2.putText(cv_image, f"FPS: {self.average_fps:.2f}", (10, 30),
+ cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2)
+
+ if time.time() - self.start_time >= 1.0:
+ self.average_fps = self.frame_count / self.total_elapsed_time
+
+ self.frame_count = 0
+ self.total_elapsed_time = 0
+ self.start_time = time.time()
+
output = self.bridge.cv2_to_imgmsg(cv_image, "bgr8")
self.img_pub.publish(output)
diff --git a/object_detection/package.xml b/object_detection/package.xml
index fc13de3..1bd237b 100644
--- a/object_detection/package.xml
+++ b/object_detection/package.xml
@@ -3,7 +3,7 @@
object_detection
0.0.0
- This is a ROS 2 package aimed at providing an interfacing between Deep Learning models and the ROS architecture using a plug-and-play modular arhitecture
+ This is a ROS 2 package aimed at providing an interfacing between Deep Learning models and the ROS architecture using a plug-and-play modular architecture
singh
Apache 2.0
diff --git a/object_detection/setup.py b/object_detection/setup.py
index c1fcbea..5602255 100644
--- a/object_detection/setup.py
+++ b/object_detection/setup.py
@@ -21,8 +21,8 @@
zip_safe=True,
maintainer='singh',
maintainer_email='jasmeet0915@gmail.com',
- description='TODO: Package description',
- license='TODO: License declaration',
+ description='Plug-and-Play ROS 2 package for Perception in Robotics',
+ license='Apache 2.0',
tests_require=['pytest'],
entry_points={
'console_scripts': [
From 10d83e63e7574393a8ba9de250ea3dcc5f01489f Mon Sep 17 00:00:00 2001
From: topguns837
Date: Wed, 14 Feb 2024 22:33:24 +0530
Subject: [PATCH 17/17] Made the detector_type param case-insensitive
---
.../object_detection/ObjectDetection.py | 29 ++++++++++---------
1 file changed, 16 insertions(+), 13 deletions(-)
diff --git a/object_detection/object_detection/ObjectDetection.py b/object_detection/object_detection/ObjectDetection.py
index 5ac3b48..844e09f 100644
--- a/object_detection/object_detection/ObjectDetection.py
+++ b/object_detection/object_detection/ObjectDetection.py
@@ -65,12 +65,8 @@ def __init__(self):
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()
+ # Load the detector
+ self.load_detector()
self.img_pub = self.create_publisher(Image, self.output_img_topic, 10)
self.bb_pub = None
@@ -95,15 +91,22 @@ 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()
+ for detector_name in self.available_detectors:
+ if self.detector_type.lower() == detector_name.lower():
+
+ detector_mod = importlib.import_module(".Detectors." + detector_name,
+ "object_detection")
+ detector_class = getattr(detector_mod, detector_name)
+ self.detector = detector_class()
+
+ self.detector.build_model(self.model_dir_path, self.weight_file_name)
+ self.detector.load_classes(self.model_dir_path)
- 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(detector_name))
+ return
- print("Your detector: {} has been loaded !".format(self.detector_type))
+ raise ModuleNotFoundError(self.detector_type + " Detector specified in config was not found. " +
+ "Check the Detectors dir for available detectors.")
def detection_cb(self, img_msg):
cv_image = self.bridge.imgmsg_to_cv2(img_msg, "bgr8")