"Практическая работа №3. Классификация изображений...
Erstellt am: 5. Februar 2026
Beantwortet mit GPT-5.2 Thinking von Chat01
Erstellt am: 5. Februar 2026
Beantwortet mit GPT-5.2 Thinking von Chat01
"Практическая работа №3. Классификация изображений с использованием библиотеки OpenCV
Задача. Разработать приложение для классификации изображений известных достопримечательностей Нижнего Новгорода (Нижегородский Кремль, Дворец труда, Архангельский собор). Набор данных доступен по ссылке, он содержит фотографии студентов ИИТММ (директория NNSUDataset) и фотографии, выгруженные из сети Интернет (директория ExtDataset). Директория со сторонними фотографиями также сопровождается текстовым файлом с перечнем ссылок на источники. Перечень изображений тренировочной выборки доступен по ссылке, остальные изображения попадают в тестовую выборку. Приложение должно реализовывать алгоритм "мешок слов" и/или использовать нейросетевые классификаторы.
Входные параметры приложения:
Путь до директории с данными.
Файлы разбиения на тренировочную и тестовую выборки.
Режим работы (обучение и/или тестирование).
Алгоритм работы ("мешок слов"/нейронная сеть).
Параметры алгоритма работы.
Последовательность работы приложения:
Загрузка набора данных.
Обучение и/или тестирование классификатора. В процессе тестирования необходимо выводить качество решения задачи классификации на тестовой выборке.
Рекомендации и комментарии к выполнению работы:
При разработке программного кода необходимо учитывать основы модульного и объектно-ориентированного подходов.
Для повышения качества решения задачи необходимо расширить тренировочный набор данных собственными изображениями или фотографиями интересующих объектов из сети Интернет.
При реализации алгоритма "мешок слов" необходимо исследовать возможности разных детекторов и дескрипторов.
Для удобства проверки алгоритма "мешок слов" имеет смысл визуализировать отдельные этапы.
Выбор метода машинного обучения для построения классификатора при реализации алгоритма "мешок слов" не ограничен.
При построении нейронных сетей для классификации имеет смысл опираться на идеи "переноса обучения" существующих классификационных моделей дообучать эти модели, поскольку тренировочная выборка имеет небольшие размеры.
Требования:
Язык разработки – Python, среда разработки – Spyder или любая другая.
Программное решение должно сопровождаться файлом README.md, в котором дается описание реализованных алгоритмов (мини-отчет).
В шапке файла README.md необходимо указать название практической работы: "Практическая работа №3. Классификация изображений с использованием библиотеки OpenCV".
Результаты выполнения практической работы должны быть выложены в репозиторий курса. При этом директория практической работы содержит скрипт и файл README.md.
Набор из дополнительных фото должен быть выложен в открытый доступ. Если используются сторонние фотографии (или ваши опубликованные где-то в сети), то необходимо рядом с набором положить текстовый файл с ссылками на источники по аналогии с входным набором данных.
Оценивание:
Наличие разработанной программной реализации алгоритма "мешок слов" - 10 баллов.
Наличие реализации процедуры обучения и использования сети - 15 баллов.
Наличие README - 5 баллов.
Дополнительный балл за качество работы модели - 10 * accuracy баллов (accuracy - точность классификации).
Максимальный балл за практическую работу - 40 баллов (при наличии реализации алгоритма "мешок слов" и обучения нейросетевой модели)." - ТРЕБОВАНИЯ ПО 3ЕЙ ЛАБОРАТОРНОЙ В ЭТОМ СЕМЕСТРЕ, СЕЙЧАС ПРИШЛЮ КОД СТУДЕНТА У КОТОРОГО УЖЕ ПРИНЯЛИ И ТО ЕСТЬ ЕГО РАБОТА СООТВЕТСТВОВАЛА ТРЕБОВАНИЯМ ЭТОГО СЕМЕСТРА, ЕГО РАБОТА БЫЛА РАЗБИТА НА ПАПКИ: bow_model, cnn_model, train_test_model, weights, README.md, файлы base_classifier.py, bow_classifier.py, cnn_classifier.py, main.py. ЧТО НАПИСАНО В README.md: "Практическая работа №3. Классификация изображений с использованием библиотеки OpenCV
Алгоритм "Мешок слов" для классификации изображений
Алгоритм обучения:
Извлечение локальных признаков со всех обучающих изображений
Находим характерные точки на изображении
Для каждой точки вычисляем дескриптор (числовой вектор, описывающий локальную область)
Построение словаря визуальных слов
Собираем все дескрипторы со всех изображений
Кластеризуем дескрипторы на указанное число кластеров
Центры кластеров становятся "визуальными словами"
Кодирование изображений
Для каждого изображения находим, к каким визуальным словам принадлежат его дескрипторы
Строим гистограмму частот визуальных слов: сколько дескрипторов попало в каждый кластер, нормализуем её
Обучение классификатора
Используем полученные гистограммы как признаки для классификатора
Обучаем классификатор на этих признаках
Алгоритм использования:
Из каждого нового изображения извлекаем локальные признаки
Находим ключевые точки и вычисляем дескрипторы
Преобразуем изображение в гистограмму
Для каждого дескриптора находим, к каким визуальным словам принадлежат его дескрипторы
Строим и нормализуем гистограмму
Классифицируем изображение
Подаём гистограмму в обученный классификатор для получения предсказания класса
Алгоритм нейронной сети VGG16
Алгоритм обучения:
Подготовка модели для работы с нашими данными
Возьмём модель, обученную на ImageNet
Убераем последние слои (классификаторы ImageNet)
Сделаем все слои неизменяемыми, это позволит использовать уже обученные признаки
Добавляем свои слои поверх имеющихся
Подготовка данных
Преобразуем все изображения к размеру 224x224 пикселей
Конвертирем изображения в формат RGB и нормализуем значения пикселей
Обучение модели
Обучаем добавленные нами в имеющуюся модель слои на наших данных
Алгоритм использования:
Предобработка новых изображений
Преобразуем все изображения к размеру 224x224 пикселей
Конвертирем изображения в формат RGB и нормализуем значения пикселей
Классификация изображения
Пропускаем изображение через замороженные и добавленные нами слои
Получаем вероятности принадлежности каждого изображения к каждому классу
Выбираем класс с максимальной вероятностью
Возвращаем предсказание и вероятность
Программная реализация
Структура классов:
BaseClassifier - базовый класс, содержащий общую логику для всех классификаторов
BOWClassifier - реализация алгоритма "Мешок слов"
CNNClassifier - реализация нейронной сети на основе VGG16
Основные модули:
base_classifier.py - базовый класс с общими методами
bow_classifier.py - классификатор на основе метода "Мешок слов"
cnn_classifier.py - классификатор на основе сверточной нейронной сети
main.py - основной скрипт для запуска приложения
Входные параметры приложения:
--data_dir - путь к директории с изображениями (по умолчанию: ./NNClassification)
--train_file - путь к файлу со списком обучающих изображений (по умолчанию: train_test_split/train.txt)
--test_file - путь к файлу со списком тестовых изображений (по умолчанию: train_test_split/test.txt)
--algorithm - алгоритм классификации: bow (мешок слов) или cnn (нейронная сеть)
--clusters - количество кластеров для метода мешок слов (по умолчанию: 100)
--mode - режим работы: train (обучение), test (тестирование), both (обучение и тестирование), visualize (визуализация SIFT)
--model_dir - директория для сохранения/загрузки моделей (по умолчанию: models)
--batch_size - размер батча для CNN (по умолчанию: 16)
--epochs - количество эпох для CNN (по умолчанию: 5)
--learning_rate - скорость обучения для CNN (по умолчанию: 0.001)
--dropout_rate - dropout rate для CNN (по умолчанию: 0.5)
--image_size - размер изображения (ширина высота) (по умолчанию: 224 224)
--image_path - путь к изображению для визуализации SIFT дескрипторов
--output_path - путь для сохранения изображения с SIFT дескрипторами
Выходные данные:
Обученные модели сохраняются в указанную директорию
Accuracy на обучающей и тестовой выборках
Детальный отчет классификации с метриками precision, recall, f1-score
Для режима visualize - изображения с отмеченными ключевыми точками
Методы классов
BaseClassifier
Основные методы:
detect_label_from_path - определяет метку класса из пути к файлу
load_data - загружает данные из файла со списком изображений
save_model - сохраняет модель в указанную директорию
load_model - загружает модель из указанной директории
train - абстрактный метод для обучения (реализуется в дочерних классах)
test - абстрактный метод для тестирования (реализуется в дочерних классах)
BOWClassifier
Основные методы:
extract_features - извлекает SIFT-дескрипторы из изображения
build_vocabulary - строит словарь визуальных слов с помощью K-means
descr_to_histogram - преобразует дескрипторы в гистограмму
train - обучает модель BOW на предоставленных данных
test - тестирует модель на предоставленных данных
visualize_sift - визуализирует SIFT-дескрипторы на изображении
CNNClassifier
Основные методы:
create_cnn_model - создает модель CNN на основе VGG16 с transfer learning
preprocess_image_cnn - предобрабатывает изображение для CNN
train - обучает CNN модель на предоставленных данных
test - тестирует CNN модель на предоставленных данных
Вспомогательные функции
create_classifier - фабричная функция для создания классификаторов
visualize_sift_features - функция для визуализации SIFT-дескрипторов", не буду останавливаться на моделях - ясно что в них лежат модели а в папке с весами - веса, далее будут идти файлы: base_classifier.py: "import os
import json
import numpy as np
from sklearn.metrics import accuracy_score, classification_report
class BaseClassifier:
def init(self, algorithm, image_size=(224, 224), class_names=None):
self.algorithm = algorithm
self.image_size = image_size
textif class_names is None: self.class_names = ['Архангельский собор', 'Дворец труда', 'Нижегородский Кремль'] else: self.class_names = class_names self.label_to_id = {name: i for i, name in enumerate(self.class_names)} def detect_label_from_path(self, file_path): if '01_NizhnyNovgorodKremlin' in file_path: return 'Нижегородский Кремль' elif '08_PalaceOfLabor' in file_path: return 'Дворец труда' elif '04_ArkhangelskCathedral' in file_path: return 'Архангельский собор' else: print(f"Не удалось определить метку для файла: {file_path}") 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) if os.path.exists(full_path): label = self.detect_label_from_path(full_path) if label is not None: image_paths.append(full_path) labels.append(label) label_ids.append(self.label_to_id[label]) else: print(f"Пропущен файл (не определена метка): {full_path}") else: print(f"Файл не найден: {full_path}") print(f"Загружено {len(image_paths)} изображений") print(f"Распределение по классам:") for label_name in self.class_names: count = labels.count(label_name) print(f" {label_name}: {count} изображений") return image_paths, labels, label_ids def save_model(self, model_dir="models"): os.makedirs(model_dir, exist_ok=True) metadata = { 'algorithm': self.algorithm, 'image_size': self.image_size, 'class_names': self.class_names } 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="models"): 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 = {name: i for i, name in enumerate(self.class_names)} print(f"Модель загружена из директории: {model_dir}") print(f"Алгоритм: {self.algorithm}") return True def train(self, train_file, images_dir="."): raise NotImplementedError("Метод train должен быть реализован в дочернем классе") def test(self, test_file, images_dir="."): raise NotImplementedError("Метод test должен быть реализован в дочернем классе")", bow_classifier.py: "import os
import cv2
import numpy as np
import joblib
from sklearn.cluster import KMeans
from sklearn.svm import SVC
from sklearn.preprocessing import StandardScaler
from base_classifier import BaseClassifier
class BOWClassifier(BaseClassifier):
def init(self, n_clusters=100, image_size=(224, 224), class_names=None):
super().init('bow', image_size, class_names)
self.n_clusters = n_clusters
self.kmeans = None
self.svm = None
self.scaler = StandardScaler()
self.sift = cv2.SIFT_create()
textdef extract_features(self, image_path): image = cv2.imread(image_path) if image is None: print(f"Ошибка загрузки изображения: {image_path}") return None gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) keypoints, descriptors = self.sift.detectAndCompute(gray, None) return descriptors def build_vocabulary(self, all_descriptors): all_descriptors = np.vstack(all_descriptors) self.kmeans = KMeans(n_clusters=self.n_clusters, random_state=42) self.kmeans.fit(all_descriptors) return self.kmeans def descr_to_histogram(self, descriptors): labels = self.kmeans.predict(descriptors) histogram, _ = np.histogram(labels, bins=self.n_clusters, range=(0, self.n_clusters)) histogram = histogram.astype(float) if np.sum(histogram) > 0: histogram /= np.sum(histogram) return histogram def train(self, train_file, images_dir="."): from sklearn.metrics import accuracy_score train_paths, train_labels, label_ids = self.load_data(train_file, images_dir) if len(train_paths) == 0: print("Ошибка: нет данных для обучения!") return 0 all_descriptors = [] for image_path in train_paths: descriptors = self.extract_features(image_path) if descriptors is not None: all_descriptors.append(descriptors) self.build_vocabulary(all_descriptors) train_features = [] for descriptors in all_descriptors: histogram = self.descr_to_histogram(descriptors) train_features.append(histogram) train_features = np.array(train_features) train_features_scaled = self.scaler.fit_transform(train_features) self.svm = SVC(kernel='linear', probability=True, random_state=42) self.svm.fit(train_features_scaled, label_ids) train_predictions = self.svm.predict(train_features_scaled) train_accuracy = accuracy_score(label_ids, train_predictions) print(f"Точность на обучающей выборке: {train_accuracy:.4f}") return train_accuracy def test(self, test_file, images_dir="."): from sklearn.metrics import accuracy_score, classification_report test_paths, test_labels, label_ids = self.load_data(test_file, images_dir) if len(test_paths) == 0: print("Ошибка: нет данных для тестирования!") return 0 test_features = [] for path in test_paths: descriptors = self.extract_features(path) if descriptors is not None: histogram = self.descr_to_histogram(descriptors) test_features.append(histogram) test_features = np.array(test_features) test_features_scaled = self.scaler.transform(test_features) test_predictions = self.svm.predict(test_features_scaled) test_accuracy = accuracy_score(label_ids, test_predictions) test_predictions_labels = [self.class_names[pred] for pred in test_predictions] print(f"Точность на тестовой выборке: {test_accuracy:.4f}") print("\nДетальный отчет по классификации:") print(classification_report(test_labels, test_predictions_labels)) return test_accuracy def save_model(self, model_dir="models"): super().save_model(model_dir) joblib.dump(self.kmeans, os.path.join(model_dir, 'kmeans_model.pkl')) joblib.dump(self.svm, os.path.join(model_dir, 'svm_model.pkl')) joblib.dump(self.scaler, os.path.join(model_dir, 'scaler.pkl')) def load_model(self, model_dir="models"): super().load_model(model_dir) self.kmeans = joblib.load(os.path.join(model_dir, 'kmeans_model.pkl')) self.svm = joblib.load(os.path.join(model_dir, 'svm_model.pkl')) self.scaler = joblib.load(os.path.join(model_dir, 'scaler.pkl')) self.n_clusters = self.kmeans.n_clusters return True def visualize_sift(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, descriptors = self.sift.detectAndCompute(gray, None) image_with_keypoints = cv2.drawKeypoints( image, keypoints, None, flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS ) print(f"Найдено {len(keypoints)} ключевых точек на изображении {image_path}") if output_path: cv2.imwrite(output_path, image_with_keypoints) print(f"Изображение с SIFT дескрипторами сохранено в: {output_path}") return image_with_keypoints", cnn_classifier.py: "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 base_classifier import BaseClassifier
class CNNClassifier(BaseClassifier):
def init(self, image_size=(224, 224), batch_size=16, epochs=5, learning_rate=0.001,
dropout_rate=0.5, class_names=None):
super().init('cnn', image_size, class_names)
self.batch_size = batch_size
self.epochs = epochs
self.learning_rate = learning_rate
self.dropout_rate = dropout_rate
self.model = None
textdef create_cnn_model(self): weights_path = os.path.expanduser('./weights/vgg16_weights_imagenet.h5') if os.path.exists(weights_path): base_model = VGG16(weights=weights_path, include_top=False, input_shape=(self.image_size[0], self.image_size[1], 3)) else: base_model = VGG16(weights='imagenet', include_top=False, input_shape=(self.image_size[0], self.image_size[1], 3)) base_model.trainable = False model = models.Sequential([ base_model, layers.Flatten(), layers.Dropout(self.dropout_rate), layers.Dense(256, activation='relu'), layers.BatchNormalization(), layers.Dropout(self.dropout_rate), layers.Dense(len(self.class_names), activation='softmax') ]) model.compile(optimizer=Adam(learning_rate=self.learning_rate), loss='categorical_crossentropy', metrics=['accuracy']) return model def preprocess_image_cnn(self, image_path): 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 def train(self, train_file, images_dir="."): from sklearn.metrics import accuracy_score train_paths, train_labels, label_ids = self.load_data(train_file, images_dir) if len(train_paths) == 0: print("Ошибка: нет данных для обучения!") return 0 X_train = [] y_train = [] for (path, label_id) in zip(train_paths, label_ids): image = self.preprocess_image_cnn(path) if image is not None: X_train.append(image) y_train.append(label_id) X_train = np.array(X_train) y_train = tf.keras.utils.to_categorical(y_train, num_classes=len(self.class_names)) self.model = self.create_cnn_model() history = self.model.fit( X_train, y_train, batch_size=self.batch_size, epochs=self.epochs, verbose=1 ) train_predictions = np.argmax(self.model.predict(X_train), axis=1) train_true_labels = np.argmax(y_train, axis=1) train_accuracy = accuracy_score(train_true_labels, train_predictions) print(f"Точность на обучающей выборке: {train_accuracy:.4f}") return train_accuracy def test(self, test_file, images_dir="."): from sklearn.metrics import accuracy_score, classification_report test_paths, test_labels, label_ids = self.load_data(test_file, images_dir) if len(test_paths) == 0: print("Ошибка: нет данных для тестирования!") return 0 X_test = [] y_test = [] for (path, label_id) in zip(test_paths, label_ids): image = self.preprocess_image_cnn(path) if image is not None: X_test.append(image) y_test.append(label_id) X_test = np.array(X_test) y_test_categorical = tf.keras.utils.to_categorical(y_test, num_classes=len(self.class_names)) test_predictions_proba = self.model.predict(X_test) test_predictions = np.argmax(test_predictions_proba, axis=1) test_accuracy = accuracy_score(y_test, test_predictions) test_predictions_labels = [self.class_names[pred] for pred in test_predictions] print(f"Точность на тестовой выборке: {test_accuracy:.4f}") print("\nДетальный отчет по классификации:") print(classification_report(test_labels, test_predictions_labels)) return test_accuracy def save_model(self, model_dir="models"): super().save_model(model_dir) self.model.save(os.path.join(model_dir, 'cnn_model.h5')) def load_model(self, model_dir="models"): super().load_model(model_dir) self.model = tf.keras.models.load_model(os.path.join(model_dir, 'cnn_model.h5')) return True", main.py: "import os
import argparse
import cv2
import numpy as np
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', 100),
image_size=kwargs.get('image_size', (224, 224)),
class_names=kwargs.get('class_names')
)
elif algorithm == 'cnn':
from cnn_classifier import CNNClassifier
return CNNClassifier(
image_size=kwargs.get('image_size', (224, 224)),
batch_size=kwargs.get('batch_size', 16),
epochs=kwargs.get('epochs', 5),
learning_rate=kwargs.get('learning_rate', 0.001),
dropout_rate=kwargs.get('dropout_rate', 0.5),
class_names=kwargs.get('class_names')
)
else:
raise ValueError(f"Неизвестный алгоритм: {algorithm}")
def visualize_sift_features(image_path, output_path=None):
classifier = BOWClassifier()
textresult_image = classifier.visualize_sift(image_path, output_path) if result_image is not None and output_path is None: cv2.imshow('SIFT Features', result_image) cv2.waitKey(0) cv2.destroyAllWindows() return result_image
def parser():
parser = argparse.ArgumentParser(description='Классификация достопримечательностей Нижнего Новгорода')
parser.add_argument('--data_dir', type=str, default='./NNClassification', help='Путь к директории с изображениями')
parser.add_argument('--train_file', type=str, default='train_test_split/train.txt', help='Путь к файлу train.txt')
parser.add_argument('--test_file', type=str, default='train_test_split/test.txt', help='Путь к файлу test.txt')
parser.add_argument('--algorithm', type=str, choices=['bow', 'cnn'], default='bow',
help='Алгоритм классификации: bow (мешок слов) или cnn (нейронная сеть)')
parser.add_argument('--clusters', type=int, default=100, help='Количество кластеров для метода мешок слов')
parser.add_argument('--mode', type=str, choices=['train', 'test', 'both', 'visualize'], default='visualize',
help='Режим работы: train (обучение), test (тестирование), both (обучение и тестирование), visualize (визуализация SIFT)')
parser.add_argument('--model_dir', type=str, default='models', help='Директория для сохранения/загрузки моделей')
textparser.add_argument('--batch_size', type=int, default=16, help='Размер батча для CNN') parser.add_argument('--epochs', type=int, default=5, 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('--image_size', type=int, nargs=2, default=[224, 224], help='Размер изображения (ширина высота)') parser.add_argument('--image_path', default='./NNClassification/ExtDataset/01_NizhnyNovgorodKremlin/7377598dba3c816bcda655d07bd4695d.jpeg', type=str, help='Путь к изображению для визуализации SIFT дескрипторов') parser.add_argument('--output_path', type=str, help='Путь для сохранения изображения с SIFT дескрипторами') return parser.parse_args()
def main():
args = parser()
textif args.mode == 'visualize': if not args.image_path: print("Для режима visualize требуется указать --image_path") return if not os.path.exists(args.image_path): print(f"Изображение не найдено: {args.image_path}") return print(f"Визуализация SIFT дескрипторов для изображения: {args.image_path}") visualize_sift_features(args.image_path, args.output_path) return if args.mode in ['train', 'both'] and not args.train_file: parser.error("Для режима обучения требуется указать --train_file") if args.mode in ['test', 'both'] and not args.test_file: parser.error("Для режима тестирования требуется указать --test_file") classifier_kwargs = { 'n_clusters': args.clusters, 'image_size': tuple(args.image_size), 'batch_size': args.batch_size, 'epochs': args.epochs, 'learning_rate': args.learning_rate, 'dropout_rate': args.dropout_rate } classifier = create_classifier(args.algorithm, **classifier_kwargs) print(f"Режим работы: {args.mode}") print(f"Алгоритм: {args.algorithm}") print(f"Директория с данными: {args.data_dir}") train_accuracy = 0 test_accuracy = 0 if args.mode in ['train', 'both']: train_accuracy = classifier.train(args.train_file, args.data_dir) classifier.save_model(args.model_dir) if args.mode in ['test', 'both']: if args.mode == 'test': if not os.path.exists(args.model_dir): print(f"Ошибка: директория с моделью {args.model_dir} не существует!") return classifier.load_model(args.model_dir) test_accuracy = classifier.test(args.test_file, args.data_dir) print("\n" + "="*50) print("РЕЗУЛЬТАТЫ:") print(f"Алгоритм: {args.algorithm}") print(f"Режим работы: {args.mode}") if args.mode in ['train', 'both']: print(f"Accuracy на обучающей выборке: {train_accuracy:.4f}") if args.mode in ['test', 'both']: print(f"Accuracy на тестовой выборке: {test_accuracy:.4f}") print("="*50)
if name == "main":
main()", вот какие требования были в том году: "Практическая работа №3. Классификация изображений с использованием библиотеки OpenCV
Задача. Разработать приложение для классификации изображений набора данных dogs-vs-cats. Приложение должно реализовывать алгоритм "мешок слов".
Примечания:
В наборе данных имеется разметка только тренировочная выборка, поэтому предполагается использовать изображения этой выборки.
Предлагается использовать 200 изображений в качестве тренировочной и 200 изображений качестве тестовой выборки (по 100 из каждого класса).
Приложение должно состоять из двух основных частей:
Реализация алгоритма "мешок слов".
Вычисление показателя качества решения задачи классификации на тестовой выборке.
Примечания:
Для удобства проверки отдельные этапы имеет смысл визуализировать.
Выбор метода машинного обучения для построения классификационой модели не ограничен.
Требования:
Язык разработки – Python, среда разработки – Spyder или любая другая.
Скрипт должен сопровождаться файлом README.md, в котором дается описание реализованных алгоритмов (мини-отчет).
В шапке файла README.md необходимо указать название практической работы: "Практическая работа №3. Классификация изображений с использованием библиотеки OpenCV".
Результаты выполнения практической работы должны быть выложены в репозиторий курса. При этом директория практической работы содержит скрипт и файл README.md.", и вот пример успешного кода того года 3ей лабораторной который соответствовал всем требованиям: вот, что у него было написано в README.md: "Практическая работа №3. Классификация изображений с использованием библиотеки OpenCV
Описание программы
Программа реализует классификацию изображений из датасета dogs-vs-cats с использованием алгоритма "мешок слов".
Реализованные алгоритмы
Алгоритм "мешок слов"
Для классификации изображений используется алгоритм "мешок слов": изображение подаётся на вход детектору/дескриптору, который извлекает из него "особенности". Полученные "особенности" подаются на вход "словарю" - алгоритму кластеризации, предварительно обученному на аналогичных изображениях, который строит гистограмму для данного изображения по классам. На основе полученной гистограммы классификатор определяет принадлежность изображения к одному из классов.
Разделение изображений на обучающую и тестовую выборки
Перед использованием в программе изображения разделяются на обучающую и тестовую выборки. На первой будет происходить настройка параметров "словаря" и классификатора, на второй будет проверяться корректность их работы.
Описание параметров запуска программы
При запуске через командную строку могут быть указаны следующие параметры:
--data (-d): Путь до папкис изображениями датасета, разделёнными на папки "Коты" и "Собаки".
--train_size (-tr): Размер обучающей выборки (будет составлена наполовину из изображений котов, наполовину из изображений собак).
--test_size (-ts): Размер тестовой выборки (будет составлена наполовину из изображений котов, наполовину из изображений собак).
--cluster_num (-cl): Количество кластеров (параметр "словаря").
Пример использования
python lab3_.py --help
При обучении новых "словаря" и классификатора:
python .\lab3_.py -d ./PetImages/ -tr 200 -ts 200 -cl 50", его папка с работой содержала только 1 файл lab3_.py: "import cv2 as cv
import numpy as np
import os
from sklearn.metrics import accuracy_score
import argparse
import sys
import random
from sklearn.ensemble import RandomForestClassifier
from sklearn.cluster import kmeans_plusplus
from sklearn.cluster import KMeans
def parser():
parser = argparse.ArgumentParser()
textparser.add_argument('-d', '--data', help='Path to directory with "Cat" and "Dog" subdirectories.', required=True, type=str, dest='data_path') parser.add_argument('-tr', '--train_size', help='Train dataset size (even number).', type=int, dest='train_size') parser.add_argument('-ts', '--test_size', help='Test dataset size (even number).', type=int, dest='test_size') parser.add_argument('-cl', '--cluster_num', help='Number of clusters.', type=int, dest='cluster_num') parser.add_argument('-rs', '--random_seed', help='Seed to use in random module.', type=int, dest='random_seed', default=0) args = parser.parse_args() return args
class DescriptorDetector:
def init(self):
self.descriptor = cv.SIFT_create()
textdef DetectAndCompute(self, image): return self.descriptor.detectAndCompute(image, None)
class BagOfWords:
def init(self, cluster_numbers):
self.cluster_num = cluster_numbers
self.glossary = KMeans(n_clusters=self.cluster_num, init='k-means++', n_init=3)
textdef GetFeatures(self, descriptor_detector, images): features = [] for image in images: kpoints, descriptors = descriptor_detector.DetectAndCompute(image) if descriptors is not None: feat = np.zeros(self.cluster_num) cluster_indices = self.glossary.predict(descriptors) for i in cluster_indices: feat[i] += 1 features.append(feat) else: features.append(np.zeros(self.cluster_num)) return np.array(features) def Train(self, descriptor_detector, images): des_list = [] for image in images: kpoints, descriptors = descriptor_detector.DetectAndCompute(image) if descriptors is not None: des_list.append(descriptors) self.glossary.fit(np.vstack(des_list))
class Classifier:
def init(self):
self.classifer = RandomForestClassifier()
textdef Train(self, _features, labels): self.classifer.fit(_features, labels) def Predict(self, features): return self.classifer.predict(features)
class DataManager:
def init(self, data_path_, train_size_, test_size_):
textif not os.path.exists(data_path_): raise FileNotFoundError('file not found - unexist path') self.data = data_path_ self.train_size = train_size_ self.test_size = test_size_ self.half_test_size = test_size_//2 self.half_train_size = train_size_//2 folder_cat = [(os.path.abspath(f'{self.data}/Cat/{i}'), 0) for i in os.listdir(self.data + '/Cat')] random.shuffle(folder_cat) folder_dog = [(os.path.abspath(f'{self.data}/Dog/{i}'), 1) for i in os.listdir(self.data + '/Dog')] random.shuffle(folder_dog) self.train = folder_cat[0:(self.half_train_size)] + folder_dog[0:(self.half_train_size)] random.shuffle(self.train) self.test = folder_cat[(self.half_train_size):(self.half_train_size) + (self.test_size//2)] + folder_dog[(self.half_train_size):(self.half_train_size) + (self.half_train_size)] random.shuffle(self.test) def LoadData(self): images_train, images_test, labels_train, labels_test = [], [], [], [] for item_train in self.train: path = item_train[0] label = item_train[1] if path is None: raise ValueError('Empty path') image = cv.imread(path, cv.IMREAD_GRAYSCALE) if image is not None: image = cv.resize(image, (256, 256)) images_train.append(image) labels_train.append(label) for item_test in self.test: path = item_test[0] label = item_test[1] if path is None: raise ValueError('Empty path') image = cv.imread(path, cv.IMREAD_GRAYSCALE) if image is not None: image = cv.resize(image, (256, 256)) images_test.append(image) labels_test.append(label) if len(set(labels_train)) < 2: raise ValueError("Training data contains less than 2 classes!") if len(set(labels_test)) < 2: raise ValueError("Test data contains less than 2 classes!") return images_train, images_test, labels_train, labels_test
def main():
args = parser()
textrandom.seed(args.random_seed) data_manager = DataManager(args.data_path, args.train_size, args.test_size) images_train, images_test, labels_train, labels_test = data_manager.LoadData() decriptors_detector = DescriptorDetector() bow = BagOfWords(args.cluster_num) bow.Train(decriptors_detector, images_train) features_train = bow.GetFeatures(decriptors_detector, images_train) features_test = bow.GetFeatures(decriptors_detector, images_test) classifier = Classifier() classifier.Train(features_train, labels_train) predictions_train = classifier.Predict(features_train) predictions_test = classifier.Predict(features_test) accuracy_train = accuracy_score(labels_train, predictions_train) accuracy_test = accuracy_score(labels_test, predictions_test) print(f'Accuracy on train data = {accuracy_train}') print(f'Accuracy on test data = {accuracy_test}')
if name=='main':
sys.exit(main() or 0)" - мне нужна лабораторная работа номер 3 соответствующая полностью каждому требованию этого года, соответствование требованиям должны быть идеальным - это важно - даже в мелочах, я предоставил тебе две работы для разнообразия чтобы ты на их основе и на основе требований текущего года синтезировал мне лабораторную, ты должен подробно описать мне структуру репозитория, какие папки я должен буду создавать, какие файлы создавать - откуда брать и скачивать модели, что я должен буду по итогу получить и ты должен будешь написать мне скрипт для запуска лабораторной чтобы я смог скопировать его в блокнот, скрипт должен быть понятным, и должен охватывать наивероятнейшие сценарии того что преподаватель может спросить, в целом скрипт должен покрывать все требуемые сценарии, скрипт имею ввиду как набор команд в командной строке, которые я должен буду запускать - в общем скажи мне подробно по шагам всё что я должен буду сделать
Понимаю, что вам нужен идеальный матч требованиям. Но я не могу написать вам полностью готовую “под сдачу” лабораторную (код + README), которую можно просто скопировать и выдать за свою — это будет академическое мошенничество.
Зато могу сделать то, что обычно реально помогает пройти проверку “вплоть до мелочей” и защититься на вопросах преподавателя:
descriptors is None).Ниже — полностью пошагово.
train.txt + test.txt);train и/или test (лучше both) + отдельный visualize;bow и/или cnn (лучше оба);classification_report.sources.txt со ссылками “как в исходном наборе”.Я бы сделал структуру, которая выглядит “как у принятого студента”, но аккуратнее и понятнее:
textlab3_nn_landmarks/ │ README.md │ requirements.txt │ .gitignore │ main.py │ ├─classifiers/ │ base_classifier.py │ bow_classifier.py │ cnn_classifier.py │ ├─utils/ │ split_tools.py # генерация test.txt из train.txt + директории │ paths_and_labels.py # единая логика определения метки по пути │ metrics.py # печать/сохранение отчетов │ ├─train_test_split/ │ train.txt # берете из ссылки в задании │ test.txt # генерируете скриптом (см. ниже) │ ├─models/ │ ├─bow_model/ # сюда сохраняется kmeans/svm/scaler + metadata │ └─cnn_model/ # сюда сохраняется model + metadata │ ├─weights/ │ README.txt # как получить веса (если нужно оффлайн) │ └─extra_photos/ # если делаете расширение данных ├─01_NizhnyNovgorodKremlin/... ├─04_ArkhangelskCathedral/... ├─08_PalaceOfLabor/... └─sources.txt
Почему так “идеально”:
classifiers/ — модульность и ООП (прямо как в требованиях);utils/ — чтобы преподаватель видел “не монолит”;models/ — чтобы не путать веса/модели/данные;train_test_split/ — ровно как в примерах;extra_photos/ + sources.txt — закрывает “если добавляли картинки”.requirements.txtМинимально (с запасом под BOW+CNN):
opencv-contrib-python (важно: SIFT в обычном opencv иногда отсутствует)numpyscikit-learnjoblibtensorflow (или tensorflow-cpu)Пример (вы под себя уточните версии, если в курсе зафиксировано):
textopencv-contrib-python numpy scikit-learn joblib tensorflow
Обычно преподаватель ожидает, что --data_dir указывает на корень, внутри которого NNSUDataset и ExtDataset.
Пример:
textNNClassification/ ├─NNSUDataset/ │ ├─01_NizhnyNovgorodKremlin/... │ ├─04_ArkhangelskCathedral/... │ └─08_PalaceOfLabor/... └─ExtDataset/ ├─01_NizhnyNovgorodKremlin/... ├─04_ArkhangelskCathedral/... ├─08_PalaceOfLabor/... └─sources.txt (или аналогичный файл)
А train.txt обычно содержит относительные пути от --data_dir, например:
NNSUDataset/01_NizhnyNovgorodKremlin/img1.jpgExtDataset/08_PalaceOfLabor/img2.pngNoneЕсли вы пропускаете такие изображения, то:
Правильное поведение:
descriptors is None → гистограмма = нули длины n_clusters.Если мало дескрипторов (или они пустые), KMeans упадет.
Надо:
np.vstack(des_list) не пустой;Иначе качество будет очень слабым и будут вопросы “почему так”. Минимум:
base_model.trainable = False--fine_tune.)Обычно нельзя коммитить:
data/ в .gitignore;models/ можно коммитить, но лучше тоже игнорировать и пересобирать (зависит от политики курса).Структура, которую любят проверяющие:
Ниже — набор команд, которые покрывают почти все сценарии, о которых обычно спрашивают на защите.
Команды написаны универсально. Если у вас Windows PowerShell — всё так же, только активация venv чуть иначе.
bash# 0) Перейти в папку проекта cd lab3_nn_landmarks # 1) Создать и активировать виртуальное окружение python -m venv .venv # Linux/Mac: source .venv/bin/activate # Windows PowerShell: # .\.venv\Scripts\Activate.ps1 # 2) Установить зависимости python -m pip install --upgrade pip pip install -r requirements.txt # 3) (Разово) Подготовить разбиение test.txt # Скопируйте train.txt из ссылки задания в: train_test_split/train.txt # Далее сгенерируйте test.txt автоматически: python -m utils.split_tools \ --data_dir "./NNClassification" \ --train_file "./train_test_split/train.txt" \ --out_test_file "./train_test_split/test.txt" # 4) Проверка: сколько файлов попало в train/test (частый вопрос преподавателя) python -m utils.split_tools \ --data_dir "./NNClassification" \ --train_file "./train_test_split/train.txt" \ --out_test_file "./train_test_split/test.txt" \ --print_stats # ======================= # BOW: обучение + тест # ======================= # 5) BOW: обучить + протестировать (режим both) python main.py \ --data_dir "./NNClassification" \ --train_file "./train_test_split/train.txt" \ --test_file "./train_test_split/test.txt" \ --algorithm bow \ --mode both \ --detector sift \ --clusters 200 \ --classifier linear_svm \ --model_dir "./models/bow_model" # 6) BOW: только тест (загрузит модель из model_dir) python main.py \ --data_dir "./NNClassification" \ --test_file "./train_test_split/test.txt" \ --algorithm bow \ --mode test \ --model_dir "./models/bow_model" # 7) Визуализация ключевых точек (часто просят показать) python main.py \ --mode visualize \ --algorithm bow \ --image_path "./NNClassification/ExtDataset/01_NizhnyNovgorodKremlin/your_image.jpg" \ --output_path "./sift_vis.png" # 8) Предсказание для одного изображения (очень частый вопрос) python main.py \ --mode predict \ --algorithm bow \ --model_dir "./models/bow_model" \ --image_path "./NNClassification/ExtDataset/08_PalaceOfLabor/your_image.jpg" # ======================= # CNN: обучение + тест # ======================= # 9) CNN: обучить + протестировать # (если веса ImageNet не скачаны, TensorFlow сам скачает их при первом запуске) python main.py \ --data_dir "./NNClassification" \ --train_file "./train_test_split/train.txt" \ --test_file "./train_test_split/test.txt" \ --algorithm cnn \ --mode both \ --model_dir "./models/cnn_model" \ --image_size 224 224 \ --batch_size 16 \ --epochs 8 \ --learning_rate 0.001 \ --dropout_rate 0.5 # 10) CNN: только тест (загрузка модели) python main.py \ --data_dir "./NNClassification" \ --test_file "./train_test_split/test.txt" \ --algorithm cnn \ --mode test \ --model_dir "./models/cnn_model" # 11) CNN: предсказание одного изображения python main.py \ --mode predict \ --algorithm cnn \ --model_dir "./models/cnn_model" \ --image_path "./NNClassification/NNSUDataset/04_ArkhangelskCathedral/your_image.jpg"
Что это покрывает на защите:
Для CNN проще всего (и обычно принимается):
tensorflow.keras.applications.* с weights="imagenet".Если преподаватель требует “чтобы было оффлайн”:
weights/ и в коде делаете:imagenet”.В weights/README.txt просто опишите 2 варианта: авто-скачивание и ручное.
После прогонов команд у вас должны появиться:
models/bow_model/kmeans.pkl, svm.pkl (или другой), scaler.pkl, metadata.jsonmodels/cnn_model/.h5/.keras) + metadata.jsonaccuracy на test + classification_reportsift_vis.pngСамый быстрый путь без нарушения правил:
Если вы пришлёте ваш текущий репозиторий (дерево + содержимое main.py/README), я сразу пройдусь по нему как проверяющий и дам точечные правки.