Всем привет!
В этой статье я расскажу, как я автоматически генерировал 42 стикера для Телеграма на основе изображений из интернет-магазина плакатов. На сайте продаются плакаты с разными забавными надписями, но соответствующих стикеров в Телеграме нет. Попробуем сделать сами. Единственная проблема состоит в следующем: чтобы сделать один стикер, нужно скачать фотографию плаката с сайта, отделить надпись от фона в фотошопе и сохранить в нужном разрешении, чтобы она соответствовала требованиям телеграма к стикерам. Поскольку изображений 42, это муторное и трудоемкое занятие. Давайте автоматизируем.
Итак, план таков:
Парсим фотографии с сайта интернет-магазина.
Отделяем текст от фона и убираем тени, делаем фото похожим на скан.
Подгоняем размер изображения под требования к стикерам, добавляем прозрачные пиксели.
Парсим фото с сайта
Первым делом, заведем список urls, который будет содержать ссылки на страницу с каждым конкретным плакатом. Это нужно, чтобы скачать хайрезы, ведь в общей галерее плакатов картинки в маленьком разрешении. Так что смотрим, куда ведут ссылки с каждой картинки в галерее.
import requests from bs4 import BeautifulSoup import urllib.request url = 'https://demonpress.ecwid.com/%D0%9F%D0%BB%D0%B0%D0%BA%D0%B0%D1%82%D1%8B-c26701164' headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36'} text = requests.get(url, headers=headers) soup = BeautifulSoup(text.content, 'html.parser') urls = [] for i in soup.find_all('a', attrs = {"class": "grid-product__image"}): urls += [i['href']]
В галерее плакаты представлены по 15 штук, так что далее пробегаемся по страницам 2, 3 и 4.

Для перехода на эти страницы достаточно добавить к адресу ?offset=15 или 30 или 45, в зависимости от того, на какую страницу хотим попасть. Это позволяет спарсить остальные ссылки.
for i in ['15','30','45']: url_next = url + '?offset=' + i text = requests.get(url_next, headers=headers) soup = BeautifulSoup(text.content, 'html.parser') for i in soup.find_all('a', attrs = {"class": "grid-product__image"}): urls += [i['href']]
Теперь дело за малым, пройти по всем ссылкам из списка urls и скачать картинку из карточки товара.
for url in urls: text = requests.get(url, headers=headers) soup = BeautifulSoup(text.content, 'html.parser') for j in soup.find_all('img', {'class':'details-gallery__picture details-gallery__photoswipe-index-0'}): urllib.request.urlretrieve(j['src'], j['title'].replace('/','').replace('*','').replace('?','')+'.jpg')
Картинки для понятности будем называть фразами с этих плакатов, для этого убираем из названий символы "/", "?" и "*".
Коррекция изображений
К сожалению, на сайте используются фотографии плакатов, а не сканы, так что использовать их сразу не получится. Картинки на сайте выглядят вот так:

Свет падает сбоку, из-за этого на картинке появляется некрасивая тень. Сами буквы могут быть темнее в затененных частях изображения. Более того, из-за этого для отделения букв от фона не получится воспользоваться порогом по яркости. Буквы в светлых частях изображения могут быть светлее, чем фон в темных его частях.
Для исправления ситуации воспользуемся тем фактом, что все плакаты представляют собой красные буквы на белом фоне. Поскольку каждый пиксель – это вектор из трех чисел, можно посчитать стандартное отклонение его компонент. Если пиксель серый – стандартное отклонение будет маленьким независимо от того, темный пиксель или светлый. Если пиксель красный – стандартное отклонение будет большим, поскольку имеется доминирующая компонента. Экспериментальным путем было установлено, что хорошим пороговым значением отклонения будет 30.
Далее просто заменяем все пиксели фона на (245, 245, 245), а пиксели букв на (200, 17, 11). Фон не совсем белый, но так смотрится лучше, поскольку в реальной жизни идеально белой бумаги не бывает, а нам надо имитировать плакат.
def remove_bg(input_img: np.ndarray) -> np.ndarray: img = input_img.copy() for i in range(0, img.shape[0]): for j in range(0, img.shape[1]): if img[i][j].std() < 30: img[i][j][0] = 245 img[i][j][1] = 245 img[i][j][2] = 245 else: img[i][j][0] = 200 img[i][j][1] = 17 img[i][j][2] = 11 return img
На выходе имеем такой результат:

Остался последний шаг. Телеграм требует, чтобы стикеры были в формате png c разрешением 512x512. Наши картинки не квадратные, поэтому их придется дополнить прозрачными пикселями справа. (UPD: прозрачные пиксели до��авлять не обязательно, достаточно ужать до 512 по большей стороне)
Для этого воспользуемся библиотекой PIL.
from PIL import Image def transp_bg_and_resize(input_img: np.ndarray) -> np.ndarray: # Меняем размер img = input_img.copy() a = 512/img.shape[0] x = int(img.shape[0]*a) y = int(img.shape[1]*a) img = resize(img, (x, y)) # Вставляем на прозрачный фон blank = Image.new("RGBA",(512,512), (0,0,0,0)) img = Image.fromarray((img * 255).astype(np.uint8)) blank.paste(img, (0,0)) return blank
На выходе получаем изображение 512х512 пикселей. Его можно использовать в качестве стикера.

Осталось проделать описанное выше для всех картинок и готово.
files = [] for i in os.listdir('.'): if i[-4::] == '.jpg': files += [i] for file in files: img = mpimg.imread(file).copy() img = remove_bg(img) img = transp_bg_and_resize(img) img.save(f'result/{file[0:-4]}', 'png') print('Done!')
Получилось вот так:
