feature-editor / utils /image_processing.py
Your Name
Update README.md to reflect new title, features, and usage instructions for the AI-Powered Facial and Body Feature Editor. Adjusted SDK version and enhanced ethical usage notice.
1431308
import numpy as np
import cv2
from PIL import Image
def preprocess_image(image):
"""
Preprocess the input image for AI model processing.
Args:
image (numpy.ndarray): Input image in numpy array format
Returns:
numpy.ndarray: Preprocessed image
"""
# Convert to RGB if needed
if len(image.shape) == 2:
image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB)
elif image.shape[2] == 4:
# Handle RGBA images by removing alpha channel
image = image[:, :, :3]
# Resize if needed (models typically expect specific dimensions)
# Using 512x512 as a common size for diffusion models
height, width = image.shape[:2]
max_dim = 512
if height > max_dim or width > max_dim:
# Maintain aspect ratio
if height > width:
new_height = max_dim
new_width = int(width * (max_dim / height))
else:
new_width = max_dim
new_height = int(height * (max_dim / width))
image = cv2.resize(image, (new_width, new_height), interpolation=cv2.INTER_AREA)
# Normalize pixel values to [0, 1]
image = image.astype(np.float32) / 255.0
return image
def postprocess_image(edited_image, original_image, mask=None):
"""
Postprocess the edited image, blending it with the original if needed.
Args:
edited_image (numpy.ndarray): Edited image from the AI model
original_image (numpy.ndarray): Original input image
mask (numpy.ndarray, optional): Mask used for blending
Returns:
PIL.Image: Final processed image
"""
# Convert back to uint8 range [0, 255]
if edited_image.max() <= 1.0:
edited_image = (edited_image * 255.0).astype(np.uint8)
if original_image.max() <= 1.0:
original_image = (original_image * 255.0).astype(np.uint8)
# Resize edited image to match original if needed
if edited_image.shape[:2] != original_image.shape[:2]:
edited_image = cv2.resize(
edited_image,
(original_image.shape[1], original_image.shape[0]),
interpolation=cv2.INTER_LANCZOS4
)
# If mask is provided, blend the edited and original images
if mask is not None:
# Ensure mask is properly sized
if mask.shape[:2] != original_image.shape[:2]:
mask = cv2.resize(
mask,
(original_image.shape[1], original_image.shape[0]),
interpolation=cv2.INTER_LINEAR
)
# Ensure mask is in proper format (single channel, values between 0 and 1)
if len(mask.shape) > 2:
mask = mask[:, :, 0]
if mask.max() > 1.0:
mask = mask / 255.0
# Apply Gaussian blur to mask for smoother blending
mask = cv2.GaussianBlur(mask, (15, 15), 0)
# Expand mask dimensions for broadcasting
mask_3d = np.expand_dims(mask, axis=2)
mask_3d = np.repeat(mask_3d, 3, axis=2)
# Blend images
blended = (mask_3d * edited_image) + ((1 - mask_3d) * original_image)
final_image = blended.astype(np.uint8)
else:
final_image = edited_image
# Convert to PIL Image for Gradio
return Image.fromarray(final_image)
def apply_quality_matching(edited_image, reference_image):
"""
Match the quality, lighting, and texture of the edited image to the reference image.
Args:
edited_image (numpy.ndarray): Edited image to adjust
reference_image (numpy.ndarray): Reference image to match quality with
Returns:
numpy.ndarray: Quality-matched image
"""
# Convert to LAB color space for better color matching
edited_lab = cv2.cvtColor(edited_image, cv2.COLOR_RGB2LAB)
reference_lab = cv2.cvtColor(reference_image, cv2.COLOR_RGB2LAB)
# Split channels
edited_l, edited_a, edited_b = cv2.split(edited_lab)
reference_l, reference_a, reference_b = cv2.split(reference_lab)
# Match luminance histogram
matched_l = match_histogram(edited_l, reference_l)
# Recombine channels
matched_lab = cv2.merge([matched_l, edited_a, edited_b])
matched_rgb = cv2.cvtColor(matched_lab, cv2.COLOR_LAB2RGB)
# Ensure values are in valid range
matched_rgb = np.clip(matched_rgb, 0, 1.0)
return matched_rgb
def match_histogram(source, reference):
"""
Match the histogram of the source image to the reference image.
Args:
source (numpy.ndarray): Source image channel
reference (numpy.ndarray): Reference image channel
Returns:
numpy.ndarray: Histogram-matched image channel
"""
# Calculate histograms
src_hist, src_bins = np.histogram(source.flatten(), 256, [0, 256], density=True)
ref_hist, ref_bins = np.histogram(reference.flatten(), 256, [0, 256], density=True)
# Calculate cumulative distribution functions
src_cdf = src_hist.cumsum()
src_cdf = src_cdf / src_cdf[-1]
ref_cdf = ref_hist.cumsum()
ref_cdf = ref_cdf / ref_cdf[-1]
# Create lookup table
lookup_table = np.zeros(256)
for i in range(256):
# Find the closest value in ref_cdf to src_cdf[i]
lookup_table[i] = np.argmin(np.abs(ref_cdf - src_cdf[i]))
# Apply lookup table
result = lookup_table[source.astype(np.uint8)]
return result.astype(np.uint8)