Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
9d45a98
add empty package
confusedlama May 18, 2024
2ff6705
add messages for vision
confusedlama May 18, 2024
182ed0e
add array message for bounding boxes
confusedlama May 18, 2024
d8533fe
add seperate lists for different obstacles
confusedlama May 20, 2024
f7a3bfb
add dependencies for ros messages and dynamic reconfigure
confusedlama May 20, 2024
12bddf0
remove unused tests
confusedlama May 20, 2024
5c2fa8e
add a bunch of stuff to setup
confusedlama May 20, 2024
03a9ef6
add vision node
confusedlama May 20, 2024
43e243c
add dynamic reconfigure param file to gitignore
confusedlama May 20, 2024
9d56740
add config
confusedlama May 20, 2024
6f8f72f
add launch file
confusedlama May 20, 2024
be0bf06
use Robot message instead of custom message type
confusedlama May 20, 2024
70176bd
remove custom messages that are no longer needed
confusedlama May 20, 2024
c1f3efe
remove rest of unused message types
confusedlama May 20, 2024
966dfac
rename package to bitbots_<package>
confusedlama May 20, 2024
61edfe8
remove build dependent files from git
confusedlama May 20, 2024
fab7775
make debug mode dynamic
confusedlama May 20, 2024
20d536e
update dependencies
confusedlama May 20, 2024
dc8e967
make debug mode actually do something
confusedlama May 20, 2024
04893f3
fix dependency
confusedlama May 20, 2024
6b6ecaa
remove sim param in launch file
confusedlama May 21, 2024
a827d69
remove ros__parameters
confusedlama May 21, 2024
a1f5d4c
fix dependency names
confusedlama May 21, 2024
214ad6e
get build dependent files back because they are actually really impor…
confusedlama May 21, 2024
14ba794
remove rest of sim param
confusedlama May 23, 2024
cf95af0
stop including range.yaml as params file
confusedlama May 24, 2024
a37c0fb
update params in 1s intervals
confusedlama May 24, 2024
f3c34df
update parameters in image_callback
confusedlama May 29, 2024
a39f792
use same topic as vision for robot annotations
confusedlama May 29, 2024
c7b08f4
Merge branch 'main' into feature/technical_challenge_vision
Flova May 30, 2024
28a8730
Merge branch 'main' into feature/technical_challenge_vision
Flova Jul 7, 2024
09f4f22
Fix and tune tcv
Flova Jul 7, 2024
34ef2ad
Increase obstacle size
Flova Jul 7, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
from typing import Optional, Tuple

import cv2
import numpy as np
import rclpy
import rclpy.logging
from ament_index_python.packages import get_package_share_directory
from cv_bridge import CvBridge
from rclpy.node import Node
from sensor_msgs.msg import Image
from soccer_vision_2d_msgs.msg import Robot, RobotArray

from bitbots_technical_challenge_vision.bitbots_technical_challenge_vision_params import (
bitbots_technical_challenge_vision,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not suuuper important, but I think it is a bit confusing that you named your parameters bitbots_technical_challenge_vision, which is the same as the python file and the package name. But because this is for a technical challenge, it is okay.

)


class TechnicalChallengeVision(Node):
def __init__(self):
super().__init__("bitbots_technical_challenge_vision")

self._package_path = get_package_share_directory("bitbots_technical_challenge_vision")
self._cv_bridge = CvBridge()
self._annotations_pub = self.create_publisher(RobotArray, "/robots_in_image", 10)
self._debug_img_pub = self.create_publisher(Image, "/bitbots_technical_challenge_vision_debug_img", 10)
self._debug_clrmp_pub_blue = self.create_publisher(
Image, "/bitbots_technical_challenge_vision_debug_clrmp_blue", 10
)
self._debug_clrmp_pub_red = self.create_publisher(
Image, "/bitbots_technical_challenge_vision_debug_clrmp_red", 10
)
self._img_sub = self.create_subscription(Image, "/camera/image_proc", self.image_callback, 10)
self._param_listener = bitbots_technical_challenge_vision.ParamListener(self)
self._params = self._param_listener.get_params()

def create_robot_msg(self, x: int, y: int, w: int, h: int, t: int) -> Robot:
"""
Creates a Robot message from a robots bounding box data and its color.

:param x: bb top left x
:param y: bb top left y
:param w: bb width
:param h: bb height
:param t: robot team
:return: robot message for that robot
"""
robot = Robot()

