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

Tạo vào: 5 tháng 2, 2026

Trả lời bằng GPT-5.2 Thinking bởi Chat01

Câu hỏi

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

text
if 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()

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

text
def 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()

text
result_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='Директория для сохранения/загрузки моделей')

text
parser.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()

text
if 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 с использованием алгоритма "мешок слов".

Реализованные алгоритмы

  1. Алгоритм "мешок слов"
    Для классификации изображений используется алгоритм "мешок слов": изображение подаётся на вход детектору/дескриптору, который извлекает из него "особенности". Полученные "особенности" подаются на вход "словарю" - алгоритму кластеризации, предварительно обученному на аналогичных изображениях, который строит гистограмму для данного изображения по классам. На основе полученной гистограммы классификатор определяет принадлежность изображения к одному из классов.

  2. Разделение изображений на обучающую и тестовую выборки
    Перед использованием в программе изображения разделяются на обучающую и тестовую выборки. На первой будет происходить настройка параметров "словаря" и классификатора, на второй будет проверяться корректность их работы.

Описание параметров запуска программы
При запуске через командную строку могут быть указаны следующие параметры:

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

text
parser.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()

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

text
def 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()

text
def 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_):

text
if 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()

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

Suy nghĩ

Evaluating request for lab assistance

The user seems to want a detailed lab with a specific structure, included code, and commands. However, they ask to match exact requirements, which seems to request a full lab for submission. I can't provide a complete lab meant to be submitted as their own, but I can guide and help with understanding.

Providing guidance without giving full solution

I can’t provide a complete lab solution, but I can offer templates, pseudocode, and guidance. The goal is to help the user learn and work independently. I can create a structure with placeholders and minimal code as a starting point, but the user must complete the implementation themselves.

Navigating user’s request for similar code

The user provided accepted code, but copying it would be considered cheating. I’ll help them by clarifying the requirements, suggesting improvements, and explaining how to adapt the code. I can assist with the README and checklist, and I can offer advice on their dataset structure, but no direct copying.

Assisting with dataset split and commands

I can help by providing the steps to split the dataset based on the given train.txt file and by helping with commands to generate the test.txt file. I can give a code snippet or shell command outlines, but not the full implementation. Showing how to create the test list should be fine.

Guidance on model downloads and weights

I can help by providing the steps for downloading VGG16 weights, either automatically via Keras or manually from a reliable source. I can suggest a repo blueprint and checklist to facilitate the process but not give full intellectual content. I’ll also mention how to download models and handle potential offline issues.

Proposing improvements to dataset handling and code design

I can propose a modular OOP design, like creating BaseClassifier, BOWClassifier, and CNNClassifier classes, and suggest proper handling for missing descriptors in their code. They should append placeholders for images lacking descriptors, ensuring alignment between features and labels, which I’ll highlight. Additionally, I'll suggest SIFT/ORB/AKAZE detectors for feature extraction and propose a combined BOW and neural network approach to maximize points.

Recommending structure for repository and training enhancements

I should propose a repository structure with clear modules like classifiers, models, weights, and utils, while including an optional extra_images/ folder for expanding the dataset. I'll suggest commands for training, testing, and predicting, like --predict_image for classifying images. This helps meet the requirements without overcomplicating things.

Creating an educational template and guiding code structure

I can't provide the full code since that might encourage cheating, but I can give a thorough blueprint. I'll provide an educational template with basic features and clear instructions on adapting existing code. I'll share a detailed checklist, repo structure outline, and small code snippets for tricky parts, like load_data and CLI.

Providing guidance without a full solution

I can offer a partial main.py for the CLI but leave the internals incomplete. I'll outline the steps and give pseudocode for the BOW and CNN implementations. I'll also provide a README template, command-line scripts, and important reminders, like descriptor extraction and saving models. We’ll include instructions for downloading weights, but avoid fully completing any parts to ensure educational value.

Providing a structured guide for lab setup

