Welcome to ROSMASTER M1 PRO repository
9.Finger control
Finger Control
1. Course Content
Run the example program to detect fingers using the mediapipe framework, then calculate the size of the angle formed by the thumb tip, wrist joint, and index finger tip to change the image processing effect.
2. Program Startup
Run the finger control program node in the terminal
xxxxxxxxxxros2 launch yahboomcar_bringup mediapipe_demo.launch.py demo:=FingerCtrl
After the program runs, press the F key to switch the image processing effect, then change the size of the angle by changing the distance between the thumb and the index finger, thereby controlling the image processing effect.


3. Core Source Code Analysis
Program code path:
xxxxxxxxxx~/yahboomcar_ros2_ws/yahboomcar_ws/src/common_demo/common_demo/mediapipe/09_FingerCtrl.py
Import the necessary library files,
xxxxxxxxxximport mathimport timeimport cv2 as cvimport numpy as npimport mediapipe as mpimport rclpyfrom rclpy.node import Nodefrom cv_bridge import CvBridgefrom sensor_msgs.msg import Imagefrom arm_msgs.msg import ArmJointsimport cv2Initialize the data and define the Publisher and Subscriber,
xxxxxxxxxxdef __init__(self, name): super().__init__(name) #Define the image processing effect list self.effect = ["color", "thresh", "blur", "hue", "enhance"] self.volBar = 400 self.pTime = self.cTime = self.volPer = self.value = self.index = 0 #Use the class in the mediapipe library to define a hand object self.mpHand = mp.solutions.hands self.mpDraw = mp.solutions.drawing_utils self.hands = self.mpHand.Hands( static_image_mode=False, max_num_hands=2, min_detection_confidence=0.5, min_tracking_confidence=0.5 ) #Define the properties of the joint connection lines, which will be used in the subsequent joint point connection function self.lmDrawSpec = mp.solutions.drawing_utils.DrawingSpec(color=(0, 0, 255), thickness=-1, circle_radius=15) self.drawSpec = mp.solutions.drawing_utils.DrawingSpec(color=(0, 255, 0), thickness=10, circle_radius=10) self.rgb_bridge = CvBridge() #Define the Subscriber for the color image topic self.sub_rgb = self.create_subscription(Image,"/camera/color/image_raw",self.get_RGBImageCallBack,100)Color image callback function,
xdef get_RGBImageCallBack(self,msg): #Use CvBridge to convert the color image message data into image data frame = self.rgb_bridge.imgmsg_to_cv2(msg, "bgr8") action = cv2.waitKey(1) #Call the function to detect the palm and draw the palm joint connection diagram img = self.findHands(frame) #Call findPosition to obtain the coordinates of the finger joint list lmList = self.findPosition(frame, draw=False) if len(lmList) != 0: #Calculate the angle angle = self.calc_angle(4, 0, 8) x1, y1 = lmList[4][1], lmList[4][2] x2, y2 = lmList[8][1], lmList[8][2] cx, cy = (x1 + x2) // 2, (y1 + y2) // 2 cv.circle(img, (x1, y1), 15, (255, 0, 255), cv.FILLED) cv.circle(img, (x2, y2), 15, (255, 0, 255), cv.FILLED) cv.line(img, (x1, y1), (x2, y2), (255, 0, 255), 3) cv.circle(img, (cx, cy), 15, (255, 0, 255), cv.FILLED) if angle <= 10: cv.circle(img, (cx, cy), 15, (0, 255, 0), cv.FILLED) #Calculate self.volBar = np.interp(angle, [0, 70], [400, 150]) self.volPer = np.interp(angle, [0, 70], [0, 100]) self.value = np.interp(angle, [0, 70], [0, 255]) # print("angle: {},value: {}".format(angle, value)) # Perform threshold binarization: values greater than the threshold value are represented by 255, and values less than the threshold value are represented by 0 if self.effect[self.index]=="thresh": gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY) frame = cv.threshold(gray, self.value, 255, cv.THRESH_BINARY)[1] # Perform Gaussian filtering; (21, 21) means the length and width of the Gaussian matrix are both 21, and the standard deviation is value elif self.effect[self.index]=="blur": frame = cv.GaussianBlur(frame, (21, 21), np.interp(self.value, [0, 255], [0, 11])) # Color space conversion, HSV to BGR elif self.effect[self.index]=="hue": frame = cv.cvtColor(frame, cv.COLOR_BGR2HSV) frame[:, :, 0] += int(self.value) frame = cv.cvtColor(frame, cv.COLOR_HSV2BGR) # Adjust the contrast elif self.effect[self.index]=="enhance": enh_val = self.value / 40 clahe = cv.createCLAHE(clipLimit=enh_val, tileGridSize=(8, 8)) lab = cv.cvtColor(frame, cv.COLOR_BGR2LAB) lab[:, :, 0] = clahe.apply(lab[:, :, 0]) frame = cv.cvtColor(lab, cv.COLOR_LAB2BGR) #Press the F key to switch the processing effect if action == ord('f'): self.index += 1 if self.index >= len(self.effect): self.index = 0
cv.rectangle(img, (50, 150), (85, 400), (255, 0, 0), 3) cv.rectangle(img, (50, int(self.volBar)), (85, 400), (0, 255, 0), cv.FILLED) cv.putText(img, f'{int(self.volPer)}%', (40, 450), cv.FONT_HERSHEY_COMPLEX, 1, (0, 255, 0), 3) #Merge the images dst = self.frame_combine(frame, img) cv.imshow('dst', dst)findPosition obtains the id of each joint and the xy coordinates of each joint,
xxxxxxxxxxdef findPosition(self, frame, draw=True): #Create a detection list to store the detection results self.lmList = [] if self.results.multi_hand_landmarks: #Iterate through the detection results and add them to the self.lmList list, which represents the id of each detected joint and the xy coordinates of the joint for id, lm in enumerate(self.results.multi_hand_landmarks[0].landmark): # print(id,lm) h, w, c = frame.shape cx, cy = int(lm.x * w), int(lm.y * h) # print(id, lm.x, lm.y, lm.z) self.lmList.append([id, cx, cy]) if draw: cv.circle(frame, (cx, cy), 15, (0, 0, 255), cv.FILLED) return self.lmListAs shown in the figure below, the ID of each joint of the finger,

calc_angle calculates the angle; here it calculates the angle of the thumb tip, wrist joint, and index finger tip,
def calc_angle(self, pt1, pt2, pt3): point1 = self.lmList[pt1][1], self.lmList[pt1][2] point2 = self.lmList[pt2][1], self.lmList[pt2][2] point3 = self.lmList[pt3][1], self.lmList[pt3][2] a = self.get_dist(point1, point2) b = self.get_dist(point2, point3) c = self.get_dist(point1, point3) try: radian = math.acos((math.pow(a, 2) + math.pow(b, 2) - math.pow(c, 2)) / (2 * a * b)) angle = radian / math.pi * 180 except: angle = 0 return abs(angle)