Skip to content

Commit

Permalink
0.0.1, detection
Browse files Browse the repository at this point in the history
  • Loading branch information
gbr1 committed Aug 9, 2022
1 parent bcc40b6 commit a2538db
Show file tree
Hide file tree
Showing 15 changed files with 366 additions and 0 deletions.
44 changes: 44 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,46 @@
# edgeimpulse_ros
ROS2 wrapper for Edge Impulse


## How to install

1. install edge_impulse_linux: <br>
`pip3 install edge_impulse_linux`

2. on some boards and aarch64 these are required (e.g. vm on mac m1): <br>
`sudo apt-get install libatlas-base-dev libportaudio2 libportaudiocpp0 portaudio19-dev` <br>
`pip3 install pyaudio` <br>

3. download your .eim file as **"linux board"** and choose your architecture

4. make your eim file as executable: <br>
`cd /path/to/your/eim/file` <br>
`chmod +x <file>.eim` <br>

5. clone this repo in your workspace: <br>
`cd ~/dev_ws/src`
`git clone https://github.com/gbr1/edgeimpulse_ros`

6. check dependencies: <br>
`cd ~/dev_ws` <br>
`rosdep install --from-paths src --ignore-src -r -y` <br>

7. build: <br>
`colcon build --symlink-install` <br>
`source install/setup.bash` <br>


## How to run

