|
|
from __future__ import annotations |
|
|
import numpy as np |
|
|
from PIL import Image |
|
|
from sklearn.cluster import KMeans |
|
|
from .utils import pil_to_np, np_to_pil |
|
|
from .config import Config |
|
|
|
|
|
|
|
|
def apply_uniform_quantization(image: Image.Image, levels: int) -> Image.Image: |
|
|
""" |
|
|
Apply uniform color quantization to reduce color variations. |
|
|
|
|
|
Args: |
|
|
image: Input PIL Image |
|
|
levels: Number of quantization levels per channel |
|
|
|
|
|
Returns: |
|
|
Quantized PIL Image |
|
|
""" |
|
|
img_array = pil_to_np(image) |
|
|
|
|
|
|
|
|
quantized = np.zeros_like(img_array) |
|
|
for channel in range(3): |
|
|
|
|
|
channel_data = img_array[:, :, channel] |
|
|
|
|
|
|
|
|
quantized_channel = np.round(channel_data * (levels - 1)) / (levels - 1) |
|
|
quantized_channel = np.clip(quantized_channel, 0, 1) |
|
|
quantized[:, :, channel] = quantized_channel |
|
|
|
|
|
return np_to_pil(quantized) |
|
|
|
|
|
|
|
|
def apply_kmeans_quantization(image: Image.Image, k_colors: int) -> Image.Image: |
|
|
""" |
|
|
Apply K-means clustering for color quantization. |
|
|
|
|
|
Args: |
|
|
image: Input PIL Image |
|
|
k_colors: Number of colors to reduce to |
|
|
|
|
|
Returns: |
|
|
Quantized PIL Image |
|
|
""" |
|
|
img_array = pil_to_np(image) |
|
|
h, w, c = img_array.shape |
|
|
|
|
|
|
|
|
pixels = img_array.reshape(-1, c) |
|
|
|
|
|
|
|
|
kmeans = KMeans(n_clusters=k_colors, random_state=42, n_init=10) |
|
|
kmeans.fit(pixels) |
|
|
|
|
|
|
|
|
labels = kmeans.labels_ |
|
|
quantized_pixels = kmeans.cluster_centers_[labels] |
|
|
|
|
|
|
|
|
quantized_img = quantized_pixels.reshape(h, w, c) |
|
|
|
|
|
return np_to_pil(quantized_img) |
|
|
|
|
|
|
|
|
def apply_color_quantization(image: Image.Image, config: Config) -> Image.Image: |
|
|
""" |
|
|
Apply color quantization based on configuration. |
|
|
|
|
|
Args: |
|
|
image: Input PIL Image |
|
|
config: Configuration object |
|
|
|
|
|
Returns: |
|
|
Quantized PIL Image |
|
|
""" |
|
|
if config.use_uniform_q: |
|
|
return apply_uniform_quantization(image, config.q_levels) |
|
|
elif config.use_kmeans_q: |
|
|
return apply_kmeans_quantization(image, config.k_colors) |
|
|
else: |
|
|
|
|
|
return image |
|
|
|
|
|
|
|
|
def analyze_quantization_effect(original: Image.Image, quantized: Image.Image) -> dict: |
|
|
""" |
|
|
Analyze the effect of quantization on the image. |
|
|
|
|
|
Args: |
|
|
original: Original image |
|
|
quantized: Quantized image |
|
|
|
|
|
Returns: |
|
|
Dictionary with analysis results |
|
|
""" |
|
|
orig_array = pil_to_np(original) |
|
|
quant_array = pil_to_np(quantized) |
|
|
|
|
|
|
|
|
diff = np.abs(orig_array - quant_array) |
|
|
|
|
|
|
|
|
mse = np.mean((orig_array - quant_array) ** 2) |
|
|
psnr = 20 * np.log10(1.0 / np.sqrt(mse)) if mse > 0 else float('inf') |
|
|
|
|
|
|
|
|
orig_colors = len(np.unique(orig_array.reshape(-1, 3), axis=0)) |
|
|
quant_colors = len(np.unique(quant_array.reshape(-1, 3), axis=0)) |
|
|
|
|
|
return { |
|
|
'mse': float(mse), |
|
|
'psnr': float(psnr), |
|
|
'mean_difference': float(np.mean(diff)), |
|
|
'max_difference': float(np.max(diff)), |
|
|
'original_colors': orig_colors, |
|
|
'quantized_colors': quant_colors, |
|
|
'color_reduction_ratio': orig_colors / quant_colors if quant_colors > 0 else float('inf') |
|
|
} |
|
|
|