126 lines
4.3 KiB
Python
126 lines
4.3 KiB
Python
import io
|
|
import cv2
|
|
import logging
|
|
import numpy as np
|
|
import requests
|
|
from PIL import Image, ImageFilter, ImageEnhance
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Настройки API
|
|
API_URL = "https://profile.sibcem.ru/SibCemProfile/api/photo-bot/upload/"
|
|
API_KEY = "sibcem_bot_2026_a7f3k9m2x8q1w5e4r6t"
|
|
|
|
|
|
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:
|
|
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:
|
|
"""
|
|
Отправляет фото и координаты в Django API.
|
|
Возвращает JSON от сервера.
|
|
"""
|
|
x1, y1, x2, y2 = coords
|
|
try:
|
|
response = requests.post(
|
|
API_URL,
|
|
headers={"X-API-Key": API_KEY},
|
|
data={
|
|
"user_id": user_id,
|
|
"crop_x1": x1,
|
|
"crop_y1": y1,
|
|
"crop_x2": x2,
|
|
"crop_y2": y2,
|
|
},
|
|
files={
|
|
"image": ("photo.jpg", image_bytes, "image/jpeg")
|
|
},
|
|
timeout=30
|
|
)
|
|
response.raise_for_status()
|
|
return response.json()
|
|
except requests.exceptions.RequestException as e:
|
|
logger.error(f"Ошибка API: {e}")
|
|
return {"status": "error", "reason": f"Ошибка сети: {str(e)}"}
|
|
except Exception as e:
|
|
logger.error(f"Ошибка отправки в API: {e}")
|
|
return {"status": "error", "reason": f"Ошибка: {str(e)}"}
|
|
|
|
|
|
def prepare_ad_photo(input_path: str, user_id: str, output_path: str = None, target_size: int = 96) -> dict:
|
|
"""
|
|
Отправляет фото на сервер через API.
|
|
output_path и target_size больше не используются (оставлены для обратной совместимости).
|
|
"""
|
|
try:
|
|
# 1. Читаем байты
|
|
if isinstance(input_path, bytes):
|
|
image_bytes = input_path
|
|
else:
|
|
with open(input_path, 'rb') as f:
|
|
image_bytes = f.read()
|
|
|
|
# 2. Ищем лицо и получаем координаты
|
|
coords = get_crop_coordinates(image_bytes)
|
|
if not coords:
|
|
return {"success": False, "error": "Лицо не найдено! Пожалуйста, сделайте фото анфас при хорошем освещении."}
|
|
|
|
# 3. Отправляем в API
|
|
api_result = _upload_to_api(image_bytes, user_id, coords)
|
|
|
|
# 4. Возвращаем результат
|
|
if api_result.get("status") == "ok":
|
|
return {
|
|
"success": True,
|
|
"photo_id": api_result.get("photo_id"),
|
|
"verdict": api_result.get("verdict"),
|
|
"message": api_result.get("message"),
|
|
}
|
|
else:
|
|
return {
|
|
"success": False,
|
|
"error": api_result.get("reason", "Неизвестная ошибка API")
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Ошибка в prepare_ad_photo: {e}")
|
|
return {"success": False, "error": str(e)}
|