diff --git a/.gitignore b/.gitignore
index ccf61ec68afc49c8e726ea3533e0777fadd032d5..be4f5e98d6808f994d353a44523ce04d4154bf78 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
+*.onnx
+
 # Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,python,macos,windows,jupyternotebooks
 # Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,python,macos,windows,jupyternotebooks
 
diff --git a/lab-4/task_1.py b/lab-4/task_1.py
new file mode 100644
index 0000000000000000000000000000000000000000..2a9b809212dd91491fe05b24b71eb971b2ed9e47
--- /dev/null
+++ b/lab-4/task_1.py
@@ -0,0 +1,118 @@
+import os
+import requests
+
+import cv2
+
+
+DIRECTORY = "lab-4"
+
+# Инициализация детектора лиц YuNET
+if not os.path.exists(f"{DIRECTORY}/face_detection_yunet_2023mar.onnx"):
+    print("Загрузка face_detection_yunet_2023mar.onnx ...")
+    url = "https://github.com/opencv/opencv_zoo/raw/refs/heads/main/models/face_detection_yunet/face_detection_yunet_2023mar.onnx"
+    response = requests.get(url)
+    with open(f"{DIRECTORY}/face_detection_yunet_2023mar.onnx", "wb") as f:
+        f.write(response.content)
+    print("Загрузка face_detection_yunet_2023mar.onnx завершена")
+
+detector = cv2.FaceDetectorYN.create(
+    f"{DIRECTORY}/face_detection_yunet_2023mar.onnx", "", (320, 320), 0.9, 0.3, 5000
+)
+
+# Инициализация распознавателя лиц
+if not os.path.exists(f"{DIRECTORY}/face_recognition_sface_2021dec.onnx"):
+    print("Загрузка face_recognition_sface_2021dec.onnx ...")
+    url = "https://github.com/opencv/opencv_zoo/raw/refs/heads/main/models/face_recognition_sface/face_recognition_sface_2021dec.onnx"
+    response = requests.get(url)
+    with open(f"{DIRECTORY}/face_recognition_sface_2021dec.onnx", "wb") as f:
+        f.write(response.content)
+    print("Загрузка face_recognition_sface_2021dec.onnx завершена")
+
+recognizer = cv2.FaceRecognizerSF.create(
+    f"{DIRECTORY}/face_recognition_sface_2021dec.onnx", ""
+)
+
+# Загрузка референсного изображения
+ref_image = cv2.imread(f"{DIRECTORY}/task_1_reference.jpg")
+if ref_image is None:
+    print("Не удалось загрузить референсное изображение")
+    exit()
+
+# Обнаружение лица на референсном изображении
+height, width = ref_image.shape[:2]
+detector.setInputSize((width, height))
+_, faces = detector.detect(ref_image)
+
+if faces is None:
+    print("На референсном изображении не найдено лиц")
+    exit()
+
+# Извлечение признаков референсного лица
+ref_face = faces[0]
+aligned_ref_face = recognizer.alignCrop(ref_image, ref_face)
+ref_feature = recognizer.feature(aligned_ref_face)
+
+# Инициализация видеопотока с веб-камеры
+cap = cv2.VideoCapture(0)
+if not cap.isOpened():
+    print("Не удалось открыть веб-камеру")
+    exit()
+
+# Порог косинусного сходства для распознавания
+COSINE_THRESHOLD = 0.4
+L2_THRESHOLD = 1.2
+
+while True:
+    ret, frame = cap.read()
+    if not ret:
+        print("Не удалось получить кадр")
+        break
+
+    # Обнаружение лиц в кадре
+    frame_height, frame_width = frame.shape[:2]
+    detector.setInputSize((frame_width, frame_height))
+    _, faces = detector.detect(frame)
+
+    if faces is not None:
+        for face in faces:
+            # Получение координат ограничивающего прямоугольника
+            bbox = list(map(int, face[:4]))
+            x, y, w, h = bbox
+
+            # Извлечение признаков текущего лица
+            aligned_face = recognizer.alignCrop(frame, face)
+            current_feature = recognizer.feature(aligned_face)
+
+            # Сравнение с референсным лицом
+            cosine_score = recognizer.match(
+                ref_feature, current_feature, cv2.FaceRecognizerSF_FR_COSINE
+            )
+            l2_score = recognizer.match(
+                ref_feature, current_feature, cv2.FaceRecognizerSF_FR_NORM_L2
+            )
+
+            # Определение цвета прямоугольника
+            color = (0, 0, 255)  # Красный по умолчанию
+            if cosine_score >= COSINE_THRESHOLD or l2_score <= L2_THRESHOLD:
+                print(f"{cosine_score=:.3f}, {l2_score=:.3f}")
+                if cosine_score >= COSINE_THRESHOLD and l2_score <= L2_THRESHOLD:
+                    print("Сработали оба порога")
+                elif cosine_score >= COSINE_THRESHOLD:
+                    print("Сработал порог косинусного сходства")
+                elif l2_score <= L2_THRESHOLD:
+                    print("Сработал порог L2 сходства")
+                color = (0, 255, 0)  # Зеленый для референсного лица
+
+            # Отрисовка прямоугольника
+            cv2.rectangle(frame, (x, y), (x + w, y + h), color, 2)
+
+    # Отображение результата
+    cv2.imshow("Face Detection", frame)
+
+    # Выход по нажатию 'q'
+    if cv2.waitKey(1) & 0xFF == ord("q"):
+        break
+
+# Освобождение ресурсов
+cap.release()
+cv2.destroyAllWindows()
diff --git a/lab-4/task_1_reference.jpg b/lab-4/task_1_reference.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..725de14d0cd3206586c43fcfc7863babda90064b
Binary files /dev/null and b/lab-4/task_1_reference.jpg differ
diff --git a/lab-4/task_2.py b/lab-4/task_2.py
new file mode 100644
index 0000000000000000000000000000000000000000..358beb19aa31459efdb54959ec7d4a9e029ee48e
--- /dev/null
+++ b/lab-4/task_2.py
@@ -0,0 +1,143 @@
+# app.py
+import os
+import numpy as np
+import streamlit as st
+from deepface import DeepFace
+from PIL import Image
+import faiss
+import pandas as pd
+
+# Конфигурации
+EMBEDDING_SIZE = 128  # Для FaceNet
+DATABASE_PATH = "lab-4/task_2_faces"
+INDEX_PATH = "lab-4/task_2_faiss_index.index"
+NAMES_PATH = "lab-4/task_2_names.npy"
+STAFF_CSV = "lab-4/task_2_staff_photo.csv"  # Добавим путь к CSV файлу
+
+def build_faiss_database():
+    st.write("Построение базы данных...")
+    if not os.path.exists(DATABASE_PATH):
+        st.error("Папка с лицами не найдена, создаю папку")
+        st.write("Загрузите изображения лиц в папку и нажмите кнопку 'Построить/Обновить базу' ещё раз")
+        os.makedirs(DATABASE_PATH)
+        return
+
+    # Загрузка данных о сотрудниках
+    try:
+        staff_df = pd.read_csv(STAFF_CSV)
+        filename_to_name = dict(zip(staff_df['filename'], staff_df['name']))
+    except Exception as e:
+        st.error(f"Ошибка чтения CSV файла: {str(e)}")
+        return
+
+    embeddings = []
+    names = []
+    
+    progress_bar = st.progress(0)
+    files = sorted(os.listdir(DATABASE_PATH))
+    mx = len(files)
+    
+    for i, img_file in enumerate(files):
+        img_path = os.path.join(DATABASE_PATH, img_file)
+        try:
+            # Извлечение лица и эмбеддинга
+            img = Image.open(img_path)
+            face = DeepFace.extract_faces(img_path=img_path, detector_backend='opencv')[0]
+            embedding = DeepFace.represent(
+                img_path=img_path,
+                model_name='Facenet',
+                detector_backend='opencv'
+            )[0]['embedding']
+            
+            embeddings.append(embedding)
+            # Используем имя из CSV вместо имени файла
+            person_name = filename_to_name.get(img_file, img_file)
+            names.append(person_name)
+        except Exception as e:
+            st.error(f"Ошибка обработки {img_path}: {str(e)}")
+        progress_bar.progress(i / mx)
+    
+    if len(embeddings) == 0:
+        return
+
+    # Создание FAISS индекса
+    embeddings = np.array(embeddings).astype('float32')
+    index = faiss.IndexFlatL2(EMBEDDING_SIZE)
+    index.add(embeddings)
+    
+    # Сохранение индекса и имен
+    faiss.write_index(index, INDEX_PATH)
+    np.save(NAMES_PATH, np.array(names))
+    st.success(f"База данных построена: {len(names)} лиц")
+
+def search_face(query_img, threshold=0.5):
+    if not os.path.exists(INDEX_PATH):
+        st.error("Сначала постройте базу данных")
+        return None, None
+
+    try:
+        # Конвертация PIL Image в numpy array
+        query_img_array = np.array(query_img)
+        
+        # Извлечение эмбеддинга для запроса
+        face = DeepFace.extract_faces(img_path=query_img_array, detector_backend='opencv')[0]
+        query_embedding = DeepFace.represent(
+            img_path=query_img_array,
+            model_name='Facenet',
+            detector_backend='opencv'
+        )[0]['embedding']
+        
+        # РџРѕРёСЃРє РІ FAISS
+        index = faiss.read_index(INDEX_PATH)
+        names = np.load(NAMES_PATH)
+        
+        # Поиск ближайшего соседа
+        query_embedding = np.array([query_embedding]).astype('float32')
+        distances, indices = index.search(query_embedding, 1)
+        
+        if distances[0][0] <= threshold:
+            return names[indices[0][0]], distances[0][0]
+        else:
+            return "Unknown", distances[0][0]
+            
+    except Exception as e:
+        st.error(f"Ошибка распознавания: {str(e)}")
+        return None, None
+
+# Streamlit интерфейс
+st.title("рџ”Ќ Face Recognition System")
+
+# Секция для построения базы данных
+st.sidebar.header("База данных лиц")
+if st.sidebar.button("Построить/Обновить базу"):
+    build_faiss_database()
+
+# Секция для поиска
+st.header("Поиск лица")
+uploaded_file = st.file_uploader("Загрузите изображение лица", type=['jpg', 'png', 'jpeg'])
+
+if uploaded_file is not None:
+    image = Image.open(uploaded_file)
+    st.image(image, caption="Загруженное изображение", use_container_width=True)
+    
+    if st.button("Найти совпадения"):
+        name, distance = search_face(image, threshold=1000)
+        if name:
+            st.subheader(f"Результат: {name}")
+            st.write(f"Расстояние: {distance:.4f}")
+            if name == "Unknown":
+                st.error("Совпадений не найдено в базе данных")
+            else:
+                st.success("Совпадение найдено!")
+                # Загружаем данные о сотрудниках
+                staff_df = pd.read_csv(STAFF_CSV)
+                # Ищем запись сотрудника по имени
+                staff_record = staff_df[staff_df['name'] == name]
+                if not staff_record.empty:
+                    # Получаем имя файла фотографии
+                    photo_filename = staff_record.iloc[0]['filename']
+                    photo_path = os.path.join(DATABASE_PATH, photo_filename)
+                    if os.path.exists(photo_path):
+                        matched_image = Image.open(photo_path)
+                        st.image(matched_image, caption=f"Фотография сотрудника: {name}", 
+                                use_container_width=True)
\ No newline at end of file
diff --git a/lab-4/task_3_1.py b/lab-4/task_3_1.py
new file mode 100644
index 0000000000000000000000000000000000000000..44e3e8f545291ab6dea8695909a30dc4551fc3bd
--- /dev/null
+++ b/lab-4/task_3_1.py
@@ -0,0 +1,207 @@
+import cv2
+import pandas as pd
+import numpy as np
+from deepface import DeepFace
+from tqdm import tqdm
+import os
+import pickle
+import hashlib
+
+# Конфигурация
+VIDEO_DIR = "lab-4/task_3_1_references/videos"
+TIMECODE_CSV_DIR = "lab-4/task_3_1_references/labels"
+REFERENCE_DIR = "lab-4/task_3_1_references/photos"
+CACHE_FILE = "lab-4/task_3_1_cache/embeddings_cache.pkl"
+DIR_HASH_FILE = "lab-4/task_3_1_cache/dir_hash.txt"
+THRESHOLD = 0.1  # Порог косинусного сходства
+FRAME_SKIP = 50  # Каждый 50-й кадр
+
+video_files = [f for f in os.listdir(VIDEO_DIR) if f.endswith((".mp4", ".avi", ".mov"))]
+print("Доступные видео:")
+for idx, video in enumerate(video_files):
+    print(f"{idx + 1}: {video}")
+
+video_choice = int(input("Выберите номер видео для обработки: ")) - 1
+VIDEO_PATH = os.path.join(VIDEO_DIR, video_files[video_choice])
+
+# Загрузка соответствующего файла с таймкодами
+TIMECODE_CSV = os.path.join(
+    TIMECODE_CSV_DIR,
+    video_files[video_choice].replace(video_files[video_choice].split(".")[-1], "csv"),
+)
+
+
+def get_dir_hash(directory):
+    """Вычисляет хэш директории на основе имен файлов и их размеров"""
+    hash_md5 = hashlib.md5()
+    for root, _, files in os.walk(directory):
+        for file in sorted(files):  # сортировка для стабильности хэша
+            filepath = os.path.join(root, file)
+            file_size = os.path.getsize(filepath)
+            hash_md5.update(f"{filepath}{file_size}".encode())
+    return hash_md5.hexdigest()
+
+
+def normalize_embedding(embedding):
+    return embedding / np.linalg.norm(embedding)
+
+
+def load_reference_embeddings():
+    # Проверяем существующий кэш
+    current_hash = get_dir_hash(REFERENCE_DIR)
+    if os.path.exists(CACHE_FILE) and os.path.exists(DIR_HASH_FILE):
+        with open(DIR_HASH_FILE, "r") as f:
+            cached_hash = f.read().strip()
+        if cached_hash == current_hash:
+            print("Загрузка эмбеддингов из кэша...")
+            with open(CACHE_FILE, "rb") as f:
+                return pickle.load(f)
+
+    print("Вычисление новых эмбеддингов...")
+    embeddings = {}
+    for person in tqdm(os.listdir(REFERENCE_DIR)):
+        person_dir = os.path.join(REFERENCE_DIR, person)
+        if os.path.isdir(person_dir):
+            for img_name in os.listdir(person_dir):
+                img_path = os.path.join(person_dir, img_name)
+                try:
+                    # Сначала извлекаем лицо
+                    faces = DeepFace.extract_faces(
+                        img_path=img_path,
+                        detector_backend="opencv",
+                        enforce_detection=False,
+                    )
+                    if faces:  # Если лицо найдено
+                        # Получаем эмбеддинг для найденного лица
+                        embedding = normalize_embedding(
+                            DeepFace.represent(
+                                img_path=faces[0][
+                                    "face"
+                                ],  # Используем извлеченное лицо
+                                model_name="Facenet",
+                                detector_backend="opencv",
+                                enforce_detection=False,
+                            )[0]["embedding"]
+                        )
+                        if person not in embeddings:
+                            embeddings[person] = []
+                        embeddings[person].append(embedding)
+                except Exception as e:
+                    print(f"Ошибка обработки {img_path}: {str(e)}")
+                    continue
+
+    # Сохраняем новый кэш
+    with open(CACHE_FILE, "wb") as f:
+        pickle.dump(embeddings, f)
+    with open(DIR_HASH_FILE, "w") as f:
+        f.write(current_hash)
+
+    return embeddings
+
+
+print("\nЗагрузка эталонных эмбеддингов...")
+reference_embeddings = load_reference_embeddings()
+print(f"Загружено {len(reference_embeddings)} эталонных личностей")
+
+# Загрузка разметки
+print("\nЗагрузка разметки из CSV...")
+timecodes = pd.read_csv(TIMECODE_CSV)
+timecodes["from"] = pd.to_timedelta(timecodes["from"]).dt.total_seconds()
+timecodes["to"] = pd.to_timedelta(timecodes["to"]).dt.total_seconds()
+# Преобразование строки persons в список
+timecodes["persons"] = timecodes["persons"].apply(
+    lambda x: [person.strip() for person in x.split(",")] if isinstance(x, str) else x
+)
+print(f"Загружено {len(timecodes)} временных меток")
+
+# Инициализация видео
+print("\nИнициализация видео...")
+cap = cv2.VideoCapture(VIDEO_PATH)
+fps = cap.get(cv2.CAP_PROP_FPS)
+total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
+print(f"FPS: {fps:.2f}")
+print(f"Всего кадров: {total_frames}")
+print(f"Длительность видео: {total_frames/fps:.2f} секунд")
+
+# Метрики
+true_positives = 0
+false_positives = 0
+
+print("\nНачинаем обработку кадров...")
+# Обработка кадров
+for frame_num in tqdm(range(0, total_frames, FRAME_SKIP)):
+    cap.set(cv2.CAP_PROP_POS_FRAMES, frame_num)
+    ret, frame = cap.read()
+    if not ret:
+        break
+
+    # Определение текущего времени
+    current_time = frame_num / fps
+
+    # Получение ожидаемых лиц
+    current_frames = timecodes[
+        (timecodes["from"] <= current_time) & (timecodes["to"] >= current_time)
+    ]
+    expected = []
+    for persons_list in current_frames["persons"]:
+        if isinstance(persons_list, list):
+            expected.extend(persons_list)
+    expected = list(set(expected))  # удаление дубликатов
+
+    try:
+        # Обнаружение лиц
+        detected_faces = DeepFace.extract_faces(
+            frame, detector_backend="opencv", enforce_detection=False
+        )
+    except Exception as exc:
+        print(f"Поиск на кадре {frame_num} не удался: {exc=}")
+        continue
+
+    # print(f"{expected=}")
+
+    for face in detected_faces:
+        try:
+            # Получение эмбеддинга
+            query_embedding = normalize_embedding(
+                DeepFace.represent(
+                    img_path=face["face"],
+                    model_name="Facenet",
+                    detector_backend="opencv",
+                    enforce_detection=False,
+                )[0]["embedding"]
+            )
+
+            # Поиск совпадений
+            best_match = ("Unknown", 1)
+            for person, embeddings in reference_embeddings.items():
+                avg_distance = np.mean(
+                    [np.linalg.norm(query_embedding - emb) for emb in embeddings]
+                )
+                if avg_distance < best_match[1]:
+                    best_match = (person, avg_distance)
+            # Проверка совпадения
+            # print(f"{best_match=}")
+            if best_match[1] > THRESHOLD:
+                if best_match[0] in expected:
+                    true_positives += 1
+                else:
+                    false_positives += 1
+            else:
+                false_positives += 1
+
+        except Exception as exc:
+            print(f"Сопоставление на кадре {frame_num} не удалось: {exc=}")
+            false_positives += 1
+
+# Расчет точности
+precision = (
+    true_positives / (true_positives + false_positives)
+    if (true_positives + false_positives) > 0
+    else 0
+)
+print("\nРезультаты анализа:")
+print(f"Правильные обнаружения (True Positives): {true_positives}")
+print(f"Ложные обнаружения (False Positives): {false_positives}")
+print(f"Precision: {precision:.4f}")
+cap.release()
+print("\nОбработка завершена!")
diff --git a/lab-4/task_3_2.py b/lab-4/task_3_2.py
new file mode 100644
index 0000000000000000000000000000000000000000..79ebec4f7392c4a88bdbb41f328260211e6f7e82
--- /dev/null
+++ b/lab-4/task_3_2.py
@@ -0,0 +1,310 @@
+"""## Часть 2. Оценить точность работы методов из DeepFace на аугментированных данных (2 балла).
+
+Необходимо собрать собственный набор данных из **различных** изображений Вашего лица с разных ракурсов, желательно настоящие фотографии из личного архива (20 штук)\
+Возьмите эталонное изображение (как в паспорте) и при помощи библиотеки [DeepFace](https://github.com/serengil/deepface) проверьте его на соответствие всему датасету. Посчитайте метрику Precision. \
+\
+Примените каждую из перечисленных ниже аугментаций (**по-отдельности**) ко всему датасету и измерьте метрику Precision для измененнного датасета:
+*   Поворот изображения на 45° и 90°.
+*   Добавление шума (Gaussian Noise).
+*   Изменение яркости (увеличение и уменьшение на 50%).
+*   Размытие с различными параметрами.
+\
+Реузультаты соберите в таблицу вида:
+
+Метод | Исходный датасет | Поворот на 45° | Поворот на 90° | Изображение с шумом |
+--- | ----|--- | --- | --- |
+VGG-Face | 0 | 0 | 0 | 0 |
+Facenet | 0 | 0 | 0 | 0 |
+Facenet512 | 0 | 0 | 0 | 0 |
+OpenFace | 0 | 0 | 0 | 0 |
+DeepFace | 0 | 0 | 0 | 0 |
+DeepID | 0 | 0 | 0 | 0 |
+ArcFace | 0 | 0 | 0 | 0 |
+Dlib | 0 | 0 | 0 | 0 |
+SFace | 0 | 0 | 0 | 0 |
+GhostFaceNet | 0 | 0 | 0 | 0 |
+"""
+
+import os
+import cv2
+import numpy as np
+from deepface import DeepFace
+from PIL import Image, ImageEnhance, ImageFilter
+import pandas as pd
+import matplotlib.pyplot as plt
+from tqdm import tqdm
+
+# Путь к эталонному изображению
+reference_image_path = "lab-4/task_3_2_reference.jpg"
+
+# Путь к папке с изображениями датасета
+dataset_folder = "lab-4/task_3_2_references"
+image_paths = [os.path.join(dataset_folder, img) for img in os.listdir(dataset_folder)]
+
+"""Поворот на 45 и 90 градусов:"""
+
+
+def rotate_image(image, angle):
+    (h, w) = image.shape[:2]
+    center = (w // 2, h // 2)
+    M = cv2.getRotationMatrix2D(center, angle, 1.0)
+    rotated = cv2.warpAffine(image, M, (w, h))
+    return rotated
+
+
+"""Добавление шума:"""
+
+
+def add_noise(image):
+    row, col, ch = image.shape
+    mean = 0
+    var = 0.1
+    sigma = var**0.5
+    gauss = np.random.normal(mean, sigma, (row, col, ch))
+    gauss = gauss.reshape(row, col, ch)
+    noisy = image + gauss * 50
+    return noisy.astype(np.uint8)
+
+
+"""Изменение яркости:"""
+
+
+def adjust_brightness(image, factor):
+    img = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
+    enhancer = ImageEnhance.Brightness(img)
+    brightened = enhancer.enhance(factor)
+    return cv2.cvtColor(np.array(brightened), cv2.COLOR_RGB2BGR)
+
+
+"""Размытие:"""
+
+
+def apply_blur(image, kernel_size=(5, 5)):
+    return cv2.GaussianBlur(image, kernel_size, 0)
+
+
+def apply_average_blur(image, kernel_size=(5, 5)):
+    return cv2.blur(image, kernel_size)
+
+
+def apply_bilateral_blur(image, d=9, sigma_color=75, sigma_space=75):
+    return cv2.bilateralFilter(image, d, sigma_color, sigma_space)
+
+
+"""Распознавание лиц:"""
+
+
+def calculate_recall(reference_embedding, dataset_embeddings, threshold=0.5):
+    print(f"Вычисление метрики recall с порогом {threshold}")
+    true_positives = 0
+    false_negatives = 0
+
+    for i, embedding_list in enumerate(dataset_embeddings):
+        if isinstance(embedding_list, list) and len(embedding_list) > 0:
+            embedding_dict = embedding_list[0]
+            if isinstance(embedding_dict, dict) and "embedding" in embedding_dict:
+                embedding = np.array(embedding_dict["embedding"])
+                distance = np.linalg.norm(reference_embedding - embedding)
+                if distance < threshold:
+                    true_positives += 1
+                else:
+                    false_negatives += 1
+            else:
+                print(
+                    f"Предупреждение: Неожиданный формат embedding_dict в элементе {i+1}"
+                )
+                false_negatives += 1
+        else:
+            print(f"Предупреждение: Лицо не обнаружено в элементе {i+1}")
+            false_negatives += 1
+
+    recall = true_positives / 20
+    print(
+        f"Вычисление recall завершено, {recall=:.3f}. Истинно положительных: {true_positives}, Ложно отрицательных: {false_negatives}"
+    )
+    return recall
+
+
+def test_transformations(image_paths):
+    results = {
+        model: {}
+        for model in [
+            "Facenet",
+            "VGG-Face",
+            "Facenet512",
+            "OpenFace",
+            # "DeepFace",
+            "DeepID",
+            "ArcFace",
+            "Dlib",
+            "SFace",
+            "GhostFaceNet",
+        ]
+    }
+
+    for model_name in results.keys():
+        print(f"\nТестирование модели: {model_name}")
+
+        # First extract face from reference image
+        print("Извлечение лица из эталонного изображения...")
+        try:
+            faces = DeepFace.extract_faces(
+                img_path=reference_image_path,
+                detector_backend="opencv",
+                enforce_detection=False,
+            )
+
+            if faces:
+                # Get reference embedding using extracted face
+                print("Получение эталонного эмбеддинга...")
+                reference_embedding_list = DeepFace.represent(
+                    img_path=faces[0]["face"],
+                    model_name=model_name,
+                    detector_backend="opencv",
+                    enforce_detection=False,
+                )
+                reference_embedding = np.array(reference_embedding_list[0]["embedding"])
+            else:
+                print("Лицо не обнаружено в эталонном изображении")
+                continue
+        except Exception as e:
+            print(f"Ошибка при обработке эталонного изображения: {str(e)}")
+            continue
+
+        print("Обработка исходного датасета...")
+        original_embeddings = process_dataset(image_paths, model_name=model_name)
+        results[model_name]["Исходный датасет"] = calculate_recall(
+            reference_embedding, original_embeddings
+        )
+
+        print("Обработка поворота на 45°...")
+        rotated_45_embeddings = process_dataset(
+            image_paths,
+            model_name=model_name,
+            transform=lambda img: rotate_image(img, 45),
+            transform_name="РџРѕРІРѕСЂРѕС‚ РЅР° 45В°",
+        )
+        results[model_name]["РџРѕРІРѕСЂРѕС‚ РЅР° 45В°"] = calculate_recall(
+            reference_embedding, rotated_45_embeddings
+        )
+
+        print("Обработка поворота на 90°...")
+        rotated_90_embeddings = process_dataset(
+            image_paths,
+            model_name=model_name,
+            transform=lambda img: rotate_image(img, 90),
+            transform_name="РџРѕРІРѕСЂРѕС‚ РЅР° 90В°",
+        )
+        results[model_name]["РџРѕРІРѕСЂРѕС‚ РЅР° 90В°"] = calculate_recall(
+            reference_embedding, rotated_90_embeddings
+        )
+
+        print("Обработка изображений с шумом...")
+        noisy_embeddings = []
+        res = process_dataset(
+            image_paths,
+            model_name=model_name,
+            transform=add_noise,
+            transform_name="Изображение с шумом",
+        )
+        noisy_embeddings.extend(res)
+        res = process_dataset(
+            image_paths,
+            model_name=model_name,
+            transform=lambda img: adjust_brightness(img, 0.5),
+            transform_name="Изображение с уменьшенной яркостью",
+        )
+        noisy_embeddings.extend(res)
+        res = process_dataset(
+            image_paths,
+            model_name=model_name,
+            transform=lambda img: adjust_brightness(img, 1.5),
+            transform_name="Изображение с увеличенной яркостью",
+        )
+        noisy_embeddings.extend(res)
+        res = process_dataset(
+            image_paths,
+            model_name=model_name,
+            transform=apply_blur,
+            transform_name="Размытие",
+        )
+        noisy_embeddings.extend(res)
+        results[model_name]["Изображение с шумом"] = calculate_recall(
+            reference_embedding, noisy_embeddings
+        )
+
+    return results
+
+
+def process_dataset(image_paths, model_name, transform=None, transform_name="Без трансформации"):
+    embeddings = []
+
+    for img_path in tqdm(
+        image_paths,
+        desc=f"Обработка изображений ({model_name}, трансформация={transform_name})",
+    ):
+        try:
+            img = cv2.imread(img_path)
+            if transform:
+                img = transform(img)
+
+            # First extract face
+            faces = DeepFace.extract_faces(
+                img_path=img, detector_backend="opencv", enforce_detection=False
+            )
+
+            if faces:  # If face was found
+                # Get embedding using the extracted face
+                embedding_list = DeepFace.represent(
+                    img_path=faces[0]["face"],  # Use the extracted face
+                    model_name=model_name,
+                    detector_backend="opencv",
+                    enforce_detection=False,
+                )
+                embeddings.append(embedding_list)
+            else:
+                print(f"Лицо не обнаружено в {os.path.basename(img_path)}")
+                embeddings.append([])
+        except Exception as e:
+            print(
+                f"Ошибка обработки {os.path.basename(img_path)} моделью {model_name}: {str(e)}"
+            )
+            embeddings.append([])
+    return embeddings
+
+
+def create_results_table(results):
+    # Convert results dictionary to DataFrame format
+    data = {
+        "Метод": list(results.keys()),
+        "Исходный датасет": [results[model]["Исходный датасет"] for model in results],
+        "РџРѕРІРѕСЂРѕС‚ РЅР° 45В°": [results[model]["РџРѕРІРѕСЂРѕС‚ РЅР° 45В°"] for model in results],
+        "РџРѕРІРѕСЂРѕС‚ РЅР° 90В°": [results[model]["РџРѕРІРѕСЂРѕС‚ РЅР° 90В°"] for model in results],
+        "Изображение с шумом": [
+            results[model]["Изображение с шумом"] for model in results
+        ],
+    }
+
+    results_df = pd.DataFrame(data)
+    return results_df
+
+
+def main():
+    print("Запуск оценки распознавания лиц...")
+    print(f"Используется эталонное изображение: {reference_image_path}")
+    print(f"Папка с датасетом: {dataset_folder}")
+
+    results = test_transformations(image_paths)
+
+    print("\nСоздание таблицы результатов...")
+    results_df = create_results_table(results)
+    print("\nТаблица результатов:")
+    print(results_df)
+
+    print("\nСохранение результатов в CSV...")
+    results_df.to_csv("lab-4/task_3_2_results.csv", index=False)
+    print("Результаты успешно сохранены")
+
+
+if __name__ == "__main__":
+    main()
diff --git a/lab-4/task_3_2_reference.jpg b/lab-4/task_3_2_reference.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..ab8e27f52c1513772335c883cdf309855ce8155a
Binary files /dev/null and b/lab-4/task_3_2_reference.jpg differ
diff --git a/lab-4/task_3_2_results.csv b/lab-4/task_3_2_results.csv
new file mode 100644
index 0000000000000000000000000000000000000000..7ca691ba9ffa526942fb5bf797a9c5d81e1b2edd
--- /dev/null
+++ b/lab-4/task_3_2_results.csv
@@ -0,0 +1,10 @@
+Метод,Исходный датасет,Поворот на 45°,Поворот на 90°,Изображение с шумом
+Facenet,1.0,1.0,1.0,4.0
+VGG-Face,0.0,0.0,0.0,0.0
+Facenet512,1.0,1.0,1.0,4.0
+OpenFace,1.0,1.0,1.0,4.0
+DeepID,0.95,0.95,0.95,3.8
+ArcFace,1.0,1.0,1.0,4.0
+Dlib,1.0,1.0,1.0,4.0
+SFace,1.0,1.0,1.0,3.4
+GhostFaceNet,0.95,0.65,0.5,3.65