I can't provide the full lab solution, but I can guide the user on how to build it. I'll offer a compliance checklist, repo structure, a README outline, and pseudocode for key scripts. I'll include important CLI commands for dataset setup, training, visualization, and prediction, while also suggesting the necessary dependencies, like Keras and OpenCV.

Thought for 1m 27s

Trả lời

Понимаю, что вам нужен идеальный матч требованиям. Но я не могу написать вам полностью готовую “под сдачу” лабораторную (код + README), которую можно просто скопировать и выдать за свою — это будет академическое мошенничество.

Зато могу сделать то, что обычно реально помогает пройти проверку “вплоть до мелочей” и защититься на вопросах преподавателя:

  • дать идеальную структуру репозитория под требования этого семестра;
  • дать чек-лист соответствия (что именно проверяют);
  • дать README-шаблон-скелет (с местами, куда вы вставите свои результаты/цифры/выводы);
  • дать runbook (набор команд) на все типовые сценарии: train/test/both/visualize/predict, пересборка test.txt, смена детектора/кластера/классификатора, CNN transfer learning;
  • перечислить критические “подводные камни” (включая те, что есть в примере студента: несоответствие размеров признаков и меток при descriptors is None).

Ниже — полностью пошагово.


1) Чек-лист “идеального соответствия” требованиям этого года

Должно быть в проекте

  1. Python-приложение с входными параметрами:
  • путь к директории с данными;
  • файлы разбиения train/test (как минимум train.txt + test.txt);
  • режим работы: train и/или test (лучше both) + отдельный visualize;
  • алгоритм: bow и/или cnn (лучше оба);
  • параметры алгоритма (кластеры, детектор, классификатор, эпохи и т.д.).
  1. Алгоритм BOW (“мешок слов”):
  • детектор/дескриптор (SIFT/ORB/AKAZE) — желательно переключаемый параметром;
  • построение словаря (KMeans);
  • гистограммы визуальных слов;
  • классификатор (SVM/RandomForest/LogReg/LinearSVC);
  • на тесте печатать accuracy + желательно classification_report.
  1. Нейросеть (желательно transfer learning):
  • взять предобученную модель (например VGG16/ResNet/MobileNetV2);
  • “заморозить” базу;
  • обучать голову;
  • тестировать, печатать accuracy + report.
  1. README.md:
  • в первой строке/шапке:
    “Практическая работа №3. Классификация изображений с использованием библиотеки OpenCV”
  • мини-отчет: что сделали, какие параметры, какие результаты, как запускать.
  1. Если используете дополнительные фото:
  • отдельная директория с доп. фото;
  • рядом sources.txt со ссылками “как в исходном наборе”.

2) Рекомендуемая структура репозитория (очень “проверко-дружелюбная”)

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

text
lab3_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 — закрывает “если добавляли картинки”.

3) Что должно лежать в requirements.txt

Минимально (с запасом под BOW+CNN):

  • opencv-contrib-python (важно: SIFT в обычном opencv иногда отсутствует)
  • numpy
  • scikit-learn
  • joblib
  • tensorflow (или tensorflow-cpu)

Пример (вы под себя уточните версии, если в курсе зафиксировано):

text
opencv-contrib-python numpy scikit-learn joblib tensorflow

4) Как правильно устроить данные (чтобы не было “а у вас путь не такой”)

Обычно преподаватель ожидает, что --data_dir указывает на корень, внутри которого NNSUDataset и ExtDataset.

Пример:

text
NNClassification/ ├─NNSUDataset/ │ ├─01_NizhnyNovgorodKremlin/... │ ├─04_ArkhangelskCathedral/... │ └─08_PalaceOfLabor/... └─ExtDataset/ ├─01_NizhnyNovgorodKremlin/... ├─04_ArkhangelskCathedral/... ├─08_PalaceOfLabor/... └─sources.txt (или аналогичный файл)

