Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
484 changes: 242 additions & 242 deletions lab2.md

Large diffs are not rendered by default.

402 changes: 201 additions & 201 deletions lab3.md

Large diffs are not rendered by default.

90 changes: 90 additions & 0 deletions lab3/file_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
"""Модуль для управления файлами и директориями с изображениями."""

from pathlib import Path
from typing import List, Tuple, Optional, Dict, Any


class FileManager:
"""Класс для работы с файлами и директориями изображений."""

# Поддерживаемые форматы изображений
SUPPORTED_FORMATS = ['.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.tif', '.webp',
'.JPG', '.JPEG', '.PNG', '.BMP', '.TIFF', '.TIF', '.WEBP']

@staticmethod
def find_image_files(folder_path: str) -> List[Path]:
"""Находит все файлы изображений в указанной папке"""
input_path = Path(folder_path)

if not input_path.exists():
raise FileNotFoundError(f"Папка '{folder_path}' не найдена!")

if not input_path.is_dir():
raise ValueError(f"'{folder_path}' не является папкой!")

try:
image_files = []
for format_ext in FileManager.SUPPORTED_FORMATS:
image_files.extend(input_path.glob(f'*{format_ext}'))

return sorted(image_files) # Сортируем для предскатуемого порядка

except PermissionError as e:
raise PermissionError(f"Нет прав доступа к папке '{folder_path}'") from e

@staticmethod
def create_output_directory(output_folder: str) -> Path:
"""Создает выходную директорию если она не существует"""
try:
output_path = Path(output_folder)
output_path.mkdir(parents=True, exist_ok=True)
return output_path
except PermissionError as e:
raise PermissionError(f"Нет прав для создания директории '{output_folder}'") from e

@staticmethod
def process_single_image(input_path: Path, output_path: Path,
angle: float, verbose: bool = False) -> Tuple[bool, Dict[str, Any]]:
"""Обрабатывает одно изображение"""
try:
if verbose:
print(f" Обработка: {input_path.name}")

# Локальный импорт для избежания циклической зависимости
from image_processor import ImageProcessor

# Загружаем изображение
img = ImageProcessor.load_image(str(input_path))
if img is None:
print(f" ⚠️ Не удалось загрузить: {input_path.name}")
return False, {}

# Получаем информацию об исходном изображении
img_info = ImageProcessor.get_image_info(img)

# Поворачиваем изображение
rotated_img = ImageProcessor.rotate_image(img, angle)

# Получаем информацию о повернутом изображении
rotated_info = ImageProcessor.get_image_info(rotated_img)

# Сохраняем результат
success = ImageProcessor.save_image(rotated_img, str(output_path))

if not success:
print(f" ✗ Не удалось сохранить: {output_path.name}")
return False, {}

# Собираем полную информацию
full_info: Dict[str, Any] = {
'filename': input_path.name,
'original': img_info,
'rotated': rotated_info,
'output_filename': output_path.name
}

return True, full_info

except Exception as e:
print(f" ✗ Ошибка при обработке {input_path.name}: {str(e)}")
return False, {}
119 changes: 119 additions & 0 deletions lab3/image_processor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
"""Модуль для обработки и поворота изображений."""

import cv2
import numpy as np
from typing import Tuple, Optional, Dict, Any
import matplotlib.pyplot as plt


class ImageProcessor:
"""Класс для обработки и поворота изображений."""

@staticmethod
def rotate_image(image: np.ndarray, angle: float) -> np.ndarray:
"""Поворачивает изображение на заданный угол вокруг центра"""
if image is None or image.size == 0:
raise ValueError("Пустое или некорректное изображение")

try:
# Получаем размеры изображения
(h, w) = image.shape[:2]

