Фикс: поддержка WebP через PIL fallback

This commit is contained in:
2026-08-14 11:41:48 +07:00
parent a38fc7d889
commit ff6216cdf3
+61 -33
View File
@@ -3,7 +3,7 @@ import cv2
import logging import logging
import numpy as np import numpy as np
import requests import requests
from PIL import Image, ImageFilter, ImageEnhance from PIL import Image
logger = logging.getLogger(__name__) 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" 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: def get_crop_coordinates(image_bytes: bytes) -> tuple | None:
""" """
Ищет лицо на фото и возвращает координаты квадратного кропа. Ищет лицо на фото и возвращает координаты квадратного кропа.
@@ -19,39 +39,47 @@ def get_crop_coordinates(image_bytes: bytes) -> tuple | None:
Returns: Returns:
(crop_x1, crop_y1, crop_x2, crop_y2) или None если лицо не найдено. (crop_x1, crop_y1, crop_x2, crop_y2) или None если лицо не найдено.
""" """
img = cv2.imdecode(np.frombuffer(image_bytes, np.uint8), cv2.IMREAD_COLOR) try:
if img is None: 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 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: 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: 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)} return {"success": False, "error": str(e)}