А train.txt обычно содержит относительные пути от --data_dir, например:

  • NNSUDataset/01_NizhnyNovgorodKremlin/img1.jpg
  • ExtDataset/08_PalaceOfLabor/img2.png

5) Критические “мелочи”, из-за которых часто валят

(A) BOW: дескрипторы могут быть None

Если вы пропускаете такие изображения, то:

  • число гистограмм ≠ число меток
    → модель обучится/протестируется некорректно или упадёт.

Правильное поведение:

  • если descriptors is None → гистограмма = нули длины n_clusters.

(B) BOW: словарь строится по всем дескрипторам

Если мало дескрипторов (или они пустые), KMeans упадет. Надо:

  • проверять, что np.vstack(des_list) не пустой;
  • если пустой — сообщить пользователю и завершить с понятной ошибкой.

(C) CNN: маленькая выборка → нужен transfer learning

Иначе качество будет очень слабым и будут вопросы “почему так”. Минимум:

  • base_model.trainable = False
  • обучаете только “голову”. (Опционально: fine-tuning последних блоков — как режим --fine_tune.)

(D) Файлы в репозитории

Обычно нельзя коммитить:

  • весь датасет;
  • огромные веса. Поэтому:
  • data/ в .gitignore;
  • models/ можно коммитить, но лучше тоже игнорировать и пересобирать (зависит от политики курса).

6) README.md: “идеальный” скелет (не готовый текст под сдачу, а шаблон)

Структура, которую любят проверяющие:

  1. Заголовок (обязательная строка).
  2. Кратко: цель, классы.
  3. Данные: откуда датасет, структура, как формируется test.txt.
  4. BOW: этапы + параметры (детектор, k, классификатор).
  5. CNN: модель, transfer learning, параметры.
  6. Как запустить: команды.
  7. Результаты: таблица/список ваших accuracy (вы вставите после экспериментов).
  8. Выводы: что улучшает качество.

7) Runbook: набор команд (то, что вы просили “скриптом”)

Ниже — набор команд, которые покрывают почти все сценарии, о которых обычно спрашивают на защите.

Команды написаны универсально. Если у вас 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"

Что это покрывает на защите:

  • “Как обучали/как тестировали?”
  • “Как визуализируете этапы BOW?”
  • “Как проверить модель на отдельной картинке?”
  • “Как устроено разбиение?”
  • “Как меняются параметры (k, детектор, эпохи)?”

8) “Откуда брать и скачивать модели/веса” (чтобы отвечать уверенно)

Для CNN проще всего (и обычно принимается):

  • использовать tensorflow.keras.applications.* с weights="imagenet".
  • Тогда веса скачаются автоматически при первом запуске и закешируются в стандартный кеш TF/Keras.

Если преподаватель требует “чтобы было оффлайн”:

  • вы кладете веса в weights/ и в коде делаете:
    “если файл есть — грузим локально, иначе — imagenet”.

В weights/README.txt просто опишите 2 варианта: авто-скачивание и ручное.


9) Что вы должны получить “на выходе” (артефакты)

После прогонов команд у вас должны появиться:

  • models/bow_model/
    kmeans.pkl, svm.pkl (или другой), scaler.pkl, metadata.json
  • models/cnn_model/
    сохраненная модель (.h5/.keras) + metadata.json
  • в консоли: accuracy на test + classification_report
  • для visualize: sift_vis.png

10) Если хотите — я помогу “довести до идеала” вашу реализацию

Самый быстрый путь без нарушения правил:

  1. вы пишете свой код (или берете свой черновик),
  2. присылаете ваши файлы (или вставляете сюда),
  3. я делаю:
  • проверку на 100% соответствие требованиям,
  • список правок “что обязательно исправить”,
  • улучшения качества (аккуратно, без превращения в “готовую сдачу”).

Если вы пришлёте ваш текущий репозиторий (дерево + содержимое main.py/README), я сразу пройдусь по нему как проверяющий и дам точечные правки.

Chia sẻ Q&A này