"Практическая работа №3. Классификация изображений...

Créé le : 24 décembre 2025

Répondu en utilisant GPT-5.2 Pro par Chat01

Question

"Практическая работа №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}")

text
pred_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)

if name == "main":
main()" но я получаю ошибку во время работы команды: "D:\practial_work_3>python scripts/main.py --mode both --algorithm cnn --epochs 10 --batch_size 8 --plot_history
2025-12-24 11:17:55.842294: I tensorflow/core/util/port.cc:153] oneDNN custom operations are on. You may see slightly different numerical results due to floating-point round-off errors from different computation orders. To turn them off, set the environment variable TF_ENABLE_ONEDNN_OPTS=0.
2025-12-24 11:17:57.888248: I tensorflow/core/util/port.cc:153] oneDNN custom operations are on. You may see slightly different numerical results due to floating-point round-off errors from different computation orders. To turn them off, set the environment variable TF_ENABLE_ONEDNN_OPTS=0.
C:\Users\maxim\AppData\Local\Programs\Python\Python313\Lib\site-packages\keras\src\export\tf2onnx_lib.py:8: FutureWarning: In the future np.object will be defined as the corresponding NumPy scalar.
if not hasattr(np, "object"):
Создание расширенной обучающей выборки...
Оригинальных изображений: 131
Дополнительных изображений: 16
Всего в расширенной выборке: 147
Файл сохранен: ./NNClassification\train_test_split\train_extended.txt

КЛАССИФИКАЦИЯ ДОСТОПРИМЕЧАТЕЛЬНОСТЕЙ НИЖНЕГО НОВГОРОДА

Алгоритм: cnn
Режим: both
Данные: ./NNClassification
Обучающая выборка: ./NNClassification\train_test_split\train_extended.txt
Тестовая выборка: ./NNClassification\train_test_split/test.txt

============================================================
ОБУЧЕНИЕ МОДЕЛИ

============================================================
ОБУЧЕНИЕ СВЕРТОЧНОЙ НЕЙРОННОЙ СЕТИ
Базовая модель: VGG16

Загружено 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

