im_process / app /image_processing.py
Jaizxzx's picture
Added more image editing features.
bdce3ce
import numpy as np
import cv2
import rembg
from PIL import Image
import io
from rembg.bg import (
alpha_matting_cutout,
apply_background_color,
naive_cutout
)
class ImageProcessor:
"""
Comprehensive image processing class for background removal and image transformations
"""
BG_REMOVAL_METHODS = {
"basic": 0,
"alpha_matting": 1,
"colored_background": 2,
"mask_only": 3
}
@staticmethod
def remove_background(
input_image: np.ndarray,
method: str | int = "basic",
foreground_threshold: int = 240,
background_threshold: int = 10,
erode_size: int = 10,
bg_color: tuple = (173, 216, 230, 255) # Light blue by default
) -> np.ndarray:
"""
Remove background from the input image using various methods
Args:
input_image (np.ndarray): Input image array
method (str | int): Background removal method or its ID
- "basic" or 0: Default rembg removal
- "alpha_matting" or 1: Alpha matting method
- "colored_background" or 2: Colored background
- "mask_only" or 3: Only output the mask
foreground_threshold (int): Threshold for foreground in alpha matting
background_threshold (int): Threshold for background in alpha matting
erode_size (int): Size of erode structure for alpha matting
bg_color (tuple): RGBA color tuple for colored background
Returns:
np.ndarray: Processed image with modified background
"""
try:
# Convert numpy array to PIL Image if needed
if not isinstance(input_image, Image.Image):
input_image = Image.fromarray(input_image)
# Convert method to string if it's an integer
if isinstance(method, int):
method = {v: k for k, v in ImageProcessor.BG_REMOVAL_METHODS.items()}.get(method, "basic")
# Convert method to lowercase
method = method.lower()
if method == "basic":
output = rembg.remove(input_image)
elif method == "alpha_matting":
mask = rembg.remove(input_image, only_mask=True)
output = alpha_matting_cutout(
input_image,
mask=mask,
foreground_threshold=foreground_threshold,
background_threshold=background_threshold,
erode_structure_size=erode_size
)
elif method == "colored_background":
mask = rembg.remove(input_image, only_mask=True)
cutout = naive_cutout(input_image, mask)
output = apply_background_color(cutout, bg_color)
elif method == "mask_only":
output = rembg.remove(input_image, only_mask=True)
else:
print(f"Unknown method '{method}', falling back to basic removal")
output = rembg.remove(input_image)
# Convert back to numpy array
return np.array(output)
except Exception as e:
print(f"Background removal error: {e}")
return np.array(input_image)
@staticmethod
def apply_image_filter(
image: np.ndarray,
filter_type: str = "none"
) -> np.ndarray:
"""
Apply various image filters
Args:
image (np.ndarray): Input image
filter_type (str): Type of filter to apply
Returns:
np.ndarray: Processed image
"""
# Ensure image is in the right format
if image.shape[-1] == 4: # If RGBA, convert to RGB
image = cv2.cvtColor(image, cv2.COLOR_RGBA2RGB)
filters = {
"grayscale": lambda img: cv2.cvtColor(img, cv2.COLOR_RGB2GRAY),
"sepia": lambda img: ImageProcessor._apply_sepia(img),
"blur": lambda img: cv2.GaussianBlur(img, (5, 5), 0),
"sharpen": lambda img: ImageProcessor._sharpen_image(img),
"edge_detect": lambda img: cv2.Canny(img, 100, 200),
"none": lambda img: img,
"emboss": lambda img: ImageProcessor._apply_emboss(img),
"sketch": lambda img: ImageProcessor._apply_sketch(img),
"watercolor": lambda img: ImageProcessor._apply_watercolor(img),
"invert": lambda img: cv2.bitwise_not(img)
}
# Apply selected filter
return filters.get(filter_type.lower(), filters["none"])(image)
@staticmethod
def _apply_sepia(image: np.ndarray) -> np.ndarray:
"""Apply sepia tone to image"""
sepia_matrix = np.array([
[0.272, 0.534, 0.131],
[0.349, 0.686, 0.168],
[0.393, 0.769, 0.189]
])
sepia_image = cv2.transform(image, sepia_matrix)
return np.clip(sepia_image, 0, 255).astype(np.uint8)
@staticmethod
def _sharpen_image(image: np.ndarray) -> np.ndarray:
"""Sharpen image using kernel"""
kernel = np.array([
[-1, -1, -1],
[-1, 9, -1],
[-1, -1, -1]
])
return cv2.filter2D(image, -1, kernel)
@staticmethod
def _apply_emboss(image: np.ndarray) -> np.ndarray:
"""Apply emboss effect"""
kernel = np.array([[-2,-1,0], [-1,1,1], [0,1,2]])
return cv2.filter2D(image, -1, kernel) + 128
@staticmethod
def _apply_sketch(image: np.ndarray) -> np.ndarray:
"""Create pencil sketch effect"""
gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
blur = cv2.GaussianBlur(gray, (5,5), 0)
sketch = cv2.divide(gray, blur, scale=256.0)
return cv2.cvtColor(sketch, cv2.COLOR_GRAY2RGB)
@staticmethod
def _apply_watercolor(image: np.ndarray) -> np.ndarray:
"""Create watercolor effect"""
temp = cv2.stylization(image, sigma_s=60, sigma_r=0.6)
return cv2.edgePreservingFilter(temp, flags=1, sigma_s=64, sigma_r=0.2)
@staticmethod
def adjust_image(
image: np.ndarray,
brightness: float = 1.0,
contrast: float = 1.0,
saturation: float = 1.0,
rotation: int = 0,
flip_horizontal: bool = False,
flip_vertical: bool = False
) -> np.ndarray:
"""
Apply various adjustments to the image
Args:
image: Input image
brightness: Brightness factor (0.0 to 2.0)
contrast: Contrast factor (0.0 to 2.0)
saturation: Saturation factor (0.0 to 2.0)
rotation: Rotation angle in degrees
flip_horizontal: Whether to flip horizontally
flip_vertical: Whether to flip vertically
"""
try:
# Convert to float for processing
img_float = image.astype(float)
# Apply brightness
img_float = cv2.multiply(img_float, brightness)
# Apply contrast
mean = np.mean(img_float)
img_float = (img_float - mean) * contrast + mean
# Apply saturation
if len(image.shape) == 3: # Only for color images
hsv = cv2.cvtColor(np.clip(img_float, 0, 255).astype(np.uint8), cv2.COLOR_RGB2HSV)
hsv[:, :, 1] = hsv[:, :, 1] * saturation
img_float = cv2.cvtColor(hsv, cv2.COLOR_HSV2RGB).astype(float)
# Clip values
img_float = np.clip(img_float, 0, 255).astype(np.uint8)
# Apply rotation
if rotation != 0:
center = (image.shape[1] // 2, image.shape[0] // 2)
matrix = cv2.getRotationMatrix2D(center, rotation, 1.0)
img_float = cv2.warpAffine(img_float, matrix, (image.shape[1], image.shape[0]))
# Apply flips
if flip_horizontal:
img_float = cv2.flip(img_float, 1)
if flip_vertical:
img_float = cv2.flip(img_float, 0)
return img_float
except Exception as e:
print(f"Image adjustment error: {e}")
return image
@staticmethod
def convert_to_format(
image: np.ndarray,
format: str = "png"
) -> bytes:
"""Convert image to specific format"""
pil_image = Image.fromarray(image)
byte_arr = io.BytesIO()
pil_image.save(byte_arr, format=format.upper())
return byte_arr.getvalue()
def process_image(
input_image: np.ndarray,
remove_bg: bool = True,
bg_method: str | int = "basic",
filter_type: str = "none",
**bg_params
) -> np.ndarray:
"""
Main image processing function
Args:
input_image (np.ndarray): Input image
remove_bg (bool): Whether to remove background
bg_method (str | int): Background removal method or its ID
filter_type (str): Filter to apply
**bg_params: Additional parameters for background removal
- foreground_threshold (int): For alpha matting
- background_threshold (int): For alpha matting
- erode_size (int): For alpha matting
- bg_color (tuple): For colored background
Returns:
np.ndarray: Processed image
"""
if remove_bg:
input_image = ImageProcessor.remove_background(
input_image,
method=bg_method,
**bg_params
)
processed_image = ImageProcessor.apply_image_filter(
input_image,
filter_type
)
return processed_image