"""
Module: easycv.py
This module defines the function that will make it easier to work with OpenCV. A plugin can directly engage with OpenCV
using *import cv2* statement, but many frequently used operations can be done using the functions in this module.
The methods are all static and there is no need to create an instance of the class CVEssentials
"""
import numpy as np
from .utility import DebugUtility, OSUtility
import threading
# importing OpenCV for each platform
import cv2
[docs]
class CVEssentials:
# This function will convert every black frame in the given image to transparent and return
[docs]
@staticmethod
def RemovePixelsOfColor(image, red, blue, green):
""" Converts all pixels in an image of a certain color to transparent using cv2 and numpy
:param image: The image matrix.
:type image: numpy.ndarray, shape (height, width, channels), dtype uint8 or float32
:param red: The red color value for the color to be removed.
:type red: int (0-255)
:param blue: The blue color value for the color to be removed.
:type blue: int (0-255)
:param green: The green color value for the color to be removed.
:type green: int (0-255)
:returns: bgra with all black pixels converted to transparent
:rtype: numpy.ndarray, shape (height, width, channels), dtype uint8 or float32
"""
# Coinvert from BGR to BGRA
bgra = cv2.cvtColor(image, cv2.COLOR_BGR2BGRA)
try:
# Slice of alpha channel
alpha = bgra[:, :, 3]
# Use logical indexing to set alpha channel to 0 where BGR=0
alpha[np.all(bgra[:, :, 0:3] == (int(blue), int(green), int(red)), 2)] = 0
except Exception as e:
DebugUtility.Err(e, DebugUtility.InspectFrame())
return bgra
# Function to overlay transparent image 2 on image 1
[docs]
@staticmethod
def OverlayTransparent(background_img, img_to_overlay_t, x, y, blur=5, overlay_size=(640,480)):
""" Overlays a transparant PNG onto another image using CV2
:param background_img: The background image
:type background_img: numpy.ndarray, shape (height, width, channels), dtype uint8 or float32
:param img_to_overlay_t: The transparent image to overlay (has alpha channel)
:type img_to_overlay_t: numpy.ndarray, shape (height, width, channels), dtype uint8 or float32
:param x: x location to place the top-left corner of our overlay
:type x: int
:param y: y location to place the top-left corner of our overlay
:type y: int
:param overlay_size: The size to scale our overlay to (tuple), no scaling if None
:type overlay_size: int[]
:param blur: The Blur amount for corner smoothing
:type blur: float
:returns: Background image with overlay on top
:rtype: int[]
"""
bg_img = background_img.copy()
try:
if blur % 2 == 0:
blur += 1
if overlay_size is not None:
img_to_overlay_t = cv2.resize(img_to_overlay_t.copy(), overlay_size)
# Extract the alpha mask of the RGBA image, convert to RGB
b, g, r, a = cv2.split(img_to_overlay_t)
overlay_color = cv2.merge((b, g, r))
# Apply some simple filtering to remove edge noise
# mask = cv2.blur(a, (blur, blur))
mask = cv2.medianBlur(a, blur)
mask = cv2.GaussianBlur(mask, (blur, blur), 0)
h, w, _ = overlay_color.shape
roi = bg_img[y:y + h, x:x + w]
# Black-out the area behind the overlay image in original ROI
img1_bg = cv2.bitwise_and(roi.copy(), roi.copy(), mask=cv2.bitwise_not(mask))
# Mask out the overlay image from the image.
img2_fg = cv2.bitwise_and(overlay_color, overlay_color, mask=mask)
# Update the original image with our new ROI
bg_img[y:y + h, x:x + w] = cv2.add(img1_bg, img2_fg)
except Exception as e:
DebugUtility.Err(e, DebugUtility.InspectFrame())
return bg_img
# Function to remove any range of colors and replace with another frame
[docs]
@staticmethod
def RemoveColorRange(frame, background, brights, darks, dim, blur=0):
""" Removes a range of color pixels from the frame and replaces with background. This function can be used for effects like Green/Blue Screen
:param frame: the main front frame as numpty array
:type frame: numpy.ndarray, shape (height, width, channels), dtype uint8 or float32
:param background: the frame that needs to replace the color range
:param brights: the upper color range of colors in [b, g, r] format
:type brights: float array as [b,g,r] values
:param darks: the lower color range of colors in [b, g, r] format
:type darks: float array as [b,g,r] values
:param dim: dimensions of the frame as tuple (width, height)
:type dim: tuple (width, height)
:returns: frame which has all pixels of provided color range in frame, replaced by background
:rtype: frame as numpy.ndarray, shape (height, width, channels), dtype uint8 or float32
"""
f = frame
if blur % 2 == 0:
blur += 1
try:
frame = cv2.resize(frame, dim)
image = cv2.resize(background, dim)
u_green = np.array(brights)
l_green = np.array(darks)
mask = cv2.inRange(frame, l_green, u_green)
if blur > 0:
mask = cv2.medianBlur(mask, blur)
res = cv2.bitwise_and(frame, frame, mask=mask)
f = frame - res
f = np.where(f == 0, image, f)
except Exception as e:
DebugUtility.Err(e, DebugUtility.InspectFrame())
return f
# Function to replace one frame with the other
[docs]
@staticmethod
def OverlayImage (frame, overlay, xPos, yPos, overlay_size):
""" This function overlays one image over the provided frame at the provided x and y positions scaled to a specific size.
Make sure that the overlay image does not go out of bounds when overlaying.The provided frame should be greater than the size of the overlay plus it's position values.
:param frame: The frame on which the image will be overlayed
:type frame: numpy.ndarray, shape (height, width, channels), dtype uint8 or float32
:param overlay: The image frame to overlay on the original frame
:type overlay: numpy.ndarray, shape (height, width, channels), dtype uint8 or float32
:param xPos: The x position of where the overlay will start
:type xPos: int
:param yPos: The y position of where the overlay will start
:type yPos: int
:param overlay_size: The dimensions of the image to be overlayed
:type overlay_size: tuple (width,height)
:returns: Returns the frame after overlaying image over the current frame
:rtype: numpy.ndarray, shape (height, width, channels), dtype uint8 or float32
"""
if frame.shape[0] < overlay_size[1] or frame.shape[1] < overlay_size[0]:
frame = cv2.putText(img=frame,
text="Error: Overlay Frame Bigger than Original Frame.",
org= (10,50),
fontFace=cv2.FONT_HERSHEY_SIMPLEX,
fontScale=.5,
color=(255, 255, 255))
return frame
else:
overlayedFrame = frame
overlay = cv2.resize(overlay, overlay_size)
xPos = int(xPos)
yPos = int(yPos)
try:
overlayedFrame[yPos:yPos + overlay.shape[0], xPos:xPos + overlay.shape[1]] = overlay
except Exception as e:
DebugUtility.Err(e, DebugUtility.InspectFrame())
return overlayedFrame
# Function to show an imshowwindow in a separate thread
[docs]
@staticmethod
def showPreview(frame):
th = threading.Thread(target=lambda: CVEssentials.showPreviewWindow(frame), daemon=True)
th.start()
[docs]
@staticmethod
def showPreviewWindow(frame):
cv2.imshow("Preview Window", frame)
cv2.waitKey(None)
[docs]
@staticmethod
def resizeWithAspectRatio(curr_height, curr_width, image, scale):
# calculate new animation size which fits into the framesize
ratio = 1.0 * image.shape[1] / image.shape[0]
height = 0
width = 0
# calculate new width and height
height = int(curr_height * scale)
width = int(height * ratio)
if (width > curr_width):
width = curr_width
height = int(width * (1.0 * image.shape[0]/image.shape[1]))
return width, height
[docs]
@staticmethod
def showErrorOnVideo(frame, error_message, color=(0, 0, 255)):
try:
frame = cv2.putText(img=frame,
text="Error: " + error_message,
org=(10, 50),
fontFace=cv2.FONT_HERSHEY_SIMPLEX,
fontScale=.5,
color=color)
except Exception as e:
DebugUtility.Err(e, DebugUtility.InspectFrame())
return frame