Skip to content

Commit f649e4d

Browse files
authored
Merge pull request #29 from LCAS/workshop_2
basic functionality for workshop 2
2 parents e50109d + cc04c87 commit f649e4d

File tree

5 files changed

+215
-5
lines changed

5 files changed

+215
-5
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import rclpy
2+
from rclpy.node import Node
3+
4+
from geometry_msgs.msg import PolygonStamped
5+
6+
class CounterBasic(Node):
7+
8+
def __init__(self):
9+
super().__init__('counter_basic')
10+
11+
self.current_stamp = None
12+
self.object_counter = 0
13+
self.frame_counter = -1
14+
15+
self.subscriber = self.create_subscription(PolygonStamped, "/object_polygon", self.total_counter_callback, 10)
16+
17+
def counter_callback(self, data):
18+
19+
if data.header.stamp != self.current_stamp: # new frame detected
20+
# report the current object count
21+
if self.frame_counter >= 0:
22+
print(f'frame {self.frame_counter}: {self.object_counter} objects counted.')
23+
24+
self.frame_counter += 1 # increment frame counter
25+
self.object_counter = 0 # reset object counter
26+
self.current_stamp = data.header.stamp # refresh the current time stamp
27+
28+
# keep counting objects
29+
self.object_counter += 1
30+
31+
def total_counter_callback(self, data):
32+
# keep counting objects
33+
self.object_counter += 1
34+
print(f'{self.object_counter:d} objects counted so far.')
35+
36+
def main(args=None):
37+
rclpy.init(args=args)
38+
counter_basic = CounterBasic()
39+
40+
rclpy.spin(counter_basic)
41+
42+
counter_basic.destroy_node()
43+
rclpy.shutdown()
44+
45+
46+
if __name__ == '__main__':
47+
main()
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import rclpy
2+
from rclpy.node import Node
3+
from rclpy import qos
4+
5+
import cv2 as cv
6+
7+
from sensor_msgs.msg import Image
8+
from geometry_msgs.msg import Polygon, PolygonStamped, Point32
9+
from cv_bridge import CvBridge
10+
11+
class DetectorBasic(Node):
12+
visualisation = True
13+
data_logging = True
14+
log_path = 'evaluation/data/'
15+
seq = 0
16+
17+
def __init__(self):
18+
super().__init__('detector_basic')
19+
self.bridge = CvBridge()
20+
21+
self.min_area_size = 100.0
22+
self.countour_color = (255, 255, 0) # cyan
23+
self.countour_width = 1 # in pixels
24+
25+
self.object_pub = self.create_publisher(PolygonStamped, '/object_polygon', 10)
26+
self.image_sub = self.create_subscription(Image, '/limo/depth_camera_link/image_raw',
27+
self.image_color_callback, qos_profile=qos.qos_profile_sensor_data)
28+
29+
def image_color_callback(self, data):
30+
bgr_image = self.bridge.imgmsg_to_cv2(data, "bgr8") # convert ROS Image message to OpenCV format
31+
32+
# detect a color blob in the color image
33+
# provide the right range values for each BGR channel (set to red bright objects)
34+
bgr_thresh = cv.inRange(bgr_image, (0, 0, 80), (50, 50, 255))
35+
36+
# finding all separate image regions in the binary image, using connected components algorithm
37+
bgr_contours, _ = cv.findContours( bgr_thresh,
38+
cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
39+
40+
detected_objects = []
41+
for contour in bgr_contours:
42+
area = cv.contourArea(contour)
43+
# detect only large objects
44+
if area > self.min_area_size:
45+
# get the bounding box of the region
46+
bbx, bby, bbw, bbh = cv.boundingRect(contour)
47+
# append the bounding box of the region into a list
48+
detected_objects.append(Polygon(points = [Point32(x=float(bbx), y=float(bby)), Point32(x=float(bbw), y=float(bbh))]))
49+
if self.visualisation:
50+
cv.rectangle(bgr_image, (bbx, bby), (bbx+bbw, bby+bbh), self.countour_color, self.countour_width)
51+
52+
# publish individual objects from the list
53+
# the header information is taken from the Image message
54+
for polygon in detected_objects:
55+
self.object_pub.publish(PolygonStamped(polygon=polygon, header=data.header))
56+
57+
# log the processed images to files
58+
if self.data_logging:
59+
cv.imwrite(self.log_path + f'colour_{self.seq:06d}.png', bgr_image)
60+
cv.imwrite(self.log_path + f'mask_{self.seq:06d}.png', bgr_thresh)
61+
62+
# visualise the image processing results
63+
if self.visualisation:
64+
cv.imshow("colour image", bgr_image)
65+
cv.imshow("detection mask", bgr_thresh)
66+
cv.waitKey(1)
67+
68+
self.seq += 1
69+
70+
def main(args=None):
71+
rclpy.init(args=args)
72+
detector_basic = DetectorBasic()
73+
74+
rclpy.spin(detector_basic)
75+
76+
detector_basic.destroy_node()
77+
rclpy.shutdown()
78+
79+
if __name__ == '__main__':
80+
main()
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import rclpy
2+
from rclpy.node import Node
3+
4+
from geometry_msgs.msg import Twist
5+
6+
class MoverBasic(Node):
7+
8+
def __init__(self):
9+
super().__init__('mover_basic')
10+
self.publisher_ = self.create_publisher(Twist, 'cmd_vel', 10)
11+
timer_period = 2.0 # seconds
12+
self.timer = self.create_timer(timer_period, self.timer_callback)
13+
self.i = 0
14+
15+
def timer_callback(self):
16+
msg = Twist()
17+
msg.linear.x = 0.1
18+
self.publisher_.publish(msg)
19+
self.get_logger().info('Publishing: "{0}"'.format(msg))
20+
self.i += 1
21+
22+
23+
def main(args=None):
24+
rclpy.init(args=args)
25+
26+
mover_basic = MoverBasic()
27+
28+
rclpy.spin(mover_basic)
29+
30+
# Destroy the node explicitly
31+
# (optional - otherwise it will be done automatically
32+
# when the garbage collector destroys the node object)
33+
mover_basic.destroy_node()
34+
rclpy.shutdown()
35+
36+
37+
if __name__ == '__main__':
38+
main()
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import rclpy
2+
from rclpy.node import Node
3+
4+
from geometry_msgs.msg import Twist
5+
from sensor_msgs.msg import LaserScan
6+
7+
class MoverLaser(Node):
8+
"""
9+
A very simple Roamer implementation for LIMO.
10+
It goes straight until any obstacle is within
11+
a specified distance and then just turns left.
12+
A purely reactive approach.
13+
"""
14+
def __init__(self):
15+
"""
16+
On construction of the object, create a Subscriber
17+
to listen to lasr scans and a Publisher to control
18+
the robot
19+
"""
20+
super().__init__('mover_laser')
21+
self.publisher = self.create_publisher(Twist, "/cmd_vel", 10)
22+
self.subscriber = self.create_subscription(LaserScan, "/scan", self.laserscan_callback, 10)
23+
24+
self.angular_range = 10
25+
26+
def laserscan_callback(self, data):
27+
"""
28+
Callback called any time a new laser scan become available
29+
"""
30+
min_dist = min(data.ranges[int(len(data.ranges)/2) - self.angular_range : int(len(data.ranges)/2) + self.angular_range])
31+
print(f'Min distance: {min_dist:.2f}')
32+
msg = Twist()
33+
if min_dist < 0.5:
34+
msg.angular.z = 0.5
35+
else:
36+
msg.linear.x = 0.2
37+
self.publisher.publish(msg)
38+
39+
40+
def main(args=None):
41+
rclpy.init(args=args)
42+
mvoer_laser = MoverLaser()
43+
rclpy.spin(mvoer_laser)
44+
45+
mvoer_laser.destroy_node()
46+
rclpy.shutdown()
47+
48+
49+
if __name__ == '__main__':
50+
main()

src/rob2002_tutorial/setup.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,8 @@
2525
'console_scripts': [
2626
'mover_basic = rob2002_tutorial.mover_basic:main',
2727
'mover_laser = rob2002_tutorial.mover_laser:main',
28-
'mover_spinner = rob2002_tutorial.mover_spinner:main',
29-
'mover_waypoints = rob2002_tutorial.mover_waypoints:main',
3028
'detector_basic = rob2002_tutorial.detector_basic:main',
31-
'detector_better = rob2002_tutorial.detector_dblcounting:main',
32-
'detector_3d = rob2002_tutorial.detector_3d:main',
3329
'counter_basic = rob2002_tutorial.counter_basic:main',
34-
'counter_3d = rob2002_tutorial.counter_3d:main',
3530
],
3631
},
3732
)

0 commit comments

Comments
 (0)