# import rclpy
# from rclpy.node import Node
# from sensor_msgs.msg import Image
# import cv_bridge
import time
import numpy as np
import cv2 as cv
# from geometry_msgs.msg import Twist
## User-defined parameters: (Update these values to your liking)
# Minimum size for a contour to be considered anything
MIN_AREA = 500
# Minimum size for a contour to be considered part of the track
MIN_AREA_TRACK = 5000
# Robot's speed when following the line
LINEAR_SPEED = 0.2
# Proportional constant to be applied on speed when turning
# (Multiplied by the error value)
KP = 1.5 / 100
# If the line is completely lost, the error value shall be compensated by:
LOSS_FACTOR = 1.2
# Send messages every $TIMER_PERIOD seconds
TIMER_PERIOD = 0.06
# When about to end the track, move for ~$FINALIZATION_PERIOD more seconds
FINALIZATION_PERIOD = 4
# The maximum error value for which the robot is still in a straight line
MAX_ERROR = 30
# BGR values to filter only the selected color range
lower_bgr_values = np.array([0, 0, 0])
upper_bgr_values = np.array([180, 255, 46])
finalization_countdown = None
right_mark_count = 0
just_seen_right_mark = None
just_seen_line = None
class MyNode:
def __init__(self):
self.crop_w_start = -1
# 订阅图像信息
self.image_input = cv.imread('img/black.jpg')
while True:
self.timer_callback()
time.sleep(0.06)
def crop_size(self, height, width):
"""
裁剪感兴趣的区域
"""
return (1 * height // 3, height, width // 4, 3 * width // 4)
def get_contour_data(self, mask, out):
"""
Return the centroid of the largest contour in
the binary image 'mask' (the line)
and return the side in which the smaller camera_namecontour is (the track mark)
(If there are any of these contours),
and draw all contours on 'out' image
"""
# 获取轮廓
contours, _ = cv.findContours(mask, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_NONE)
mark = {}
line = {}
for contour in contours:
M = cv.moments(contour)
# Search more about Image Moments on Wikipedia :)
if M['m00'] > MIN_AREA:
# if countor.area > MIN_AREA:
if (M['m00'] > MIN_AREA_TRACK):
# Contour is part of the track
line['x'] = self.crop_w_start + int(M["m10"] / M["m00"])
line['y'] = int(M["m01"] / M["m00"])
# plot the area in light blue
cv.drawContours(out, contour, -1, (255, 255, 0), 1)
cv.putText(out, str(M['m00']), (int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"])),
cv.FONT_HERSHEY_PLAIN, 2, (255, 255, 0), 2)
else:
# Contour is a track mark
if (not mark) or (mark['y'] > int(M["m01"] / M["m00"])):
# if there are more than one mark, consider only
# the one closest to the robot
mark['y'] = int(M["m01"] / M["m00"])
mark['x'] = self.crop_w_start + int(M["m10"] / M["m00"])
# plot the area in pink
cv.drawContours(out, contour, -1, (255, 0, 255), 1)
cv.putText(out, str(M['m00']), (int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"])),
cv.FONT_HERSHEY_PLAIN, 2, (255, 0, 255), 2)
if mark and line:
# if both contours exist
if mark['x'] > line['x']:
mark_side = "right"
else:
mark_side = "left"
else:
mark_side = None
return (line, mark_side)
def timer_callback(self):
global error
# global image_input
global just_seen_line
global just_seen_right_mark
global should_move
global right_mark_count
global finalization_countdown
# Wait for the first image to be received
if type(self.image_input) != np.ndarray:
return
# 获取宽度和高度
height, width, _ = self.image_input.shape
image = self.image_input.copy()
# 裁剪感兴趣的区域
crop_h_start, crop_h_stop, self.crop_w_start, crop_w_stop = self.crop_size(height, width)
# 获取感兴趣的图片区域
crop = image[crop_h_start:crop_h_stop, self.crop_w_start:crop_w_stop]
# 获取二值化的图 非0表示直线
# (过滤颜色值 只看到轮廓)
mask = cv.inRange(crop, lower_bgr_values, upper_bgr_values)
# get the centroid of the biggest contour in the picture,
# and plot its detail on the cropped part of the output image
# 获取图片中最大轮廓的质心,
# 并在输出图像的裁剪部分绘制其细节
output = image
line, mark_side = self.get_contour_data(mask, output[crop_h_start:crop_h_stop, self.crop_w_start:crop_w_stop])
# also get the side in which the track mark "is"
print(f'line:{line}')
print(f'mask_side:{mark_side}')
# message = Twist()
if line:
# if there even is a line in the image:
# (as the camera could not be reading any lines)
x = line['x']
# error:= The difference between the center of the image
# and the center of the line
error = x - width // 2
# message.linear.x = LINEAR_SPEED
just_seen_line = True
# plot the line centroid on the image
cv.circle(output, (line['x'], crop_h_start + line['y']), 5, (0, 255, 0), 7)
else:
# There is no line in the image.
# Turn on the spot to find it again.
if just_seen_line:
just_seen_line = False
error = error * LOSS_FACTOR
# message.linear.x = 0.0
if mark_side != None:
print("mark_side: {}".format(mark_side))
if (mark_side == "right") and (finalization_countdown == None) and \
(abs(error) <= MAX_ERROR) and (not just_seen_right_mark):
right_mark_count += 1
if right_mark_count > 1:
# Start final countdown to stop the robot
finalization_countdown = int(FINALIZATION_PERIOD / TIMER_PERIOD) + 1
print("Finalization Process has begun!")
just_seen_right_mark = True
else:
just_seen_right_mark = False
# Determine the speed to turn and get the line in the center of the camera.
# message.angular.z = float(error) * -KP
# print("Error: {} | Angular Z: {}, ".format(error, message.angular.z))
# Plot the boundaries where the image was cropped
cv.rectangle(output, (self.crop_w_start, crop_h_start), (crop_w_stop, crop_h_stop), (0, 0, 255), 2)
# Uncomment to show the binary picture
# cv2.imshow("mask", mask)
# Show the output image to the user
cv.imshow("output", output)
# Print the image for 5milis, then resume execution
cv.waitKey(5)
# Check for final countdown
if finalization_countdown != None:
if finalization_countdown > 0:
finalization_countdown -= 1
elif finalization_countdown == 0:
should_move = False
if __name__ == '__main__':
MyNode()