Compare commits
16 Commits
4fee048d8b
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 6217b88a47 | |||
| cc16ceb582 | |||
| 4b5e5edf5b | |||
| dd49ff0a28 | |||
| 64c15290d4 | |||
| 7398982c85 | |||
| f7391a6971 | |||
| 4af0dd0685 | |||
|
|
e4feb686ed | ||
|
|
fb900b09d9 | ||
|
|
fab1d257e5 | ||
|
|
b2129bc43e | ||
|
|
615aa26c31 | ||
|
|
cca9612086 | ||
|
|
7e5e739843 | ||
|
|
8cf526a0c4 |
19
.dockerignore
Normal file
19
.dockerignore
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Exclude development and unnecessary files from the Docker build context
|
||||||
|
.git
|
||||||
|
__pycache__
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
.gitignore
|
||||||
|
.dockerignore
|
||||||
|
README.md
|
||||||
|
MyApplication6.rar
|
||||||
|
submissions
|
||||||
|
data/
|
||||||
|
notebooks/catboost_info/
|
||||||
|
notebooks/*.ipynb
|
||||||
|
notebooks/FastMTCNN.py
|
||||||
|
notebooks/tester.py
|
||||||
|
notebooks/saved_dictionary.pkl
|
||||||
|
c4715817-515e-4815-aa0d-bfcc75d45388.jfif
|
||||||
|
models/
|
||||||
|
.env
|
||||||
29
.gitea/workflows/docker-build.yml
Normal file
29
.gitea/workflows/docker-build.yml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
name: Build and push Docker image (Gitea)
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: docker/setup-buildx-action@v3
|
||||||
|
- uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: git.byte-mate.ru
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GIT_PASSWORD }}
|
||||||
|
- uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
git.byte-mate.ru/coder/ufahack2024:latest
|
||||||
|
git.byte-mate.ru/coder/ufahack2024:${{ gitea.sha }}
|
||||||
|
|
||||||
|
# Notes:
|
||||||
|
# - Create the GIT_PASSWORD secret in the Gitea repository settings.
|
||||||
|
# - A Gitea Actions runner must be registered on the server for this
|
||||||
|
# workflow to run.
|
||||||
31
.github/workflows/docker-build.yml
vendored
Normal file
31
.github/workflows/docker-build.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
name: Build and push Docker image (GitHub)
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main ]
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY: ghcr.io
|
||||||
|
IMAGE_NAME: drho1y/ufahack2024
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: docker/setup-buildx-action@v3
|
||||||
|
- uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||||
|
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
|
||||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1,3 +1,8 @@
|
|||||||
.idea
|
.idea
|
||||||
data
|
data
|
||||||
data — копия
|
data — копия
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
.env
|
||||||
|
models/
|
||||||
20
Dockerfile
Normal file
20
Dockerfile
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
FROM python:3.10-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
libgl1 \
|
||||||
|
libglib2.0-0 \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
COPY server/ server/
|
||||||
|
COPY notebooks/model/ models/
|
||||||
|
|
||||||
|
EXPOSE 12345
|
||||||
|
|
||||||
|
ENV HOST=0.0.0.0 PORT=12345 MODEL_DIR=/app/models DATA_DIR=/app/data
|
||||||
|
|
||||||
|
CMD ["python", "-u", "server/server.py"]
|
||||||
BIN
MyApplication6.rar
Normal file
BIN
MyApplication6.rar
Normal file
Binary file not shown.
179
README.md
179
README.md
@@ -1,53 +1,150 @@
|
|||||||
# UfaHack2024
|
# UfaHack2024
|
||||||
<img src="https://images.squarespace-cdn.com/content/v1/6201920e5d62b32f53d158bb/1646055024414-55O3Y8GPH2DM30WXAWVM/Screen%2BShot%2B2021-11-18%2Bat%2B1.18.40%2BPM.png" alt="Face" width="10%" height="10%">
|

|
||||||
Hackaton UfaHack2024 in UFA
|
Хакатон UfaHack2024 в Уфе
|
||||||
|
|
||||||
____
|
---
|
||||||
|
|
||||||
## Данный программный продукт создан с помощью фреймворка для Python Socket, CutBoost, CustomTkinter, а также приложение написанное на Kotin для клиента на Android
|
## Описание проекта
|
||||||
|
Проект представляет систему распознавания лиц актёров (США / СССР-Россия) и блогеров с веб-камеры и по загруженным фотографиям. Пайплайн обработки: MTCNN (детекция лица) → DeepFace (Facenet, выделение эмбеддингов) → CatBoost (градиентный бустинг, классификация).
|
||||||
|
|
||||||
### Код реализации сервера
|
На основе десктопного приложения на CustomTkinter пользователь может запускать распознавание как из файловой папки с фотографиями, так и в реальном времени через веб-камеру. Дополнительно реализован Android-клиент на Kotlin, который отправляет фото на серверную часть по TCP-сокету.
|
||||||
```python
|
|
||||||
import socket
|
|
||||||
|
|
||||||
s = socket.socket()
|
Система обучена на нескольких категориях: актёры США, актёры СССР-Россия и блогеры. Для каждой категории строится отдельная модель CatBoost, использующая эмбеддинги лиц, извлечённые предобученной сетью Facenet.
|
||||||
host = "192.168.120.244"
|
|
||||||
port = 12345
|
|
||||||
s.bind((host, port))
|
|
||||||
s.listen(5)
|
|
||||||
|
|
||||||
while True:
|
## Стек технологий
|
||||||
con, addr = s.accept()
|
- Python (основной язык)
|
||||||
with open('FDJ.jpg', 'wb') as f:
|
- CustomTkinter (графический интерфейс)
|
||||||
while True:
|
- DeepFace + Facenet (извлечение эмбеддингов лиц)
|
||||||
print(1)
|
- MTCNN / FastMTCNN (детекция лиц)
|
||||||
data = con.recv(4096)
|
- CatBoost (градиентный бустинг)
|
||||||
if not data:
|
- OpenCV + opencv-contrib-python (обработка изображений и видео)
|
||||||
break
|
- PyTorch + torchvision (фреймворк для нейросетей)
|
||||||
f.write(data)
|
- CUDA (ускорение на GPU)
|
||||||
con.close()
|
- Kotlin (Android-клиент)
|
||||||
|
|
||||||
|
## Структура проекта
|
||||||
|
```
|
||||||
|
UfaHack2024/
|
||||||
|
├── notebooks/
|
||||||
|
│ ├── app/
|
||||||
|
│ │ ├── main.py # Точка входа, CustomTkinter
|
||||||
|
│ │ ├── Predict_photo.py # Распознавание из папки с фото
|
||||||
|
│ │ ├── Predict_video.py # Распознавание с веб-камеры
|
||||||
|
│ │ ├── PredictServer.py # TCP-сервер (192.168.120.240:12345)
|
||||||
|
│ │ ├── Start.py # Стартовое окно
|
||||||
|
│ │ ├── FastMtcnn.py # Ускоренный MTCNN
|
||||||
|
│ │ └── FDJ.jpg # Тестовое изображение
|
||||||
|
│ ├── model/
|
||||||
|
│ │ ├── saved_dictionary.pkl # Словарь имён (категория)
|
||||||
|
│ │ ├── saved_dictionary_russia.pkl # Словарь имён (Россия)
|
||||||
|
│ │ ├── saved_dictionary_bloggers.pkl # Словарь имён (блогеры)
|
||||||
|
│ │ ├── actors_usa_embeddings.pkl # Эмбеддинги (США)
|
||||||
|
│ │ └── actors_ussr_embeddings.pkl # Эмбеддинги (СССР-Россия)
|
||||||
|
│ ├── saved_dictionary.pkl # Словарь имён (корневой)
|
||||||
|
│ ├── EDA.ipynb # Разведочный анализ данных
|
||||||
|
│ ├── blur_detection.ipynb # Детекция размытия
|
||||||
|
│ ├── catboost.ipynb # Обучение CatBoost
|
||||||
|
│ ├── deepface_notebooks.ipynb # Работа с DeepFace
|
||||||
|
│ ├── FastMTCNN.py # Вспомогательный модуль
|
||||||
|
│ └── tester.py # Тестирование
|
||||||
|
├── MyApplication6.rar # Android-клиент (Kotlin)
|
||||||
|
├── .gitignore
|
||||||
|
├── requirements.txt
|
||||||
|
├── server/
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ └── server.py
|
||||||
|
├── Dockerfile
|
||||||
|
├── docker-compose.yml
|
||||||
|
├── .dockerignore
|
||||||
|
├── .github/
|
||||||
|
│ └── workflows/
|
||||||
|
│ └── docker-build.yml
|
||||||
|
├── .gitea/
|
||||||
|
│ └── workflows/
|
||||||
|
│ └── docker-build.yml
|
||||||
|
└── c4715817-515e-4815-aa0d-bfcc75d45388.jfif
|
||||||
```
|
```
|
||||||
|
|
||||||
### Код реализации клиента
|
## Установка и запуск
|
||||||
```python
|
|
||||||
import socket
|
|
||||||
|
|
||||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
1. Клонируйте репозиторий:
|
||||||
|
```
|
||||||
s.connect(('192.168.120.244', 12345)) # Подключаемся к серверу.
|
git clone https://github.com/DrHo1y/UfaHack2024.git
|
||||||
s.sendall('Hello, Habr!'.encode('utf-8')) # Отправляем фразу.
|
cd UfaHack2024
|
||||||
data = s.recv(1024) # Получаем данные из сокета.
|
cd notebooks/app
|
||||||
print(data.decode())
|
```
|
||||||
while True:
|
2. Установите зависимости (см. раздел Зависимости ниже).
|
||||||
data = s.recv(4096)
|
3. Запустите приложение:
|
||||||
if not data:
|
```
|
||||||
break
|
python main.py
|
||||||
print("Received response: " + data.decode())
|
|
||||||
|
|
||||||
s.close()
|
|
||||||
```
|
```
|
||||||
Для обучения модели исползовали [CutBoost](https://catboost.ai/)
|
|
||||||
|
|
||||||
## Реализация модели
|
### Зависимости
|
||||||

|
|
||||||
|
Все необходимые зависимости (15 пакетов) перечислены в `requirements.txt`. Установка выполняется одной командой:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
## Важные замечания
|
||||||
|
- Файлы `.cbm` (catboost_usa.cbm, catboost_ussr.cbm) **не включены** в репозиторий. Их необходимо обучить из блокнота `catboost.ipynb` или получить отдельно.
|
||||||
|
- Каталоги `data/` с фотографиями находятся в `.gitignore` и **не приложены** к репозиторию. В коде жёстко прописаны абсолютные пути вида `C://Users//fatik//PycharmProjects//UfaHack2024//data//...` — перед запуском требуется скорректировать пути под ваше окружение.
|
||||||
|
- `requirements.txt` содержит все зависимости — см. раздел «Зависимости».
|
||||||
|
- Серверная часть (`PredictServer.py`) ожидает IP-адрес `192.168.120.240:12345` — измените под свою сеть.
|
||||||
|
|
||||||
|
## Приложение для Android
|
||||||
|
В корне репозитория находится архив `MyApplication6.rar` — проект Android-приложения на Kotlin. Клиент подключается к серверу (`PredictServer.py`) по TCP-сокету, отправляет фотографию и получает результат распознавания.
|
||||||
|
|
||||||
|
## Docker / локальная разработка
|
||||||
|
|
||||||
|
В репозитории добавлена конфигурация для запуска headless-сервера распознавания лиц в Docker-контейнере (без GUI). Серверная часть находится в каталоге `server/` — это Python-скрипт без графического интерфейса, который может работать независимо от десктопного приложения.
|
||||||
|
|
||||||
|
### Быстрый старт
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up --build
|
||||||
|
```
|
||||||
|
|
||||||
|
После сборки и запуска сервер будет доступен на порту `12345` (адрес `127.0.0.1:12345`).
|
||||||
|
|
||||||
|
### Ручной запуск (без docker compose)
|
||||||
|
|
||||||
|
Собрать образ:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build -t ufahack2024 .
|
||||||
|
```
|
||||||
|
|
||||||
|
Запустить контейнер:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -p 12345:12345 -v ./models:/app/models ufahack2024
|
||||||
|
```
|
||||||
|
|
||||||
|
Контейнер запускает headless TCP-сервер из `server/server.py`, слушает порт 12345 и требует файлы `.cbm` в каталоге `./models/`. Логи выводятся в stdout. Для выполнения команд необходим установленный Docker CLI.
|
||||||
|
|
||||||
|
### Переменные окружения
|
||||||
|
|
||||||
|
| Переменная | По умолчанию | Описание |
|
||||||
|
|-------------|----------------|----------------------------------|
|
||||||
|
| `HOST` | `0.0.0.0` | Адрес для привязки сокета |
|
||||||
|
| `PORT` | `12345` | Порт TCP-сервера |
|
||||||
|
| `MODEL_DIR` | `/app/models` | Каталог с моделями CatBoost |
|
||||||
|
| `DATA_DIR` | `/data` | Каталог с данными (фотографиями) |
|
||||||
|
|
||||||
|
### Важно
|
||||||
|
|
||||||
|
- Файлы `.cbm` (catboost_usa.cbm и др.) **не включены** в репозиторий. Поместите их в каталог `./models/` на хосте перед запуском контейнера. Без них контейнер запустится, но распознавание будет недоступно (в логах появится предупреждение).
|
||||||
|
- Каталоги `./models/` и `./data/` создаются Docker автоматически, если их нет на хосте.
|
||||||
|
- Файл `.dockerignore` исключает из контекста сборки ненужные файлы (исходники блокнотов, Android-клиент, `.git` и др.).
|
||||||
|
|
||||||
|
### CI / CD
|
||||||
|
|
||||||
|
В репозитории определены два workflow-файла:
|
||||||
|
|
||||||
|
- **GitHub Actions** (`.github/workflows/docker-build.yml`) — при пуше в ветку `main` автоматически собирает образ и публикует его в `ghcr.io/drho1y/ufahack2024` (все символы в нижнем регистре). Аутентификация через `secrets.GITHUB_TOKEN`, теги — `:latest` и `:${{ github.sha }}`.
|
||||||
|
- **Gitea Actions** (`.gitea/workflows/docker-build.yml`) — публикует образ в `git.byte-mate.ru/coder/ufahack2024`. Аутентификация: имя пользователя `${{ github.repository_owner }}`, пароль из секрета `GIT_PASSWORD` (не `GITEA_TOKEN`). Требуется зарегистрированный Gitea Actions runner.
|
||||||
|
|
||||||
|
## Благодарности
|
||||||
|
Для обучения модели использовали [CatBoost](https://catboost.ai/)
|
||||||
20
docker-compose.yml
Normal file
20
docker-compose.yml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
services:
|
||||||
|
server:
|
||||||
|
build: .
|
||||||
|
ports:
|
||||||
|
- "12345:12345"
|
||||||
|
environment:
|
||||||
|
HOST: "0.0.0.0"
|
||||||
|
PORT: "12345"
|
||||||
|
MODEL_DIR: "/app/models"
|
||||||
|
DATA_DIR: "/data"
|
||||||
|
NVIDIA_VISIBLE_DEVICE: ALL
|
||||||
|
runtime: nvidia
|
||||||
|
volumes:
|
||||||
|
- ./server:/app/server
|
||||||
|
- ./models:/app/models
|
||||||
|
- ./data:/data
|
||||||
|
# Notes:
|
||||||
|
# - Place .cbm model files in ./models/ on the host before starting.
|
||||||
|
# - Docker creates missing host directories (./models/, ./data/) automatically
|
||||||
|
# (they will be owned by root on Linux).
|
||||||
BIN
notebooks/model/actors_usa_embeddings.pkl
Normal file
BIN
notebooks/model/actors_usa_embeddings.pkl
Normal file
Binary file not shown.
BIN
notebooks/model/actors_ussr_embeddings.pkl
Normal file
BIN
notebooks/model/actors_ussr_embeddings.pkl
Normal file
Binary file not shown.
BIN
notebooks/model/saved_dictionary.pkl
Normal file
BIN
notebooks/model/saved_dictionary.pkl
Normal file
Binary file not shown.
BIN
notebooks/model/saved_dictionary_bloggers.pkl
Normal file
BIN
notebooks/model/saved_dictionary_bloggers.pkl
Normal file
Binary file not shown.
BIN
notebooks/model/saved_dictionary_russia.pkl
Normal file
BIN
notebooks/model/saved_dictionary_russia.pkl
Normal file
Binary file not shown.
@@ -2,3 +2,14 @@ deepface
|
|||||||
customtkinter
|
customtkinter
|
||||||
torch
|
torch
|
||||||
torchvision
|
torchvision
|
||||||
|
catboost
|
||||||
|
mtcnn
|
||||||
|
opencv-python
|
||||||
|
opencv-contrib-python
|
||||||
|
pandas
|
||||||
|
numpy
|
||||||
|
Pillow
|
||||||
|
facenet-pytorch
|
||||||
|
imutils
|
||||||
|
tqdm
|
||||||
|
scikit-learn
|
||||||
0
server/__init__.py
Normal file
0
server/__init__.py
Normal file
186
server/server.py
Normal file
186
server/server.py
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
"""
|
||||||
|
Headless TCP server for face recognition (UfaHack2024).
|
||||||
|
Reuses the recognition logic from the desktop app but runs standalone in Docker.
|
||||||
|
No GUI dependencies (customtkinter / Tkinter).
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import socket
|
||||||
|
import tempfile
|
||||||
|
import pickle
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import cv2
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
from deepface import DeepFace
|
||||||
|
from mtcnn import MTCNN
|
||||||
|
from catboost import CatBoostClassifier
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Configuration from environment
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
HOST = os.environ.get("HOST", "0.0.0.0")
|
||||||
|
PORT = int(os.environ.get("PORT", "12345"))
|
||||||
|
MODEL_DIR = os.environ.get("MODEL_DIR", "/app/models")
|
||||||
|
DATA_DIR = os.environ.get("DATA_DIR", "/app/data")
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Logging to stdout so Docker logs capture everything
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
logging.basicConfig(
|
||||||
|
stream=sys.stdout,
|
||||||
|
level=logging.INFO,
|
||||||
|
format="%(asctime)s [%(levelname)s] %(message)s",
|
||||||
|
)
|
||||||
|
log = logging.getLogger("server")
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Globals (loaded once at startup)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
detector = None
|
||||||
|
catboost_model = None
|
||||||
|
name_dict = None
|
||||||
|
|
||||||
|
|
||||||
|
def load_models():
|
||||||
|
"""Load MTCNN detector, CatBoost model and name dictionary.
|
||||||
|
Logs warnings instead of crashing when files are absent so the container
|
||||||
|
starts and waits for connections even without .cbm files."""
|
||||||
|
global detector, catboost_model, name_dict
|
||||||
|
|
||||||
|
detector = MTCNN()
|
||||||
|
log.info("MTCNN detector initialised")
|
||||||
|
|
||||||
|
model_path = os.path.join(MODEL_DIR, "catboost_usa.cbm")
|
||||||
|
if os.path.isfile(model_path):
|
||||||
|
try:
|
||||||
|
catboost_model = CatBoostClassifier()
|
||||||
|
catboost_model.load_model(model_path)
|
||||||
|
log.info("CatBoost model loaded from %s", model_path)
|
||||||
|
except Exception as exc:
|
||||||
|
log.warning("Failed to load CatBoost model from %s: %s — recognition will be unavailable", model_path, exc)
|
||||||
|
catboost_model = None
|
||||||
|
else:
|
||||||
|
log.warning("CatBoost model not found at %s — recognition will be unavailable until it is provided", model_path)
|
||||||
|
|
||||||
|
dict_path = os.path.join(MODEL_DIR, "saved_dictionary.pkl")
|
||||||
|
if os.path.isfile(dict_path):
|
||||||
|
try:
|
||||||
|
with open(dict_path, "rb") as f:
|
||||||
|
name_dict = pickle.load(f)
|
||||||
|
log.info("Name dictionary loaded from %s", dict_path)
|
||||||
|
except Exception as exc:
|
||||||
|
log.warning("Failed to load name dictionary from %s: %s", dict_path, exc)
|
||||||
|
name_dict = None
|
||||||
|
else:
|
||||||
|
log.warning("Name dictionary not found at %s — recognition will be unavailable until it is provided", dict_path)
|
||||||
|
|
||||||
|
|
||||||
|
def recognise_face(image_array):
|
||||||
|
"""Run the full recognition pipeline on an RGB image array.
|
||||||
|
Returns (name, confidence_info) or an error message string."""
|
||||||
|
if catboost_model is None or name_dict is None:
|
||||||
|
return "recognition unavailable (models not loaded)"
|
||||||
|
|
||||||
|
detections = detector.detect_faces(image_array)
|
||||||
|
if not detections:
|
||||||
|
return "no faces detected"
|
||||||
|
|
||||||
|
# Use the first face with confidence > 0.9
|
||||||
|
for detection in detections:
|
||||||
|
confidence = detection["confidence"]
|
||||||
|
if confidence > 0.9:
|
||||||
|
x, y, w, h = detection["box"]
|
||||||
|
detected_face = image_array[int(y) : int(y + h), int(x) : int(x + w)]
|
||||||
|
|
||||||
|
embedding = DeepFace.represent(
|
||||||
|
detected_face, model_name="Facenet", enforce_detection=False
|
||||||
|
)
|
||||||
|
ebd = embedding[0]["embedding"]
|
||||||
|
|
||||||
|
# Build DataFrame exactly as Predict_photo.py does
|
||||||
|
dicter3 = {1: ebd}
|
||||||
|
data_usa = pd.DataFrame.from_dict(dicter3.items())
|
||||||
|
data_usa.rename(columns={0: "id", 1: "embd"}, inplace=True, errors="ignore")
|
||||||
|
new_cols = pd.DataFrame(data_usa["embd"].apply(pd.Series))
|
||||||
|
df_usa = pd.concat([data_usa, new_cols], axis=1)
|
||||||
|
df_usa.drop(["embd"], axis=1, inplace=True, errors="ignore")
|
||||||
|
X = df_usa.drop(["id"], axis=1)
|
||||||
|
|
||||||
|
result = catboost_model.predict(X)
|
||||||
|
idx = result[0][0]
|
||||||
|
name = name_dict.get(idx, f"unknown (index {idx})")
|
||||||
|
log.info("Recognised: %s (confidence %.3f)", name, confidence)
|
||||||
|
return name
|
||||||
|
|
||||||
|
return "no face with sufficient confidence (>0.9)"
|
||||||
|
|
||||||
|
|
||||||
|
def handle_client(conn, addr):
|
||||||
|
"""Receive a photo from one client, recognise the face and send the name back."""
|
||||||
|
log.info("Accepted connection from %s", addr)
|
||||||
|
|
||||||
|
# Read the full photo bytes until the connection closes
|
||||||
|
chunks = []
|
||||||
|
while True:
|
||||||
|
chunk = conn.recv(65536)
|
||||||
|
if not chunk:
|
||||||
|
break
|
||||||
|
chunks.append(chunk)
|
||||||
|
photo_bytes = b"".join(chunks)
|
||||||
|
|
||||||
|
if not photo_bytes:
|
||||||
|
log.info("Empty request from %s — closing", addr)
|
||||||
|
conn.close()
|
||||||
|
return
|
||||||
|
|
||||||
|
log.info("Received %d bytes from %s", len(photo_bytes), addr)
|
||||||
|
|
||||||
|
# Write photo to OS temp dir (not FDJ.jpg in CWD)
|
||||||
|
tmp = tempfile.NamedTemporaryFile(suffix=".jpg", delete=False)
|
||||||
|
try:
|
||||||
|
tmp.write(photo_bytes)
|
||||||
|
tmp.close()
|
||||||
|
|
||||||
|
img = cv2.imread(tmp.name)
|
||||||
|
if img is None:
|
||||||
|
result = "failed to decode image"
|
||||||
|
log.warning("Could not decode image from %s", addr)
|
||||||
|
else:
|
||||||
|
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
||||||
|
img = cv2.resize(img, (1080, 720))
|
||||||
|
result = recognise_face(img)
|
||||||
|
|
||||||
|
# Send plain UTF-8 bytes (NOT bit-string encoding — bug fixed)
|
||||||
|
conn.sendall(result.encode("utf-8"))
|
||||||
|
finally:
|
||||||
|
os.unlink(tmp.name)
|
||||||
|
|
||||||
|
conn.close()
|
||||||
|
log.info("Closed connection from %s", addr)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
log.info("Starting UfaHack2024 headless server")
|
||||||
|
log.info("HOST=%s, PORT=%s, MODEL_DIR=%s, DATA_DIR=%s", HOST, PORT, MODEL_DIR, DATA_DIR)
|
||||||
|
|
||||||
|
load_models()
|
||||||
|
|
||||||
|
server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
server_sock.bind((HOST, PORT))
|
||||||
|
server_sock.listen(5)
|
||||||
|
log.info("Listening on %s:%s", HOST, PORT)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
conn, addr = server_sock.accept()
|
||||||
|
try:
|
||||||
|
handle_client(conn, addr)
|
||||||
|
except Exception as exc:
|
||||||
|
log.exception("Error handling client %s: %s", addr, exc)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user