Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лабораторные. Дагаев / Проект_Кларк_Блинов_Яковлев.docx
Скачиваний:
1
Добавлен:
02.01.2026
Размер:
2.13 Mб
Скачать

2.2 Разработка модуля обработки изображений

В коде бота для сжатия изображений был реализован комплексный подход, сочетающий несколько методов обработки, выбора формата и оптимизации размера файла. Основные применяемые методы можно разделить на несколько категорий: изменение разрешения, классификация типа изображения, выбор формата сжатия и алгоритмы сжатия с контролем качества и размера.

Одним из ключевых методов является изменение разрешения изображения с сохранением пропорций. При задании максимальной ширины (max_w) и/или высоты (max_h) используется пропорциональное масштабирование: вычисляется коэффициент масштабирования как минимальное отношение целевого размера к текущему по каждой оси, чтобы изображение вписывалось в заданные границы без выхода за них. Если коэффициент больше или равен 1.0, апскейлинг не применяется — это предотвращает искусственное увеличение размера и потерю качества. Для изменения размера используется фильтр Image.LANCZOS (высокоточный интерполяционный алгоритм Ланцоша), который обеспечивает лучшее качество при уменьшении изображения за счёт учёта большего числа соседних пикселей, хотя и требует больше вычислений по сравнению с билинейной или бикубической интерполяцией (рис. 3).

w, h = img.size

tw = max_w or w

th = max_h or h

scale = min(tw / w, th / h)

if scale >= 1.0: # не апскейлим

return img

new_size = (max(1, int(w * scale)), max(1, int(h * scale)))

return img.resize(new_size, Image.LANCZOS)

Рис. 3 – Алгоритм масштабирования

Альтернативой могло бы быть использование Image.BICUBIC (быстрее, но чуть менее точно) или Image.NEAREST (для пиксельной графики, но здесь неприменимо из-за потери плавности).

Для классификации изображений на «фото» и «графику» используется эвристический метод на основе двух признаков: количества уникальных цветов и средней «резкости» краёв. Изображение уменьшается до 256×256 пикселей для ускорения анализа, затем подсчитывается количество уникальных цветов. Если уникальных цветов меньше 20000 и среднее значение яркости после применения фильтра обнаружения краёв (ImageFilter.FIND_EDGES) меньше 40, изображение классифицируется как «графика» (скриншоты, схемы, текст), иначе — как «фото» (рис. 4). Фильтр FIND_EDGES выделяет границы с помощью свёртки с ядром Лапласиана или аналогичного оператора, а среднее значение яркости результата показывает, насколько изображение насыщено резкими перепадами — для фото характерны более плавные градиенты.

rgb_small = img.convert("RGB").resize((256, 256))

colors = rgb_small.getcolors(maxcolors=1 << 20)

uniq = len(colors) if colors else (1 << 20)

edges = rgb_small.filter(ImageFilter.FIND_EDGES).convert("L")

edge_mean = sum(edges.getdata()) / (256 * 256)

# Пороговая логика (эмпирически подобрано для простоты)

if uniq < 20000 and edge_mean < 40:

return "graphics"

return "photo"

Рис. 4 – Алгоритм классификации

Этот метод является упрощённым; альтернативно можно было бы использовать более сложные алгоритмы, например, анализ гистограммы, обнаружение текста или даже нейросетевую классификацию, но они требуют больше ресурсов и сложнее в реализации.

Основной алгоритм сжатия с контролем размера файла реализован через бинарный поиск по параметру качества JPEG при заданном целевом размере в килобайтах (target_kb). Если target_kb задан, функция _jpeg_with_target выполняет бинарный поиск в диапазоне качества от 5 до 95, пытаясь найти максимальное качество, при котором размер файла не превышает целевой.

def _jpeg_with_target(img: Image.Image, q_default: int, tgt_kb: Optional[int]) -> bytes:

if tgt_kb is None:

return _save_jpeg_bytes(img, q_default)

target = max(5, tgt_kb) * 1024

lo, hi = 5, 95

Рис. 5 – Функция для сжатия jpeg

Бинарный поиск используется, потому что зависимость размера от качества нелинейна, но монотонна — увеличение качества всегда увеличивает размер (при прочих равных). Поиск ограничен 10 итерациями для баланса между точностью и скоростью. Альтернативой мог бы быть линейный алгоритм, но бинарный поиск является простым, надёжным и гарантированно находит решение за логарифмическое время, что в несколько раз быстрее линейного метода.

best = _save_jpeg_bytes(img, q_default)

