- •Проектная работа
- •Глава 1. Технологии разработки Telegram бота. 4
- •Глава 2. Разработка Telegram-бота 21
- •Перечень терминов и сокращений
- •Введение
- •Глава 1. Технологии разработки Telegram бота.
- •Обзор литературы
- •1.2 История мессенджера Telegram
- •1.3 Сравнительный анализ языков программирования для Telegram-ботов
- •1.4 Характеристика языка программирования Python
- •1.5 Обзор фреймворков и библиотек для создания ботов
- •1.6 Обзор библиотек и методов обработки изображений
- •1.7 Описание алгоритмов и форматов данных
- •1.7.1. Цветовые модели и форматы данных
- •1.7.2 Алгоритм палитризации
- •1.7.3 Алгоритм классификации изображений
- •1.7.4 Алгоритм сжатия jpeg
- •1.7.5 Алгоритм сжатия png
- •Глава 2. Разработка Telegram-бота
- •2.1 Планируемые возможности бота и подходы к их реализации
- •2.2 Разработка модуля обработки изображений
- •2.3 Разработка пользовательского интерфейса
- •2.4 Разработка функций для работы с изображениями и передачи данных
- •2.4.1 Импорты, настройки и словари
- •2.4.2 Пользовательские настройки
- •2.4.3 Функции обработки изображений
- •2.4.4 Функции работы с пользовательскими данными
- •2.4.6 Обработчики команд и сообщений
- •2.4.7 Основной цикл
- •2.5 Описание разработки Telegram-бота
- •2.6 Инструкция пользователя
- •Список литературы
- •Приложение
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 проще и надёжнее для простых задач.