text
accuracy 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."""

text
def __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):
"""Классификатор на основе алгоритма 'Мешок слов'."""

text
def __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):
"""Абстрактный базовый класс для классификаторов изображений."""

text
def __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 = []

text
for 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: """"
Подготовка всех данных для работы программы:

  1. Создает test.txt из оставшихся изображений
  2. Создает расширенный train файл с дополнительными изображениями
    """

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)

text
data_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" в конце командной строки происходит зависание - исправь эти недостатки - в целом код кажется работает корректно и приведи мне все исправления которые нужно внести также похоже что мои дополнительные изображения сейчас не добавляются к обучающей выборке - тоже это исправь

Réflexion

Troubleshooting errors

I'm mapping out errors in the code, focusing on undefined variables, blocking plots, and missing imports. Progress is steady, but I need more information for a precise solution.

Considering image paths

I'm focusing on path adjustments for additional images in the create_extended_train_file function, ensuring paths are relative to the project's root directory for effective loading.

Validating paths

I’m working on validating and rerouting image paths in create_extended_train_file, ensuring they consistently point to the correct directories, especially for additional images. Handling these paths correctly is crucial.

Adding CLI options

I’m considering CLI options to control plot visibility, using a show_plots attribute. This approach avoids script stalls while enabling essential plot generation for reports.

Ensuring robustness

I’m addressing visualization handling for headless environments, refining the approach in visualize_features to manage batch script execution without freezing, and aligning label IDs in CNNClassifier.train.

Aligning labels

I'm working on ensuring consistency in label mapping and visualization, addressing any discrepancies and focusing on aligning labels for accurate performance evaluation in the classification reports.

Refining dataset preparation

I’m enhancing the prepare_dataset function to ensure label and ID lists are filtered correctly, aligning them with evaluation needs and avoiding discrepancies in confusion matrix calculations.

Enhancing plot control

I’m adding show_plots and save_plots_dir attributes to BaseClassifier. This helps avoid script stalls by controlling plot visibility and ensuring essential plots are stored for reports.

Improving paths

I’m normalizing paths and avoiding duplicates to ensure reliability and address discrepancies for consistent data handling across the script. This approach enhances overall data management.

Adjusting file paths

I’m refining file path management and handling to ensure scripts run smoothly, addressing discrepancies without causing stalls, and making the process more reliable and efficient.

Enhancing plot control

I’m making confusion matrix plotting optional and adding CLI flags for show_plots and save_plots_dir. This ensures flexibility and avoids stalls during script execution.

Enhancing plot options

I’m making confusion matrix plotting opt-in with show_plots and save_plots_dir, ensuring clarity and avoiding script stalls. This approach keeps the process efficient and user-friendly.

Ensuring accuracy

I’m updating load_data to handle absolute and relative paths correctly, normalizing paths for better data management. This will prevent execution errors and enhance script reliability.

Digitizing support

I’m connecting to a nationwide mental health support hotline, exploring what assistance they can offer, ensuring their services are at my disposal.

Combining models

I’m working through the idea of merging CNN and BOW models. This integration would allow both to boost each other, combining their strengths for enhanced overall performance.

Clarifying plot options

I’m thinking through show_plots and save_path for confusion matrix and training histories. This streamlines plotting processes and enhances user command.

Streamlining plotting

I’m focusing on show_plots and save_plots_dir for confusion matrix and training histories. This clarity simplifies plotting processes and enhances user command.

Finalizing modifications

I’m detailing required code tweaks to fix errors, ensure smooth execution, and enhance user commands. This includes adding imports, adjusting class attributes, and refining function logic.

Providing clarity

I’m mapping out error fixes and execution tweaks for BOWClassifier and CNNClassifier. Each adjustment enhances functionality and ensures smooth running.

Enhancing script functionality

I’m mapping out path resolution and batch script improvements for main.py. This involves importing matplotlib, enhancing directory handling, and ensuring smooth script execution.

Tackling file organization

I’m working on handling Windows-style paths and normalizing them. Also, I’m adding unique filenames for confusion matrices to avoid overwrites in BOW and CNN classifications.

Tweaking parameters

I’m enhancing BOW and CNN classifier file naming to prevent overwrites and addressing batch script tweaks to handle directory conflicts better. Prioritizing clarity and progress is key.

Crafting comprehensive instructions

I’m assembling detailed code tweaks for base_classifier.py, bow_classifier.py, cnn_classifier.py, and main.py. This includes correcting errors, boosting clarity, and ensuring seamless script execution.

Streamlining path resolutions

Wrapping up the path resolution improvements, I’m tweaking prepare_all.py for consistent directory handling and ensuring the proper inclusion of plt in test_single_image.

Enhancing classifier functionality

Mapping out updates for base_classifier.py, bow_classifier.py, cnn_classifier.py, and main.py to handle path resolutions, manage plot visibility, and ensure smooth script execution.

Crafting concise patches

I’m honing code patches with diff-style changes for base_classifier.py, bow_classifier.py, cnn_classifier.py, and main.py, ensuring clarity and practicality without overwhelming details.

Enhancing function structure

I’m pulling together updates for base_classifier.py, bow_classifier.py, cnn_classifier.py, and main.py, focusing on improving clarity, path management, and plot handling. It’s interesting to see the progress.

Refining load_data method

I’m digging into the load_data method in base_classifier.py, ensuring it seamlessly handles both relative and absolute paths, managing encoding, and meticulously defining image paths and labels.

Refining import strategy

I’m piecing together methods to streamline imports in main.py by deferring TensorFlow imports to minimize overhead, reduce warnings, and ensure efficient data preparation.

Enhancing robustness

I’m suggesting improvements for model path handling, image inclusion, and import management in main.py, aiming for a more cohesive and efficient data preparation and testing process.

Refining plot handling

I'm enhancing plot_confusion_matrix by integrating robust path handling, dynamic subtitle generation, and interactive display controls for clearer, more effective visualizations.

Addressing KMeans dtype

Interestingly enough, converting ORB descriptors to float before KMeans training can prevent errors and ensure consistent vocabulary generation. This approach should enhance the process.

Reasoned for 13m 9s

Réponse

Ниже — точечные правки, которые исправляют все 3 проблемы:

  1. NameError в CNN (train_ids_filtered не определена)
  2. “Зависание” после BOW (на самом деле это блокировка на plt.show() при отрисовке матрицы ошибок)
  3. Ваши my_images реально не попадают в обучениеtrain_extended.txt записываются неправильные пути)

Я дам исправления так, чтобы у вас:

  • дополнительные фото из my_images/... точно подхватывались,
  • матрица ошибок/графики не блокировали консоль (и при желании сохранялись/показывались),
  • CNN дотренировывалась без NameError и переходила к тесту/сохранению модели.

1) Почему my_images не добавляются сейчас

В create_extended_train_file() вы добавляете строки вида:

python
file_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


2) Почему “зависает” BOW в конце

У вас в BaseClassifier.plot_confusion_matrix() есть:

python
plt.show()

plt.show() блокирует выполнение, пока вы не закроете окно. В .bat это выглядит как “зависание”.

✅ Решение:

  • матрицу ошибок строить только по флагу --plot_confusion,
  • и по умолчанию не показывать окна, а сохранять в файл,
  • а если нужно именно окно — добавить отдельный флаг --show_plots.

3) Почему CNN падает с NameError

В CNNClassifier.train() вы вызываете:

python
self.plot_confusion_matrix(train_ids_filtered, train_predictions, ...)

Но train_ids_filtered в CNN-ветке вообще нигде не создаётся.

✅ Решение: сделать фильтрацию как минимум корректной (или просто использовать train_ids), а лучше — сделать её как в BOW: если какие-то картинки вдруг не прочитались, выкидываем их и синхронизируем массивы.


Исправления по файлам

Ниже даю конкретные куски кода, которые надо заменить/добавить.


A) scripts/base_classifier.py

1) В __init__ добавьте настройки для графиков

Найдите __init__ и добавьте в конец:

python
# Настройки визуализации (управляются из main.py) self.model_base_dir = 'models' self.plot_confusion = False # строить/сохранять матрицу ошибок self.plot_history = False # строить/сохранять графики обучения (CNN) self.show_plots = False # показывать окна matplotlib (может блокировать)

2) Исправьте load_data() чтобы нормально работали ..\my_images\...

Замените внутри load_data() формирование full_path на нормализованное:

Было:

python
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):

Стало:

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):

3) Сделайте plot_confusion_matrix() неблокирующей и с сохранением в файл

Замените функцию plot_confusion_matrix целиком на эту:

python
def 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

✅ Теперь “зависания” из‑за матрицы ошибок не будет.


B) scripts/main.py

1) Добавьте импорт matplotlib.pyplot as plt

В начало файла (рядом с import cv2) добавьте:

python
import matplotlib.pyplot as plt

Иначе у вас режим --mode single потенциально упадёт, когда дойдёт до plt.figure().


2) Исправьте create_extended_train_file() чтобы my_images реально попадали в train

Замените функцию create_extended_train_file() целиком на эту:

python
def 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 файлов реально существуют).


3) Добавьте флаг --show_plots (чтобы окна можно было включать вручную)

В argparse добавьте:

python
parser.add_argument('--show_plots', action='store_true', help='Показывать окна matplotlib (может блокировать выполнение). ' 'Без этого флага графики будут только сохраняться (если включены).')

4) Перед обучением/тестом прокиньте настройки в классификатор

После:

python
classifier = 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

5) Режим single: если модели нет — нормально сообщаем, а не падаем

Замените этот кусок:

python
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

на:

python
model_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

C) scripts/bow_classifier.py

В двух местах (в train() и test()) вы вызываете plot_confusion_matrix() безусловно.

1) В train() замените блок с матрицей ошибок

Найдите:

python
self.plot_confusion_matrix(train_ids_filtered, train_predictions, "Матрица ошибок BOW (обучающая выборка)")

Замените на:

python
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="Матрица ошибок BOW (обучающая выборка)", show=self.show_plots, save_path=save_path ) print(f"Матрица ошибок сохранена: {save_path}")

2) В test() замените блок с матрицей ошибок

Найдите:

python
self.plot_confusion_matrix(test_ids_filtered, test_predictions, "Матрица ошибок BOW (тестовая выборка)")

Замените на:

python
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="Матрица ошибок BOW (тестовая выборка)", show=self.show_plots, save_path=save_path ) print(f"Матрица ошибок сохранена: {save_path}")

✅ Теперь BOW не будет “зависать” в конце по умолчанию.


D) scripts/cnn_classifier.py

1) Сделайте preprocess_image() безопасной

Замените на:

python
def 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

2) prepare_dataset() — вернуть valid_indices (чтобы не было рассинхрона)

Замените на:

python
def 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

3) Исправьте train() (NameError + условные графики)

Найдите в train() строки подготовки данных:

Было:

python
X_train, y_train = self.prepare_dataset(train_paths, train_ids)

Стало:

python
X_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]

Дальше замените оценку/матрицу ошибок на такой блок:

python
print("\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 и делает графики управляемыми.


4) Исправьте test() аналогично (фильтрация + условная матрица)

Замените:

python
X_test, y_test = self.prepare_dataset(test_paths, test_ids)

на:

python
X_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 = ... замените:

python
accuracy = self.evaluate(test_labels, test_predictions_labels, "тестовой выборке") self.plot_confusion_matrix(test_ids, test_predictions, "Матрица ошибок CNN (тестовая выборка)")

на:

python
accuracy = 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

5) Обновите plot_training_history() чтобы умела сохранять и не блокировать

Замените метод целиком на:

python
def 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)

Что будет после правок

1) my_images реально попадут в train

В логе CNN/BOW вы должны увидеть:

  • было: Загружено 131 изображений
  • станет: Загружено 147 изображений (если 16 доп. файлов реально есть)

2) BOW больше не “висит” в конце

Потому что матрица ошибок не рисуется без флага --plot_confusion.

3) CNN больше не падает

train_ids_filtered будет определена, и код пойдёт дальше (save_model + test).


Обновление ваших команд (.bat)

Ваши команды можно оставить почти такими же.

Если хотите, чтобы ничего не блокировало и всё шло автоматически — используйте так:

bat
python 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

Например:

bat
python scripts/main.py --mode both --algorithm bow --clusters 150 --detector sift --plot_confusion --show_plots

Если хотите, я могу ещё одним шагом сделать так, чтобы доп. изображения автоматически копировались в отдельную папку внутри NNClassification (например MyDataset/) и всё было “в одном датасете” без ..\my_images\.... Но текущий фикс уже полностью решает вашу проблему и ничего не ломает в структуре.

Partager cette Q&R