Welcome to DOFBOT-Ultra repository
6. Block color sorting
Block Color Sorting
Please ensure that the web control program backend process is not running in the background. To close it, refer to the [Get Started Quickly - 2. Access the Terminal] section in [Disable the Auto-Start Web Control Backend Process]. After closing, please restart DofbotUltra!!!
Before starting this function, you need to close the web control program backend process.
For all functions involving cube block grasping, please use 30x30x30 wooden blocks. Non-standard sized objects may cause servo #6 to stall and wear out.
1. Function Description
After the program starts, the robotic arm will perform color recognition based on the set HSV values. After pressing the space bar, the robotic arm will lower its claw to grasp the recognized block, then place it at a designated location. After placement, it will return to the recognition posture.
The vision and grasping logic in this tutorial will automatically switch based on
CAMERA_TYPE, but the camera startup command must be selected according to the actual camera type:
DaBai depth camera: use
dabai_dcw2.launch.pyRegular USB camera: use
usb_cam.launch.py
2. Startup and Operation
2.1. Startup Commands
Enter the following commands in the terminal to start:
x# DaBai depth cameraros2 launch orbbec_camera dabai_dcw2.launch.py
# Regular USB cameraros2 launch dofbot_ultra_info usb_cam.launch.py
# Start low-level control:ros2 run dofbot_ultra_driver arm_driver# Start inverse kinematics program:ros2 run dofbot_ultra_info kinematics_dofbotultra# Start color recognition program:ros2 run dofbot_ultra_color color_detect# Start robotic arm grasping program:ros2 run dofbot_ultra_driver grasp # Set depth camera exposureros2 service call /camera/set_color_exposure orbbec_camera_msgs/srv/SetInt32 '{data: 30}'ros2 service call /camera/set_depth_auto_exposure std_srvs/srv/SetBool '{data: true}'Notes:
The camera startup command must be selected according to the actual camera type.
Except for the camera startup command, the startup methods for other nodes remain consistent.
2.2. Operation Flow
After starting the terminal, place blocks of different colors. When the camera captures an image, use the following keys to process the image:
[i] or [I]: Enter color recognition mode, directly load the HSV values calibrated in the previous program for recognition;
[r] or [R]: Reset program parameters, enter color selection mode, use the mouse to select a region of the block, obtain the HSV values of this region, and release the mouse to enter color recognition mode;
Space bar: Start grasping the recognized color block.
After entering color recognition mode, if the current HSV values still cannot filter out other colors, you can fine-tune the HSV values using the dynamic parameter tuner. Enter the following command in the terminal to start the dynamic parameter tuner:
xxxxxxxxxxros2 run rqt_reconfigure rqt_reconfigure
You can modify the HSV values using sliders.

As shown in the figure below, when the left image (binary) only displays the uniquely recognized color, click on the color image frame and press the space bar. The robotic arm will automatically select the coordinate calculation method based on the current CAMERA_TYPE:
Depth camera mode: Calculate the grasping point combined with the center point depth value;
Regular USB camera mode: Calculate the grasping point combined with plane calibration results.
Subsequently, the robotic arm will lower its claw to grasp the recognized color block.

3. Program Flowchart
color_detect.py

