7. ROS+OpenCV Vision Projects
7.1 Color Threshold Adjustment
Objects may appear differently in color under various light sources, which can affect games involving color recognition. This lesson will guide you through using the LabConfig tool to adjust the color threshold and mitigate this issue effectively.
7.1.1 Enable LabConfig
Note
The input command should be case-sensitive, and keywords can be complemented using Tab key.
(1) Start ROSPug robot dog, and access the robot system desktop using NoMachine.
(2) Double-click
to start the LabConfig tool. If the software is enabled successfully, you can skip step (3) to (6).
(3) If LabConfig cannot be opened, double-click
to open the command-line terminal.
(4) Execute the command below:
roslaunch lab_config lab_config_manager.launch
(5) Run the following command, and hit Enter key to enable the camera service
roslaunch pug_peripherals camera.launch
(6) Double-click
to open the LabConfig tool.
7.1.2 Introduction to LabConfig Interface
The LabConfig interface is divided into two parts, including display area and parameter adjustment area.
(1) Display area: The image on the left side is the camera-processed image, while the image on the right side is the original.
Note
If the live camera feed cannot be displayed properly, indicating a failure in camera connection, please ensure that the connection cable is securely plugged in.
(2) Parameter adjustment area: adjust the color threshold parameters. The functions of the icons are listed below:
| Icon | Function |
|---|---|
![]() |
Drag the sliders L, A and B to adjust the L, A and B components of the live camera feed. |
![]() |
Select the color to be adjusted. |
![]() |
Apply the color threshold currently adjusted. |
![]() |
Save the adjustment result. |
![]() |
Add new colors to be recognized. |
7.1.3 Adjust Color Threshold
(1) Start LabConfig. Select the color to be adjusted in the drop-down menu. Take adjusting red as example.
(2) Set the ‘min’ value of L, A and B components as ‘0’, and the ‘max’ values to ‘255’.
(3) Position the colored object within the camera’s field of view, consult the LAB color space distribution map, and fine-tune the L, A, and B components to align with the desired color recognition range.
If the red hue is closer to ‘+a’, adjust by increasing the A component. This involves maintaining the ‘max’ value of the A component unchanged while increasing its ‘min’ value until the color object area on the left side of the screen turns white, while the remaining area turns black.
(4) Fine-tune the remaining L and B components based on the environmental conditions. If the red hue appears lighter, increase the ‘min’ value of the L component. Conversely, if the red hue appears darker, decrease the ‘max’ value of the L component. If the red hue appears warmer, increase the ‘min’ value of the B component; if it appears cooler, reduce the ‘max’ value of the B component.
The table below displays the parameter information for LAB threshold adjustment.
| Color component | Range | Color zone |
|---|---|---|
| L | 0~255 | Black-White(-L ~ +L) |
| A | 0~255 | Green-Red(-a ~ +a) |
| B | 0~255 | Blue-Yellow(-b ~ +b) |
(5) Click-on ‘Apply’ button to apply the color threshold parameters currently adjusted.
(6) Click the “Save” button to preserve the color threshold adjustment parameters.
7.1.4 Add New Recognition Color
In addition to the three pre-defined recognition colors, users have the option to add other identifiable colors. Here, purple will be used as an example. The specific steps are as follows:
(1) Open LabConfig, then click-on ‘Add’ button.
(2) Name the color, for example ‘purple’.
(3) Position the colored object within the camera’s field of view, then adjust the L, A, and B component sliders until the color object area on the left side of the screen turns white, while the remaining areas turn black.
Note
To access the detailed instructions, please refer to section 7.1.3 Adjust Color Threshold.
(4) Click the ‘Apply’ button to apply the currently adjusted color threshold parameters.
(5) Click-on ‘Save’ button to save the the currently adjusted color threshold parameters.
7.2 Color Recognition
Note
If color recognition is inaccurate, please refer to 7.1 Color Threshold Adjustment for further adjustments.
Ensure there are no other objects in the camera’s field of view that contain the recognized color, as this may affect gameplay effectiveness.
7.2.1 Program Logic
First, the color must be identified. To achieve this, we utilize the Lab color space for processing, converting the image color space from RGB to Lab. Subsequently, operations such as binarization, erosion, and dilation are applied to the image to extract the maximum contour containing the target color. Finally, the color recognition results are provided back through both the display screen and the terminal interface.
7.2.2 Operation Steps
Note
The input command should be case sensitive.
(1) Start ROSPug robot dog, and access the robot system desktop using NoMachine.
(2) Click-on
to open the command-line terminal.
(3) Execute the following command, and hit Enter key to initiate the color recognition program.
rosrun pug_example color_detect_demo.py
(4) If you need to terminate this program, use short-cut ‘Ctrl+C’. If the program cannot be stopped, please retry.
7.2.3 Program Outcome
When red, green, or blue is recognized, the color name will be printed in the lower left corner of the screen and also displayed in the terminal. Additionally, the target item will be highlighted with a circle of the corresponding color on the screen.
Take recognizing red sphere as example.
7.2.4 Program Parameters Explanation
The source code of this program is saved in: /home/pug/src/pug_example/scripts/detect_and_track/color_detect_demo.py
Image Processing
(1) Gaussian Filtering
Before converting the image color space from RGB to Lab, it undergoes denoising. The GaussianBlur() function from the cv2 library is employed for this purpose, performing Gaussian filtering on the image.
53 | frame_gb = cv2.GaussianBlur(frame_resize, (3, 3), 3) |
The parameters in the function brackets are as follows:
The first parameter, frame_resize, represents the input image.
The second parameter, (3, 3), denotes the Gaussian kernel size.
The third parameter, 3, indicates the variance allowed around the mean value in Gaussian filtering. A larger value allows greater variance around the mean, while a smaller value allows less variance around the mean.
(2) Thresholding Processing
Utilize inRange() function from cv2 library to perform thresholding on the image.
61 62 63 64 65 66 67 68 | if i in ['red', 'green', 'blue', 'purple']: frame_mask = cv2.inRange(frame_lab, (color_range_list[i]['min'][0], color_range_list[i]['min'][1], color_range_list[i]['min'][2]), (color_range_list[i]['max'][0], color_range_list[i]['max'][1], color_range_list[i]['max'][2])) |
The first parameter in the function brackets represents the input image. The second and third parameters denote the lower and upper limits of the threshold, respectively. When the RGB color value of a pixel falls within the specified range, the pixel is assigned a value of 1; otherwise, it is assigned a value of 0.
(3) Erosion and Dilation
To reduce interference and achieve smoother images, erosion and dilation operations are applied.
69 70 | eroded = cv2.erode(frame_mask, cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))) dilated = cv2.dilate(eroded, cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))) |
The erode() function performs erosion operations on images. In the code example eroded = cv2.erode(frame_mask, cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))), the parameters in the brackets have the following meanings:
The first parameter, frame_mask, represents the input image.
The second parameter, cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)), defines the structural element or kernel that determines the operation’s nature. The first parameter within parentheses specifies the kernel shape, and the second parameter specifies the kernel size.
Similarly, the dilate() function is used to dilate images. The parameters in its parentheses have the same meanings as those in the erode() function.
(4) Obtain the Contour with The Maximum Area
Upon completing the aforementioned image processing, the next step involves obtaining the contour of the recognized target, which utilizes the findContours() function in the cv2 library. The parameters within the function brackets are as follows:
71 | contours = cv2.findContours(dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[-2] |
① The first parameter represents the input image.
② The second parameter denotes the contour retrieval mode.
③ The third parameter specifies the contour approximation method.
Identify the contour with the largest area among the contours obtained. To minimize interference, a minimum threshold value must be set. Only when the area exceeds this threshold is the target contour considered valid.
37 38 | if contour_area_temp > 50: area_max_contour = c |
(5) Feedback Information
After obtaining the contour with the maximum area, the recognition target is highlighted with a circle using the circle() function from the cv2 library. The circle’s color corresponds to the recognition color.
83 | cv2.circle(img, (centerX, centerY), radius, range_rgb[color_area_max], 2) |
Display the detected color on the returned image, using the putText() function from the cv2 library.
{lineno-start=}
cv2.putText(img, "Color: " + detect_color, (10, img.shape[0] - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.65, draw_color, 2)
The parameters within the function brackets have the following meanings:
① The first parameter, img, represents the input image.
② The second parameter, ("Color: " + detect_color), specifies the content to be displayed.
③ The third parameter, (10, img.shape[0] - 10), determines the display position.
④ The fourth parameter, cv2.FONT_HERSHEY_SIMPLEX, defines the font type.
⑤ The fifth parameter, 0.65, indicates the font size.
⑥ The sixth parameter, draw_color, represents the font color.
⑦ The seventh parameter, 2, signifies the font thickness.
7.3 Color Localization & Tracking
Note
Please ensure that objects with the same or similar color as the one being tracked are not present within the camera’s field of view, as this may affect game.
During tracking, ensure that the color block does not move too quickly.
If color recognition accuracy is unsatisfactory, refer to 7.1 Color Threshold Adjustment for guidance on adjustments.
The object is tracked using images captured by a high-definition camera. When the target color block is positioned within the camera’s field of view, the leg servos will adjust their position to mimic the movement of the target color block.
7.3.1 Program Logic
The camera captures an RGB image, which is then resized and processed with Gaussian blur. The image’s color space is converted from RGB to LAB (for more details on the LAB color space, refer to OpenCV Computer Vision Course->Image Processing—Color Space Conversion.
Next, the color thresholds set in 7.1 Color Threshold Adjustment are used to match the image. Once the colors are detected, a morphological opening operation is applied, and the detected colors are highlighted with circles.
Opening Operation: Erosion followed by dilation. This removes small objects, smooths shape boundaries, and preserves the object’s area. It also eliminates small noise particles and separates connected objects.
Erosion: Reduces the boundary points of objects, causing the boundary to shrink inward, and removes objects smaller than the structuring element.
Dilation: Expands the boundary of objects, merging background points into the object and causing the boundary to expand outward.
Finally, the center of the color block is calculated based on the circle’s position. The positional deviation between the color block’s center and the image center is then determined. A PID algorithm is used to adjust the robot in real-time, allowing the gimbal servo to follow the movement of the color block.
7.3.2 Operation Steps
Note
The input command should be case sensitive, and keywords can be complemented using Tab key.
(1) Start ROSPug robot dog, and access the robot system desktop using NoMachine.
(2) Click-on
to open the command-line terminal.
(3) Execute the following command, and hit Enter key to initiate the color tracking program.
rosrun pug_example color_tracking_demo.py
(4) If you need to terminate this program, use short-cut ‘Ctrl+C’. If the program cannot be stopped, please retry.
7.3.3 Program Outcome
After activating the mode, position the purple ball in front of the HD camera. The returned image will show the detected color of the target, and the robot dog will track and follow the target’s vertical movements.
7.3.4 Code Analysis
The source code of this program is saved in :/home/pug/src/pug_example/scripts/detect_and_track/color_tracking_demo.py
Import Function Library
1 2 3 4 5 6 7 8 9 10 11 | #!/usr/bin/env python3 # encoding: utf-8 import cv2 import math import time import rospy import numpy as np from pug_sdk import pid, common from pug_control.msg import Pose from sensor_msgs.msg import Image from pug_control.srv import SetActionName |
(1) cv2: OpenCV library used for image processing.
(2) rospy: ROS Python client library for node communication.
(3) numpy: Library for numerical computations.
(4) pid & common: Custom PID controller and utility functions.
(5) Pose, Image, SetActionName: ROS message and service types.
Global Variable Definition
13 14 15 16 17 18 19 | size = (320, 180) target_color = '' color_range = None pose_pub = None y_dis = 0 y_pid = pid.PID(P=0.0003, I=0, D=0) |
Color Mapping Table
21 22 23 24 25 26 27 28 | range_rgb = { 'red': (0, 0, 255), 'blue': (255, 0, 0), 'green': (0, 255, 0), 'black': (0, 0, 0), 'white': (255, 255, 255), 'purple': (255, 0, 255), } |
Map color names to corresponding BGR values for visualization.
def run(img) Function
This is the main processing function, which performs the following operations:
(1) Image Preprocessing
55 56 | frame_resize = cv2.resize(img_copy, size, interpolation=cv2.INTER_NEAREST) frame_lab = cv2.cvtColor(frame_resize, cv2.COLOR_BGR2LAB) |
① Resize the image to improve processing efficiency.
② Convert the image to LAB color space, which is more suitable for color recognition.
(2) Color Recognition and Contour Extraction
63 64 65 66 67 | frame_mask = cv2.inRange(frame_lab, tuple(target_color_range['min']), tuple(target_color_range['max'])) eroded = cv2.erode(frame_mask, cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))) dilated = cv2.dilate(eroded, cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))) contours = cv2.findContours(dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[-2] area_max_contour, area_max = getAreaMaxContour(contours) |
① Use a mask to extract regions of a specific color.
② Apply morphological operations (erosion and dilation) to reduce noise.
③ Detect and filter contours, selecting the largest one as the target.
(3) Target Localization and Feedback Control
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | if area_max > 100: (center_x, center_y), radius = cv2.minEnclosingCircle(area_max_contour) center_x = int(common.val_map(center_x, 0, size[0], 0, img_w)) center_y = int(common.val_map(center_y, 0, size[1], 0, img_h)) radius = int(common.val_map(radius, 0, size[0], 0, img_w)) if radius > 100: return img cv2.circle(img, (int(center_x), int(center_y)), int(radius), range_rgb[target_color], 2) cv2.circle(img, (int(center_x), int(center_y)), 5, range_rgb[target_color], -1) y_pid.SetPoint = img_h / 2.0 y_pid.update(center_y) y_dis += y_pid.output y_dis = np.radians(20) if y_dis > np.radians(20) else y_dis y_dis = np.radians(-20) if y_dis < np.radians(-20) else y_dis pose_pub.publish(0, y_dis, 0, -0.13, 0, 0, 0, 0) |
① Determine the center coordinates of the target.
② Use a PID controller to calculate the pitch angle.
③ Publish posture commands to the robot control system.
Image Callback Function
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | if __name__ == '__main__': rospy.init_node('color_tracking_demo', log_level=rospy.INFO) pose_pub = rospy.Publisher('/pug_control/pose', Pose, queue_size=1) rospy.Subscriber('/csi_camera/image_rect_color', Image, image_callback) color_range = rospy.get_param('/lab_config_manager/color_range_list', {}) run_action_group_srv = rospy.ServiceProxy('/pug_control/run_action_group', SetActionName) time.sleep(0.1) run_action_group_srv('stand') target_color = 'purple' try: rospy.spin() except KeyboardInterrupt: rospy.loginfo("Shutting down") finally: cv2.destroyAllWindows() |
(1) Initialize the ROS node.
(2) Set the target color to purple.
(3) Start the service call and subscribe to the image topic.
(4) Enter the ROS main loop using rospy.spin().
7.4 KCF Target Tracking
Note
Make sure to keep the object within the camera’s view, and avoid moving too quickly. If the object goes out of the camera’s range, tracking will stop. To resume tracking, simply reselect the object.
In this lesson, we will employ the KCF algorithm to use the mouse to frame the target. The ROSPug robot dog will then identify and frame the target, displaying its coordinates on the terminal.
7.4.1 Introduction to KCF
KCF (Kernel Correlation Filter) is an algorithm proposed by Joao F. Henriques, Rui Caseiro, Pedro Martins, and Jorge Batista in 2014. It is a discriminative tracking method that utilizes the cyclic matrix of the area surrounding the target to gather positive and negative samples. These samples are then used to train a target detector. This detector is employed to determine if the predicted position in the next frame matches the target. Based on this detection, the training set and consequently the object detector are updated.
7.4.2 Program Logic
First, build a tracker using OpenCV’s KCF object tracking algorithm and subscribe to topic messages to receive the real-time camera feed.
Next, use a mouse callback function to select the tracking target in the returned image. The tracker will then provide the real-time position of the target, which is updated using a PID algorithm.
Finally, control the ROSPug to track the target vertically based on its position.
7.4.3 Operation Steps
Note
The input command should be case sensitive, and keywords can be complemented using Tab key.
(1) Start the robot dog, and access the robot system desktop using NoMachine.
(2) Double-click
to open the command-line terminal.
(3) Execute the following command and hit Enter key to disable the auto-start service.
rosrun pug_example kcf_tracking.py
(4) If you need to terminate the program, use short-cut ‘Ctrl+C’ to stop the program.
7.4.4 Program Outcome
After starting the game, position the target tracking object within the camera’s field of view. Press ‘s’ and use the left mouse button to select it on the screen. While selecting, the chosen area will be highlighted with a blue box. Release the left mouse button and press ‘Space’ or ‘Enter’ on the keyboard to confirm the selection; the box will turn green, indicating tracking can begin. If you need to reselect the tracking target, right-click the mouse on the screen to clear it, then repeat the selection process as described above.
7.4.5 Program Analysis
The source code is saved in : /home/pug/src/pug_example/scripts/detect_and_track/kcf_tracking.py
**Initialize Configuration: init **
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | def __init__(self, name): rospy.init_node(name) self.name = name self.y_dis = 0 self.y_pid = pid.PID(P=0.0005, I=0, D=0) self.image_queue = queue.Queue(maxsize=2) self.fps = FPS() signal.signal(signal.SIGINT, self.shutdown) self.running = True self.mouse_click = False self.selection = None self.track_window = None self.drag_start = None self.start_circle = True self.start_click = False self.params = cv2.TrackerNano_Params() model_path = os.path.split(os.path.realpath(__file__))[0] self.params.backbone = os.path.join(model_path, 'nanotrack_backbone_sim.onnx') self.params.neckhead = os.path.join(model_path, 'nanotrack_head_sim.onnx') self.tracker = cv2.TrackerNano_create(self.params) cv2.namedWindow(name, 1) cv2.setMouseCallback(name, self.onmouse) self.image_sub = rospy.Subscriber("/csi_camera/image_rect_color", Image, self.image_callback, queue_size=1) self.pose_pub = rospy.Publisher('/pug_control/pose', Pose, queue_size=1) self.run_action_group_srv = rospy.ServiceProxy('/pug_control/run_action_group', SetActionName) time.sleep(0.2) self.run_action_group_srv('stand') self.run() |
(1) Initialize the ROS node and related parameters (e.g., PID controller, image queue).
(2) Load the NanoTrack model file (in ONNX format) for object tracking.
(3) Create an image display window and bind a mouse event callback function.
(4) Subscribe to the image topic /csi_camera/image_rect_color.
(5) Publish posture information to /pug_control/pose.
(6) Call the service /pug_control/run_action_group to control the action group.
Image Callback Function: image_callback
52 53 54 55 56 57 58 59 | def image_callback(self, ros_image: Image): rgb_image = np.ndarray(shape=(ros_image.height, ros_image.width, 3), dtype=np.uint8, buffer=ros_image.data) if self.image_queue.full(): self.image_queue.get() self.image_queue.put(rgb_image) |
(1) Receives ROS image messages and places them into the image queue.
(2) Uses queue.Queue to buffer image data and prevent accumulation caused by processing delays.
Mouse Event Processing: onmouse
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | def onmouse(self, event, x, y, flags, param): if event == cv2.EVENT_LBUTTONDOWN: self.mouse_click = True self.drag_start = (x, y) self.track_window = None if self.drag_start: xmin = min(x, self.drag_start[0]) ymin = min(y, self.drag_start[1]) xmax = max(x, self.drag_start[0]) ymax = max(y, self.drag_start[1]) self.selection = (xmin, ymin, xmax, ymax) if event == cv2.EVENT_LBUTTONUP: self.mouse_click = False self.drag_start = None self.track_window = self.selection self.selection = None if event == cv2.EVENT_RBUTTONDOWN: self.mouse_click = False self.selection = None self.track_window = None self.drag_start = None self.start_circle = True self.start_click = False self.run_action_group_srv('stand') self.tracker = cv2.TrackerNano_create(self.params) |
Allows the user to select the target area by drawing a box in the image window using the mouse.
Image Processing: image_proc
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | def image_proc(self, image): if self.start_circle: if self.track_window: cv2.rectangle(image, (self.track_window[0], self.track_window[1]), (self.track_window[2], self.track_window[3]), (0, 0, 255), 2) elif self.selection: cv2.rectangle(image, (self.selection[0], self.selection[1]), (self.selection[2], self.selection[3]), (0, 0, 255), 2) if self.mouse_click: self.start_click = True if self.start_click: if not self.mouse_click: self.start_circle = False if not self.start_circle: print('start tracking') bbox = (self.track_window[0], self.track_window[1], self.track_window[2] - self.track_window[0], self.track_window[3] - self.track_window[1]) self.tracker.init(image, bbox) else: ok, box = self.tracker.update(image) if ok and min(box) > 0: p1 = (int(box[0]), int(box[1])) p2 = (int(p1[0] + box[2]), int(p1[1] + box[3])) cv2.rectangle(image, p1, p2, (0, 255, 0), 2, 1) center_y = (p1[1] + p2[1]) / 2 img_h, img_w = image.shape[:2] self.y_pid.SetPoint = img_h / 2.0 self.y_pid.update(center_y) self.y_dis += self.y_pid.output self.y_dis = np.radians(20) if self.y_dis > np.radians(20) else self.y_dis self.y_dis = np.radians(-20) if self.y_dis < np.radians(-20) else self.y_dis self.pose_pub.publish(0, self.y_dis, 0, -0.13, 0, 0, 0, 0) else: # Tracking failure cv2.putText(image, "Tracking failure detected !", (10, 460), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 0), 1) self.fps.update() result_image = self.fps.show_fps(image) return result_image |
(1) Display the currently selected target region.
(2) Use the KCF algorithm to track the target.
(3) If tracking is successful, calculate the target’s center position and adjust the robot’s posture using the PID controller.
(4) If tracking fails, display the message “Tracking failure detected!”
Main Loop: run
133 134 135 136 137 138 139 140 141 142 143 144 | def run(self): while self.running: image = self.image_queue.get(block=True) try: result_image = self.image_proc(np.copy(image)) except BaseException as e: print(e) result_image = image cv2.imshow(self.name, cv2.cvtColor(result_image, cv2.COLOR_RGB2BGR)) cv2.waitKey(1) self.run_action_group_srv('stand') rospy.signal_shutdown('shutdown') |
(1) Continuously retrieve images from the image queue for processing and display the results. (2) Listen for the SIGINT signal (Ctrl+C) to gracefully shut down the node and perform cleanup operations.
7.5 Autonomous Line Following
7.5.1 Program Logic
Line following is a common challenge in robot competitions. While traditional methods often rely on two or four-channel sensors, the ROSPug robot achieves autonomous line following using its visual module to detect line colors and process images.
The process begins with identifying the line color using the Lab color space. The image is converted from RGB to Lab and then processed with binarization, erosion, and dilation to isolate the contours of the target color, which are highlighted with rectangles.
Once the color is recognized, the robot uses image feedback to calculate the line’s position. It then adjusts the ROSPug’s movement to follow the line and displays the line coordinates on the terminal, enabling effective autonomous line-following.
7.5.2 Operation Steps
Note
The input command should be case and space sensitive.
(1) Start the robot dog, and access the robot system desktop using NoMachine.
(2)
Double-click to open the command-line terminal.
(3) Execute the command to terminate the app auto-start service.
rosrun pug_example visual_patrol_demo.py
(4) If you need to terminate this game, press ‘Ctrl+C’ on the terminal. If the program cannot be stopped, please retry.
7.5.3 Program Outcome
Note
The default detection color is black.
Use black electrical tape to set a path, and put ROSPug on the black line. After initiating the game, the robot dog will move along the black line.
7.5.4 Function Extension
Change Followed Color
By default, the line-following color is set to black. To change the line-following color to a different one, such as red, please follow these steps:
(1) Execute the command and hit Enter key to edit the autonomous line following program.
vim visual_patrol_demo.py
(2) Locate the code as indicated in red frame below.
(3) Press ‘i’ key to enter the editing mode, then change ‘black’ to ‘red’. After modification, press ‘Esc’ key, input ‘:wq’ and press Enter key to save and exit the editing.
(4) Run the following command to initiate the game, and check the game effect.
rosrun pug_example visual_patrol_demo.py
7.5.5 Program Parameter Analysis
The source code of this program is saved in: /home/hiwonder/pug/src/pug_example/scripts/almighty_functions/visual_patrol_demo.py
Subscribe to a Publishing Topic
38 39 40 41 42 43 44 45 46 | self.image_sub = rospy.Subscriber("/csi_camera/image_rect_color", Image, self.image_callback, queue_size=1) self.pose_pub = rospy.Publisher('/pug_control/pose', Pose, queue_size=1) self.gait_pub = rospy.Publisher('/pug_control/gait', Gait, queue_size=1) self.cmd_vel_pub = rospy.Publisher('/cmd_vel', Twist, queue_size=10) self.run_action_group_srv = rospy.ServiceProxy('/pug_control/run_action_group', SetActionName) time.sleep(0.1) self.gait_pub.publish(0.2, 0.15, 0.0, 0.05) self.pose_pub.publish(0, math.radians(-18), 0, -0.13, 0, 0, 0, 0.5) self.run() |
(1) Subscribe to the camera topic /csi_camera/image_rect_color to receive images.
(2) Publish pose messages to the topic /pug_control/pose and gait messages to the topic /pug_control/gait.
(3) Publish velocity messages to the topic /cmd_vel.
Image Processing
The image is processed using OpenCV library functions to extract the contours of the color to be recognized. Then, the center point coordinates of the detected target are calculated.
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | for r in self.roi: roi_h = self.roi_h_list[n] blobs = frame_gb[r[0]:r[1], r[2]:r[3]] frame_lab = cv2.cvtColor(blobs, cv2.COLOR_BGR2LAB) n += 1 if self.line_color in self.lab_data: frame_mask = cv2.inRange(frame_lab, (self.lab_data[self.line_color]['min'][0], self.lab_data[self.line_color]['min'][1], self.lab_data[self.line_color]['min'][2]), (self.lab_data[self.line_color]['max'][0], self.lab_data[self.line_color]['max'][1], self.lab_data[self.line_color]['max'][2])) opened = cv2.morphologyEx(frame_mask, cv2.MORPH_OPEN, np.ones((6, 6), np.uint8)) closed = cv2.morphologyEx(opened, cv2.MORPH_CLOSE, np.ones((6, 6), np.uint8)) contours = cv2.findContours(closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_TC89_L1)[-2] max_contours = self.getAreaMaxContour(contours)[0] if max_contours is not None: rect = cv2.minAreaRect(max_contours) box = np.int0(cv2.boxPoints(rect)) for i in range(4): box[i, 1] = int(box[i, 1] + (n - 1) * roi_h + self.roi[0][0]) cv2.drawContours(image, [box], -1, (0, 0, 255, 255), 2) pt1_x, pt1_y = box[0, 0], box[0, 1] pt3_x, pt3_y = box[2, 0], box[2, 1] center_x, center_y = (pt1_x + pt3_x) / 2, (pt1_y + pt3_y) / 2 cv2.circle(image, (int(center_x), int(center_y)), 5, (0, 0, 255), -1) center_.append([center_x, center_y]) centroid_x_sum += center_x * r[4] weight_sum += r[4] |
Movement Control
Calculate the forward and turning speeds based on the center point coordinates, then publish velocity messages to control the movement of the machine.
{lineno-start=}
twist = Twist()
twist.linear.x = 0.15
if weight_sum is not 0:
line_centerx = int(centroid_x_sum / weight_sum)
deflection_angle = -math.atan((line_centerx - (img_w / 2.0)) / (img_h / 2.0))/2
# print(deflection_angle)
# self.pid.SetPoint = 0.138
self.pid.update(deflection_angle)
twist.angular.z = common.set_range(-self.pid.output, -1.0, 1.0)
self.cmd_vel_pub.publish(twist)
7.6 Autonomous Ball Shooting
7.6.1 Program Logic
Initialize the robot dog object, load the color threshold configuration file, define basic postures such as standing and bending, as well as gait parameters. Control the robot dog’s movement in a thread, adjusting its advancement based on the recognized position of color blocks to move left and right.
Next, the main thread continuously loops to capture images, recognizing color blocks within the images, and passing them to the motion control thread. The camera captures the image, adjusts it to a suitable resolution, converts it to the LAB color space for color threshold segmentation, and identifies the contour with the largest area as the target. The center coordinates of its minimum circumscribed circle are obtained.
Finally, the robot dog moves towards the target position. Upon reaching the target, it transitions to the hitting state, adjusts its position as necessary, performs the hitting action, then transitions to the end state and exits the program. Key functions and classes utilized include Camera, color threshold processing, contour detection, and more. The primary objective is to control the robot dog’s movement through color recognition to accomplish a simple ball delivery task. Colors, motion control logic, etc. can be customized as needed.
7.6.2 Operation Steps
Note
The input command should be case and space sensitive.
(1) Start the robot dog, and access the robot system desktop using NoMachine.
(2)
Double-click to open the command-line terminal.
(3) Execute the command below to terminate the app auto-start service.
rosrun pug_example kick_ball_demo.py
(4) If you need to terminate this game, press ‘Ctrl+C’ on the terminal. If the program cannot be stopped, please retry.
7.6.3 Program Outcome
Note
The default detection color is purple.
Place the required balls on the field and position the ROSPug robot dog on the field, ensuring that the balls are within the camera’s field of view. The distance between the balls and the robot should not exceed 1 meter.
7.6.4 Function Extension
Change Followed Color
By default, the system recognizes the ball color as purple. If you need to change the recognized color, for example to red, please follow these steps:
(1) Execute the command vim kick_ball_demo.py and hit Enter key to edit the ball shooting program.
vim kick_ball_demo.py
(2) Locate the code as indicated in red frame below.
(3) Press the “i” key to enter editing mode, then change “purple” to “red”. Once the modification is complete, press the “Esc” key. Next, enter “:wq” and press Enter to save and exit.
(4) To initiate the independent kicking game, enter the following command. You can restart the game to view the updated effects.
rosrun pug_example kick_ball_demo.py
7.6.5 Program Parameter Analysis
The source code of this program is saved in:/home/hiwonder/pug/src/pug_example/scripts/almighty_functions/kick_ball_demo.py
Subscribe to Publishing Topic
30 31 32 33 34 35 | self.image_sub = rospy.Subscriber("/csi_camera/image_rect_color", Image, self.image_callback, queue_size=1) self.pose_pub = rospy.Publisher('/pug_control/pose', Pose, queue_size=1) self.gait_pub = rospy.Publisher('/pug_control/gait', Gait, queue_size=1) self.velocity_pub = rospy.Publisher('/pug_control/velocity_move', Velocity, queue_size=1) self.run_action_group_srv = rospy.ServiceProxy('/pug_control/run_action_group', SetActionName) time.sleep(0.1) |
(1) Subscribe to the camera topic /csi_camera/image_rect_color to receive images.
(2) Publish pose messages to the topic /pug_control/pose and gait messages to the topic /pug_control/gait.
Ball Recognition
Run image processing in the main thread to obtain the ball’s coordinate information and print it to the terminal. Then, use the ball’s center coordinates to control the robot dog to track the ball, as shown in the figure below:
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 |
def run(self):
while self.running:
image = self.image_queue.get(block=True)
if self.status == 'find_ball' or self.status == 'kick_ball':
GaussianBlur_img = cv2.GaussianBlur(image, (3, 3), 3)
frame_lab = cv2.cvtColor(GaussianBlur_img, cv2.COLOR_BGR2LAB)
frame_mask = cv2.inRange(frame_lab,
(self.lab_data[self.target_color]['min'][0],
self.lab_data[self.target_color]['min'][1],
self.lab_data[self.target_color]['min'][2]),
(self.lab_data[self.target_color]['max'][0],
self.lab_data[self.target_color]['max'][1],
self.lab_data[self.target_color]['max'][2]))
eroded = cv2.erode(frame_mask, cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)))
dilated = cv2.dilate(eroded, cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)))
contours = cv2.findContours(dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[-2]
areaMax_contour = self.getAreaMaxContour(contours)[0]
if self.ball_position_queue.full():
self.ball_position_queue.get()
if areaMax_contour is not None:
ball = cv2.minEnclosingCircle(areaMax_contour)
ball_centerx = int(ball[0][0])
ball_centery = int(ball[0][1])
# print(ball_centerx, ball_centery, ball[1])
self.ball_position_queue.put([ball_centerx, ball_centery])
cv2.circle(image, (ball_centerx, ball_centery), int(ball[1]), (0, 255, 0), 2)
cv2.circle(image, (ball_centerx, ball_centery), 5, (0, 255, 0), -1)
else:
self.ball_position_queue.put([-1, -1])
cv2.imshow(self.name, image)
cv2.waitKey(1)
self.run_action_group_srv('stand')
rospy.signal_shutdown('shutdown')
|
Ball Shooting Control
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | def action_thread(self): while self.running: ball_position = self.ball_position_queue.get(block=True) if ball_position[0] != -1: ball_center_x, ball_center_y = ball_position[0], ball_position[1] # print(ball_center_x, ball_center_y) if self.status == 'find_ball': if ball_center_y < 250 and 200 < ball_center_x < 600: self.velocity_pub.publish(0.1, 0, 0, False) time.sleep(0.5) elif ball_center_y < 300 and 200 < ball_center_x < 600: self.velocity_pub.publish(0.05, 0, 0, False) time.sleep(0.5) elif ball_center_x > 600: self.velocity_pub.publish(0.01, 0, math.radians(-10), False) elif ball_center_x < 540: self.velocity_pub.publish(0.01, 0, math.radians(10), False) else: self.status = 'kick_ball' elif self.status == 'kick_ball' : if ball_center_y < 330: self.velocity_pub.publish(0.05, 0, 0, False) time.sleep(0.5) elif ball_center_x > 600: self.velocity_pub.publish(0.0, 0, math.radians(-10), False) elif ball_center_x < 540: self.velocity_pub.publish(0.0, 0, math.radians(10), False) else: self.velocity_pub.publish(0.08, 0, 0, False) time.sleep(0.8) self.velocity_pub.publish(0, 0, 0, True) time.sleep(0.5) self.run_action_group_srv('stand') self.run_action_group_srv('kick_ball_right') self.status = 'End' elif self.status == 'End': time.sleep(0.01) else: time.sleep(0.01) |
Run the robot’s movement functions using multithreading. Once the ball’s coordinates are obtained, control the robot’s movement to approach and align with the ball for kicking. When the robot is correctly positioned, execute the kicking action sequence.




