| |
| |
|
|
| import numpy as np |
| from PIL import Image |
| from collections import Counter |
|
|
| class ContentClassifier: |
| """Lightweight content detection - FROZEN.""" |
| |
| def __init__(self): |
| self.categories = [ |
| 'simple_graphic', 'human_hair', 'human', 'anime', |
| 'logo_icon', 'product_white_bg', 'general_photo', 'complex' |
| ] |
| |
| def classify(self, image: Image.Image) -> dict: |
| """Classify image content.""" |
| if image.mode != 'RGB': |
| image = image.convert('RGB') |
| |
| small = image.copy() |
| small.thumbnail((150, 150), Image.Resampling.LANCZOS) |
| np_img = np.array(small) |
| h, w = np_img.shape[:2] |
| |
| signals = { |
| 'dimensions': (w, h), |
| 'aspect_ratio': h / w if w > 0 else 0, |
| 'border_uniformity': self._border_uniformity(np_img), |
| 'edge_density': self._edge_density(np_img), |
| 'color_complexity': self._color_complexity(small), |
| 'skin_score': self._skin_score(np_img), |
| 'border_connected': self._border_connected(np_img), |
| 'texture': self._texture(np_img) |
| } |
| |
| category = self._classify(signals) |
| confidence = self._calculate_confidence(category, signals) |
| |
| return { |
| 'category': category, |
| 'confidence': confidence, |
| 'signals': signals |
| } |
| |
| def _border_uniformity(self, np_img: np.ndarray) -> float: |
| h, w = np_img.shape[:2] |
| border_pixels = [] |
| step = max(1, min(h, w) // 10) |
| |
| for x in range(0, w, step): |
| border_pixels.append(tuple(np_img[0, x][:3])) |
| border_pixels.append(tuple(np_img[h-1, x][:3])) |
| for y in range(0, h, step): |
| border_pixels.append(tuple(np_img[y, 0][:3])) |
| border_pixels.append(tuple(np_img[y, w-1][:3])) |
| |
| if not border_pixels: |
| return 0.0 |
| |
| unique = len(set(border_pixels)) |
| return 1 - (unique / len(border_pixels)) |
| |
| def _edge_density(self, np_img: np.ndarray) -> float: |
| gray = np.mean(np_img, axis=2).astype(np.float32) |
| grad_x = np.abs(gray[:, 1:] - gray[:, :-1]) |
| grad_y = np.abs(gray[1:, :] - gray[:-1, :]) |
| |
| edges = (grad_x > 25).sum() + (grad_y > 25).sum() |
| total = (gray.shape[0] - 1) * gray.shape[1] + gray.shape[0] * (gray.shape[1] - 1) |
| |
| return edges / total if total > 0 else 0 |
| |
| def _color_complexity(self, image: Image.Image) -> float: |
| quantized = image.quantize(colors=32) |
| unique = len(quantized.getcolors()) |
| return min(1.0, unique / 32) |
| |
| def _skin_score(self, np_img: np.ndarray) -> float: |
| h, w = np_img.shape[:2] |
| pixels = [] |
| step = max(1, min(h, w) // 5) |
| for y in range(0, h, step): |
| for x in range(0, w, step): |
| pixels.append(np_img[y, x][:3]) |
| |
| skin = 0 |
| for r, g, b in pixels: |
| if (r > 60 and g > 40 and b > 20 and |
| r > g and r > b and |
| abs(r - g) < 60 and abs(r - b) < 60): |
| skin += 1 |
| |
| return skin / len(pixels) if pixels else 0 |
| |
| def _border_connected(self, np_img: np.ndarray) -> float: |
| h, w = np_img.shape[:2] |
| border_colors = set() |
| border_colors.add(tuple(np_img[0, 0][:3])) |
| border_colors.add(tuple(np_img[0, w-1][:3])) |
| border_colors.add(tuple(np_img[h-1, 0][:3])) |
| border_colors.add(tuple(np_img[h-1, w-1][:3])) |
| |
| interior_colors = set() |
| step = max(1, min(h, w) // 4) |
| for y in range(h//4, 3*h//4, step): |
| for x in range(w//4, 3*w//4, step): |
| interior_colors.add(tuple(np_img[y, x][:3])) |
| |
| overlap = border_colors & interior_colors |
| return len(overlap) / max(len(border_colors), 1) |
| |
| def _texture(self, np_img: np.ndarray) -> float: |
| gray = np.mean(np_img, axis=2).astype(np.float32) |
| variance = np.var(gray) |
| return min(1.0, variance / 5000) |
| |
| def _classify(self, signals: dict) -> str: |
| s = signals |
| |
| |
| if (s['color_complexity'] < 0.3 and |
| s['edge_density'] > 0.3 and |
| s['border_uniformity'] > 0.7): |
| return 'simple_graphic' |
| |
| |
| if (s['skin_score'] > 0.15 and |
| s['texture'] > 0.3 and |
| s['edge_density'] > 0.2): |
| return 'human_hair' |
| |
| |
| if s['skin_score'] > 0.1: |
| return 'human' |
| |
| |
| if (s['skin_score'] < 0.08 and |
| s['color_complexity'] > 0.3 and |
| s['texture'] < 0.3 and |
| s['edge_density'] > 0.3): |
| return 'anime' |
| |
| |
| if (s['edge_density'] > 0.4 and |
| s['color_complexity'] < 0.3 and |
| s['border_uniformity'] > 0.6): |
| return 'logo_icon' |
| |
| |
| if (s['border_uniformity'] > 0.6 and |
| s['color_complexity'] < 0.5 and |
| s['edge_density'] < 0.4): |
| return 'product_white_bg' |
| |
| |
| if s['color_complexity'] > 0.3: |
| return 'general_photo' |
| |
| return 'complex' |
| |
| def _calculate_confidence(self, category: str, signals: dict) -> float: |
| base = 0.7 |
| |
| if category == 'simple_graphic': |
| if signals['border_uniformity'] > 0.8: |
| base += 0.2 |
| elif category == 'human_hair': |
| if signals['skin_score'] > 0.2 and signals['texture'] > 0.4: |
| base += 0.2 |
| elif category == 'human': |
| if signals['skin_score'] > 0.15: |
| base += 0.2 |
| |
| return min(1.0, base) |