4. Core Code Analysis
4.1. color_detect.py
Code path:
xxxxxxxxxxRaspberry Pi:/home/yahboom/dofbot_ultra_ws/src/dofbot_ultra_color/dofbot_ultra_color/color_detect.pyRDK:/home/sunrise/dofbot_ultra_ws/src/3d/dofbot_ultra_color/dofbot_ultra_color/color_detect.py
astra_common code library path:
xxxxxxxxxxRaspberry Pi:/home/yahboom/dofbot_ultra_ws/src/dofbot_ultra_color/dofbot_ultra_color/astra_common.pyRDK:/home/sunrise/dofbot_ultra_ws/src/3d/dofbot_ultra_color/dofbot_ultra_color/astra_common.py
Import necessary libraries,
xxxxxxxxxximport cv2import rclpyfrom rclpy.node import Nodeimport numpy as npfrom message_filters import ApproximateTimeSynchronizer, Subscriberfrom sensor_msgs.msg import Imagefrom std_msgs.msg import Float32, Boolfrom cv_bridge import CvBridgeimport cv2 as cv
import timeimport mathimport osencoding = ['16UC1', '32FC1']import tf_transformations as tfimport transforms3d as tfs# Import custom image processing libraryfrom dofbot_ultra_color.astra_common import *from dofbot_ultra_interface.msg import *
Initialize program parameters, create publishers, subscribers, etc.,
xxxxxxxxxxdef __init__(self): super().__init__('color_detect') self.declare_param() # Robotic arm posture for recognizing color blocks self.init_joints = [90.0, 120, 0.0, 0.0, 90, 90] # Create publisher for color block information self.pub_ColorInfo = self.create_publisher(AprilTagInfo, "PosInfo", 1) self.pubPoint = self.create_publisher(ArmJoint, "TargetAngle", 1) # Create subscriber for gesture recognition results self.grasp_status_sub = self.create_subscription(Bool, 'grasp_done', self.GraspStatusCallback, 1) # Create two subscribers, subscribing to color image topic and depth image topic self.depth_image_sub = Subscriber(self, Image, "/camera/color/image_raw", qos_profile=1) self.rgb_image_sub = Subscriber(self, Image, "/camera/depth/image_raw", qos_profile=1) # Time-synchronize the subscribed color and depth image messages self.TimeSynchronizer = ApproximateTimeSynchronizer([self.depth_image_sub, self.rgb_image_sub],queue_size=10,slop=0.5) self.TimeSynchronizer.registerCallback(self.Color_Detect) # Save the xy coordinates of the color block center self.y = 0 self.x = 0 # Create bridge for converting color and depth image topic message data to image data self.rgb_bridge = CvBridge() self.depth_bridge = CvBridge() # Initialize region coordinates self.Roi_init = () # Initialize HSV values self.hsv_range = () # Initialize recognized color block information, representing color block center x coordinate, center y coordinate, and minimum enclosing circle radius r self.circle = (0, 0, 0) # Dynamic parameter adjustment flag, True means perform dynamic parameter adjustment self.dyn_update = True # Mouse selection flag self.select_flags = False self.gTracker_state = False self.windows_name = 'frame' self.Track_state = 'init' # Create color detection object self.color = color_detect() # Initialize row and column coordinates of region coordinates self.cols, self.rows = 0, 0 # Initialize mouse-selected xy coordinates self.Mouse_XY = (0, 0) # Center coordinates xy of color block after image processing self.cx = 0 self.cy = 0 # Default HSV threshold file path, this file stores the previously saved HSV values self.hsv_text = "/home/yahboom/dofbot_ultra_ws/src/dofbot_ultra_color/dofbot_ultra_color/colorHSV.text" #RDK:self.hsv_text = "/home/sunrise/dofbot_ultra_ws/src/3d/dofbot_ultra_color/dofbot_ultra_color/colorHSV.text" # Minimum enclosing circle radius of color block after image processing self.circle_r = 0 # Prevent misrecognition of other scattered points # Flag for publishing AprilTag information, publish /PosInfo topic data when True self.pubPos_flag = False
Let's mainly look at the image processing function Color_Detect,
xxxxxxxxxxdef Color_Detect(self,color_frame,depth_frame): #rgb_image # Receive color image topic message, convert message data to image data rgb_image = self.rgb_bridge.imgmsg_to_cv2(color_frame,'bgr8') result_image = np.copy(rgb_image) #depth_image # Receive depth image topic message, convert message data to image data depth_image = self.depth_bridge.imgmsg_to_cv2(depth_frame, encoding[1]) frame = cv.resize(depth_image, (640, 480)) depth_image_info = frame.astype(np.float32) action = cv.waitKey(10) & 0xFF result_image = cv.resize(result_image, (640, 480)) # Pass the obtained color image as a parameter to process, and simultaneously pass the keyboard event action result_frame, binary = self.process(result_image,action) # Determine that self.cx and self.cy are not 0, indicating the color block has been processed, and when the enclosing circle radius is greater than 30, it means a color block meeting the HSV threshold has been detected if self.cx!=0 and self.cy!=0 and self.circle_r>30: if self.cx<=640 or self.cy <=480: center_x, center_y = self.cx,self.cy self.x = int(center_x) self.y = int(center_y) # Create message, calculate depth information and assign values to the data inside pos = AprilTagInfo() pos.x = center_x pos.y = center_y pos.z = depth_image_info[self.y,self.x]/1000 # Determine whether the value of self.pubPos_flag is True, True means the message can be published if self.pubPos_flag == True: self.pub_ColorInfo.publish(pos) self.pubPos_flag = False # Determine whether the binary image exists, if so display both color and binary images, otherwise only display the color image if len(binary) != 0: cv.imshow(self.windows_name, ManyImgs(1, ([result_frame, binary]))) else: cv.imshow(self.windows_name, result_frame)Image processing function self.process,
xxxxxxxxxxdef process(self, rgb_img, action): rgb_img = cv.resize(rgb_img, (640, 480)) binary = [] # Determine key event, when space is pressed, change the information publishing flag status, self.pubPos_flag being True means information topic can be published if action == 32: self.pubPos_flag = True # Determine key event, when i or I is pressed, change state to recognition mode elif action == ord('i') or action == ord('I'): self.Track_state = "identify" # Determine key event, when r or R is pressed, reset all parameters and enter color selection mode elif action == ord('r') or action == ord('R'): self.Reset() # Determine state value, if it is init, it means initial state value, at this time mouse can be used to select region if self.Track_state == 'init': cv.namedWindow(self.windows_name, cv.WINDOW_AUTOSIZE) # Select a region color within the specified window cv.setMouseCallback(self.windows_name, self.onMouse, 0) # Determine color selection flag, true means color can be selected if self.select_flags == True: cv.line(rgb_img, self.cols, self.rows, (255, 0, 0), 2) cv.rectangle(rgb_img, self.cols, self.rows, (0, 255, 0), 2) # Determine whether the selected region exists if self.Roi_init[0] != self.Roi_init[2] and self.Roi_init[1] != self.Roi_init[3]: # Call the Roi_hsv function in the created color detection object self.color, returns the processed color image and HSV values rgb_img, self.hsv_range = self.color.Roi_hsv(rgb_img, self.Roi_init) self.gTracker_state = True self.dyn_update = True else: self.Track_state = 'init' # Determine state value, if it is "identify", it means color recognition can be performed elif self.Track_state == "identify": # Determine whether HSV threshold file exists, if so read the values inside and assign to hsv_range if os.path.exists(self.hsv_text): self.hsv_range = read_HSV(self.hsv_text) # Otherwise change state to init for color selection else: self.Track_state = 'init' if self.Track_state != 'init': # Determine the length of self.hsv_range value, that is, determine whether this value exists, when the length is not 0, enter color detection function if len(self.hsv_range) != 0: # Call the object_follow function in the created color detection object self.color, pass in the color image and self.hsv_range (i.e., HSV threshold), returns the processed color image, binary image, and information of shapes meeting the HSV threshold, including center point coordinates and its minimum enclosing circle radius rgb_img, binary, self.circle,_= self.color.object_follow(rgb_img, self.hsv_range) # Assign the returned values to self.cx and self.cy storing the center values, and assign the minimum enclosing circle radius to self.circle_r self.cx = self.circle[0] self.cy = self.circle[1] self.circle_r = self.circle[2] # Determine dynamic parameter update flag, True means the hsv_text file can be updated and values on the parameter server can be modified if self.dyn_update == True: write_HSV(self.hsv_text, self.hsv_range) params = {'Hmin': self.hsv_range[0][0], 'Hmax': self.hsv_range[1][0], 'Smin': self.hsv_range[0][1], 'Smax': self.hsv_range[1][1], 'Vmin': self.hsv_range[0][2], 'Vmax': self.hsv_range[1][2]} self.dyn_client.update_configuration(params) self.dyn_update = False return rgb_img, binary4.2. grasp.py
Please refer to section 4.2 【grasp.py】 in the tutorial 【3D Sorting and Grasping Course - 1. AprilTag ID sorting】.