# Бинарный поиск по качеству (ограничим ~10 итерациями)

for _ in range(10):

mid = (lo + hi) // 2

cand = _save_jpeg_bytes(img, mid)

if len(cand) > target:

hi = mid - 1

else:

best = cand

lo = mid + 1

return best

Рис. 6 – Бинарный поиск

Для формата JPEG применяются стандартные настройки сжатия: используется прогрессивный режим (progressive=True), который позволяет изображению постепенно проявляться при загрузке в браузере и часто даёт меньший размер файла; применяется субдискретизация цветовых каналов 4:2:0 (subsampling="4:2:0"), которая уменьшает разрешение цветности вдвое по осям, что хорошо подходит для фотографий, так как человеческий глаз менее чувствителен к цветовым деталям. Качество (quality) варьируется от 1 до 95, где 1 — максимальное сжатие с потерей качества, 95 — минимальное сжатие. Опция optimize=True включает дополнительную оптимизацию таблиц Хаффмана, что немного уменьшает размер без потерь.

def _save_jpeg_bytes(img: Image.Image, quality: int) -> bytes:

buf = io.BytesIO()

img.save(

buf,

format="JPEG",

quality=max(1, min(95, quality)),

optimize=True,

progressive=True, # лучше для «проявки» и часто компактнее

subsampling="4:2:0" # стандартный компромисс для фото

)

return buf.getvalue()

Рис. 7 – Алгоритм сохранения для фотографий

Альтернативные подходы могли бы включать использование более современных форматов, таких как WebP или AVIF, которые обеспечивают лучшее сжатие, но требуют дополнительных библиотек и могут иметь проблемы с совместимостью.

Рис. 8 – Сжатие изображения

Для формата PNG в режиме «графика» применяется палитризация с использованием адаптивной палитры (Image.ADAPTIVE) и дизеринга по алгоритму Флойда–Штайнберга (dither=Image.FLOYDSTEINBERG). Палитризация преобразует изображение из полноцветного RGB (до 16,7 млн цветов) в индексированное с ограниченной палитрой (по умолчанию 128 цветов). Алгоритм Флойда–Штайнберга распространяет ошибку квантования цвета на соседние пиксели, что создаёт иллюзию большего количества цветов и снижает эффект «полос».

def _save_png_palettized_bytes(img: Image.Image, colors: int) -> bytes:

# Палитризация + дизеринг Флойда–Штайнберга — сильно уменьшает «графику»

pimg = img.convert("P", palette=Image.ADAPTIVE, colors=max(2, min(256, int(colors))),

dither=Image.FLOYDSTEINBERG)

buf = io.BytesIO()

# Для PNG стандартный дефлейт; Pillow сам подберёт фильтры строк (в т.ч. Paeth)

pimg.save(buf, format="PNG", optimize=True)

return buf.getvalue()

Рис. 9 – Алгоритм сохранения для графики

Это особенно эффективно для скриншотов и схем, где много сплошных цветов. PNG использует сжатие без потерь, но палитризация сама по себе является потерей информации, хотя визуально часто незаметной.

Управление метаданными реализовано через опцию strip_exif: при активации удаляются EXIF-данные (параметры съёмки, геолокация) и ICC-профили (информация о цветовом пространстве). Это уменьшает размер файла и повышает приватность. Удаление происходит путём удаления соответствующих полей из словаря img.info.

def _apply_strip_exif(img: Image.Image, strip: bool) -> None:

# В Pillow EXIF хранится в info["exif"] (если есть)

if strip:

img.info.pop("exif", None)

img.info.pop("icc_profile", None)

Рис. 10 – Метаданные

Рис. 11 – Удаление метаданных

Сборка PDF из изображений выполняется с предварительной конвертацией всех страниц в JPEG с качеством 90 для унификации формата, затем используется библиотека img2pdf, которая создаёт PDF, встраивая изображения как отдельные страницы с сохранением их размеров.

jpg_pages = []

for raw in images:

with Image.open(io.BytesIO(raw)) as im:

if im.mode not in ("RGB", "L"):

im = im.convert("RGB")

b = io.BytesIO()

im.save(b, format="JPEG", quality=90, optimize=True)

jpg_pages.append(b.getvalue())

return img2pdf.convert(jpg_pages)

Рис. 12 – Алгоритм сохранения в PDF

Рис. 13 – Объединение изображений

Альтернативой могло бы быть использование ReportLab или PyPDF2 для более гибкого управления, но img2pdf проще и надёжнее для простых задач.