26. ROS2-AI Vision Line Following Course
26.1 Line Locating
26.1.1 Program Logic
Before line following, program PuppyPi to locate the line first.
Firstly, program to recognize color. Use Lab color space to convert the image from RGB into Lab. Then, perform binaryzation, corrosion, dilation, etc., on the image to obtain the contour which contains the target color. Next, mark the contour with rectangle.
Next, acquire the diagonal points of the rectangle, and draw the center of line.
Lastly, display the information about the center of line on the terminal.
26.1.2 Operation Steps
Note
The input command should be case and space sensitive.
(1) Power on the robot, then follow the steps in 3.3 Docker Container Introduction and Entry and 3.4 ROS Version Switch Tool Guide to connect via the VNC remote control software and switch to the ROS 2 environment.
(2) Click
to open Terminator ROS2 terminal, then execute the following command in sequence:
ros2 launch example visual_patrol_demo.launch.py model:=0
(3) If want to close this game, we can press “Ctrl+C”. If it fails to close the game, please try again.
26.1.3 Program Outcome
Note
The program is default to recognize red.
Use red electrical tape to set the line. Then place PuppyPi on the red line. After the line is recognized by PuppyPi, the line will be framed on the camera returned image and the center of line will be drawn. At the same time, the coordinate of the line center will be displayed on the terminal.
26.1.4 Program Analysis Analysis
The source code of this program is located within the Docker container at:
/home/ubuntu/ros2_ws/src/example/example/advanced_functions/visual_patrol_demo.py
Image Processing
(1) Import Function Package
4 5 6 7 8 9 10 11 12 13 14 15 | import cv2 import math import time import numpy as np import yaml from cv_bridge import CvBridge, CvBridgeError import rclpy from rclpy.node import Node from sensor_msgs.msg import Image from std_srvs.srv import Empty from puppy_control_msgs.msg import Velocity, Pose, Gait |
Import the required modules through import statements: math provides a range of mathematical functions and constants for related calculations; rospy is used for ROS communication; from sensor_msgs.msg import Image: import Image information type from sensor_msgs.msg. Sensor_msgs package provides information definition and camera image of various sensor data. Puppy control imports action group.
(2) Obtain the Maximal Contour
29 30 31 32 | self.__isRunning = False self.__target_color = 'red' self.line_centerx = -1 self.img_centerx = 320 |
Set the line color to red.
(3) Gussian Filtering
Before converting the image from RGB into Lab space, denoise the image and use GaussianBlur() function in cv2 library for Gaussian filtering.
frame_gb = cv2.GaussianBlur(frame_resize, (3, 3), 3)
The meaning of the parameters in bracket is as follow
The first parameter frame_resize is the input image
The second parameter (3, 3) is the size of Gaussian kernel
The third parameter 3 is the allowable variance around the average in Gaussian filtering. The larger the value, the larger the allowable variance.
(4) Binaryzation Processing
Adopt inRange() function in cv2 library to perform binaryzation on the image.
216 217 218 | frame_mask = cv2.inRange(frame_lab, np.array(color_range['min'], dtype=np.uint8), np.array(color_range['max'], dtype=np.uint8)) |
The line of code frame_mask = cv2.inRange(frame_lab, np.array(color_range['min'], dtype=np.uint8), np.array(color_range['max'], dtype=np.uint8)) uses the cv2.inRange function to create a binary mask.
(5) Open Operation and Close Operation
To reduce interference and make the image smoother, it is necessary to process the image.
220 221 | opened = cv2.morphologyEx(frame_mask, cv2.MORPH_OPEN, np.ones((5, 5), np.uint8)) closed = cv2.morphologyEx(opened, cv2.MORPH_CLOSE, np.ones((5, 5), np.uint8)) |
cv2.MORPH_OPEN refers to open operation where corrosion will be conducted first, then dilation. cv2.MORPH_CLOSE indicates close operation where dilation will be conducted first, then corrosion.
Take opened = cv2.morphologyEx(frame_mask, cv2.MORPH_OPEN, np.ones((6, 6), np.uint8)) for example. The meaning of the parameters in bracket is as follow.
① The first parameter frame_mask is the input image.
② The second parameter cv2.MORPH_OPEN refers to processing method, open operation.
③ The third parameter np.ones((6, 6), np.uint8) is frame size.
(6) Acquire the Maximum Contour
After processing the image, acquire the contour of the target to be recognized, which involves findContours() function in cv2 library.
231 232 233 | contours, _ = cv2.findContours(closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) cnt_large, area = self.getAreaMaxContour(contours) self.get_logger().debug(f"ROI {idx + 1}: 找到 {len(contours)} 个轮廓,最大面积为 {area}") |
The first parameter in the function’s parentheses is the input image; the second parameter is the contour retrieval mode; the third parameter is the contour approximation method.
The self.getAreaMaxContour function is called, passing the contours list, and it returns the largest contour cnt_large along with its corresponding area area.
178 179 180 181 182 183 184 185 186 187 188 189 190 | def getAreaMaxContour(self, contours): """找出面积最大的轮廓""" contour_area_max = 0 area_max_contour = None for c in contours: contour_area_temp = math.fabs(cv2.contourArea(c)) if contour_area_temp > contour_area_max: contour_area_max = contour_area_temp if contour_area_temp > 50: area_max_contour = c return area_max_contour, contour_area_max |
Acquire the Position
(1) Frame the Line
Call drawContours() function to set the rectangle pattern and frame the line.
cv2.drawContours(img, [box], -1, (0, 0, 255), 2)
(2) Draw the Center
Next, acquire the diagonal points of the rectangle, and draw the line center through circle() function.
245 246 247 248 249 | cv2.drawContours(img, [box], -1, (0, 0, 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(img, (int(center_x), int(center_y)), 5, (0, 0, 255), -1) |
26.1.5 Function Extension
Modify Default Recognition Color
By default, the program is configured to recognize the color red as the target. The following steps demonstrate how to modify the default target color to green:
(1) Open a terminal
and run the following command to enter the directory containing the program:
cd ~/ros2_ws/src/example/example/advanced_functions
(2) Use the vim editor to open the program file:
vim visual_patrol_demo.py
(3) Find the line of code responsible for setting the target color. Refer to the image below for guidance.
Note
In vim, you can jump to a specific line by typing the line number, then pressing Shift + G.(Note: The line number shown in the image is for reference only. Please locate the actual line in your file.)
(4) Press the i key to enter insert mode, and change the line to: self.set_target('green')
(5) After editing, press the Esc key, then type the following to save and exit:
:wq
(6) Run the command below to restart the program and observe the updated behavior:
ros2 launch example visual_patrol_demo.launch.py model:=0
Adding a New Recognizable Color
In addition to the default colors configured in the program, users can add custom colors for recognition. This section provides step-by-step instructions for adding yellow as a new detectable color:
(1) Open a terminal
and run the following command to enter the directory containing the program:
~/.stop_ros.sh
(2) Start the USB camera node with the following command:
ros2 launch peripherals usb_cam.launch.py
(3) Open a new terminal
and navigate to the LAB Tool directory:
cd /home/ubuntu/software/lab_tool
(4) Run the PC software:
python3 main.py
(5) Click the “Add” button located at the bottom right corner of the interface.
(6) In the popup dialog, enter “yellow” as the new color name and click “OK”.
(7) From the color selection panel at the bottom right, select “yellow”.
(8) Place a yellow object within the camera’s field of view. Adjust the L, A, and B sliders until the yellow area appears white on the left side of the interface, while all other areas appear black.
(9) After adjustment, click “Save” to save the settings, then close the LAB Tool.
(10) Verify that the new settings are saved correctly by opening the LAB configuration file:
cd software/lab_tool/ && vim lab_config.yaml
(11) Following the instructions in section “5.1 Modify Default Recognition Color”, modify the default detection color to yellow.
Note
These three values represent the BGR color for the display font only and do not affect recognition accuracy. You can find appropriate BGR values online for customization. For yellow, use the BGR value (0, 30, 150). Add the corresponding LAB values for yellow to the color recognition code section as well.
(12) After completing the changes, restart the program with the following command. Place a yellow object in front of the camera to confirm it is correctly recognized in the output:
ros2 launch example visual_patrol_demo.launch.py model:=0
26.2 Auto Line Following
Note
If PuppyPi’s performance is not desired, we can debug according to “26.2.5 Function Extension -> Close Debugging Interface and Printed Data”.
26.2.1 Program Logic
PuppyPi can recognize the color of line and use algorithm to process the image so as to realize line following.
Firstly, program to recognize the color of line. Use Lab color space to convert the image from RGB into Lab. Then, perform binaryzation, corrosion, dilation, etc., on the image to obtain the contour which contains the target color. Next, mark the contour with rectangle.
After color recognition, perform calculation based on the location of line in the image to control PuppyPi to move along the line.
26.2.2 Operation Steps
Note
The input command should be case and space sensitive.
(1) Power on the robot, then follow the steps in 3.3 Docker Container Introduction and Entry and 3.4 ROS Version Switch Tool Guide to connect via the VNC remote control software and switch to the ROS 2 environment.
(2) Click
to open Terminator ROS2 terminal, then enter the following command:
ros2 launch example visual_patrol_demo.launch.py model:=1
(3) If want to close this game, we can press “Ctrl+C”. If it fails to close the game, please try again.
26.2.3 Program Outcome
Note
The program is default to detect red.
Use the red electrical tape to set the line, and place PuppyPi on the red line. After the game starts, it will move along the red line.
26.2.4 Program Analysis
The source code of this program is stored in the Docker container:
/home/ubuntu/ros2_ws/src/example/example/advanced_functions/visual_patrol_demo.py
Move Following the Line
In the first lesson, we introduced how to locate lines. Following that, we can use the line’s center coordinate information to control the robot to follow the line, as shown in the figure below.
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 | def run(self, img): """处理图像,检测红色线条并计算中心位置""" size = (320, 240) img_h, img_w = img.shape[:2] frame_resize = cv2.resize(img, size, interpolation=cv2.INTER_LINEAR) frame_gb = cv2.GaussianBlur(frame_resize, (3, 3), 3) centroid_x_sum = 0 weight_sum = 0 for idx, r in enumerate(self.roi): roi_h = self.roi_h_list[idx] blobs = frame_gb[r[0]:r[1], r[2]:r[3]] frame_lab = cv2.cvtColor(blobs, cv2.COLOR_BGR2LAB) detect_color = self.__target_color if detect_color in self.color_range_list: color_range = self.color_range_list[detect_color] frame_mask = cv2.inRange(frame_lab, np.array(color_range['min'], dtype=np.uint8), np.array(color_range['max'], dtype=np.uint8)) # 形态学操作 opened = cv2.morphologyEx(frame_mask, cv2.MORPH_OPEN, np.ones((5, 5), np.uint8)) closed = cv2.morphologyEx(opened, cv2.MORPH_CLOSE, np.ones((5, 5), np.uint8)) # 调试信息:显示掩模图像 cv2.imshow('Mask', closed) cv2.waitKey(1) else: self.line_centerx = -1 self.get_logger().warn(f"目标颜色 '{detect_color}' 不在颜色阈值列表中。") return img |
Walk
We mainly control the robot dog’s movement by calling the functions PuppyPosePub.publish(), PuppyGaitConfigPub.publish(), and PuppyVelocityPub.publish().
135 136 137 138 139 140 141 142 143 144 145 146 147 | # 发布初始姿态 pose_msg = Pose() pose_msg.stance_x = float(self.PuppyPose['stance_x']) pose_msg.stance_y = float(self.PuppyPose['stance_y']) pose_msg.x_shift = float(self.PuppyPose['x_shift']) pose_msg.height = float(self.PuppyPose['height']) pose_msg.roll = float(self.PuppyPose['roll']) pose_msg.pitch = float(self.PuppyPose['pitch']) pose_msg.yaw = float(self.PuppyPose['yaw']) pose_msg.run_time = int(500) self.PuppyPosePub.publish(pose_msg) self.get_logger().info("发布初始姿态。") time.sleep(0.2) |
The PuppyPosePub.publish() function is used to control the robot dog’s posture during movement.
For example, in the code PuppyPosePub.publish(stance_x=PuppyPose['stance_x'], stance_y=PuppyPose['stance_y'], x_shift=PuppyPose['x_shift'], height=PuppyPose['height'], pitch=PuppyPose['pitch'], roll=PuppyPose['roll']), the parameters inside the parentheses have the following meanings:
(1) The first parameter, stance_x, represents the additional distance between the robot dog’s four legs along the x-axis, measured in centimeters.
(2) The second parameter, stance_y, represents the additional distance between the four legs along the y-axis, measured in centimeters.
(3) The third parameter, x_shift, represents the distance the four legs move in the same direction along the x-axis. A smaller value results in the robot leaning forward while walking, and a larger value causes it to lean backward. Adjusting x_shift helps maintain the robot’s balance during movement, measured in centimeters.
(4) The fourth parameter, height, is the height of the robot dog, specifically the vertical distance from the tips of the feet to the thigh joint, measured in centimeters.
(5) The fifth parameter, roll, refers to the roll angle of the robot dog, measured in degrees.
(6) The sixth parameter, pitch, refers to the pitch angle of the robot dog, measured in degrees.
The Puppy.gait_config(overlap_time) function is used to control the robot dog’s gait during movement.
151 152 153 154 155 156 157 158 159 | # 发布步态配置 gait_msg = Gait() gait_msg.overlap_time = float(self.GaitConfig['overlap_time']) gait_msg.swing_time = float(self.GaitConfig['swing_time']) gait_msg.clearance_time = float(self.GaitConfig['clearance_time']) gait_msg.z_clearance = float(self.GaitConfig['z_clearance']) self.PuppyGaitConfigPub.publish(gait_msg) self.get_logger().info("发布步态配置。") time.sleep(0.2) |
For example, in the code Puppy.gait_config(overlap_time = GaitConfig['overlap_time'], swing_time = GaitConfig['swing_time'], clearance_time = GaitConfig['clearance_time'], z_clearance = GaitConfig['z_clearance']), the parameters inside the parentheses have the following meanings:
(1) The first parameter, overlap_time, represents the duration for which all four knee joints are in contact with the ground, measured in seconds.
(2) The second parameter, swing_time, represents the duration for which the knee joints are fully lifted off the ground, measured in seconds.
(3) The third parameter, clearance_time, refers to the time interval between the front and rear feet, measured in seconds.
(4) The fourth parameter, z_clearance, represents the height that the knee joints are lifted during movement, measured in centimeters.
26.2.5 Function Extension
Close Debugging Interface and Printed Data
As the continuous refresh of debugging interface and printed data on terminal will occupy CPU of Raspberry Pi, we can close debugging interface and printed data to tackle choppy running.
(1) Input the following command and press Enter to edit the program file.
cd ros2_ws/src/example/example/advanced_functions/
(2) Open the program file using the Vim editor by entering the following command:
vim visual_patrol_demo.py
(3) Next, jump to this line of code.
Note
we can input the line code and press “Shift+G” to jump to the corresponding line.
(4)Press “i” key to enter editing mode. Then add “#” in front of the codes in the red frame to comment.
(5) After modification, press “Esc” and input “:wq” and press Enter to save and exit editing.
:wq
(6) Input the following command to restart the game and check PuppyPi’s performance.
ros2 launch example visual_patrol_demo.launch.py model:=1
(7) If you need to view the debugging screen again (real-time feedback from the camera), you can uncomment the content boxed in step 3), i.e., remove the “#” in front of the code, then save, as shown in the following figure:
Change the Followed Color
The default target color for this feature is red. If you wish to change it—for example, to black—please refer to section 26.1.5 Function Extension for detailed instructions.
After making the necessary changes, enter the corresponding command to launch the feature.
ros2 launch example visual_patrol_demo.launch.py model:=1