1

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)
Christoph Rackwitz
  • 11,317
  • 4
  • 27
  • 36
aaagge
  • 13
  • 3
  • assuming the gloves are not a perfect color, it might be better to change the hue – ti7 May 24 '23 at 17:20

1 Answers1

0

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

Stefan
  • 355
  • 2
  • 6
  • I tried omitting the cv2.cvtColor() and it worked, thank you. But I still don't know much about the color spaces so I still don't get how it works. The only thing I know about BGR and RGB is the order of the channel. May I ask how the blue looks like orange if I omit cv2.cvtColor()? and will it be like that for all shades of blue? – aaagge May 26 '23 at 15:11
  • @aaagge Well, the reason is precisely what you mentioned - the channel order. If you don't apply `cv2.cvtColor()`, then you pass a BGR image to MediaPipe, and it treats it as an RGB image (so, for example, where it expects to find a Red channel, it actually has a Blue channel). And yes, this should apply to all shades of blue (to some extent). However, I suggest you take a look at the hue-adjustment script I added to the answer, it can help you fine-tune the hue offset so that your particular glove colour is closer to whatever skin-colour MediaPipe works with. – Stefan May 27 '23 at 08:44
  • I did not know that Mediapipe always treat the input as an RGB image so I was confused at first. But now I know how it works, thanks to your explanation. And I also checked out the hue-adjustment script that you added. I deeply appreciate it since it was really helpful on finding the right hue offset for the gloves. – aaagge May 28 '23 at 14:17