Compare commits

..

16 Commits

Author SHA1 Message Date
6217b88a47 Обновить docker-compose.yml
Some checks failed
Build and push Docker image (Gitea) / build (push) Failing after 26m58s
2026-09-01 20:32:14 +07:00
cc16ceb582 fix: replace removed libgl1-mesa-glx with libgl1 in Dockerfile
Some checks failed
Build and push Docker image (Gitea) / build (push) Has been cancelled
2026-09-01 20:18:00 +07:00
4b5e5edf5b docs: update Docker information in README
Some checks failed
Build and push Docker image (Gitea) / build (push) Has been cancelled
2026-09-01 20:13:35 +07:00
dd49ff0a28 Обновить .gitea/workflows/docker-build.yml
Some checks failed
Build and push Docker image (Gitea) / build (push) Failing after 8m14s
2026-09-01 20:05:13 +07:00
64c15290d4 Обновить .github/workflows/docker-build.yml
Some checks failed
Build and push Docker image (Gitea) / build (push) Failing after 14s
2026-09-01 20:04:34 +07:00
7398982c85 Обновить .gitea/workflows/docker-build.yml
Some checks failed
Build and push Docker image (Gitea) / build (push) Failing after 16s
2026-09-01 20:00:39 +07:00
f7391a6971 feat: add Docker setup and CI workflows for headless server
Some checks failed
Build and push Docker image (Gitea) / build (push) Failing after 2m12s
2026-09-01 19:55:28 +07:00
4af0dd0685 docs: correct README for UfaHack2024 project 2026-09-01 19:26:38 +07:00
skylights
e4feb686ed Update README.md 2024-03-30 08:00:40 +03:00
skylights
fb900b09d9 Update README.md 2024-03-30 07:54:10 +03:00
Chingush
fab1d257e5 Add files via upload 2024-03-30 09:22:47 +05:00
skylights
b2129bc43e Update README.md 2024-03-30 07:19:12 +03:00
skylights
615aa26c31 Update README.md 2024-03-30 07:01:36 +03:00
skylights
cca9612086 Update README.md 2024-03-30 07:01:08 +03:00
skylights
7e5e739843 Update README.md 2024-03-30 07:00:31 +03:00
skylights
8cf526a0c4 add all models 2024-03-30 06:48:51 +03:00
16 changed files with 460 additions and 42 deletions

19
.dockerignore Normal file
View 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

View 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
View 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
View File

@@ -1,3 +1,8 @@
.idea
data
data — копия
data — копия
__pycache__/
*.pyc
*.pyo
.env
models/

20
Dockerfile Normal file
View 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

Binary file not shown.

179
README.md
View File

@@ -1,53 +1,150 @@
# 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
![](c4715817-515e-4815-aa0d-bfcc75d45388.jfif)
Хакатон UfaHack2024 в Уфе
____
---
## Данный программный продукт создан с помощью фреймворка для Python Socket, CutBoost, CustomTkinter, а также приложение написанное на Kotin для клиента на Android
## Описание проекта
Проект представляет систему распознавания лиц актёров (США / СССР-Россия) и блогеров с веб-камеры и по загруженным фотографиям. Пайплайн обработки: MTCNN (детекция лица) → DeepFace (Facenet, выделение эмбеддингов) → CatBoost (градиентный бустинг, классификация).
### Код реализации сервера
```python
import socket
На основе десктопного приложения на CustomTkinter пользователь может запускать распознавание как из файловой папки с фотографиями, так и в реальном времени через веб-камеру. Дополнительно реализован Android-клиент на Kotlin, который отправляет фото на серверную часть по TCP-сокету.
s = socket.socket()
host = "192.168.120.244"
port = 12345
s.bind((host, port))
s.listen(5)
Система обучена на нескольких категориях: актёры США, актёры СССР-Россия и блогеры. Для каждой категории строится отдельная модель CatBoost, использующая эмбеддинги лиц, извлечённые предобученной сетью Facenet.
while True:
con, addr = s.accept()
with open('FDJ.jpg', 'wb') as f:
while True:
print(1)
data = con.recv(4096)
if not data:
break
f.write(data)
con.close()
## Стек технологий
- Python (основной язык)
- CustomTkinter (графический интерфейс)
- DeepFace + Facenet (извлечение эмбеддингов лиц)
- MTCNN / FastMTCNN (детекция лиц)
- CatBoost (градиентный бустинг)
- OpenCV + opencv-contrib-python (обработка изображений и видео)
- PyTorch + torchvision (фреймворк для нейросетей)
- CUDA (ускорение на GPU)
- 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)
s.connect(('192.168.120.244', 12345)) # Подключаемся к серверу.
s.sendall('Hello, Habr!'.encode('utf-8')) # Отправляем фразу.
data = s.recv(1024) # Получаем данные из сокета.
print(data.decode())
while True:
data = s.recv(4096)
if not data:
break
print("Received response: " + data.decode())
s.close()
1. Клонируйте репозиторий:
```
git clone https://github.com/DrHo1y/UfaHack2024.git
cd UfaHack2024
cd notebooks/app
```
2. Установите зависимости (см. раздел Зависимости ниже).
3. Запустите приложение:
```
python main.py
```
Для обучения модели исползовали [CutBoost](https://catboost.ai/)
## Реализация модели
![f](https://github.com/SKYLIGHTSUFA/UfaHack2024/blob/main/c4715817-515e-4815-aa0d-bfcc75d45388.jfif)
### Зависимости
Все необходимые зависимости (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
View 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).

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -2,3 +2,14 @@ deepface
customtkinter
torch
torchvision
catboost
mtcnn
opencv-python
opencv-contrib-python
pandas
numpy
Pillow
facenet-pytorch
imutils
tqdm
scikit-learn

0
server/__init__.py Normal file
View File

186
server/server.py Normal file
View 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()