5.1 MiB
Лаб 9. Обработка изображений
В данной работе будем использовать библиотек mahotas.
Я буду использовать библиотку OpenCV, потому что она написана на C++, а это круто
Загружаем изображения
import os
import cv2
import numpy as np
import matplotlib.pyplot as plt
# Функция для загрузки изображений из папки на OpenCV
def load_images(folder, target_size=(512, 512), allowed_extensions=("jpg", "jpeg", "png", "bmp", "gif")):
images = []
filenames = []
if not os.path.exists(folder):
raise FileNotFoundError(f"Папка {folder} не найдена.")
print(f"Проверка файлов в папке {folder}...")
for filename in sorted(os.listdir(folder)):
print(f"Проверяется файл: {filename}")
if filename.lower().endswith(allowed_extensions):
image_path = os.path.join(folder, filename)
print(f"Загружаем изображение: {image_path}")
try:
image = cv2.imread(image_path)
if image is None:
print(f"Ошибка загрузки: {filename}")
continue
image_resized = cv2.resize(image, target_size)
images.append(image_resized)
filenames.append(filename)
except Exception as e:
print(f"Ошибка при загрузке и обработке изображения {filename}: {e}")
return images, filenames
# Сохранение файла
# mh.imsave()
# Указываем путь к папке с изображениями
folder_path = "../static/images"
images, filenames = load_images(folder_path)
# Проверяем, были ли загружены изображения
if not images:
print("Нет изображений для отображения.")
else:
# Ограничиваем количество изображений для отображения
count_images_to_display = min(20, len(images))
# Количество столбцов для отображения
cols = 5
rows = count_images_to_display // cols + (count_images_to_display % cols > 0)
# Настройка фигуры для отображения
plt.figure(figsize=(15, 5 * rows))
for i in range(count_images_to_display):
plt.subplot(rows, cols, i + 1)
# Отображаем изображение и Конвертируем BGR -> RGB
plt.imshow(cv2.cvtColor(images[i], cv2.COLOR_BGR2RGB))
plt.title(f"{filenames[i]}") # Подпись с именем файла
plt.axis('off') # Отключаем оси
# Отображаем изображения
plt.tight_layout()
plt.show()
Сделаем предобработку
for i, image in enumerate(images):
# Получаем размеры изображения
im, w, _ = image.shape
size = image.size
# Получение RGB
print("\n\n Получение RGB матрицы")
plt.imshow(image)
plt.show()
r, g, b = image.transpose((2, 0, 1))
display(r, g, b)
Применим методы предобработки изображений
Будет несколько этап предобработки изображений, таких как изменение размеров, цвета, оттенков и увеличение контраста.
import mahotas as mh
# Функция предобработки изображений на библиотеке mahotas
def preprocess_images_on_mh(images):
processed_images = []
for image in images:
# Изменение размеров
image_resized = mh.resize.resize_rgb_to(image, (100, 100))
# Увеличение контраста с помощью гистограммы. Преобразование в оттенки серого
image_gray = mh.colors.rgb2gray(image_resized)
# Растяжение контраста (замена mh.equalize)
image_eq = mh.stretch(image_gray)
processed_images.append(image_eq)
return np.array(processed_images)
# Функция отображения оригинала и обработанного изображения
def display_image(original, processed, index):
plt.figure(figsize=(10, 5))
# Оригинальное изображение
plt.subplot(1, 2, 1)
plt.imshow(original[index])
plt.title(f'Оригинал {index+1}')
plt.axis('off')
# Обработанное изображение
plt.subplot(1, 2, 2)
plt.imshow(processed[index], cmap='gray')
plt.title(f'Обработанное {index+1}')
plt.axis('off')
plt.show()
print("На Mahotas")
# Применяем предобработку
processed_images_on_mahotas = preprocess_images_on_mh(images)
display_image(images, processed_images_on_mahotas, i)
# Функция предобработки изображений на библиотеке CV2
def preprocess_images(images):
processed_images = []
for image in images:
# Изменение размера
image_resized = cv2.resize(image, (256, 256))
# Преобразование в оттенки серого
image_gray = cv2.cvtColor(image_resized, cv2.COLOR_BGR2GRAY)
# Увеличим контраст с помощью выравнивания гистограммы
image_eq = cv2.equalizeHist(image_gray)
processed_images.append(image_eq)
return np.array(processed_images)
print("На OpenCV")
procesed_images = preprocess_images(images)
display_image(images, procesed_images, i)
Работа с гаммой изображения и встроенные методы преобразования
new_greenless_image = mh.as_rgb(r, g * 0, b)
plt.imshow(new_greenless_image)
plt.show()
# Удаляем зелёный канал
greenless_image = image.copy()
greenless_image[:, :, 1] = 0 # Обнуляем G-канал
plt.imshow(greenless_image)
plt.title("Без зелёного канала")
plt.axis("off")
plt.show()
grey_image_mh = mh.colors.rgb2gray(image)
sepia_image = mh.colors.rgb2gray(image)
fig, axes = plt.subplots(1, 2, figsize=(12, 8))
axes[0].imshow(grey_image_mh, cmap="gray")
axes[0].set_title("Greyscale")
axes[1].imshow(sepia_image)
axes[1].set_title("Sepia")
plt.show()
# Матрица для эффекта сепии
sepia_filter = np.array([[0.393, 0.769, 0.189],
[0.349, 0.686, 0.168],
[0.272, 0.534, 0.131]])
# Применение фильтра
sepia_image = cv2.transform(image.astype(np.float32) / 255, sepia_filter)
sepia_image = np.clip(sepia_image * 255, 0, 255).astype(np.uint8) # Ограничиваем диапазон
fig, axes = plt.subplots(1, 2, figsize=(12, 8))
axes[0].imshow(image, cmap="gray")
axes[0].set_title("Greyscale")
axes[0].axis("off")
axes[1].imshow(sepia_image)
axes[1].set_title("Sepia")
axes[1].axis("off")
plt.show()
Фильтрация:
Удалим шумы
Изменим резкость
Определим границы с помощью Canny
Воспользуемя гистерезис, для окончательного определения границы с использованием двух пороговых значений: нижнего и верхнего
def apply_filters(image):
# Удалим шумы в картинке
image_blur = cv2.GaussianBlur(image, (5, 5), 0)
# Повысим резкость изображения
kernel = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]])
image_sharp = cv2.filter2D(image_blur, -1, kernel)
# Определим границы
image_edges = cv2.Canny(image_sharp, 200, 200)
return image_edges
def preprocess_images(images):
process_images = []
for image in images:
# Изменение размера
image_resized = cv2.resize(image, (256, 256))
# Преобразование в оттенки серого
image_gray = cv2.cvtColor(image_resized, cv2.COLOR_BGR2GRAY)
# Удаляем зелёный канал
greenless_image = image_gray.copy()
greenless_image[:, 1] = 0 # Обнуляем G-канал
# Увеличим контраст с помощью гистограммы
image_eq = cv2.equalizeHist(greenless_image)
process_images.append(image_eq)
return np.array(process_images)
# Выполнение предобработки изображений
processed_images = preprocess_images(images)
# Применяем фильтры к каждому изображению
filtered_images = np.array([apply_filters(image) for image in processed_images])
# Функция для отображения одного изображения
def display_single_image(original, processed, index):
plt.figure(figsize=(10, 5))
# Отображение оригинального изображения
plt.subplot(1, 2, 1)
plt.imshow(cv2.cvtColor(original[index], cv2.COLOR_BGR2RGB))
plt.title('Оригинальное изображение')
plt.axis('off')
# Отображение обработанного изображения
plt.subplot(1, 2, 2)
plt.imshow(processed[index], cmap='gray')
plt.title('Обработанное изображение')
plt.axis('off')
plt.show()
index = 1
display_single_image(images, filtered_images, index)
Извлекем признаки из изображений¶
Сначало цветовые признаки, потом текстурные признаки
import os
import cv2
import numpy as np
import mahotas
from sklearn.model_selection import train_test_split
from sklearn.svm import SVC
from sklearn.metrics import classification_report, confusion_matrix
def load_images_from_folder(main_folder):
images = []
filenames = []
# Проверяем, существует ли основная папка
if not os.path.exists(main_folder):
raise FileNotFoundError(f"Папка {main_folder} не найдена")
# Рекурсивно проходим по всем подпапкам
for root, _, files in os.walk(main_folder):
class_name = os.path.basename(root)
for filename in files:
file_path = os.path.join(root, filename)
# Проверяем расширение файла
if filename.lower().endswith(('.png', '.jpg', '.jpeg')):
image = cv2.imread(file_path)
if image is not None:
image = cv2.resize(image, (512, 512))
images.append(image)
filenames.append(class_name)
return images, filenames
def extract_color_features(image):
color_features = []
if len(image.shape) == 2:
# Чёрно-белое изображение
color_features.extend([
np.mean(image), np.std(image),
*cv2.calcHist([image], [0], None, [256], [0, 256]).flatten()
])
else:
# Цветное изображение
for channel in range(image.shape[2]):
channel_image = image[:, :, channel]
color_features.extend([
np.mean(channel_image), np.std(channel_image),
*cv2.calcHist([channel_image], [0], None, [256], [0, 256]).flatten()
])
return np.array(color_features)
def extract_texture_features(image):
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
haralick_features = mahotas.features.haralick(gray_image).mean(axis=0)
return haralick_features
def extract_features(images):
features = []
for image in images:
color_features = extract_color_features(image)
texture_features = extract_texture_features(image)
combined_features = np.hstack([color_features, texture_features])
features.append(combined_features)
return np.array(features)
data_folder = "../static/images"
images, filenames = load_images_from_folder(data_folder)
if len(images) == 0:
raise ValueError("Не загружено ни одного изображения. Проверьте папку images и наличие картинок")
print(f"Загружено {len(images)} изображений из {len(np.unique(filenames))} классов")
# Извлечение признаков
features_array = extract_features(images)
filenames_array = np.array(filenames)
if len(np.unique(filenames_array)) < 2:
raise ValueError("Для классификации нужно как минимум 2 класса")
# Разделение данных
X_train, X_test, y_train, y_test = train_test_split(features_array, filenames_array, test_size=0.2, stratify=filenames_array, random_state=42)
# Обучение модели
clf = SVC(kernel='linear')
clf.fit(X_train, y_train)
# Оценка модели
y_pred = clf.predict(X_test)
print("\nConfusion Matrix:")
print(confusion_matrix(y_test, y_pred))
print("\nОтчёт о классификации:")
print(classification_report(y_test, y_pred, zero_division=1))
Теперь обучим модель
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.svm import SVC
from sklearn.metrics import classification_report, confusion_matrix
folder = "../static/images"
images, labels = load_images_from_folder(folder)
features_array = extract_features(images)
# Преобразуем в массив Numpy
filenames_array = np.array(filenames)
if len(np.unique(filenames_array)) < 2:
raise ValueError("Для классификации нужно как минимум 2 класса")
# Разделю выборки на тренировочную и тестовую выборки
X_train, X_test, y_train, y_test = train_test_split(features_array, filenames_array, test_size = 0.2, stratify=filenames_array, random_state=42)
# Стандартизируем данные
scaler = StandardScaler()
x_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
# Обучим классификатор SVM
model = SVC(kernel='linear', C=1)
model.fit(X_train, y_train)
# Предсказание на тестовых данных
y_pred = model.predict(X_test)
# Оценки и метрики качества
print(confusion_matrix(y_test, y_pred))
print(classification_report(y_test, y_pred))
Модель не научилась нормально предсказывать. Наверное из-за маленького датасета
Попробуем решить проблему Аугментации данных для 10 случайных изображений¶
import numpy as np
import cv2
import mahotas
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import classification_report, confusion_matrix
def load_and_prepare_data():
# Создаём тестовые данные (4 изображения 256x256)
processed_images = np.random.randint(0, 256, (4, 128, 128), dtype=np.uint8)
# 12 меток для 4 изображений
labels = np.array([0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3])
# Преобразуем в 1 метку на изображение (берем первую для каждого)
unique_labels = []
unique_images = []
for i in range(0, len(labels), 3):
unique_images.append(processed_images[i//3])
unique_labels.append(labels[i])
return np.array(unique_images), np.array(unique_labels)
def augment_data(images, labels, augment_count=40):
images = np.array(images)
labels = np.array(labels)
if len(images) != len(labels):
raise ValueError(f"Несоответствие размеров: {len(images)} изображений и {len(labels)} меток")
# Создаём объект ImageDataGenerator для аугментации
datagen = ImageDataGenerator(
rotation_range=10,
width_shift_range=0.1,
height_shift_range=0.1,
shear_range=0.1,
zoom_range=0.1,
horizontal_flip=True,
fill_mode='nearest'
)
augmented_images = []
augmented_labels = []
for image, label in zip(images, labels):
image = np.expand_dims(image, axis=-1) if len(image.shape) == 2 else image
image = np.expand_dims(image, axis=0) # Добавляем batch-измерение
# Генерация 10 аугментированных вариантов
for _ in range(augment_count):
augmented_image = next(datagen.flow(image, batch_size=1))[0].astype(np.uint8)
augmented_images.append(augmented_image.squeeze())
augmented_labels.append(label)
return np.array(augmented_images), np.array(augmented_labels)
def extract_features(images):
# Извлечение признаков с проверками
features = []
for image in images:
# Цветовые признаки
channels = [image] if len (image.shape) == 2 else [image[:, :, i] for i in range(image.shape[2])]
color_features = []
for channel in range(image.shape[2]):
channel_image = image[:, :, channel]
color_features.extend([np.mean(channel_image), np.std(channel_image),
*cv2.calcHist([channel_image], [0], None, [256], [0, 256]).flatten()
])
# Текстура
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) if image.shape[2] == 3 else image[:, :, 0]
texture_features = mahotas.features.haralick(gray).mean(axis=0)
features.append(np.hstack([color_features, texture_features]))
return np.array(features)
def extract_texture_features(image):
# Если изображение уже чёрно-белое (одноканальное), преобразовывать не нужно
if len(image.shape) == 2 or image.shape[2] == 1:
gray_image = image if len(image.shape) == 2 else image[:, :, 0]
else:
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
return mahotas.features.haralick(gray_image).mean(axis=0)
def extract_features(images):
# Извлекает признаки из всех изображений.
return np.array([np.hstack([extract_color_features(image), extract_texture_features(image)]) for image in images])
# Основной поток выполнения
try:
# 1. Загрузка данных
processed_images, labels = load_and_prepare_data()
print(f"Загружено {len(processed_images)} изображений и {len(labels)} меток")
# 2. Аугментация
augmented_images, augmented_labels = augment_data(processed_images, labels)
print(f"Создано {len(augmented_images)} аугментированных изображений")
# 3. Преобразование в RGB (если нужно)
if augmented_images.shape[-1] == 1:
augmented_images = np.repeat(augmented_images, 3, axis=-1)
# 4. Извлечение признаков
features = extract_features(augmented_images)
print(f"Извлечено {len(features)} наборов признаков")
# 5. Разделение данных
X_train, X_test, y_train, y_test = train_test_split(
features, augmented_labels, test_size=0.2, random_state=42
)
print(f"Обучающая выборка: {len(X_train)}, тестовая: {len(X_test)}")
# 6. Масштабирование
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
# 7. Обучение модели (пример)
from sklearn.ensemble import RandomForestClassifier
model = RandomForestClassifier()
model.fit(X_train, y_train)
# 8. Оценка
y_pred = model.predict(X_test)
print(classification_report(y_test, y_pred))
print(confusion_matrix(y_test, y_pred))
except Exception as e:
print(f"Ошибка: {str(e)}")
Другое дело. Существенный прирост по сравнению с сырым обучением. Ранее модель тупо предсказывала один класс (accuracy ≈ 0.39). После аугментации accuracy поднялась до 0.75, а большинство F1-метрик вышли на уровень 0.7–0.9. Классы 1 и 2 решаются почти идеально: recall = 1.0, F1 > 0.9, класс 0 тоже неплох — F1 = 0.71, но половину «0» модель путает с «3» (4 из 10), Класс 3 остаётся слабым: precision/recall по 0.43, F1 = 0.43. Он часто смешивается с другими. Вероятно, для класса 3 аугментации либо слишком мало, либо они слишком «одинаковые», и модель не охватывает вариативность этого класса. Нужно добавить больше данных, тогда станет лучше