| 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') |
| } |
|
|