Week 6 Line Following¶
Copy all the code below into your line_follower.py
file. Then, review the annotations to understand how it all works.
#!/usr/bin/env python3
import rospy
import cv2
from cv_bridge import CvBridge, CvBridgeError
from sensor_msgs.msg import Image
from tb3 import Tb3Move
class LineFollower(object): # (1)!
def __init__(self):
node_name = "line_follower"
rospy.init_node(node_name, anonymous=True)
self.rate = rospy.Rate(5)
self.cvbridge_interface = CvBridge()
self.img_sub = rospy.Subscriber("/camera/rgb/image_raw", Image, self.camera_cb)
self.robot_controller = Tb3Move()
self.ctrl_c = False
rospy.on_shutdown(self.shutdown_ops)
def shutdown_ops(self):
self.robot_controller.stop()
cv2.destroyAllWindows()
self.ctrl_c = True
def camera_cb(self, img_data):
try:
cv_img = self.cvbridge_interface.imgmsg_to_cv2(img_data, desired_encoding="bgr8")
except CvBridgeError as e:
print(e)
_, width, _ = cv_img.shape # (2)!
crop_width = 500
crop_height = 40
crop_x = int((width / 2) - (crop_width / 2))
cropped_img = cv_img[10:crop_height, 10:crop_x+crop_width]
hsv_img = cv2.cvtColor(cropped_img, cv2.COLOR_BGR2HSV)
lower = (100, 100, 100)
upper = (255, 255, 255)
mask = cv2.inRange(hsv_img, lower, upper)
res = cv2.bitwise_and(cropped_img, cropped_img, mask = mask)
m = cv2.moments(mask)
cy = m['m10'] / (m['m00'] + 1e-5) # (3)!
cz = m['m01'] / (m['m00'] + 1e-5)
cv2.circle(res, (int(cy), int(cz)), 10, (255, 0, 0), 2)
cv2.imshow("filtered image", res)
cv2.waitKey(1)
y_error = cy - (width / 2) # (4)!
kp = 1.0 / 50.0
fwd_vel = 0.1 # (5)!
ang_vel = kp * y_error # (6)!
print(f"Y-error = {y_error:.3f} pixels, ang_vel = {ang_vel:.3f} rad/s")
self.robot_controller.set_move_cmd(fwd_vel, ang_vel)
self.robot_controller.{BLANK} # (7)!
def main(self):
while not self.ctrl_c:
self.rate.sleep()
if __name__ == '__main__':
lf_instance = LineFollower()
try:
lf_instance.main()
except rospy.ROSInterruptException:
pass
-
A lot of the things in here you will already be familiar with from the previous exercises, so we won't go into too much detail on all of this again. The main thing you will notice is that we have once again built a class structure around this, which should now be familiar to you from previous weeks of this course.
-
In this case we want our robot to follow the line that is printed on the floor. We do this by applying the same image processing steps as in the previous exercises, to isolate the colours associated with the line and calculate its location in the robot's viewpoint.
-
We'll use the centroid component
cy
to determine how far the robot needs to turn in order to keep the line in the centre of its vision: -
We are implementing proportional control here.
Ideally, we want the centre of the line on the floor to be in the centre of the robot's viewpoint at all times: this is our target position. The actual position is where the line on the floor actually is, i.e.: the
cy
centroid component. The position error is then the difference between the actual and target position: -
The only way we can reduce this error is by changing the robot's angular velocity. The robot always needs to travel with forward velocity, so we define a fixed value at all times to achieve this.
-
In order to correct for our position error, we multiply it by a proportional gain (
kp
), which will provide us with an angular velocity that should start to make the error reduce.If the proportional gain is set appropriately, this should ensure that our position error (
y_error
) is always kept to a minimum, so that the robot follows the line! -
We then simply set these two velocities in our
robot_controller
object and then publish them to the/cmd_vel
topic using methods from theTb3Move()
class.Fill in the Blank!
There is a method within the
Tb3Move()
class which allows us to publish a velocity command to the/cmd_vel
topic. What is it? (Have a look at thetb3.py
source code if you need a reminder).