Search code examples
pythonmediapipe

How to Make the Hand Detection of Mediapipe Work on Hand with Gloves


I was trying to make the hand detection of mediapipe to work on hands with blue gloves in real time. But it was not working properly. The detection can still work on gloves if the color is similar to skintone. So, I tried to do some pre-processing wherein I was changing the blue pixels found on the frame into nude color. The result was it cannot detect the hand accurately - sometimes it can but it would later disappear.

Please help me fix this. I have read somewhere that this hand detection can work on blue gloves in real time by changing the color of the glove to skin tone while retaining the shading of the hand. But I have no idea on how to do that properly. I would deeply appreciate it if you can help me T-T.

def findHands(self, img, draw=True, flipType=True):
 imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

 blue  = [0,0,255]
 nude=[225, 190, 160]

 #Make mask 
 Bmask = np.all(imgRGB == blue, axis=-1)

 #Replace blue pixel into nude
 imgRGB[Bmask] = nude

 img_res=imgRGB

 #Send frame to mediapipe        
 self.results = self.hands.process(img_res)

Solution

  • The simplest solution is to omit the cv2.cvtColor(), which will cause the image channels to swap, effectively making blue look like orange. Here is an example (for mediapipe==0.9.0):

    import cv2
    import mediapipe as mp
    mp_drawing = mp.solutions.drawing_utils
    mp_drawing_styles = mp.solutions.drawing_styles
    mp_hands = mp.solutions.hands
    
    with mp_hands.Hands(
        static_image_mode=True,
        max_num_hands=2,
        min_detection_confidence=0.5) as hands:
            image = cv2.imread("gloves.jpg")
            results = hands.process(image)
    
            if results.multi_hand_landmarks:
                image_height, image_width, _ = image.shape
                for hand_landmarks in results.multi_hand_landmarks:
                    mp_drawing.draw_landmarks(
                        image,
                        hand_landmarks,
                        mp_hands.HAND_CONNECTIONS,
                        mp_drawing_styles.get_default_hand_landmarks_style(),
                        mp_drawing_styles.get_default_hand_connections_style())
            else:
                print("no hands were found")
                    
            cv2.imshow("result", image)
            cv2.waitKey(0)
    

    This should be sufficient for MediaPipe to recognize the hands:

    recognized hands in blue gloves

    Alternatively, you can also make a fine-tuning of the color change using Hue offset in HSV schema (adapted from here). This would be a "better" solution since it only changes the hue but does not swap the channels.

    Edit: I've created a simple Hue-tunning "GUI-like" script that helps to find suitable Hue-offset:

    import cv2
    import mediapipe as mp
    mp_drawing = mp.solutions.drawing_utils
    mp_drawing_styles = mp.solutions.drawing_styles
    mp_hands = mp.solutions.hands
    
    window_title = 'Recognition result'
    trackbar_title = 'Hue offset'
    
    class HueHelper:
        def __init__(self):
            self.mp_hands = mp_hands.Hands(static_image_mode=True,
                                            max_num_hands=2,
                                            min_detection_confidence=0.5)
            self.img_bgr = None
    
        def apply_hue_offset(self,image, hue_offset):# 0 is no change; 0<=huechange<=180
            # convert img to hsv
            img_hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
            h = img_hsv[:,:,0]
            s = img_hsv[:,:,1]
            v = img_hsv[:,:,2]
            # shift the hue
            # cv2 will clip automatically to avoid color wrap-around
            img_hsv = cv2.add(h, hue_offset)
            # combine new hue with s and v
            img_hsv = cv2.merge([img_hsv,s,v])
            # convert from HSV to BGR
            return cv2.cvtColor(img_hsv, cv2.COLOR_HSV2BGR)
    
        def on_trackbar_change(self, trackbar_hue_offset):
            img_bgr_modified = self.recognize(self.img_bgr.copy(), trackbar_hue_offset)
            cv2.imshow(window_title, img_bgr_modified)
    
        def recognize(self, img_bgr, hue_offset):
            img_bgr = self.apply_hue_offset(img_bgr, hue_offset)
            img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
            results = self.mp_hands.process(img_rgb)
    
            if results.multi_hand_landmarks:
                for hand_landmarks in results.multi_hand_landmarks:
                    mp_drawing.draw_landmarks(
                        img_bgr,
                        hand_landmarks,
                        mp_hands.HAND_CONNECTIONS,
                        mp_drawing_styles.get_default_hand_landmarks_style(),
                        mp_drawing_styles.get_default_hand_connections_style())
            else:
                print('no hands were found')
            return img_bgr
        
        def run(self, img_path):
            self.img_bgr = cv2.imread(img_path)
            if self.img_bgr is None: print('Image was not found!')
            self.on_trackbar_change(0)
            # Hue range is 0-179: https://docs.opencv.org/4.x/df/d9d/tutorial_py_colorspaces.html
            cv2.createTrackbar(trackbar_title, window_title, 0, 179, self.on_trackbar_change)
            cv2.waitKey(0)
            cv2.destroyAllWindows()
    
    if __name__ == '__main__':
        h = HueHelper()
        h.run('gloves.jpg')
    
    

    Again, a demo:

    Hue offset helper demo