Launch the node: <br>
`ros2 run edgeimpulse_ros image_classification --ros-args -p model.filepath:="</absolute/path/to/your/eim/file.eim>" -r /detection/input/image:="/your_image_topic"`
` <br>



***Copyright © 2022 Giovanni di Dio Bruno - gbr1.github.io***





Empty file added edgeimpulse_ros/__init__.py
Empty file.
Binary file added edgeimpulse_ros/__pycache__/__init__.cpython-38.pyc
Binary file not shown.
Binary file not shown.
Binary file not shown.
182 changes: 182 additions & 0 deletions edgeimpulse_ros/image_classification.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
# Copyright 2022 Giovanni di Dio Bruno - gbr1.github.io

# 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 unittest import result
from .submodules import device_patches
import cv2

import rclpy
from rclpy.node import Node

from cv_bridge import CvBridge

from sensor_msgs.msg import Image

#from vision_msgs.msg import BoundingBox2DArray
#from vision_msgs.msg import VisionInfo

import os
import time
import sys, getopt
import numpy as np
from edge_impulse_linux.image import ImageImpulseRunner



class EI_Image_node(Node):
def __init__(self):

self.occupied = False
self.img = None
self.cv_bridge = CvBridge()

super().__init__('ei_image_classifier_node')
self.init_parameters()
self.ei_classifier = self.EI_Classifier(self.modelfile, self.get_logger())
#self.publisher = self.create_publisher(BoundingBox2DArray,'/edge_impulse/detection',1)

self.timer_parameter = self.create_timer(2,self.parameters_callback)

self.image_publisher = self.create_publisher(Image,'/detection/output/image',1)
self.timer_classify = self.create_timer(0.01,self.classify_callback)
self.timer_classify.cancel()
self.subscription = self.create_subscription(Image,'/detection/input/image',self.listener_callback,1)
self.subscription


def init_parameters(self):
self.declare_parameter('model.filepath','')
self.modelfile= self.get_parameter('model.filepath').get_parameter_value().string_value

self.declare_parameter('show.overlay', True)
self.show_overlay = self.get_parameter('show.overlay').get_parameter_value().bool_value

self.declare_parameter('show.labels',True)
self.show_labels_on_image = self.get_parameter('show.labels').get_parameter_value().bool_value

self.declare_parameter('show.classification_info', False)
self.show_extra_classification_info = self.get_parameter('show.classification_info').get_parameter_value().bool_value







def parameters_callback(self):
self.show_labels_on_image = self.get_parameter('show.labels').get_parameter_value().bool_value
self.show_extra_classification_info = self.get_parameter('show.classification_info').get_parameter_value().bool_value
self.show_overlay = self.get_parameter('show.overlay').get_parameter_value().bool_value




def listener_callback(self, msg):
if len(msg.data):
current_frame = self.cv_bridge.imgmsg_to_cv2(msg)
current_frame = cv2.cvtColor(current_frame, cv2.COLOR_BGR2RGB)
if not self.occupied:
self.img = current_frame
self.timer_classify.reset()

def classify_callback(self):
self.occupied = True

# classify
features, cropped, res = self.ei_classifier.classify(self.img)

#prepare output
if "classification" in res["result"].keys():
if self.show_extra_classification_info:
self.get_logger().info('Result (%d ms.) ' % (res['timing']['dsp'] + res['timing']['classification']), end='')
for label in self.labels:
score = res['result']['classification'][label]
if self.show_extra_classification_info:
self.get_logger().info('%s: %.2f\t' % (label, score), end='')


elif "bounding_boxes" in res["result"].keys():
if self.show_extra_classification_info:
self.get_logger().info('Found %d bounding boxes (%d ms.)' % (len(res["result"]["bounding_boxes"]), res['timing']['dsp'] + res['timing']['classification']))

for bb in res["result"]["bounding_boxes"]:
if self.show_extra_classification_info:
self.get_logger().info('%s (%.2f): x=%d y=%d w=%d h=%d' % (bb['label'], bb['value'], bb['x'], bb['y'], bb['width'], bb['height']))
if self.show_overlay:
img_res = cv2.rectangle(cropped, (bb['x'], bb['y']), (bb['x'] + bb['width'], bb['y'] + bb['height']), (255, 0, 0), 1)
if self.show_labels_on_image:
composed_label = bb['label']+' '+str(round(bb['value'],2))
img_res = cv2.putText(img_res, composed_label, (bb['x'], bb['y']-5), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255,0,0),1)

cropped=cv2.cvtColor(cropped, cv2.COLOR_RGB2BGR)

# publish message
self.image_publisher.publish(self.cv_bridge.cv2_to_imgmsg(cropped,"bgr8"))

self.occupied= False
self.timer_classify.cancel()


class EI_Classifier:
def __init__(self, modelfile, logger):
self.runner = None
self.labels = None
self.logger = logger
with ImageImpulseRunner(modelfile) as self.runner:
try:
self.model_info = self.runner.init()
#print(self.model_info)
self.logger.info('Model loaded successfully!')
self.logger.info('Model owner: '+ self.model_info['project']['owner'])
self.logger.info('Model name: ' + self.model_info['project']['name'])
self.logger.info('Model version: ' + str(self.model_info['project']['deploy_version']))
self.logger.info('Model type: '+ self.model_info['model_parameters']['model_type'])
self.labels = self.model_info['model_parameters']['labels']

except:
self.logger.error('Issue on opening .eim file')
if (self.runner):
self.runner.stop()


def __del__(self):
if (self.runner):
self.runner.stop()

def classify(self, img):
try:
# classification
features, cropped = self.runner.get_features_from_image(img)
res = self.runner.classify(features)
return features, cropped, res
except:
# somenthing went wrong >_<
self.logger.error('Error on classification')


def main():
rclpy.init()
node = EI_Image_node()
rclpy.spin(node)

node.destroy_node()
rclpy.shutdown()


if __name__ == "__main__":
main()



Binary file not shown.
15 changes: 15 additions & 0 deletions edgeimpulse_ros/submodules/device_patches.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import os

def get_device():
# On Jetson Nano `OPENBLAS_CORETYPE=ARMV8` needs to be set, otherwise including OpenCV
# throws an illegal instruction error
if (os.path.exists('/proc/device-tree/model')):
with open('/proc/device-tree/model', 'r') as f:
model = f.read()
if ('NVIDIA Jetson Nano' in model):
return 'jetson-nano'
return 'unknown'

device = get_device()
if (device == 'jetson-nano'):
os.environ['OPENBLAS_CORETYPE'] = 'ARMV8'
24 changes: 24 additions & 0 deletions package.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>edgeimpulse_ros</name>
<version>0.0.1</version>
<description>ROS2 wrapper for Edge Impulse</description>
<maintainer email="[email protected]">gbr1</maintainer>
<license>Apache 2.0</license>

<depend>vision_opencv</depend>

<test_depend>ament_copyright</test_depend>
<test_depend>ament_flake8</test_depend>
<test_depend>ament_pep257</test_depend>
<test_depend>python3-pytest</test_depend>

<!--<exec_depend>vision_msgs</exec_depend>-->
<exec_depend>sensor_msgs</exec_depend>
<exec_depend>ros2launch</exec_depend>

<export>
<build_type>ament_python</build_type>
</export>
</package>
Empty file added resource/edgeimpulse_ros
Empty file.
4 changes: 4 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[develop]
script-dir=$base/lib/edgeimpulse_ros
[install]
install-scripts=$base/lib/edgeimpulse_ros
26 changes: 26 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from setuptools import setup

package_name = 'edgeimpulse_ros'
submodules = 'edgeimpulse_ros/submodules'
setup(
name=package_name,
version='0.0.1',
packages=[package_name, submodules],
data_files=[
('share/ament_index/resource_index/packages',
['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
],
install_requires=['setuptools'],
zip_safe=True,
maintainer='Giovanni di Dio Bruno',
maintainer_email='[email protected]',
description='ROS2 wrapper for Edge Impulse',
license='Apache 2.0',
tests_require=['pytest'],
entry_points={
'console_scripts': [
'image_classification = edgeimpulse_ros.image_classification:main'
],
},
)
23 changes: 23 additions & 0 deletions test/test_copyright.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Copyright 2015 Open Source Robotics Foundation, Inc.
#
# 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 ament_copyright.main import main
import pytest


@pytest.mark.copyright
@pytest.mark.linter
def test_copyright():
rc = main(argv=['.', 'test'])
assert rc == 0, 'Found errors'
25 changes: 25 additions & 0 deletions test/test_flake8.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Copyright 2017 Open Source Robotics Foundation, Inc.
#
# 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 ament_flake8.main import main_with_errors
import pytest


@pytest.mark.flake8
@pytest.mark.linter
def test_flake8():
rc, errors = main_with_errors(argv=[])
assert rc == 0, \
'Found %d code style errors / warnings:\n' % len(errors) + \
'\n'.join(errors)
23 changes: 23 additions & 0 deletions test/test_pep257.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Copyright 2015 Open Source Robotics Foundation, Inc.
#
# 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 ament_pep257.main import main
import pytest


@pytest.mark.linter
@pytest.mark.pep257
def test_pep257():
rc = main(argv=['.', 'test'])
assert rc == 0, 'Found code style errors / warnings'

0 comments on commit a2538db

Please sign in to comment.