From ff6216cdf36fb6450471fbf01a18de9696a44532 Mon Sep 17 00:00:00 2001 From: dddennnisss Date: Fri, 14 Aug 2026 11:41:48 +0700 Subject: [PATCH] =?UTF-8?q?=D0=A4=D0=B8=D0=BA=D1=81:=20=D0=BF=D0=BE=D0=B4?= =?UTF-8?q?=D0=B4=D0=B5=D1=80=D0=B6=D0=BA=D0=B0=20WebP=20=D1=87=D0=B5?= =?UTF-8?q?=D1=80=D0=B5=D0=B7=20PIL=20fallback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- photo_bot/photo_processor.py | 94 +++++++++++++++++++++++------------- 1 file changed, 61 insertions(+), 33 deletions(-) diff --git a/photo_bot/photo_processor.py b/photo_bot/photo_processor.py index b6cde0e..8d8ed9b 100644 --- a/photo_bot/photo_processor.py +++ b/photo_bot/photo_processor.py @@ -3,7 +3,7 @@ import cv2 import logging import numpy as np import requests -from PIL import Image, ImageFilter, ImageEnhance +from PIL import Image logger = logging.getLogger(__name__) @@ -12,6 +12,26 @@ API_URL = "https://profile.sibcem.ru/SibCemProfile/api/photo-bot/upload/" API_KEY = "sibcem_bot_2026_a7f3k9m2x8q1w5e4r6t" +def _image_to_cv2(image_bytes: bytes): + """Конвертирует байты изображения в OpenCV матрицу (поддержка webp).""" + # Сначала пробуем cv2.imdecode + nparr = np.frombuffer(image_bytes, np.uint8) + img = cv2.imdecode(nparr, cv2.IMREAD_COLOR) + if img is not None: + return img + + # Если не сработало, пробуем через PIL (для webp и других форматов) + try: + pil_img = Image.open(io.BytesIO(image_bytes)) + if pil_img.mode != 'RGB': + pil_img = pil_img.convert('RGB') + img = cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR) + return img + except Exception as e: + logger.error(f"Не удалось открыть изображение: {e}") + return None + + def get_crop_coordinates(image_bytes: bytes) -> tuple | None: """ Ищет лицо на фото и возвращает координаты квадратного кропа. @@ -19,38 +39,46 @@ def get_crop_coordinates(image_bytes: bytes) -> tuple | None: Returns: (crop_x1, crop_y1, crop_x2, crop_y2) или None если лицо не найдено. """ - img = cv2.imdecode(np.frombuffer(image_bytes, np.uint8), cv2.IMREAD_COLOR) - if img is None: + try: + img = _image_to_cv2(image_bytes) + if img is None: + logger.warning("Не удалось декодировать изображение") + return None + + gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + + cascade_path = cv2.data.haarcascades + 'haarcascade_frontalface_default.xml' + face_cascade = cv2.CascadeClassifier(cascade_path) + faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=8, minSize=(50, 50)) + + if len(faces) == 0: + logger.warning("Лицо не найдено на фото") + return None + + if len(faces) > 1: + faces = sorted(faces, key=lambda f: f[2] * f[3], reverse=True) + + x, y, w, h = faces[0] + + margin = int(w * 0.3) + y1 = max(0, y - int(margin * 1.5)) + y2 = min(img.shape[0], y + h + margin) + x1 = max(0, x - margin) + x2 = min(img.shape[1], x + w + margin) + + side = min(x2 - x1, y2 - y1) + center_x, center_y = (x1 + x2) // 2, (y1 + y2) // 2 + crop_x1 = max(0, center_x - side // 2) + crop_y1 = max(0, center_y - side // 2) + crop_x2 = crop_x1 + side + crop_y2 = crop_y1 + side + + logger.info(f"Лицо найдено: ({x},{y},{w},{h}), кроп: ({crop_x1},{crop_y1},{crop_x2},{crop_y2})") + return (crop_x1, crop_y1, crop_x2, crop_y2) + + except Exception as e: + logger.error(f"Ошибка в get_crop_coordinates: {e}", exc_info=True) return None - - gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) - - cascade_path = cv2.data.haarcascades + 'haarcascade_frontalface_default.xml' - face_cascade = cv2.CascadeClassifier(cascade_path) - faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=8, minSize=(50, 50)) - - if len(faces) == 0: - return None - - if len(faces) > 1: - faces = sorted(faces, key=lambda f: f[2] * f[3], reverse=True) - - x, y, w, h = faces[0] - - margin = int(w * 0.3) - y1 = max(0, y - int(margin * 1.5)) - y2 = min(img.shape[0], y + h + margin) - x1 = max(0, x - margin) - x2 = min(img.shape[1], x + w + margin) - - side = min(x2 - x1, y2 - y1) - center_x, center_y = (x1 + x2) // 2, (y1 + y2) // 2 - crop_x1 = max(0, center_x - side // 2) - crop_y1 = max(0, center_y - side // 2) - crop_x2 = crop_x1 + side - crop_y2 = crop_y1 + side - - return (crop_x1, crop_y1, crop_x2, crop_y2) def _upload_to_api(image_bytes: bytes, user_id: str, coords: tuple) -> dict: @@ -121,5 +149,5 @@ def prepare_ad_photo(input_path: str, user_id: str, output_path: str = None, tar } except Exception as e: - logger.error(f"Ошибка в prepare_ad_photo: {e}") + logger.error(f"Ошибка в prepare_ad_photo: {e}", exc_info=True) return {"success": False, "error": str(e)}