Startseite bisherige Projekte Tools/Snippets Bücherempfehlungen Publikationen Impressum Datenschutzerklärung

OpenCV, MediaPipe und die Webcam

Herbst 2021


Kann man das tolle Kamera-Bluring in der Videokonferenz eigentlich auch selbst machen? Ja, mit den Frameworks OpenCV und MediaPipe.
Läuft gerade noch erträglich auf einem Raspberry PI 4.


# Blurring the Webcam using OpenCV and Mediapipe
# Partialy from here: https://pythonrepo.com/repo/HxnDev-Live-Background-Blur
# See also https://google.github.io/mediapipe/solutions/selfie_segmentation

# Key Bindings
# f = show time
# g = gray
# space = play old frames (max 60)
# b = blur (none, background, all)
 
import cv2
import mediapipe as mp
import numpy as np
import time

mp_drawing = mp.solutions.drawing_utils
mp_selfie_segmentation = mp.solutions.selfie_segmentation

# Screen size
screenx = 1920
screeny = 1080

# create a black image as big as the screen
bg_img = np.zeros((screeny, screenx, 3), np.uint8)
bg_img[:, 0:screeny] = (0, 0, 0)

BG_COLOR = (192, 192, 192) # gray

cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 320);
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 240);

# Some experiments:
#cap.set(cv2.CAP_PROP_FPS, 0.1); # set fps
#cap.set(cv2.CAP_PROP_FOURCC, cv2.FOURCC('M', 'J', 'P', 'G'));
#codec = 0x47504A4D # MJPG
#cap.set(cv2.CAP_PROP_FOURCC, codec)

# Experiment with these for checking lag:
cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
cap.set(cv2.CAP_PROP_BUFFERSIZE, 2)

window_name = "window"
cv2.namedWindow(window_name, cv2.WND_PROP_FULLSCREEN)
cv2.setWindowProperty(window_name, cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)

font = cv2.FONT_HERSHEY_SIMPLEX

# for calculation of time per frame
framecount = 0
starttime = 0
captime = 0
proctime = 0

# status
blur = 0 # start with no blur
showtime = False
hide = False # space pressed
gray = False

# for storing old frames
old_frames = []
old_frameindex = 0
old_framedir = 1
MAX_NUM_OLD_FRAMES = 60

with mp_selfie_segmentation.SelfieSegmentation(
    model_selection=1) as selfie_segmentation:
    
  bg_image = None
  
  while cap.isOpened():
      
    starttime = time.time()
    
    success, image = cap.read()
    
    captime = time.time()
    
    if not success:
        print("Ignoring empty camera frame.")
        continue

    if not hide: # no space pressed
        
        if blur == 1:
            # segment and blur background
            image = cv2.cvtColor(cv2.flip(image, 1), cv2.COLOR_BGR2RGB)
            image.flags.writeable = False
            results = selfie_segmentation.process(image)

            image.flags.writeable = True
            image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)

            condition = np.stack(
              (results.segmentation_mask,) * 3, axis=-1) > 0.1
            # The background can be customized.
            #   a) Load an image (with the same width and height of the input image) to
            #      be the background, e.g., bg_image = cv2.imread('/path/to/image/file')
            #   b) Blur the input image by applying image filtering, e.g.,
            #      bg_image = cv2.GaussianBlur(image,(55,55),0)
            if bg_image is None:
              # create new bg image
              bg_image = cv2.GaussianBlur(image,(55,55),0)
              
            if (framecount % 30) == 0:
                bg_image = cv2.GaussianBlur(image,(55,55),0) # update bg image
            framecount += 1
            
            output_image = np.where(condition, image, bg_image)
        elif blur == 0:
            # no blur, just flip
            output_image = cv2.flip(image, 1)
        else:
            # blur all
            output_image = cv2.flip(image, 1)
            output_image = cv2.GaussianBlur(output_image,(55,55),0)

    if hide:
        # space was pressed, play old frames
        output_image = old_frames[old_frameindex]
        old_frameindex += old_framedir
        if old_frameindex == 0 or old_frameindex == len(old_frames)-1: # bounds reached, change direction
            old_framedir *= -1;
    else:
        # remember old frames
        if len(old_frames) > MAX_NUM_OLD_FRAMES: 
            old_frames = old_frames[1:]
        old_frames.append(output_image)
        

    # calc resized size
    scale = 4.0
    nw = int(output_image.shape[1] * scale)
    nh = int(output_image.shape[0] * scale)
    
    # resize
    output_image = cv2.resize(output_image, (nw, nh), interpolation = cv2.INTER_NEAREST)
    
    proctime = time.time()
    
    if showtime:
        cv2.putText(output_image, "C "+str((captime-starttime)*100//1)+", P "+str((proctime-captime)*100//1), (7, 70), font, 3, (100, 255, 0), 3, cv2.LINE_AA)

    # position in black image:
    # calculate the offsets required to center the image
    x_offset = (screenx - nw)//2
    y_offset = (screeny - nh)//2
    
    # insert the smaller image into the larger image, centered
    bg_img[y_offset:y_offset+output_image.shape[0], x_offset:x_offset+output_image.shape[1]] = output_image
    
    disp_img = bg_img;
    
    if gray:
        disp_img = cv2.cvtColor(bg_img, cv2.COLOR_BGR2GRAY)
        
	# show result
    cv2.imshow(window_name, disp_img)
        
    # check keyboard input
    key = cv2.waitKey(5) & 0xFF
    if key == 27: # escape
        break
    elif key == 98: # b
        blur = (blur + 1)%3
    elif key == 32: # space
        hide = not hide
        old_frameindex = len(old_frames)-1
        old_framedir = -1
    elif key == 102: # f
        showtime = not showtime
    elif key == 103: # g
        gray = not gray
    #else:
    #    print(key)
    
cap.release()
cv2.destroyAllWindows()






Impressum - Datenschutzerklärung