As you all know, Zoom has become the veritable king of online-meeting platforms, and is widely used by corporates as well as Universities.
Online meetings however come with their own set of problems, for example, every time you want to change anything in video, you have to go to zoom settings first. But isn’t that impractical, especially when you are presenting something and immediately want to zoom-in or blur the background. What if you need to turn the camera off right away because your roommate has unknowingly entered the camera frame. Today we will solve all these problems using Python and computer vision.
- Setting Up the Development Environment
1.1 Here’s a list of the project requirements:
- Virtual camera
- Detection of hand gestures
- Detect if the hand is present
- Detect gestures made by the user
- Detection of more than one person
- Stop the camera if more than one person is detected
- Stop the camera if no one is detected
- Selfie segmentation
- Separate the person from the background
- Blur the background
1.2 Finding the Solutions
Start by finding a way to connect the output of the camera with Zoom. For this, you’ll need a virtual camera. If you have ever used any video-calling software, you must have seen there is an option for selecting camera input.
The Integrated Webcam output takes input directly from the webcam, its frames therefore cannot be edited or changed without a mediator. You’ll thus need a system that not only takes input from the webcam, but also lets you manipulate and then publish the edited frames.
This flowchart shows exactly what a virtual camera lets you do:
After zeroing onto a virtual camera, the next step is to automate the process of manipulation and publishing with the help of Python. We will use pyvirtualcam to send the edited frames to the virtual camera.
Now that you have a frame that can be edited, start planning a pipeline that can make appropriate changes to the frame, before it is sent to the webcam.
It’s a good idea to begin with processes that you’ll be running forever, like:
- Hand Gestures
- Face Detection
When we go looking for something that can detect our hand gestures and identify them correctly, MediaPipe Hands wins hands down. That’s because MediaPipe Hands can detect the points on hands and effectively track them. It also tells us which gesture we made. So, our pipeline has MediaPipe Hands running all through, right from the beginning, trying to detect hands and recognize any gestures they make.
Similarly, you can use MediaPipe Face Detection to find the number of faces in a frame.
Next, try blurring the background, using MediaPipe Selfie Segmentation.
Check out this final pipeline for it performs all our requirements!
A good part of this project relies on MediaPipe and its modules, so it is important you understand exactly how we use them. To learn what else MediaPipe can do, visit our blog post on MediaPipe.
2.1 MediaPipe Hands
First, learn how the Coordinate system works, only then use MediaPipe Hands.
Before you start coding, it’s important you know that the y axis is inverted.
The MediaPipe Hands module will return coordinates of 20 points on fingers.
Note: It’s easy to detect gestures using a SVC or a DL model. As MediaPipe Hands already does the heavy work of getting the coordinates, simply use these coordinates for gesture detection.
- Our first gesture is a pinch to zoom and check if the fist is closed.
- Basically, we want to know the distance between these two points: the INDEX_FINGER_TIP and the THUMB_TIP.
- Also, ensure that the following points: MIDDLE_FINGER_TIP, RING_FINGER_TIP and PINKY_TIP are below the MIDDLE_FINGER_MCP, RING_FINGER_MCP and the PINKY_MCP respectively.
- The next gesture is Hand up.
A simple check that tells whether the WRIST is below the MIDDLE_FINGER_TIP or not is enough for this.
- For Hand down, this check is reversed.
You check if the WRIST is truly above the MIDDLE_FINGER_TIP.
- For this three-finger gesture, do the following checks:
- Are the INDEX_FINGER_TIP, MIDDLE_FINGER_TIP and the RING_FINGER_TIP above the INDEX_FINGER_MCP, MIDDLE_FINGER_MCP and the RING_FINGER_MCP respectively?
- Is the PINKY_TIP below the PINKY_MCP?
- Finally, for the two-finger gesture, check if:
- the INDEX_FINGER_TIP and the MIDDLE_FINGER_TIP are actually above the
INDEX_FINGER_MCP and the MIDDLE_FINGER_MCP respectively
- the PINKY_TIP and RING_FINGER_TIP are below the PINKY_MCP and RING_FINGER_MCP respectively.
2.2 MediaPipe Face Detection
Here, MediaPipe detects the faces and you simply fetch its findings.
2.3 MediaPipe Selfie Segmentation
MediaPipe Selfie Segmentation allows us to separate a person from his or her background.
MediaPipe Selfie Segmentation creates a mask around the person and gives us a matrix sized the same as the image. However, you still need to convert the 2-D matrix into 3-D to match the images.
Consider the above image: The white part is the foreground, whereas the black area forms its background. Simply use this information to change the background.
3. Setting Up the Development Environment
Create a new folder and in it a file called
Add the following contents to this file:
absl-py==1.0.0 attrs==21.4.0 cycler==0.11.0 fonttools==4.33.3 kiwisolver==1.4.2 matplotlib==3.5.1 mediapipe==0.8.9.1 numpy==1.22.3 opencv-contrib-python==126.96.36.199 packaging==21.3 Pillow==9.1.0 protobuf==3.20.1 pyparsing==3.0.8 python-dateutil==2.8.2 pyvirtualcam==0.9.1 six==1.16.0
Now, run these commands:
python3 -m venv zoom-gestures source zoom-gestures/bin/activate pip3 install -r requirements.txt
Virtual Camera Setup
For linux:- Install v4l2loopback
Note: Follow the documentation of pyvirtualcam, whether you are on windows or mac.
sudo modprobe v4l2loopback
Create empty Python files, in line with the structure given below.
. ├── main.py ├── requirements.txt ├── utils │ ├── face_detection.py │ ├── faceutils.py │ ├── fingerutils.py │ ├── handsutils.py │ ├── __init__.py │ └── zoomutils.py
You are now ready for coding!
Note:- For in-depth information on the code functions, do refer to the comments given supporting them.
We begin here with
To make things easy, go define all the finger points we discussed in the MediaPipe Hands section.
WRIST = 0 INDEX_FINGER_PIP = 6 INDEX_FINGER_TIP = 8 MIDDLE_FINGER_MCP = 9 MIDDLE_FINGER_PIP = 10 MIDDLE_FINGER_TIP = 12 RING_FINGER_MCP = 13 RING_FINGER_PIP = 14 RING_FINGER_TIP = 16 PINKY_MCP = 17 PINKY_PIP = 18 PINKY_TIP = 20
Once the fingers are defined, create a function for each gesture. Each of these functions intakes a landmark array, which it fetches from MediaPipe Hands.
def is_fist_closed(points): """ Args: points: landmarks from mediapipe Returns: boolean check if fist is closed """ return points[MIDDLE_FINGER_MCP].y < points[MIDDLE_FINGER_TIP].y and points[PINKY_MCP].y < points[PINKY_TIP].y and \ points[RING_FINGER_MCP].y < points[RING_FINGER_TIP].y def hand_down(points): """ Args: points: landmarks from mediapipe Returns: boolean check if hand is down i.e. inverted """ return points[MIDDLE_FINGER_TIP].y > points[WRIST].y def hand_up(points): """ Args: points: landmarks from mediapipe Returns: boolean check if hand is up """ return points[MIDDLE_FINGER_TIP].y < points[WRIST].y def two_signal(points): """ Args: points: landmarks from mediapipe Returns: boolean check if fingers show two """ return points[INDEX_FINGER_TIP].y < points[INDEX_FINGER_PIP].y and points[MIDDLE_FINGER_TIP].y < points[ MIDDLE_FINGER_PIP].y and points[RING_FINGER_PIP].y < points[ RING_FINGER_TIP].y and \ points[PINKY_PIP].y < \ points[PINKY_TIP].y def three_signal(points): """ Args: points: landmarks from mediapipe Returns: boolean check if fingers show three """ return points[INDEX_FINGER_TIP].y < points[INDEX_FINGER_PIP].y and points[MIDDLE_FINGER_TIP].y < points[ MIDDLE_FINGER_PIP].y and points[RING_FINGER_PIP].y > points[ RING_FINGER_TIP].y and \ points[PINKY_PIP].y < \ points[PINKY_TIP].y
Now, it’s time for face detection.
You just have to get the total number of detections.
So, go to file
import cv2 import mediapipe as mp def face_detect(image): """ Args: image: frame captured by camera Returns: The number of faces """ # Use Mediapipe face detection mp_face_detection = mp.solutions.face_detection # choose face detection criteria with mp_face_detection.FaceDetection( model_selection=0, min_detection_confidence=0.5) as face_detection: # Make the image non-writable since the detection needs no write access # Doing so also improves performance image.flags.writeable = False # Convert image from RGB to BGR image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # process the image results = face_detection.process(image) # If any face is detected return the number of faces if results.detections: return len(results.detections)
Next, go to
import cv2import mediapipe as mpimport numpy as np def background_blur(image): """ Args: image: frame captured by camera Returns: The image with a blurred background """ mp_selfie_segmentation = mp.solutions.selfie_segmentation with mp_selfie_segmentation.SelfieSegmentation( model_selection=1) as selfie_segmentation: # Convert Image to RGB from BGR image = cv2.cvtColor(cv2.flip(image, 1), cv2.COLOR_BGR2RGB) # Make image readable before processing to increase the performance image.flags.writeable = False results = selfie_segmentation.process(image) image.flags.writeable = True # Convert Image to BGR from RGB image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) # Add a bilateral filter mask = cv2.ximgproc.jointBilateralFilter(np.uint8(results.segmentation_mask), image, 15, 5, 5) # Create a condition for blurring the background condition = np.stack( (results.segmentation_mask,) * 3, axis=-1) > 0.1 # Remove map the image on blurred background output_image = np.where(condition, image, mask) # Flip the output output_image = cv2.flip(output_image, 1) return output_image
Here, you create a condition to check if:
- the coordinates given in the segmentation mask are greater than 1.0, which indicates foreground
- or these coordinates are less than 1.0, indicating they belong to the background
We can blur the background and apply this filter to the image and then return it.
To bring this all together, let us go to
Import all the necessary modules and initialize the global variables. Also, initialize the MediaPipe Hands module and use it to detect the presence of hands.
# Imports import math import cv2 import numpy as np import mediapipe as mp from . import faceutils from . import face_detection from . import fingerutils from . import zoomutils # Set all status to false video_status = False blur_status = False detect_face_status = False # Set default Zoom Factor zoom_factor = 1 # Initialization of mediapipe Hands mp_hands = mp.solutions.hands hands = mp_hands.Hands() """MediaPipe Hands() """
Create a function called
mediapipe_gestures, which takes two inputs:
- Firstly, the image
- Secondly, a cropped frame, which helps reduce the processing load. (More on this when we discuss the main function.)
def mediapipe_gestures(img, cropped_img): """ Args: img: current frame cropped_img: cropped image for hands detection Returns: frame with applied effects """ # Crop the image for area specific detection cropped_img_rgb = cv2.cvtColor(cropped_img, cv2.COLOR_BGR2RGB) # Fetch the results on cropped image results = hands.process(cropped_img_rgb) # Set global variable values global video_status global zoom_factor global blur_status global detect_face_status # Detect faces detect_face = face_detection.face_detect(img) if detect_face is None or detect_face != 1: detect_face_status = True if detect_face == 1: detect_face_status = False # Create frame with a black img stopped_img = np.zeros([100, 100, 3], dtype=np.uint8)
Detect the finger gestures and perform actions based on it.
if results.multi_hand_landmarks: for handLms in results.multi_hand_landmarks: zoom_arr =  # coordinates of points on index finger and thumb top h, w, c = img.shape landmarks = handLms.landmark for lm_id, lm in enumerate(landmarks): # Convert landmark coordinates to actual image coordinates cx, cy = int(lm.x * w), int(lm.y * h) # Append the coordinates if lm_id == 4 or lm_id == 8: zoom_arr.append((cx, cy)) # Check if hand is inverted or down if fingerutils.hand_down(landmarks): video_status = True # Check if three signal is given if fingerutils.three_signal(landmarks): blur_status = True # Check if two signal is given if fingerutils.two_signal(landmarks): blur_status = False # Check if hand is up and continue the capture if fingerutils.hand_up(landmarks): video_status = False # Check if fingers are detected fists are closed and hand is up so video is on if len(zoom_arr) > 1 and fingerutils.is_fist_closed(landmarks) and fingerutils.hand_up(landmarks): p1 = zoom_arr p2 = zoom_arr # Calculate the distance between two fingertips dist = math.sqrt(pow(p1 - p2, 2) + pow(p1 - p2, 2)) # Zoom in or out if 150 <= dist <= 300: zoom_factor = zoomutils.fetch_zoom_factor(dist) # If the hand was down or there is more than one person in frame if video_status is True or detect_face_status is True: img = stopped_img # If blur is on blur the image if blur_status: img = faceutils.background_blur(img) # Zoom the image according to the needs img = zoomutils.zoom_center(img, zoom_factor) return img
# Imports import cv2 import pyvirtualcam from pyvirtualcam import PixelFormat from utils import handsutils import platform def main(): # Start video capture and set defaults device_val = None cap = cv2.VideoCapture(0) pref_width = 1280 pref_height = 720 pref_fps = 30 cap.set(cv2.CAP_PROP_FRAME_WIDTH, pref_width) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, pref_height) cap.set(cv2.CAP_PROP_FPS, pref_fps) width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) fps = cap.get(cv2.CAP_PROP_FPS) os = platform.system() if os == "Linux": device_val = "/dev/video2" with pyvirtualcam.Camera(width, height, fps, device=device_val, fmt=PixelFormat.BGR) as cam: print('Virtual camera device: ' + cam.device) while True: success, img = cap.read() cropped_img = img[0:720, 0:400] img = handsutils.mediapipe_gestures(img, cropped_img) img = cv2.resize(img, (1280, 720)) cam.send(img) cam.sleep_until_next_frame() if __name__ == '__main__': """ Main Function """ main()
Once you capture the frame, just get the processed image, using
medipipe_gestures. It is important to pass a
cropped_image for hand detection because you don’t want the program to go detecting gestures outside your particular region.
You now have a clear understanding of the basic concepts of MediaPipe. Also, you know how multiple modules of MediaPipe can be used together.
One additional fact: Besides Zoom, this program also works on any other software that can detect and use virtual cameras.
More on Mediapipe
|Hang on, the journey doesn’t end here. We have some more exciting blog posts for you to explore!!!|
1. Building a Poor Body Posture Detection and Alert System using MediaPipe
2. Creating Snapchat/Instagram filters using Mediapipe
3. Center Stage for Zoom Calls using MediaPipe
4. Drowsy Driver Detection using Mediapipe
5. Comparing Yolov7 and Mediapipe Pose Estimation models
Never Stop Learning!!!