robot.bb.center.position.x = float(x + (w / 2))
robot.bb.center.position.y = float(y + (h / 2))
robot.bb.size_x = float(w)
robot.bb.size_y = float(h)
robot.attributes.team = t

return robot

def process_image(
self, img: np.ndarray, debug_img: np.ndarray, arg
) -> Tuple[list[Robot], list[Robot], np.ndarray, np.ndarray]:
"""
gets annotations from the camera image

:param img: ndarray containing the camera image
:param debug_img: copy of img debug annotations should be drawn here
:param arg: __RosParameters object containing the dynamic parameters
:return: [[blue_robots], [red_robots], clrmp_blue, clrmp_red]
"""
# convert img to hsv to isolate colors better
img = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

# get color maps
blue_map = cv2.inRange(
img,
(arg.blue_lower_h, arg.blue_lower_s, arg.blue_lower_v),
(arg.blue_upper_h, arg.blue_upper_s, arg.blue_upper_v),
)

red_map = cv2.inRange(
img,
(arg.red_lower_h, arg.red_lower_s, arg.red_lower_v),
(arg.red_upper_h, arg.red_upper_s, arg.red_upper_v),
)

# get contours in color maps
blue_contours, _ = cv2.findContours(blue_map, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
red_contours, _ = cv2.findContours(red_map, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)

# get lists of bounding boxes
blue_robots = []
red_robots = []

def annotate(x, y, w, h, c) -> Optional[np.ndarray]:
if not arg.debug_mode:
return None
return cv2.rectangle(
debug_img,
(x, y),
(x + w, y + h),
c,
2,
)

for cnt in blue_contours:
x, y, w, h = cv2.boundingRect(cnt)
if min(w, h) >= arg.min_size and max(h, w) <= arg.max_size:
# draw bb on debug img
annotate(x, y, w, h, (255, 0, 0))

# TODO I think 1 is for the blue team?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can/should use the constant in the message for that.

blue_robots.append(self.create_robot_msg(x, y, w, h, 1))

for cnt in red_contours:
x, y, w, h = cv2.boundingRect(cnt)
if min(w, h) >= arg.min_size and max(h, w) <= arg.max_size:
# draw bb on debug img
annotate(x, y, w, h, (0, 0, 255))

red_robots.append(self.create_robot_msg(x, y, w, h, 2))

return blue_robots, red_robots, blue_map, red_map, debug_img

def image_callback(self, msg: Image):
# get dynamic parameters
if self._param_listener.is_old(self._params):
self._param_listener.refresh_dynamic_parameters()
self._params = self._param_listener.get_params()

arg = self._params

# set variables
img = self._cv_bridge.imgmsg_to_cv2(img_msg=msg, desired_encoding="bgr8")
header = msg.header

if arg.debug_mode:
debug_img = np.copy(img)
else:
debug_img = None

# get annotations
blue_robots, red_robots, blue_map, red_map, debug_img = self.process_image(img, debug_img, arg)
robots = []
robots.extend(blue_robots)
robots.extend(red_robots)

# make output message
robot_array_message = RobotArray()
robot_array_message.header = header
robot_array_message.robots = robots

# publish output message
self._annotations_pub.publish(robot_array_message)

if arg.debug_mode:
# make debug image message
debug_img_msg = self._cv_bridge.cv2_to_imgmsg(cvim=debug_img, encoding="bgr8", header=header)

# publish debug image
self._debug_img_pub.publish(debug_img_msg)

# make color map messages
clrmp_blue_img = cv2.cvtColor(blue_map, cv2.COLOR_GRAY2BGR)
clrmp_blue_msg = self._cv_bridge.cv2_to_imgmsg(cvim=clrmp_blue_img, encoding="bgr8", header=header)

clrmp_red_img = cv2.cvtColor(red_map, cv2.COLOR_GRAY2BGR)
clrmp_red_msg = self._cv_bridge.cv2_to_imgmsg(clrmp_red_img, encoding="bgr8", header=header)

# publish color map messages
self._debug_clrmp_pub_blue.publish(clrmp_blue_msg)
self._debug_clrmp_pub_red.publish(clrmp_red_msg)


def main(args=None):
rclpy.init(args=args)
node = TechnicalChallengeVision()
try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
node.destroy_node()


if __name__ == "__main__":
main()
Loading