"Практическая работа №3. Классификация изображений...
Created on: December 24, 2025
Answered using GPT-5.2 Pro by Chat01
Created on: December 24, 2025
Answered using GPT-5.2 Pro by Chat01
"Практическая работа №3. Классификация изображений с использованием библиотеки OpenCV
Задача. Разработать приложение для классификации изображений известных достопримечательностей Нижнего Новгорода (Нижегородский Кремль, Дворец труда, Архангельский собор). Набор данных доступен по ссылке, он содержит фотографии студентов ИИТММ (директория NNSUDataset) и фотографии, выгруженные из сети Интернет (директория ExtDataset). Директория со сторонними фотографиями также сопровождается текстовым файлом с перечнем ссылок на источники. Перечень изображений тренировочной выборки доступен по ссылке, остальные изображения попадают в тестовую выборку. Приложение должно реализовывать алгоритм "мешок слов" и/или использовать нейросетевые классификаторы.
Входные параметры приложения:
Путь до директории с данными.
Файлы разбиения на тренировочную и тестовую выборки.
Режим работы (обучение и/или тестирование).
Алгоритм работы ("мешок слов"/нейронная сеть).
Параметры алгоритма работы.
Последовательность работы приложения:
Загрузка набора данных.
Обучение и/или тестирование классификатора. В процессе тестирования необходимо выводить качество решения задачи классификации на тестовой выборке.
Рекомендации и комментарии к выполнению работы:
При разработке программного кода необходимо учитывать основы модульного и объектно-ориентированного подходов.
Для повышения качества решения задачи необходимо расширить тренировочный набор данных собственными изображениями или фотографиями интересующих объектов из сети Интернет.
При реализации алгоритма "мешок слов" необходимо исследовать возможности разных детекторов и дескрипторов.
Для удобства проверки алгоритма "мешок слов" имеет смысл визуализировать отдельные этапы.
Выбор метода машинного обучения для построения классификатора при реализации алгоритма "мешок слов" не ограничен.
При построении нейронных сетей для классификации имеет смысл опираться на идеи "переноса обучения" существующих классификационных моделей дообучать эти модели, поскольку тренировочная выборка имеет небольшие размеры.
Требования:
Язык разработки – Python, среда разработки – Spyder или любая другая.
Программное решение должно сопровождаться файлом README.md, в котором дается описание реализованных алгоритмов (мини-отчет).
В шапке файла README.md необходимо указать название практической работы: "Практическая работа №3. Классификация изображений с использованием библиотеки OpenCV".
Результаты выполнения практической работы должны быть выложены в репозиторий курса. При этом директория практической работы содержит скрипт и файл README.md.
Набор из дополнительных фото должен быть выложен в открытый доступ. Если используются сторонние фотографии (или ваши опубликованные где-то в сети), то необходимо рядом с набором положить текстовый файл с ссылками на источники по аналогии с входным набором данных.
Оценивание:
Наличие разработанной программной реализации алгоритма "мешок слов" - 10 баллов.
Наличие реализации процедуры обучения и использования сети - 15 баллов.
Наличие README - 5 баллов.
Дополнительный балл за качество работы модели - 10 * accuracy баллов (accuracy - точность классификации).
Максимальный балл за практическую работу - 40 баллов (при наличии реализации алгоритма "мешок слов" и обучения нейросетевой модели)." - требования к лабе, у меня сейчас следующая структура проекта как на скриншоте, или как здесь: "practical_work_3/ # Главная папка проекта (можно назвать иначе, но лучше так)
│
├── NNClassification/ # [СКАЧАТЬ И РАСПАКОВАТЬ] Папка с исходными данными
│ ├── NNSUDataset/
│ │ ├── 01_NizhnyNovgorodKremlin/
│ │ ├── 04_ArkhangelskCathedral/
│ │ └── 08_PalaceOfLabor/
│ ├── ExtDataset/
│ │ ├── 01_NizhnyNovgorodKremlin/
│ │ ├── 04_ArkhangelskCathedral/
│ │ ├── 08_PalaceOfLabor/
│ │ └── sources.txt # Файл с ссылками на источники
│ └── train_test_split/ # [СКАЧАТЬ И ПОМЕСТИТЬ СЮДА]
│ ├── train.txt
│ └── test.txt
│
├── my_images/ # [СОЗДАТЬ] ПАПКА С ВАШИМИ ДОПОЛНИТЕЛЬНЫМИ ФОТО
│ ├── Нижегородский Кремль/
│ ├── Дворец труда/
│ ├── Архангельский собор/
│ └── my_sources.txt # Файл с ссылками на ВАШИ источники фото
│
├── models/ # [СОЗДАСТСЯ АВТОМАТИЧЕСКИ] Папка для сохранения обученных моделей
│ ├── bow_model/
│ │ ├── metadata.json
│ │ ├── kmeans_model.pkl
│ │ ├── svm_model.pkl
│ │ └── scaler.pkl
│ └── cnn_model/
│ ├── metadata.json
│ └── cnn_model.h5
│
├── scripts/ # [СОЗДАТЬ] Папка для скриптов Python
│ ├── base_classifier.py
│ ├── bow_classifier.py
│ ├── cnn_classifier.py
│ └── main.py
│
├── weights/ # [СКАЧАТЬ] Папка для весов предобученной сети VGG16
│ └── vgg16_weights_tf_dim_ordering_tf_kernels_notop.h5
│
├── requirements.txt # [СОЗДАТЬ] Файл зависимостей
└── README.md # [СОЗДАТЬ] Файл отчета", мой main файл: """"
Главный скрипт для классификации достопримечательностей Нижнего Новгорода
Обеспечивает интерфейс командной строки для работы с алгоритмами
"""
import os
import sys
import argparse
import cv2
sys.path.insert(0, os.path.dirname(os.path.abspath(file)))
from bow_classifier import BOWClassifier
from cnn_classifier import CNNClassifier
def create_classifier(algorithm, **kwargs):
"""Фабричная функция для создания классификаторов."""
if algorithm == 'bow':
return BOWClassifier(
n_clusters=kwargs.get('n_clusters', 200),
image_size=kwargs.get('image_size', (224, 224)),
detector_type=kwargs.get('detector_type', 'sift')
)
elif algorithm == 'cnn':
return CNNClassifier(
image_size=kwargs.get('image_size', (224, 224)),
batch_size=kwargs.get('batch_size', 16),
epochs=kwargs.get('epochs', 20),
learning_rate=kwargs.get('learning_rate', 0.001),
dropout_rate=kwargs.get('dropout_rate', 0.5),
base_model_name=kwargs.get('base_model_name', 'vgg16')
)
else:
raise ValueError(f"Неизвестный алгоритм: {algorithm}")
def create_extended_train_file(original_train_file, extra_images_dir, data_dir):
"""Создает расширенный train файл с дополнительными изображениями."""
print("Создание расширенной обучающей выборки...")
text# Читаем оригинальный train.txt with open(original_train_file, 'r', encoding='utf-8') as f: original_lines = [line.strip() for line in f.readlines() if line.strip()] # Маппинг русских названий к именам папок в датасете folder_mapping = { 'Нижегородский Кремль': '01_NizhnyNovgorodKremlin', 'Дворец труда': '08_PalaceOfLabor', 'Архангельский собор': '04_ArkhangelskCathedral' } # Собираем дополнительные изображения extra_lines = [] for russian_name, dataset_name in folder_mapping.items(): folder_path = os.path.join(extra_images_dir, russian_name) if os.path.exists(folder_path): for file_name in os.listdir(folder_path): if file_name.lower().endswith(('.jpg', '.jpeg', '.png')): file_path = f"ExtDataset/{dataset_name}/{file_name}" extra_lines.append(file_path) # Объединяем all_lines = original_lines + extra_lines # Создаем новый файл extended_train_file = os.path.join(data_dir, 'train_test_split', 'train_extended.txt') with open(extended_train_file, 'w', encoding='utf-8') as f: for line in all_lines: f.write(line + '\n') print(f"Оригинальных изображений: {len(original_lines)}") print(f"Дополнительных изображений: {len(extra_lines)}") print(f"Всего в расширенной выборке: {len(all_lines)}") print(f"Файл сохранен: {extended_train_file}") return extended_train_file
def visualize_features(image_path, output_path=None, detector_type='sift'):
"""Визуализирует ключевые точки на изображении."""
classifier = BOWClassifier(detector_type=detector_type)
result_image = classifier.visualize_features(image_path, output_path)
return result_image
def test_single_image(classifier, image_path):
"""Тестирование классификатора на одном изображении."""
print(f"\nТестирование на изображении: {image_path}")
textpred_label, confidence = classifier.predict_single(image_path) if pred_label: print(f" Предсказанный класс: {pred_label}") print(f" Уверенность: {confidence:.2%}") # Показываем изображение image = cv2.imread(image_path) if image is not None: image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) plt.figure(figsize=(8, 6)) plt.imshow(image_rgb) plt.title(f'Предсказание: {pred_label} ({confidence:.1%})') plt.axis('off') plt.show() return pred_label, confidence
def main():
parser = argparse.ArgumentParser(
description='Классификация достопримечательностей Нижнего Новгорода'
)
text# Основные параметры parser.add_argument('--data_dir', type=str, default='./NNClassification', help='Директория с изображениями') parser.add_argument('--train_file', type=str, help='Файл с обучающей выборкой (если не указан, будет создан с дополнительными изображениями)') parser.add_argument('--test_file', type=str, default='train_test_split/test.txt', help='Файл с тестовой выборкой') parser.add_argument('--algorithm', type=str, choices=['bow', 'cnn'], default='bow', help='bow (мешок слов) или cnn (нейронная сеть)') parser.add_argument('--mode', type=str, choices=['train', 'test', 'both', 'visualize', 'single'], default='both', help='Режим работы') parser.add_argument('--model_dir', type=str, default='models', help='Директория для моделей') # Параметры BOW parser.add_argument('--clusters', type=int, default=200, help='Количество кластеров для BOW') parser.add_argument('--detector', type=str, choices=['sift', 'orb', 'akaze'], default='sift', help='Детектор для BOW') # Параметры CNN parser.add_argument('--batch_size', type=int, default=16, help='Размер батча для CNN') parser.add_argument('--epochs', type=int, default=20, help='Количество эпох для CNN') parser.add_argument('--learning_rate', type=float, default=0.001, help='Скорость обучения для CNN') parser.add_argument('--dropout_rate', type=float, default=0.5, help='Dropout rate для CNN') parser.add_argument('--base_model', type=str, default='vgg16', help='Базовая модель для CNN') # Параметры изображений parser.add_argument('--image_size', type=int, nargs=2, default=[224, 224], help='Размер изображения (ширина высота)') # Параметры визуализации parser.add_argument('--image_path', type=str, help='Путь к изображению для визуализации') parser.add_argument('--output_path', type=str, help='Путь для сохранения изображения') # Дополнительные параметры parser.add_argument('--extra_dir', type=str, default='my_images', help='Директория с дополнительными изображениями') parser.add_argument('--plot_confusion', action='store_true', help='Показывать матрицу ошибок') parser.add_argument('--plot_history', action='store_true', help='Показывать историю обучения') args = parser.parse_args() # Визуализация if args.mode == 'visualize': if not args.image_path: print("Для визуализации требуется указать --image_path") return print(f"Визуализация {args.detector.upper()}-дескрипторов") visualize_features(args.image_path, args.output_path, args.detector) return # Обработка путей data_dir = args.data_dir extra_dir = args.extra_dir # Автоматически создаем расширенный train файл с дополнительными изображениями if not args.train_file: original_train_file = os.path.join(data_dir, 'train_test_split', 'train.txt') train_file = create_extended_train_file(original_train_file, extra_dir, data_dir) else: train_file = os.path.join(data_dir, args.train_file) if not os.path.isabs(args.train_file) else args.train_file test_file = os.path.join(data_dir, args.test_file) if not os.path.isabs(args.test_file) else args.test_file # Создание классификатора classifier_kwargs = { 'n_clusters': args.clusters, 'detector_type': args.detector, 'image_size': tuple(args.image_size), 'batch_size': args.batch_size, 'epochs': args.epochs, 'learning_rate': args.learning_rate, 'dropout_rate': args.dropout_rate, 'base_model_name': args.base_model } classifier = create_classifier(args.algorithm, **classifier_kwargs) # Режим single if args.mode == 'single': if not args.image_path: print("Для режима single требуется указать --image_path") return model_path = os.path.join(args.model_dir, f"{args.algorithm}_model") if os.path.exists(model_path): classifier.load_model(args.model_dir) test_single_image(classifier, args.image_path) return # Режимы train, test, both print("=" * 70) print("КЛАССИФИКАЦИЯ ДОСТОПРИМЕЧАТЕЛЬНОСТЕЙ НИЖНЕГО НОВГОРОДА") print("=" * 70) print(f"Алгоритм: {args.algorithm}") print(f"Режим: {args.mode}") print(f"Данные: {data_dir}") print(f"Обучающая выборка: {train_file}") print(f"Тестовая выборка: {test_file}") train_accuracy, test_accuracy = 0, 0 # Обучение if args.mode in ['train', 'both']: print("\n" + "=" * 60) print("ОБУЧЕНИЕ МОДЕЛИ") print("=" * 60) train_accuracy = classifier.train(train_file, data_dir) classifier.save_model(args.model_dir) # Тестирование if args.mode in ['test', 'both']: if args.mode == 'test': model_path = os.path.join(args.model_dir, f"{args.algorithm}_model") if os.path.exists(model_path): classifier.load_model(args.model_dir) test_accuracy = classifier.test(test_file, data_dir) # Итоги print("\n" + "=" * 70) print("ИТОГОВЫЕ РЕЗУЛЬТАТЫ") print("=" * 70) if args.mode in ['train', 'both']: print(f"Accuracy на обучающей выборке: {train_accuracy:.4f} ({train_accuracy*100:.1f}%)") if args.mode in ['test', 'both']: print(f"Accuracy на тестовой выборке: {test_accuracy:.4f} ({test_accuracy*100:.1f}%)") extra_points = 10 * test_accuracy print(f"Дополнительные баллы: {extra_points:.1f} из 10") print("=" * 70)
TF_ENABLE_ONEDNN_OPTS=0.TF_ENABLE_ONEDNN_OPTS=0.np.object will be defined as the corresponding NumPy scalar.Алгоритм: cnn
Режим: both
Данные: ./NNClassification
Обучающая выборка: ./NNClassification\train_test_split\train_extended.txt
Тестовая выборка: ./NNClassification\train_test_split/test.txt
Загружено 131 изображений
Архангельский собор: 27 изображений
Дворец труда: 35 изображений
Нижегородский Кремль: 69 изображений
Размер обучающей выборки: (131, 224, 224, 3)
Создание модели на основе VGG16...
2025-12-24 11:18:01.752692: I tensorflow/core/platform/cpu_feature_guard.cc:210] This TensorFlow binary is optimized to use available CPU instructions in performance-critical operations.
To enable the following instructions: SSE3 SSE4.1 SSE4.2 AVX AVX2 FMA, in other operations, rebuild TensorFlow with the appropriate compiler flags.
Начало обучения...
Epoch 1/10
←[1m17/17←[0m ←[32m━━━━━━━━━━━━━━━━━━━━←[0m←[37m←[0m ←[1m14s←[0m 421ms/step - accuracy: 0.4046 - loss: 1.4531 - learning_rate: 0.0010
Epoch 2/10
←[1m17/17←[0m ←[32m━━━━━━━━━━━━━━━━━━━━←[0m←[37m←[0m ←[1m7s←[0m 416ms/step - accuracy: 0.4504 - loss: 1.2516 - learning_rate: 0.0010
Epoch 3/10
←[1m17/17←[0m ←[32m━━━━━━━━━━━━━━━━━━━━←[0m←[37m←[0m ←[1m7s←[0m 423ms/step - accuracy: 0.5725 - loss: 0.9873 - learning_rate: 0.0010
Epoch 4/10
←[1m17/17←[0m ←[32m━━━━━━━━━━━━━━━━━━━━←[0m←[37m←[0m ←[1m7s←[0m 418ms/step - accuracy: 0.6489 - loss: 0.8409 - learning_rate: 0.0010
Epoch 5/10
←[1m17/17←[0m ←[32m━━━━━━━━━━━━━━━━━━━━←[0m←[37m←[0m ←[1m8s←[0m 462ms/step - accuracy: 0.6412 - loss: 0.8115 - learning_rate: 0.0010
Epoch 6/10
←[1m17/17←[0m ←[32m━━━━━━━━━━━━━━━━━━━━←[0m←[37m←[0m ←[1m9s←[0m 514ms/step - accuracy: 0.7176 - loss: 0.6700 - learning_rate: 0.0010
Epoch 7/10
←[1m17/17←[0m ←[32m━━━━━━━━━━━━━━━━━━━━←[0m←[37m←[0m ←[1m8s←[0m 489ms/step - accuracy: 0.7176 - loss: 0.6428 - learning_rate: 0.0010
Epoch 8/10
←[1m17/17←[0m ←[32m━━━━━━━━━━━━━━━━━━━━←[0m←[37m←[0m ←[1m9s←[0m 502ms/step - accuracy: 0.7939 - loss: 0.5831 - learning_rate: 0.0010
Epoch 9/10
←[1m17/17←[0m ←[32m━━━━━━━━━━━━━━━━━━━━←[0m←[37m←[0m ←[1m9s←[0m 507ms/step - accuracy: 0.7557 - loss: 0.6074 - learning_rate: 0.0010
Epoch 10/10
←[1m17/17←[0m ←[32m━━━━━━━━━━━━━━━━━━━━←[0m←[37m←[0m ←[1m9s←[0m 505ms/step - accuracy: 0.8244 - loss: 0.4908 - learning_rate: 0.0010
Оценка качества...
←[1m5/5←[0m ←[32m━━━━━━━━━━━━━━━━━━━━←[0m←[37m←[0m ←[1m8s←[0m 1s/step
Accuracy на обучающей выборке: 1.0000 (100.0%)
Детальный отчет:
precision recall f1-score support
Архангельский собор 1.00 1.00 1.00 27
Дворец труда 1.00 1.00 1.00 35
Нижегородский Кремль 1.00 1.00 1.00 69
textaccuracy 1.00 131 macro avg 1.00 1.00 1.00 131 weighted avg 1.00 1.00 1.00 131
Traceback (most recent call last):
File "D:\practial_work_3\scripts\main.py", line 259, in <module>
main()
~~~~^^
File "D:\practial_work_3\scripts\main.py", line 231, in main
train_accuracy = classifier.train(train_file, data_dir)
File "D:\practial_work_3\scripts\cnn_classifier.py", line 131, in train
self.plot_confusion_matrix(train_ids_filtered, train_predictions,
^^^^^^^^^^^^^^^^^^
NameError: name 'train_ids_filtered' is not defined", вот остальные скрипты: Реализация сверточной нейронной сети с Transfer Learning
Использует предобученные модели (VGG16, MobileNetV2, ResNet50)
"""
import os
import cv2
import numpy as np
import tensorflow as tf
from tensorflow.keras import layers, models
from tensorflow.keras.applications import VGG16
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau
from base_classifier import BaseClassifier
class CNNClassifier(BaseClassifier):
"""Классификатор на основе сверточной нейронной сети с Transfer Learning."""
textdef __init__(self, image_size=(224, 224), batch_size=16, epochs=20, learning_rate=0.001, dropout_rate=0.5, base_model_name='vgg16'): super().__init__('cnn', image_size) self.batch_size = batch_size self.epochs = epochs self.learning_rate = learning_rate self.dropout_rate = dropout_rate self.base_model_name = base_model_name self.model = None self.history = None def create_cnn_model(self): """Создает модель CNN на основе предобученной сети.""" print(f"Создание модели на основе {self.base_model_name.upper()}...") input_shape = (self.image_size[0], self.image_size[1], 3) # Используем VGG16 как базовую модель weights_path = './weights/vgg16_weights_tf_dim_ordering_tf_kernels_notop.h5' if os.path.exists(weights_path): base_model = VGG16(weights=None, include_top=False, input_shape=input_shape) base_model.load_weights(weights_path) else: base_model = VGG16(weights='imagenet', include_top=False, input_shape=input_shape) # Замораживаем базовые слои base_model.trainable = False # Добавляем собственные слои model = models.Sequential([ base_model, layers.GlobalAveragePooling2D(), layers.Dropout(self.dropout_rate), layers.Dense(256, activation='relu'), layers.BatchNormalization(), layers.Dropout(self.dropout_rate), layers.Dense(128, activation='relu'), layers.BatchNormalization(), layers.Dropout(self.dropout_rate / 2), layers.Dense(len(self.class_names), activation='softmax') ]) # Компиляция модели optimizer = Adam(learning_rate=self.learning_rate) model.compile(optimizer=optimizer, loss='categorical_crossentropy', metrics=['accuracy']) return model def preprocess_image(self, image_path): """Предобработка изображения для CNN.""" image = cv2.imread(image_path) image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) image = cv2.resize(image, self.image_size) image = image.astype('float32') / 255.0 return image def prepare_dataset(self, image_paths, label_ids): """Подготавливает датасет для обучения/тестирования.""" X, y = [], [] for path, label_id in zip(image_paths, label_ids): image = self.preprocess_image(path) if image is not None: X.append(image) y.append(label_id) X = np.array(X) y = tf.keras.utils.to_categorical(y, num_classes=len(self.class_names)) return X, y def train(self, train_file, images_dir): """Обучение CNN модели.""" print("=" * 60) print("ОБУЧЕНИЕ СВЕРТОЧНОЙ НЕЙРОННОЙ СЕТИ") print(f"Базовая модель: {self.base_model_name.upper()}") print("=" * 60) # Загрузка данных train_paths, train_labels, train_ids = self.load_data(train_file, images_dir) # Подготовка данных X_train, y_train = self.prepare_dataset(train_paths, train_ids) print(f"Размер обучающей выборки: {X_train.shape}") # Создание модели self.model = self.create_cnn_model() # Callbacks для улучшения обучения callbacks = [ EarlyStopping(monitor='loss', patience=5, restore_best_weights=True), ReduceLROnPlateau(monitor='loss', factor=0.5, patience=3, min_lr=1e-6) ] # Обучение модели print("\nНачало обучения...") self.history = self.model.fit( X_train, y_train, batch_size=self.batch_size, epochs=self.epochs, callbacks=callbacks, verbose=1 ) # Оценка на обучающей выборке print("\nОценка качества...") train_predictions = np.argmax(self.model.predict(X_train), axis=1) train_predictions_labels = [self.id_to_label[pred] for pred in train_predictions] accuracy = self.evaluate(train_labels, train_predictions_labels, "обучающей выборке") self.plot_confusion_matrix(train_ids_filtered, train_predictions, "Матрица ошибок CNN (обучающая выборка)") return accuracy def test(self, test_file, images_dir): """Тестирование CNN модели.""" print("=" * 60) print("ТЕСТИРОВАНИЕ СВЕРТОЧНОЙ НЕЙРОННОЙ СЕТИ") print("=" * 60) # Загрузка данных test_paths, test_labels, test_ids = self.load_data(test_file, images_dir) # Подготовка данных X_test, y_test = self.prepare_dataset(test_paths, test_ids) print(f"Размер тестовой выборки: {X_test.shape}") # Предсказание print("\nВыполнение предсказаний...") test_predictions_proba = self.model.predict(X_test, verbose=0) test_predictions = np.argmax(test_predictions_proba, axis=1) test_predictions_labels = [self.id_to_label[pred] for pred in test_predictions] # Оценка качества accuracy = self.evaluate(test_labels, test_predictions_labels, "тестовой выборке") # Матрица ошибок self.plot_confusion_matrix(test_ids, test_predictions, "Матрица ошибок CNN (тестовая выборка)") return accuracy def predict_single(self, image_path): """Предсказание класса для одного изображения.""" image = self.preprocess_image(image_path) image_batch = np.expand_dims(image, axis=0) prediction_proba = self.model.predict(image_batch, verbose=0)[0] prediction_id = np.argmax(prediction_proba) prediction_label = self.id_to_label[prediction_id] confidence = prediction_proba[prediction_id] return prediction_label, confidence def plot_training_history(self): """Визуализация истории обучения.""" if self.history is None: print("История обучения отсутствует") return import matplotlib.pyplot as plt fig, axes = plt.subplots(1, 2, figsize=(12, 4)) # График точности axes[0].plot(self.history.history['accuracy'], label='Точность') axes[0].set_title('Точность модели') axes[0].set_xlabel('Эпоха') axes[0].set_ylabel('Точность') axes[0].legend() axes[0].grid(True) # График потерь axes[1].plot(self.history.history['loss'], label='Потери') axes[1].set_title('Потери модели') axes[1].set_xlabel('Эпоха') axes[1].set_ylabel('Потери') axes[1].legend() axes[1].grid(True) plt.tight_layout() plt.show() def save_model(self, model_dir): """Сохраняет модель CNN.""" model_dir = os.path.join(model_dir, 'cnn_model') super().save_model(model_dir) self.model.save(os.path.join(model_dir, 'cnn_model.h5')) if self.history is not None: np.save(os.path.join(model_dir, 'training_history.npy'), self.history.history) def load_model(self, model_dir): """Загружает модель CNN.""" model_dir = os.path.join(model_dir, 'cnn_model') super().load_model(model_dir) self.model = tf.keras.models.load_model(os.path.join(model_dir, 'cnn_model.h5')) history_path = os.path.join(model_dir, 'training_history.npy') if os.path.exists(history_path): history_dict = np.load(history_path, allow_pickle=True).item() self.history = type('History', (), {'history': history_dict})() return True """
Реализация алгоритма "Мешок слов" (Bag of Visual Words)
Использует SIFT/ORB/AKAZE детекторы и SVM классификатор
"""
import os
import cv2
import numpy as np
import joblib
import json
from sklearn.cluster import KMeans
from sklearn.svm import SVC
from sklearn.preprocessing import StandardScaler
from base_classifier import BaseClassifier
class BOWClassifier(BaseClassifier):
"""Классификатор на основе алгоритма 'Мешок слов'."""
textdef __init__(self, n_clusters=200, image_size=(224, 224), detector_type='sift'): super().__init__('bow', image_size) self.n_clusters = n_clusters self.detector_type = detector_type self.kmeans = None self.classifier = None self.scaler = StandardScaler() # Инициализация детектора if detector_type == 'sift': self.detector = cv2.SIFT_create() elif detector_type == 'orb': self.detector = cv2.ORB_create(nfeatures=1000) elif detector_type == 'akaze': self.detector = cv2.AKAZE_create() else: self.detector = cv2.SIFT_create() def extract_descriptors(self, image_path): """Извлекает дескрипторы из изображения.""" image = cv2.imread(image_path) if image is None: return np.array([]) gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) keypoints, descriptors = self.detector.detectAndCompute(gray, None) return descriptors if descriptors is not None else np.array([]) def build_vocabulary(self, all_descriptors): """Строит словарь визуальных слов.""" all_descriptors = np.vstack(all_descriptors) # Используем выборку для ускорения if len(all_descriptors) > 10000: indices = np.random.choice(len(all_descriptors), 10000, replace=False) all_descriptors = all_descriptors[indices] print(f"Построение словаря из {len(all_descriptors)} дескрипторов...") self.kmeans = KMeans(n_clusters=self.n_clusters, random_state=42, n_init=10) self.kmeans.fit(all_descriptors) print(f"Словарь из {self.n_clusters} визуальных слов построен") return self.kmeans def image_to_histogram(self, descriptors): """Преобразует дескрипторы в гистограмму визуальных слов.""" if len(descriptors) == 0: return np.zeros(self.n_clusters) labels = self.kmeans.predict(descriptors) histogram, _ = np.histogram(labels, bins=self.n_clusters, range=(0, self.n_clusters)) histogram = histogram.astype(np.float32) if np.sum(histogram) > 0: histogram /= np.sum(histogram) return histogram def train(self, train_file, images_dir): """Обучение модели BOW.""" print("=" * 60) print("ОБУЧЕНИЕ МОДЕЛИ 'МЕШОК СЛОВ'") print(f"Детектор: {self.detector_type}, Кластеров: {self.n_clusters}") print("=" * 60) # Загрузка данных train_paths, train_labels, train_ids = self.load_data(train_file, images_dir) # Извлечение дескрипторов print("\n1. Извлечение локальных признаков...") all_descriptors = [] valid_indices = [] for i, image_path in enumerate(train_paths): descriptors = self.extract_descriptors(image_path) if len(descriptors) > 0: all_descriptors.append(descriptors) valid_indices.append(i) print(f"Извлечено {sum(len(d) for d in all_descriptors)} дескрипторов") # Построение словаря print("\n2. Построение словаря визуальных слов...") self.build_vocabulary(all_descriptors) # Преобразование в гистограммы print("\n3. Преобразование изображений в гистограммы...") train_features = [] train_labels_filtered = [] train_ids_filtered = [] for idx in valid_indices: descriptors = self.extract_descriptors(train_paths[idx]) histogram = self.image_to_histogram(descriptors) train_features.append(histogram) train_labels_filtered.append(train_labels[idx]) train_ids_filtered.append(train_ids[idx]) train_features = np.array(train_features) # Масштабирование признаков print("\n4. Масштабирование признаков...") train_features_scaled = self.scaler.fit_transform(train_features) # Обучение классификатора print("\n5. Обучение классификатора...") self.classifier = SVC(kernel='linear', probability=True, random_state=42, C=1.0) self.classifier.fit(train_features_scaled, train_ids_filtered) # Оценка на обучающей выборке print("\n6. Оценка качества...") train_predictions = self.classifier.predict(train_features_scaled) train_predictions_labels = [self.id_to_label[pred] for pred in train_predictions] accuracy = self.evaluate(train_labels_filtered, train_predictions_labels, "обучающей выборке") self.plot_confusion_matrix(train_ids_filtered, train_predictions, "Матрица ошибок BOW (обучающая выборка)") return accuracy def test(self, test_file, images_dir): """Тестирование модели BOW.""" print("=" * 60) print("ТЕСТИРОВАНИЕ МОДЕЛИ 'МЕШОК СЛОВ'") print("=" * 60) # Загрузка данных test_paths, test_labels, test_ids = self.load_data(test_file, images_dir) # Преобразование тестовых изображений test_features = [] valid_indices = [] for i, path in enumerate(test_paths): descriptors = self.extract_descriptors(path) if len(descriptors) > 0: histogram = self.image_to_histogram(descriptors) test_features.append(histogram) valid_indices.append(i) test_features = np.array(test_features) # Масштабирование и предсказание test_features_scaled = self.scaler.transform(test_features) test_predictions = self.classifier.predict(test_features_scaled) # Фильтрация меток test_labels_filtered = [test_labels[i] for i in valid_indices] test_ids_filtered = [test_ids[i] for i in valid_indices] test_predictions_labels = [self.id_to_label[pred] for pred in test_predictions] # Оценка качества accuracy = self.evaluate(test_labels_filtered, test_predictions_labels, "тестовой выборке") # Матрица ошибок self.plot_confusion_matrix(test_ids_filtered, test_predictions, "Матрица ошибок BOW (тестовая выборка)") return accuracy def predict_single(self, image_path): """Предсказание класса для одного изображения.""" descriptors = self.extract_descriptors(image_path) histogram = self.image_to_histogram(descriptors) histogram_scaled = self.scaler.transform([histogram]) prediction_id = self.classifier.predict(histogram_scaled)[0] probabilities = self.classifier.predict_proba(histogram_scaled)[0] prediction_label = self.id_to_label[prediction_id] confidence = probabilities[prediction_id] return prediction_label, confidence def visualize_features(self, image_path, output_path=None): """Визуализирует ключевые точки на изображении.""" image = cv2.imread(image_path) if image is None: print(f"Ошибка загрузки изображения: {image_path}") return None gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) keypoints = self.detector.detect(gray, None) # Настраиваем флаги рисования в зависимости от детектора if self.detector_type == 'sift': flags = cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS else: flags = 0 image_with_keypoints = cv2.drawKeypoints( image, keypoints, None, flags=flags, color=(0, 255, 0) ) print(f"Найдено {len(keypoints)} ключевых точек") # Всегда показываем изображение import matplotlib.pyplot as plt # Создаем фигуру с двумя субплогами fig, axes = plt.subplots(1, 2, figsize=(15, 6)) # Оригинальное изображение original_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) axes[0].imshow(original_rgb) axes[0].set_title('Оригинальное изображение') axes[0].axis('off') # Изображение с ключевыми точками result_rgb = cv2.cvtColor(image_with_keypoints, cv2.COLOR_BGR2RGB) axes[1].imshow(result_rgb) axes[1].set_title(f'Ключевые точки ({self.detector_type.upper()})') axes[1].axis('off') plt.tight_layout() plt.show() return image_with_keypoints def save_model(self, model_dir): """Сохраняет модель BOW.""" model_dir = os.path.join(model_dir, 'bow_model') super().save_model(model_dir) joblib.dump(self.kmeans, os.path.join(model_dir, 'kmeans_model.pkl')) joblib.dump(self.classifier, os.path.join(model_dir, 'classifier_model.pkl')) joblib.dump(self.scaler, os.path.join(model_dir, 'scaler.pkl')) detector_params = { 'detector_type': self.detector_type, 'n_clusters': self.n_clusters } with open(os.path.join(model_dir, 'detector_params.json'), 'w') as f: json.dump(detector_params, f, indent=2) def load_model(self, model_dir): """Загружает модель BOW.""" model_dir = os.path.join(model_dir, 'bow_model') super().load_model(model_dir) self.kmeans = joblib.load(os.path.join(model_dir, 'kmeans_model.pkl')) self.classifier = joblib.load(os.path.join(model_dir, 'classifier_model.pkl')) self.scaler = joblib.load(os.path.join(model_dir, 'scaler.pkl')) with open(os.path.join(model_dir, 'detector_params.json'), 'r') as f: detector_params = json.load(f) self.detector_type = detector_params['detector_type'] self.n_clusters = detector_params['n_clusters'] if self.detector_type == 'sift': self.detector = cv2.SIFT_create() elif self.detector_type == 'orb': self.detector = cv2.ORB_create(nfeatures=1000) elif self.detector_type == 'akaze': self.detector = cv2.AKAZE_create() return True """
Базовый класс классификатора изображений
Реализует общий функционал для всех алгоритмов классификации
"""
import os
import json
import numpy as np
from abc import ABC, abstractmethod
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix
import matplotlib.pyplot as plt
class BaseClassifier(ABC):
"""Абстрактный базовый класс для классификаторов изображений."""
textdef __init__(self, algorithm, image_size=(224, 224)): self.algorithm = algorithm self.image_size = image_size self.class_names = ['Архангельский собор', 'Дворец труда', 'Нижегородский Кремль'] self.label_to_id = {name: i for i, name in enumerate(self.class_names)} self.id_to_label = {i: name for name, i in self.label_to_id.items()} self.model = None def detect_label_from_path(self, file_path): """Определяет метку класса из пути к файлу.""" file_path_lower = file_path.lower() if '01_nizhnynovgorodkremlin' in file_path_lower or 'нижегородский кремль' in file_path_lower: return 'Нижегородский Кремль' elif '08_palaceoflabor' in file_path_lower or 'дворец труда' in file_path_lower: return 'Дворец труда' elif '04_arkhangelskcathedral' in file_path_lower or 'архангельский собор' in file_path_lower: return 'Архангельский собор' return None def load_data(self, file_list_path, images_dir): """Загружает данные из файла со списком изображений.""" with open(file_list_path, 'r', encoding='utf-8') as f: lines = f.readlines() image_paths, labels, label_ids = [], [], [] for line in lines: line = line.strip() if not line: continue full_path = os.path.join(images_dir, line) label = self.detect_label_from_path(full_path) if label is not None and os.path.exists(full_path): image_paths.append(full_path) labels.append(label) label_ids.append(self.label_to_id[label]) print(f"Загружено {len(image_paths)} изображений") for label_name in self.class_names: count = labels.count(label_name) print(f" {label_name}: {count} изображений") return image_paths, labels, label_ids def evaluate(self, true_labels, pred_labels, dataset_type="выборке"): """Оценка качества классификации.""" accuracy = accuracy_score(true_labels, pred_labels) print(f"Accuracy на {dataset_type}: {accuracy:.4f} ({accuracy*100:.1f}%)") print("\nДетальный отчет:") print(classification_report(true_labels, pred_labels, target_names=self.class_names)) return accuracy def plot_confusion_matrix(self, y_true, y_pred, title="Матрица ошибок"): """Визуализация матрицы ошибок.""" cm = confusion_matrix(y_true, y_pred) fig, ax = plt.subplots(figsize=(8, 6)) im = ax.imshow(cm, interpolation='nearest', cmap=plt.cm.Blues) ax.figure.colorbar(im, ax=ax) ax.set(xticks=np.arange(len(self.class_names)), yticks=np.arange(len(self.class_names)), xticklabels=self.class_names, yticklabels=self.class_names, title=title, ylabel='Истинные метки', xlabel='Предсказанные метки') plt.setp(ax.get_xticklabels(), rotation=45, ha="right", rotation_mode="anchor") thresh = cm.max() / 2. for i in range(cm.shape[0]): for j in range(cm.shape[1]): ax.text(j, i, format(cm[i, j], 'd'), ha="center", va="center", color="white" if cm[i, j] > thresh else "black") fig.tight_layout() plt.show() return cm def save_model(self, model_dir): """Сохраняет модель в указанную директорию.""" os.makedirs(model_dir, exist_ok=True) metadata = { 'algorithm': self.algorithm, 'image_size': self.image_size, 'class_names': self.class_names, 'label_to_id': self.label_to_id } with open(os.path.join(model_dir, 'metadata.json'), 'w', encoding='utf-8') as f: json.dump(metadata, f, ensure_ascii=False, indent=2) print(f"Модель сохранена в {model_dir}") def load_model(self, model_dir): """Загружает модель из указанной директории.""" with open(os.path.join(model_dir, 'metadata.json'), 'r', encoding='utf-8') as f: metadata = json.load(f) self.algorithm = metadata['algorithm'] self.class_names = metadata['class_names'] self.image_size = tuple(metadata['image_size']) self.label_to_id = metadata['label_to_id'] self.id_to_label = {int(i): name for name, i in self.label_to_id.items()} print(f"Модель загружена из {model_dir}") return True @abstractmethod def train(self, train_file, images_dir): """Абстрактный метод для обучения модели.""" pass @abstractmethod def test(self, test_file, images_dir): """Абстрактный метод для тестирования модели.""" pass @abstractmethod def predict_single(self, image_path): """Абстрактный метод для предсказания класса одного изображения.""" pass и также в папке со скриптами у меня ещё есть: create_test_set.py: """"
Создание тестовой выборки из оставшихся изображений
"""
import os
def collect_all_images(data_dir):
"""Собирает все изображения из датасета."""
all_images = []
textfor dataset in ['NNSUDataset', 'ExtDataset']: dataset_path = os.path.join(data_dir, dataset) if os.path.exists(dataset_path): for class_folder in ['01_NizhnyNovgorodKremlin', '04_ArkhangelskCathedral', '08_PalaceOfLabor']: class_path = os.path.join(dataset_path, class_folder) if os.path.exists(class_path): for file_name in os.listdir(class_path): if file_name.lower().endswith(('.jpg', '.jpeg', '.png')): rel_path = f"{dataset}/{class_folder}/{file_name}" all_images.append(rel_path) return all_images
def create_test_file(data_dir, train_file_path, output_test_file):
"""Создает test.txt из изображений, не входящих в train.txt."""
print("Создание тестовой выборки...")
text# Читаем train.txt train_images = set() if os.path.exists(train_file_path): with open(train_file_path, 'r', encoding='utf-8') as f: for line in f: line = line.strip() if line: train_images.add(line) # Собираем все изображения all_images = collect_all_images(data_dir) print(f"Всего изображений в датасете: {len(all_images)}") print(f"Изображений в train.txt: {len(train_images)}") # Формируем тестовую выборку test_images = [] for img_path in all_images: if img_path not in train_images: test_images.append(img_path) # Записываем в test.txt os.makedirs(os.path.dirname(output_test_file), exist_ok=True) with open(output_test_file, 'w', encoding='utf-8') as f: for img_path in test_images: f.write(img_path + '\n') print(f"Создана тестовая выборка из {len(test_images)} изображений") print(f"Файл сохранен: {output_test_file}") return test_images
def main():
data_dir = "NNClassification"
original_train_file = os.path.join(data_dir, "train_test_split", "train.txt")
output_test_file = os.path.join(data_dir, "train_test_split", "test.txt")
create_test_file(data_dir, original_train_file, output_test_file)
if name == "main":
main()" и prepare_all: """"
Подготовка всех данных для работы программы:
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(file)))
from create_test_set import create_test_file
from main import create_extended_train_file
def prepare_all_data():
"""Подготавливает все данные для работы."""
print("=" * 60)
print("ПОДГОТОВКА ДАННЫХ ДЛЯ КЛАССИФИКАЦИИ")
print("=" * 60)
textdata_dir = "NNClassification" extra_dir = "my_images" # 1. Создаем test.txt original_train_file = os.path.join(data_dir, "train_test_split", "train.txt") test_file = os.path.join(data_dir, "train_test_split", "test.txt") if os.path.exists(original_train_file): create_test_file(data_dir, original_train_file, test_file) else: print(f"Ошибка: файл {original_train_file} не найден") # 2. Создаем расширенный train файл if os.path.exists(extra_dir): extended_train_file = create_extended_train_file(original_train_file, extra_dir, data_dir) print(f"\nИспользуйте файл: {extended_train_file}") else: print(f"\nПапка с дополнительными изображениями не найдена: {extra_dir}") print(f"Используйте оригинальный файл: {original_train_file}") print("\n" + "=" * 60) print("ПОДГОТОВКА ЗАВЕРШЕНА!") print("=" * 60)
if name == "main":
prepare_all_data()" - ошибки сейчас в том что при выполнении скриптов из этого списка: "echo 1. ПОДГОТОВКА ДАННЫХ...
echo 1.1. Создание тестовой выборки и расширенного train файла...
python scripts/prepare_all.py
echo.
echo 2. ВИЗУАЛИЗАЦИЯ КЛЮЧЕВЫХ ТОЧЕК...
echo 2.1. Визуализация SIFT-дескрипторов на изображении Кремля...
python scripts/main.py --mode visualize --image_path "NNClassification\ExtDataset\01_NizhnyNovgorodKremlin\7377598dba3c816bcda655d07bd4695d.jpeg" --detector sift
echo 2.2. Визуализация ORB-дескрипторов на изображении Дворца труда...
python scripts/main.py --mode visualize --image_path "NNClassification\ExtDataset\08_PalaceOfLabor\57_nnw.jpg" --detector orb
echo.
echo 3. АЛГОРИТМ "МЕШОК СЛОВ"...
echo 3.1. Обучение и тестирование BOW с SIFT (150 кластеров)...
python scripts/main.py --mode both --algorithm bow --clusters 150 --detector sift
echo 3.2. Обучение и тестирование BOW с ORB (100 кластеров)...
python scripts/main.py --mode both --algorithm bow --clusters 100 --detector orb
echo 3.3. Тестирование на одном изображении...
python scripts/main.py --mode single --algorithm bow --image_path "my_images\Нижегородский Кремль\IMG_1063.jpg"
echo.
echo 4. НЕЙРОННАЯ СЕТЬ (CNN)...
echo 4.1. Обучение и тестирование CNN (10 эпох)...
python scripts/main.py --mode both --algorithm cnn --epochs 10 --batch_size 8 --plot_history
echo 4.2. Тестирование на одном изображении...
python scripts/main.py --mode single --algorithm cnn --image_path "my_images\Дворец труда\shutterstock_499029421-min-1.jpg"" при обучении на одном изображении нейронной сети возникает ошибка которую я описал в начале а при выполнении этих пунктов: "echo 3. АЛГОРИТМ "МЕШОК СЛОВ"...
echo 3.1. Обучение и тестирование BOW с SIFT (150 кластеров)...
python scripts/main.py --mode both --algorithm bow --clusters 150 --detector sift
echo 3.2. Обучение и тестирование BOW с ORB (100 кластеров)...
python scripts/main.py --mode both --algorithm bow --clusters 100 --detector orb" в конце командной строки происходит зависание - исправь эти недостатки - в целом код кажется работает корректно и приведи мне все исправления которые нужно внести также похоже что мои дополнительные изображения сейчас не добавляются к обучающей выборке - тоже это исправь
Ниже — точечные правки, которые исправляют все 3 проблемы:
train_ids_filtered не определена)plt.show() при отрисовке матрицы ошибок)my_images реально не попадают в обучение (в train_extended.txt записываются неправильные пути)Я дам исправления так, чтобы у вас:
my_images/... точно подхватывались,my_images не добавляются сейчасВ create_extended_train_file() вы добавляете строки вида:
pythonfile_path = f"ExtDataset/{dataset_name}/{file_name}"
Но ваши файлы реально лежат в my_images/..., а не в NNClassification/ExtDataset/....
Поэтому load_data() их не находит (os.path.exists(full_path) = False), и в обучении остаются только 131 исходное фото (что вы и видите в логах).
✅ Решение: записывать в train_extended.txt реальные пути к файлам my_images, но так, чтобы load_data() нашёл их при images_dir=NNClassification. Самый удобный вариант — писать путь относительно NNClassification:
..\my_images\Нижегородский Кремль\IMG_1063.jpg
У вас в BaseClassifier.plot_confusion_matrix() есть:
pythonplt.show()
plt.show() блокирует выполнение, пока вы не закроете окно. В .bat это выглядит как “зависание”.
✅ Решение:
--plot_confusion,--show_plots.NameErrorВ CNNClassifier.train() вы вызываете:
pythonself.plot_confusion_matrix(train_ids_filtered, train_predictions, ...)
Но train_ids_filtered в CNN-ветке вообще нигде не создаётся.
✅ Решение: сделать фильтрацию как минимум корректной (или просто использовать train_ids), а лучше — сделать её как в BOW: если какие-то картинки вдруг не прочитались, выкидываем их и синхронизируем массивы.
Ниже даю конкретные куски кода, которые надо заменить/добавить.
scripts/base_classifier.py__init__ добавьте настройки для графиковНайдите __init__ и добавьте в конец:
python# Настройки визуализации (управляются из main.py) self.model_base_dir = 'models' self.plot_confusion = False # строить/сохранять матрицу ошибок self.plot_history = False # строить/сохранять графики обучения (CNN) self.show_plots = False # показывать окна matplotlib (может блокировать)
load_data() чтобы нормально работали ..\my_images\...Замените внутри load_data() формирование full_path на нормализованное:
Было:
pythonfull_path = os.path.join(images_dir, line) label = self.detect_label_from_path(full_path) if label is not None and os.path.exists(full_path):
Стало:
python# line может быть: # - относительным путем внутри images_dir # - относительным путем с .. (например, ..\my_images\...) # - абсолютным путем if os.path.isabs(line): full_path = os.path.normpath(line) else: full_path = os.path.normpath(os.path.join(images_dir, line)) label = self.detect_label_from_path(full_path) if label is not None and os.path.exists(full_path):
plot_confusion_matrix() неблокирующей и с сохранением в файлЗамените функцию plot_confusion_matrix целиком на эту:
pythondef plot_confusion_matrix(self, y_true, y_pred, title="Матрица ошибок", show=False, save_path=None): """Визуализация матрицы ошибок (опционально сохраняет в файл и/или показывает окно).""" cm = confusion_matrix(y_true, y_pred) fig, ax = plt.subplots(figsize=(8, 6)) im = ax.imshow(cm, interpolation='nearest', cmap=plt.cm.Blues) ax.figure.colorbar(im, ax=ax) ax.set( xticks=np.arange(len(self.class_names)), yticks=np.arange(len(self.class_names)), xticklabels=self.class_names, yticklabels=self.class_names, title=title, ylabel='Истинные метки', xlabel='Предсказанные метки' ) plt.setp(ax.get_xticklabels(), rotation=45, ha="right", rotation_mode="anchor") thresh = cm.max() / 2. if cm.size else 0 for i in range(cm.shape[0]): for j in range(cm.shape[1]): ax.text( j, i, format(cm[i, j], 'd'), ha="center", va="center", color="white" if cm[i, j] > thresh else "black" ) fig.tight_layout() # Сохраняем, если нужно if save_path: os.makedirs(os.path.dirname(save_path), exist_ok=True) fig.savefig(save_path, dpi=150, bbox_inches='tight') # Показываем, если нужно (может блокировать) if show: plt.show() # В любом случае закрываем, чтобы не копить окна/память plt.close(fig) return cm
✅ Теперь “зависания” из‑за матрицы ошибок не будет.
scripts/main.pymatplotlib.pyplot as pltВ начало файла (рядом с import cv2) добавьте:
pythonimport matplotlib.pyplot as plt
Иначе у вас режим --mode single потенциально упадёт, когда дойдёт до plt.figure().
create_extended_train_file() чтобы my_images реально попадали в trainЗамените функцию create_extended_train_file() целиком на эту:
pythondef create_extended_train_file(original_train_file, extra_images_dir, data_dir): """Создает расширенный train файл с дополнительными изображениями из my_images. Пути к доп.изображениям записываются так, чтобы они корректно находились при images_dir=data_dir. """ print("Создание расширенной обучающей выборки...") # Читаем оригинальный train.txt with open(original_train_file, 'r', encoding='utf-8') as f: original_lines = [line.strip() for line in f.readlines() if line.strip()] # Чтобы не плодить дубликаты seen = set(original_lines) # Папки доп.изображений (как у вас на диске) russian_folders = [ 'Нижегородский Кремль', 'Дворец труда', 'Архангельский собор' ] extra_lines = [] for folder_name in russian_folders: folder_path = os.path.join(extra_images_dir, folder_name) if not os.path.isdir(folder_path): continue for file_name in os.listdir(folder_path): if not file_name.lower().endswith(('.jpg', '.jpeg', '.png')): continue abs_path = os.path.abspath(os.path.join(folder_path, file_name)) # Делаем путь относительно data_dir (NNClassification), # чтобы join(images_dir, rel_path) корректно находил файл: # NNClassification\..\my_images\...\img.jpg try: rel_path = os.path.relpath(abs_path, start=os.path.abspath(data_dir)) except ValueError: # На случай разных дисков (редко, но бывает) rel_path = abs_path rel_path = rel_path.replace('\\', '/') if rel_path not in seen: extra_lines.append(rel_path) seen.add(rel_path) all_lines = original_lines + extra_lines extended_train_file = os.path.join(data_dir, 'train_test_split', 'train_extended.txt') with open(extended_train_file, 'w', encoding='utf-8') as f: for line in all_lines: f.write(line + '\n') print(f"Оригинальных изображений: {len(original_lines)}") print(f"Дополнительных изображений: {len(extra_lines)}") print(f"Всего в расширенной выборке: {len(all_lines)}") print(f"Файл сохранен: {extended_train_file}") return extended_train_file
✅ После этого при CNN/BOW обучении у вас должно стать 131 + 16 = 147 загруженных изображений (если все 16 файлов реально существуют).
--show_plots (чтобы окна можно было включать вручную)В argparse добавьте:
pythonparser.add_argument('--show_plots', action='store_true', help='Показывать окна matplotlib (может блокировать выполнение). ' 'Без этого флага графики будут только сохраняться (если включены).')
После:
pythonclassifier = create_classifier(args.algorithm, **classifier_kwargs)
добавьте:
python# Настройки графиков/папки моделей classifier.model_base_dir = args.model_dir classifier.plot_confusion = args.plot_confusion classifier.plot_history = args.plot_history classifier.show_plots = args.show_plots
single: если модели нет — нормально сообщаем, а не падаемЗамените этот кусок:
pythonmodel_path = os.path.join(args.model_dir, f"{args.algorithm}_model") if os.path.exists(model_path): classifier.load_model(args.model_dir) test_single_image(classifier, args.image_path) return
на:
pythonmodel_path = os.path.join(args.model_dir, f"{args.algorithm}_model") if not os.path.exists(model_path): print(f"Модель не найдена: {model_path}") print("Сначала обучите модель: --mode train или --mode both") return classifier.load_model(args.model_dir) test_single_image(classifier, args.image_path) return
scripts/bow_classifier.pyВ двух местах (в train() и test()) вы вызываете plot_confusion_matrix() безусловно.
train() замените блок с матрицей ошибокНайдите:
pythonself.plot_confusion_matrix(train_ids_filtered, train_predictions, "Матрица ошибок BOW (обучающая выборка)")
Замените на:
pythonif self.plot_confusion: artifacts_dir = os.path.join(self.model_base_dir, f"{self.algorithm}_model") os.makedirs(artifacts_dir, exist_ok=True) save_path = os.path.join(artifacts_dir, "confusion_train.png") self.plot_confusion_matrix( train_ids_filtered, train_predictions, title="Матрица ошибок BOW (обучающая выборка)", show=self.show_plots, save_path=save_path ) print(f"Матрица ошибок сохранена: {save_path}")
test() замените блок с матрицей ошибокНайдите:
pythonself.plot_confusion_matrix(test_ids_filtered, test_predictions, "Матрица ошибок BOW (тестовая выборка)")
Замените на:
pythonif self.plot_confusion: artifacts_dir = os.path.join(self.model_base_dir, f"{self.algorithm}_model") os.makedirs(artifacts_dir, exist_ok=True) save_path = os.path.join(artifacts_dir, "confusion_test.png") self.plot_confusion_matrix( test_ids_filtered, test_predictions, title="Матрица ошибок BOW (тестовая выборка)", show=self.show_plots, save_path=save_path ) print(f"Матрица ошибок сохранена: {save_path}")
✅ Теперь BOW не будет “зависать” в конце по умолчанию.
scripts/cnn_classifier.pypreprocess_image() безопаснойЗамените на:
pythondef preprocess_image(self, image_path): """Предобработка изображения для CNN.""" image = cv2.imread(image_path) if image is None: return None image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) image = cv2.resize(image, self.image_size) image = image.astype('float32') / 255.0 return image
prepare_dataset() — вернуть valid_indices (чтобы не было рассинхрона)Замените на:
pythondef prepare_dataset(self, image_paths, label_ids): """Подготавливает датасет для обучения/тестирования. Возвращает X, y, valid_indices (индексы успешно загруженных изображений). """ X, y = [], [] valid_indices = [] for idx, (path, label_id) in enumerate(zip(image_paths, label_ids)): image = self.preprocess_image(path) if image is None: continue X.append(image) y.append(label_id) valid_indices.append(idx) X = np.array(X, dtype=np.float32) y = tf.keras.utils.to_categorical(y, num_classes=len(self.class_names)) return X, y, valid_indices
train() (NameError + условные графики)Найдите в train() строки подготовки данных:
Было:
pythonX_train, y_train = self.prepare_dataset(train_paths, train_ids)
Стало:
pythonX_train, y_train, valid_indices = self.prepare_dataset(train_paths, train_ids) train_labels_filtered = [train_labels[i] for i in valid_indices] train_ids_filtered = [train_ids[i] for i in valid_indices]
Дальше замените оценку/матрицу ошибок на такой блок:
pythonprint("\nОценка качества...") train_predictions = np.argmax(self.model.predict(X_train, verbose=0), axis=1) train_predictions_labels = [self.id_to_label[pred] for pred in train_predictions] accuracy = self.evaluate(train_labels_filtered, train_predictions_labels, "обучающей выборке") # Матрица ошибок (если включена) if self.plot_confusion: artifacts_dir = os.path.join(self.model_base_dir, f"{self.algorithm}_model") os.makedirs(artifacts_dir, exist_ok=True) save_path = os.path.join(artifacts_dir, "confusion_train.png") self.plot_confusion_matrix( train_ids_filtered, train_predictions, title="Матрица ошибок CNN (обучающая выборка)", show=self.show_plots, save_path=save_path ) print(f"Матрица ошибок сохранена: {save_path}") # Графики обучения (если включены) if self.plot_history: artifacts_dir = os.path.join(self.model_base_dir, f"{self.algorithm}_model") os.makedirs(artifacts_dir, exist_ok=True) save_path = os.path.join(artifacts_dir, "training_history.png") self.plot_training_history(show=self.show_plots, save_path=save_path) print(f"График истории обучения сохранен: {save_path}") return accuracy
✅ Это полностью убирает NameError и делает графики управляемыми.
test() аналогично (фильтрация + условная матрица)Замените:
pythonX_test, y_test = self.prepare_dataset(test_paths, test_ids)
на:
pythonX_test, y_test, valid_indices = self.prepare_dataset(test_paths, test_ids) test_labels_filtered = [test_labels[i] for i in valid_indices] test_ids_filtered = [test_ids[i] for i in valid_indices]
И ниже, где accuracy = ... замените:
pythonaccuracy = self.evaluate(test_labels, test_predictions_labels, "тестовой выборке") self.plot_confusion_matrix(test_ids, test_predictions, "Матрица ошибок CNN (тестовая выборка)")
на:
pythonaccuracy = self.evaluate(test_labels_filtered, test_predictions_labels, "тестовой выборке") if self.plot_confusion: artifacts_dir = os.path.join(self.model_base_dir, f"{self.algorithm}_model") os.makedirs(artifacts_dir, exist_ok=True) save_path = os.path.join(artifacts_dir, "confusion_test.png") self.plot_confusion_matrix( test_ids_filtered, test_predictions, title="Матрица ошибок CNN (тестовая выборка)", show=self.show_plots, save_path=save_path ) print(f"Матрица ошибок сохранена: {save_path}") return accuracy
plot_training_history() чтобы умела сохранять и не блокироватьЗамените метод целиком на:
pythondef plot_training_history(self, show=False, save_path=None): """Визуализация истории обучения.""" if self.history is None: print("История обучения отсутствует") return import matplotlib.pyplot as plt fig, axes = plt.subplots(1, 2, figsize=(12, 4)) axes[0].plot(self.history.history.get('accuracy', []), label='Точность') axes[0].set_title('Точность модели') axes[0].set_xlabel('Эпоха') axes[0].set_ylabel('Точность') axes[0].legend() axes[0].grid(True) axes[1].plot(self.history.history.get('loss', []), label='Потери') axes[1].set_title('Потери модели') axes[1].set_xlabel('Эпоха') axes[1].set_ylabel('Потери') axes[1].legend() axes[1].grid(True) fig.tight_layout() if save_path: os.makedirs(os.path.dirname(save_path), exist_ok=True) fig.savefig(save_path, dpi=150, bbox_inches='tight') if show: plt.show() plt.close(fig)
my_images реально попадут в trainВ логе CNN/BOW вы должны увидеть:
Загружено 131 изображенийЗагружено 147 изображений (если 16 доп. файлов реально есть)Потому что матрица ошибок не рисуется без флага --plot_confusion.
train_ids_filtered будет определена, и код пойдёт дальше (save_model + test).
Ваши команды можно оставить почти такими же.
Если хотите, чтобы ничего не блокировало и всё шло автоматически — используйте так:
batpython scripts/main.py --mode both --algorithm bow --clusters 150 --detector sift python scripts/main.py --mode both --algorithm bow --clusters 100 --detector orb python scripts/main.py --mode both --algorithm cnn --epochs 10 --batch_size 8 --plot_history
--plot_history теперь будет сохранять график в models/cnn_model/training_history.png.Если хотите ещё и показывать окна (и да, это может тормозить выполнение до закрытия окна) — добавляйте:
bat--show_plots
Например:
batpython scripts/main.py --mode both --algorithm bow --clusters 150 --detector sift --plot_confusion --show_plots
Если хотите, я могу ещё одним шагом сделать так, чтобы доп. изображения автоматически копировались в отдельную папку внутри NNClassification (например MyDataset/) и всё было “в одном датасете” без ..\my_images\.... Но текущий фикс уже полностью решает вашу проблему и ничего не ломает в структуре.