# Вычисляем центр изображения
center = (w // 2, h // 2)

# Получаем матрицу поворота
rotation_matrix = cv2.getRotationMatrix2D(center, angle, 1.0)

# Вычисляем новые размеры изображения после поворота
cos = np.abs(rotation_matrix[0, 0])
sin = np.abs(rotation_matrix[0, 1])

new_w = int((h * sin) + (w * cos))
new_h = int((h * cos) + (w * sin))

# Корректируем матрицу поворота с учетом новых размеров
rotation_matrix[0, 2] += (new_w / 2) - center[0]
rotation_matrix[1, 2] += (new_h / 2) - center[1]

# Применяем аффинное преобразование
rotated_image = cv2.warpAffine(image, rotation_matrix, (new_w, new_h))

return rotated_image

except Exception as e:
raise ValueError(f"Ошибка при повороте изображения: {e}") from e

@staticmethod
def load_image(image_path: str) -> Optional[np.ndarray]:
"""Загружает изображение из файла"""
try:
img = cv2.imread(image_path)
if img is None:
return None
# Конвертируем из BGR в RGB для отображения в matplotlib
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
return img_rgb
except Exception as e:
print(f"Ошибка при загрузке изображения {image_path}: {e}")
return None

@staticmethod
def save_image(image: np.ndarray, output_path: str) -> bool:
"""Сохраняет изображение в файл"""
try:
# Конвертируем обратно в BGR для OpenCV
img_bgr = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
success = cv2.imwrite(output_path, img_bgr)
return success
except Exception as e:
print(f"Ошибка при сохранении изображения {output_path}: {e}")
return False

@staticmethod
def get_image_info(image: np.ndarray) -> Dict[str, Any]:
"""Получает информацию об изображении"""
if image is None:
return {}

return {
'original_size': image.shape,
'height': image.shape[0],
'width': image.shape[1],
'channels': image.shape[2] if len(image.shape) == 3 else 1,
'dtype': str(image.dtype),
'min_value': float(image.min()),
'max_value': float(image.max()),
'mean_value': float(image.mean())
}

@staticmethod
def display_images(original: np.ndarray, rotated: np.ndarray,
angle: float, figsize: Tuple[int, int] = (12, 6)) -> None:
"""Отображает исходное и повернутое изображения с помощью matplotlib"""
fig, axes = plt.subplots(1, 2, figsize=figsize)

# Отображаем исходное изображение
axes[0].imshow(original)
axes[0].set_title('Исходное изображение')
axes[0].axis('off')

# Добавляем информацию об исходном изображении
orig_info = f"Размер: {original.shape[1]}x{original.shape[0]}\n"
orig_info += f"Каналы: {original.shape[2] if len(original.shape) == 3 else 1}"
axes[0].text(0.5, -0.1, orig_info, transform=axes[0].transAxes,
ha='center', va='top', fontsize=10)

# Отображаем повернутое изображение
axes[1].imshow(rotated)
axes[1].set_title(f'Повернуто на {angle}°')
axes[1].axis('off')

# Добавляем информацию о повернутом изображении
rotated_info = f"Размер: {rotated.shape[1]}x{rotated.shape[0]}\n"
rotated_info += f"Каналы: {rotated.shape[2] if len(rotated.shape) == 3 else 1}"
axes[1].text(0.5, -0.1, rotated_info, transform=axes[1].transAxes,
ha='center', va='top', fontsize=10)

plt.tight_layout()
plt.show()
173 changes: 173 additions & 0 deletions lab3/lab3_var4.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
"""Основной модуль программы для поворота изображений."""

import argparse
import sys
from typing import List, Dict, Tuple, Any

# Импорт собственных модулей
from image_processor import ImageProcessor
from file_manager import FileManager
from visualizer import Visualizer


def parse_arguments() -> argparse.Namespace:
"""Парсит аргументы командной строки"""
parser = argparse.ArgumentParser(
description='Поворот всех изображений в папке на заданный угол',
formatter_class=argparse.RawDescriptionHelpFormatter,
)

parser.add_argument(
'input_folder',
type=str,
help='Путь к папке с изображениями'
)

parser.add_argument(
'angle',
type=float,
help='Угол поворота в градусах (положительный - против часовой стрелки)'
)

parser.add_argument(
'-o', '--output',
type=str,
default='rotated_images',
help='Папка для сохранения результатов (по умолчанию: rotated_images)'
)

parser.add_argument(
'-p', '--preview',
action='store_true',
help='Показать предпросмотр первого изображения с помощью matplotlib'
)

parser.add_argument(
'-v', '--verbose',
action='store_true',
help='Подробный вывод информации о каждом изображении'
)

parser.add_argument(
'--no-plot',
action='store_true',
help='Не показывать графики статистики'
)

return parser.parse_args()


def process_folder(input_folder: str, output_folder: str, angle: float,
verbose: bool = False) -> Tuple[List[Dict[str, Any]], Dict[str, Any]]:
"""Обрабатывает все изображения в папке"""
try:
# Находим файлы изображений
image_files = FileManager.find_image_files(input_folder)

if not image_files:
print("Не найдено изображений в папке!")
return [], {}

print(f"Найдено изображений: {len(image_files)}")
print(f"Угол поворота: {angle}°")

# Создаем выходную директорию
output_path = FileManager.create_output_directory(output_folder)
print(f"Выходная папка: {output_folder}")

# Обрабатываем изображения
processed_info = []
successful = 0
failed = 0

for image_path in image_files:
# Формируем имя выходного файла
name_without_ext = image_path.stem
extension = image_path.suffix
output_filename = f"{name_without_ext}_rotated_{int(angle)}deg{extension}"
output_filepath = output_path / output_filename

# Обрабатываем изображение
success, info = FileManager.process_single_image(
image_path, output_filepath, angle, verbose
)

if success:
successful += 1
processed_info.append(info)
else:
failed += 1

# Формируем статистику
statistics: Dict[str, Any] = {
'total': len(image_files),
'successful': successful,
'failed': failed,
'output_folder': str(output_path.absolute()),
'angle': angle
}

return processed_info, statistics

except FileNotFoundError as e:
print(f"Ошибка: {e}")
return [], {}
except PermissionError as e:
print(f"Ошибка доступа: {e}")
return [], {}
except Exception as e:
print(f"Неожиданная ошибка: {e}")
return [], {}


def main() -> None:
"""Основная функция программы"""
try:
# Парсинг аргументов
args = parse_arguments()

# Обработка папки
images_info, statistics = process_folder(
args.input_folder,
args.output,
args.angle,
args.verbose
)

if statistics.get('total', 0) == 0:
return

# Показ статистики
Visualizer.show_statistics(statistics)

# Подробная информация (если нужно)
if args.verbose and images_info:
Visualizer.show_detailed_info(images_info)

# Предпросмотр первого изображения (если нужно)
if args.preview and images_info:
print("\nЗагружаю первое изображение для предпросмотра...")
image_files = FileManager.find_image_files(args.input_folder)
if image_files:
first_image_path = image_files[0]
original_img = ImageProcessor.load_image(str(first_image_path))
if original_img is not None:
rotated_img = ImageProcessor.rotate_image(original_img, args.angle)
ImageProcessor.display_images(original_img, rotated_img, args.angle)

# График статистики (если не отключен)
if not args.no_plot:
Visualizer.plot_processing_results(statistics)

print("\nОбработка завершена!")

except KeyboardInterrupt:
print("\n\nПрограмма прервана пользователем")
sys.exit(1)
except Exception as e:
print(f"\nКритическая ошибка: {e}")
sys.exit(1)


if __name__ == "__main__":
main()
Loading