Pimp my webcam (stream) project
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

154 lines
6.1 KiB

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
background.py
Replace/insert different backgrounds
History:
--------
25.05.20/KQ Initial (modularized) version
27.05.20/KQ Variable naming corrected
30.05.20/KQ Tight blur & non-blocking keyboard
22.06.20/KQ Actual greenscreen function added
"""
import numpy as np
import cv2
import sys
import select
import os
import kbhit
current_R_min = 0
current_G_min = 121
current_B_min = 0
current_R_max = 141
current_G_max = 255
current_B_max = 151
current_sign = 0
kb = kbhit.KBHit() # Activate keyboard access
def do_alternate_background(current_frame, background_frame, alternate_frame, bBlur):
"""Processing pipeline to insert an alternate background (in-place!)
modelled with help from https://medium.com/fnplus/blue-or-green-screen-effect-with-open-cv-chroma-keying-94d4a6ab2743
"""
_delta_frame = cv2.absdiff(current_frame, background_frame) # Delta between background & current frame
_lower_black = np.array([0, 0, 0])
_upper_black = np.array([80, 80, 80])
_mask = cv2.inRange(_delta_frame, _lower_black, _upper_black) # Yields black&white
### Attn.: We're operating on an inverse _mask!
### dilate actually increases (white) background
### erode actually increases (black) foreground
_kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5)) # Clear artefacts
_mask = cv2.dilate(_mask, _kernel, iterations=1)
_kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (9,9)) # Clear artefacts
_mask = cv2.erode(_mask, _kernel, iterations=7)
_kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3)) # Clear artefacts
_mask = cv2.dilate(_mask, _kernel, iterations=1)
# Ref. https://stackoverflow.com/questions/10316057/filling-holes-inside-a-binary-object
if False: # Fairly subjective improvement, bounding box keeps mask more steady ...
des = cv2.bitwise_not(_mask)
contour,hier = cv2.findContours(des, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
for cnt in contour:
#cv2.drawContours(des, [cnt], 0, 255, -1)
hull = cv2.convexHull(cnt)
cv2.drawContours(des, [hull], 0, 255, -1)
_mask = cv2.bitwise_not(des)
_kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (47,47)) # Shrink again
_mask = cv2.dilate(_mask, _kernel, iterations=1) # along bounding box boundaries
_masked_image = np.copy(current_frame) # Pick current frame
_cropped = _masked_image[70:445, 70:570] # Select 'ROI' only: y1:y2, x1:x2
_masked_image = cv2.resize(_cropped, (640,480)) # w/h -> upscale!
_masked_image[_mask != 0] = [0, 0, 0] # From foreground take modified part (us!)
# Now add alternate background
if bBlur:
alternate_frame2 = cv2.blur(current_frame.copy(), (127,127)) #(63,63))
else:
alternate_frame2 = alternate_frame.copy()
alternate_frame2[_mask == 0] = [0,0,0]
# Finally mix both together
return _masked_image + alternate_frame2
def init_rgb_filter(background_frame):
"""Simple binning to adapt filter to dominant background colour
"""
global current_R_min,current_G_min,current_B_min
global current_R_max,current_G_max,current_B_max,current_sign
blurred_bg_frame = cv2.blur(background_frame, (127,127)) #(63,63))
bg_min_frame = cv2.resize(blurred_bg_frame,(4,3)) # Fast complexity reduction! BGR colour scheme
#rgb_mean = np.mean(bg_min_frame, axis=0) #Collapse to 1 x 4 x [R,G,B] mean
rgb_mean = np.mean(bg_min_frame, axis=(0,1)) # Collapse straight to [R,G,B] mean
DELTA = 50 # TODO: Test!
current_R_min = int(rgb_mean[0]-DELTA) # Now use means + tolerance for filter!
current_R_max = int(rgb_mean[0]+DELTA)
current_G_min = int(rgb_mean[1]-DELTA)
current_G_max = int(rgb_mean[1]+DELTA)
current_B_min = int(rgb_mean[2]-DELTA)
current_B_max = int(rgb_mean[2]+DELTA)
_rgb_msg = "R("+str(current_R_min)+".."+str(current_R_max)+")" + \
"G("+str(current_G_min)+".."+str(current_G_max)+")" + \
"B("+str(current_B_min)+".."+str(current_B_max)+")"
print(_rgb_msg,file=sys.stderr)
def do_alternate_greenscreen(current_frame, background_frame, alternate_frame, bBlur):
"""Processing pipeline to insert an alternate background (in-place!) w/ help of a 'greenscreen'
modelled with help from https://medium.com/fnplus/blue-or-green-screen-effect-with-open-cv-chroma-keying-94d4a6ab2743
"""
global current_R_min,current_G_min,current_B_min
global current_R_max,current_G_max,current_B_max,current_sign
_lower_green = np.array([current_R_min, current_G_min, current_B_min]) # RGB/BGR
_upper_green = np.array([current_R_max, current_G_max, current_B_max])
_mask = cv2.inRange(current_frame, _lower_green, _upper_green) # Yields black & white
_masked_image = np.copy(current_frame) # Pick current frame
_masked_image[_mask != 0] = [0, 0, 0] # From foreground take modified part (us!)
# Now add alternate background
if bBlur:
alternate_frame2 = cv2.blur(current_frame.copy(), (127,127)) #(63,63))
else:
alternate_frame2 = alternate_frame.copy()
alternate_frame2[_mask == 0] = [0,0,0]
if kb.kbhit():
c = kb.getch()
if c == '-':
current_sign = -1
if c == '+':
current_sign = 1
if c == 'r':
current_R_min = current_R_min + current_sign
if c == 'R':
current_R_max = current_R_max + current_sign
if c == 'g':
current_G_min = current_G_min + current_sign
if c == 'G':
current_G_max = current_G_max + current_sign
if c == 'b':
current_B_min = current_B_min + current_sign
if c == 'B':
current_B_max = current_B_max + current_sign
_rgb_msg = "R("+str(current_R_min)+".."+str(current_R_max)+")" + \
"G("+str(current_G_min)+".."+str(current_G_max)+")" + \
"B("+str(current_B_min)+".."+str(current_B_max)+")"
print(_rgb_msg,file=sys.stderr)
# Finally mix both together
return _masked_image + alternate_frame2