1. # import rclpy
    2. # from rclpy.node import Node
    3. # from sensor_msgs.msg import Image
    4. # import cv_bridge
    5. import time
    6. import numpy as np
    7. import cv2 as cv
    8. # from geometry_msgs.msg import Twist
    9. ## User-defined parameters: (Update these values to your liking)
    10. # Minimum size for a contour to be considered anything
    11. MIN_AREA = 500
    12. # Minimum size for a contour to be considered part of the track
    13. MIN_AREA_TRACK = 5000
    14. # Robot's speed when following the line
    15. LINEAR_SPEED = 0.2
    16. # Proportional constant to be applied on speed when turning
    17. # (Multiplied by the error value)
    18. KP = 1.5 / 100
    19. # If the line is completely lost, the error value shall be compensated by:
    20. LOSS_FACTOR = 1.2
    21. # Send messages every $TIMER_PERIOD seconds
    22. TIMER_PERIOD = 0.06
    23. # When about to end the track, move for ~$FINALIZATION_PERIOD more seconds
    24. FINALIZATION_PERIOD = 4
    25. # The maximum error value for which the robot is still in a straight line
    26. MAX_ERROR = 30
    27. # BGR values to filter only the selected color range
    28. lower_bgr_values = np.array([0, 0, 0])
    29. upper_bgr_values = np.array([180, 255, 46])
    30. finalization_countdown = None
    31. right_mark_count = 0
    32. just_seen_right_mark = None
    33. just_seen_line = None
    34. class MyNode:
    35. def __init__(self):
    36. self.crop_w_start = -1
    37. # 订阅图像信息
    38. self.image_input = cv.imread('img/black.jpg')
    39. while True:
    40. self.timer_callback()
    41. time.sleep(0.06)
    42. def crop_size(self, height, width):
    43. """
    44. 裁剪感兴趣的区域
    45. """
    46. return (1 * height // 3, height, width // 4, 3 * width // 4)
    47. def get_contour_data(self, mask, out):
    48. """
    49. Return the centroid of the largest contour in
    50. the binary image 'mask' (the line)
    51. and return the side in which the smaller camera_namecontour is (the track mark)
    52. (If there are any of these contours),
    53. and draw all contours on 'out' image
    54. """
    55. # 获取轮廓
    56. contours, _ = cv.findContours(mask, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_NONE)
    57. mark = {}
    58. line = {}
    59. for contour in contours:
    60. M = cv.moments(contour)
    61. # Search more about Image Moments on Wikipedia :)
    62. if M['m00'] > MIN_AREA:
    63. # if countor.area > MIN_AREA:
    64. if (M['m00'] > MIN_AREA_TRACK):
    65. # Contour is part of the track
    66. line['x'] = self.crop_w_start + int(M["m10"] / M["m00"])
    67. line['y'] = int(M["m01"] / M["m00"])
    68. # plot the area in light blue
    69. cv.drawContours(out, contour, -1, (255, 255, 0), 1)
    70. cv.putText(out, str(M['m00']), (int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"])),
    71. cv.FONT_HERSHEY_PLAIN, 2, (255, 255, 0), 2)
    72. else:
    73. # Contour is a track mark
    74. if (not mark) or (mark['y'] > int(M["m01"] / M["m00"])):
    75. # if there are more than one mark, consider only
    76. # the one closest to the robot
    77. mark['y'] = int(M["m01"] / M["m00"])
    78. mark['x'] = self.crop_w_start + int(M["m10"] / M["m00"])
    79. # plot the area in pink
    80. cv.drawContours(out, contour, -1, (255, 0, 255), 1)
    81. cv.putText(out, str(M['m00']), (int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"])),
    82. cv.FONT_HERSHEY_PLAIN, 2, (255, 0, 255), 2)
    83. if mark and line:
    84. # if both contours exist
    85. if mark['x'] > line['x']:
    86. mark_side = "right"
    87. else:
    88. mark_side = "left"
    89. else:
    90. mark_side = None
    91. return (line, mark_side)
    92. def timer_callback(self):
    93. global error
    94. # global image_input
    95. global just_seen_line
    96. global just_seen_right_mark
    97. global should_move
    98. global right_mark_count
    99. global finalization_countdown
    100. # Wait for the first image to be received
    101. if type(self.image_input) != np.ndarray:
    102. return
    103. # 获取宽度和高度
    104. height, width, _ = self.image_input.shape
    105. image = self.image_input.copy()
    106. # 裁剪感兴趣的区域
    107. crop_h_start, crop_h_stop, self.crop_w_start, crop_w_stop = self.crop_size(height, width)
    108. # 获取感兴趣的图片区域
    109. crop = image[crop_h_start:crop_h_stop, self.crop_w_start:crop_w_stop]
    110. # 获取二值化的图 非0表示直线
    111. # (过滤颜色值 只看到轮廓)
    112. mask = cv.inRange(crop, lower_bgr_values, upper_bgr_values)
    113. # get the centroid of the biggest contour in the picture,
    114. # and plot its detail on the cropped part of the output image
    115. # 获取图片中最大轮廓的质心,
    116. # 并在输出图像的裁剪部分绘制其细节
    117. output = image
    118. line, mark_side = self.get_contour_data(mask, output[crop_h_start:crop_h_stop, self.crop_w_start:crop_w_stop])
    119. # also get the side in which the track mark "is"
    120. print(f'line:{line}')
    121. print(f'mask_side:{mark_side}')
    122. # message = Twist()
    123. if line:
    124. # if there even is a line in the image:
    125. # (as the camera could not be reading any lines)
    126. x = line['x']
    127. # error:= The difference between the center of the image
    128. # and the center of the line
    129. error = x - width // 2
    130. # message.linear.x = LINEAR_SPEED
    131. just_seen_line = True
    132. # plot the line centroid on the image
    133. cv.circle(output, (line['x'], crop_h_start + line['y']), 5, (0, 255, 0), 7)
    134. else:
    135. # There is no line in the image.
    136. # Turn on the spot to find it again.
    137. if just_seen_line:
    138. just_seen_line = False
    139. error = error * LOSS_FACTOR
    140. # message.linear.x = 0.0
    141. if mark_side != None:
    142. print("mark_side: {}".format(mark_side))
    143. if (mark_side == "right") and (finalization_countdown == None) and \
    144. (abs(error) <= MAX_ERROR) and (not just_seen_right_mark):
    145. right_mark_count += 1
    146. if right_mark_count > 1:
    147. # Start final countdown to stop the robot
    148. finalization_countdown = int(FINALIZATION_PERIOD / TIMER_PERIOD) + 1
    149. print("Finalization Process has begun!")
    150. just_seen_right_mark = True
    151. else:
    152. just_seen_right_mark = False
    153. # Determine the speed to turn and get the line in the center of the camera.
    154. # message.angular.z = float(error) * -KP
    155. # print("Error: {} | Angular Z: {}, ".format(error, message.angular.z))
    156. # Plot the boundaries where the image was cropped
    157. cv.rectangle(output, (self.crop_w_start, crop_h_start), (crop_w_stop, crop_h_stop), (0, 0, 255), 2)
    158. # Uncomment to show the binary picture
    159. # cv2.imshow("mask", mask)
    160. # Show the output image to the user
    161. cv.imshow("output", output)
    162. # Print the image for 5milis, then resume execution
    163. cv.waitKey(5)
    164. # Check for final countdown
    165. if finalization_countdown != None:
    166. if finalization_countdown > 0:
    167. finalization_countdown -= 1
    168. elif finalization_countdown == 0:
    169. should_move = False
    170. if __name__ == '__main__':
    171. MyNode()