Как стать автором
Обновить

Обработка изображений на Python

Время на прочтение6 мин
Количество просмотров19K

1. Введение

В процессе этой статьи будет разработан фильтр для изображения, который позволит показать контуры фигур на изображение.

Для этого будем рассматривать разности значений между соседями данного пикселя (серые на картинке).

Соседи у пикселя
Соседи у пикселя

Если пиксель находится на границе какой-либо фигуры, то одни соседи будут иметь значение x, а другие соседи - y. Отсюда, чем ближе пиксель к границе, тем больше |x-y|. Тем самым, пиксели возле границы будут иметь большее значение, то есть ярче.

2. Реализация

Для обработки изображений потребуется модуль pillow (ссылка). Для удобства будем открывать изображение по ссылке. Для этого потребуется модуль urllib (ссылка). Импортируем необходимые модули.

from PIL import Image  # Для обработки изображения
from urllib.request import urlopen  # Для открытия изображения по ссылке

Откроем изображение по ссылки и приступим к обработке.

IMAGE_URL = 'https://avatars.mds.yandex.net/get-zen_doc/1587012/pub_5ccd9b67ffaa2300b352e32a_5ccda2ed02612c00b36f074c/scale_1200'
im_url = urlopen(IMAGE_URL)  # Обработаная ссылка на изображение
with Image.open(im_url) as image:  # Открытие изображения
    image_processed = Image.new('RGB', (image.size[0], image.size[1] * 2))  # Создание нового пустого изображения
    pixels = image.load()  # Получение массива пикселей
    pixels_processed = image_processed.load()

    for x in range(image.size[0]):
        for y in range(image.size[1] * 2):
            if y >= image.size[1]:
                pixels_processed[x, y] = handler_black_n_white(pixels, x, y - image.size[1], painted=True)  # Переписываем пиксели при помощи функции, написанной ранее
            else:
                pixels_processed[x, y] = pixels[x, y]  # Не меняем пиксели

image_processed.show()  # Вывод изображения

Получившееся изображение будет состоять из двух входных изображений, расположенных одно под одним. Нижнее будет обработанное. Для этого и рассматриваются в цикле for два случая.

Введем необходимые функции.

def handler_black_n_white(pixels, i, j, painted=False):
    pixels_nearby = []  # Находим список пикселей которые стоят рядом с данным пикселем
    for di in range(-1, 2):
        for dj in range(-1, 2):
            try:
                pixels_nearby.append(pixels[i + di, j + dj])
            except:
                continue
    
    total_r, total_g, total_b = 0, 0, 0
    for pixel in pixels_nearby:
        current_r, current_g, current_b = pixel
        for other_pixel in pixels_nearby:
            other_r, other_g, other_b = other_pixel  # Выполним обработку
            delta_r = delta_g = delta_b = (abs(other_r - current_r) + abs(other_g - current_g) + abs(other_b - current_b)) / 3
            total_r, total_g, total_b = total_r + delta_r, total_g + delta_g, total_b + delta_b
    n = len(pixels_nearby)
    total_r, total_g, total_b = total_r / n / n, total_g / n / n, total_b / n / n  # Разделим на n **2
    if painted:
        total_r, total_g, total_b = recolor((total_r, total_g, total_b))
    return settle(total_r), settle(total_g), settle(total_b)  # Выведем

На вход этой функции, как понятно из названия, поступают 4 аргумента. Первый - массив с пикселями изображения; второй и третий - координаты пикселя; четвёртый - изменять цвет или нет.

pixels_nearby = []  # Находим список пикселей которые стоят рядом с данным пикселем
for di in range(-1, 2):
  for dj in range(-1, 2):
    try:
      pixels_nearby.append(pixels[i + di, j + dj])
      except:
        continue

Здесь в список pixels_nearby записываются соседи данного пикселя.

total_r, total_g, total_b = 0, 0, 0
for pixel in pixels_nearby:
  current_r, current_g, current_b = pixel
  for other_pixel in pixels_nearby:
    other_r, other_g, other_b = other_pixel  # Выполним обработку
    delta_r = delta_g = delta_b = (abs(other_r - current_r) + abs(other_g - current_g) + abs(other_b - current_b)) / 3
    total_r, total_g, total_b = total_r + delta_r, total_g + delta_g, total_b + delta_b

В этих строках вычисляются попарные разности между соседними пикселями.

n = len(pixels_nearby)
total_r, total_g, total_b = total_r / n / n, total_g / n / n, total_b / n / n  # Разделим на n **2
if painted:
  total_r, total_g, total_b = recolor((total_r, total_g, total_b))
return settle(total_r), settle(total_g), settle(total_b)  # Выведем

Далее при необходимости пиксели подкрашиваются и возвращаются.

В функции handler_black_n_white использовались следующие функции.

def settle(n):
    return int(max(0, min(255, n)))

settle используется для того, чтобы пиксель имел допустимые значения.

def recolor(t):
    r, g, b = t
    max_r, max_g, max_b = 255, 0, 255  # Фиолетовый
    min_r, min_g, min_b = 0, 0, 0  # Черный
    return settle(min_r + r * (max_r - min_r) / 256), settle(min_g + g * (max_g - min_g) / 256), settle(min_b + b * (max_b - min_b) / 256)

recolor нужна для подкраски пикселей.

По аналогии с handler_black_n_white можно построить следующую функцию.

def handler_color(pixels, i, j):
    pixels_nearby = []  # Находим список пикселей которые стоят рядом с данным пикселем
    for di in range(-1, 2):
        for dj in range(-1, 2):
            try:
                pixels_nearby.append(pixels[i + di, j + dj])
            except:
                continue
    
    total_r, total_g, total_b = 0, 0, 0
    for pixel in pixels_nearby:
        current_r, current_g, current_b = pixel
        for other_pixel in pixels_nearby:
            other_r, other_g, other_b = other_pixel  # Выполним обработку
            delta_r, delta_g, delta_b = abs(other_r ** 2 - current_r ** 2) ** 0.5, abs(other_g ** 2 - current_g ** 2) ** 0.5, abs(other_b ** 2 - current_b ** 2) ** 0.5
            total_r, total_g, total_b = total_r + delta_r, total_g + delta_g, total_b + delta_b
    n = len(pixels_nearby)
    total_r, total_g, total_b = total_r / n / n, total_g / n / n, total_b / n / n  # Разделим на n **2
    return settle(total_r), settle(total_g), settle(total_b)  # Выведем

Из названия понятно, что пиксели, которые вернёт эта функция, будут цветными, поэтому recolor, не требуется.

handler_color отличается от hander_black_n_white только 15 строкой. Здесь считается корень из разности квадратов, а не просто модуль разности, как в hander_black_n_white. В следствии чего значения пикселей будут на порядок больше и можно будет различать изменения цвета.

Стоит отметить, что эта функция будет гораздо дольше исполняться из-за взятия квадрата и корня.

3. Код

Вот итоговый код.

from PIL import Image  # Для обработки изображения
from urllib.request import urlopen  # Для открытия изображения по ссылке

IMAGE_URL = 'https://avatars.mds.yandex.net/get-zen_doc/1587012/pub_5ccd9b67ffaa2300b352e32a_5ccda2ed02612c00b36f074c/scale_1200'
im_url = urlopen(IMAGE_URL)  # Обработаная ссылка на изображение

def settle(n):
    return int(max(0, min(255, n)))

def handler_color(pixels, i, j):
    pixels_nearby = []  # Находим список пикселей которые стоят рядом с данным пикселем
    for di in range(-1, 2):
        for dj in range(-1, 2):
            try:
                pixels_nearby.append(pixels[i + di, j + dj])
            except:
                continue
    
    total_r, total_g, total_b = 0, 0, 0
    for pixel in pixels_nearby:
        current_r, current_g, current_b = pixel
        for other_pixel in pixels_nearby:
            other_r, other_g, other_b = other_pixel  # Выполним обработку
            delta_r, delta_g, delta_b = abs(other_r ** 2 - current_r ** 2) ** 0.5, abs(other_g ** 2 - current_g ** 2) ** 0.5, abs(other_b ** 2 - current_b ** 2) ** 0.5
            total_r, total_g, total_b = total_r + delta_r, total_g + delta_g, total_b + delta_b
    n = len(pixels_nearby)
    total_r, total_g, total_b = total_r / n / n, total_g / n / n, total_b / n / n  # Разделим на n **2
    return settle(total_r), settle(total_g), settle(total_b)  # Выведем

def recolor(t):
    r, g, b = t
    max_r, max_g, max_b = 255, 0, 255  # Фиолетовый
    min_r, min_g, min_b = 0, 0, 0  # Черный
    return settle(min_r + r * (max_r - min_r) / 256), settle(min_g + g * (max_g - min_g) / 256), settle(min_b + b * (max_b - min_b) / 256)

def handler_black_n_white(pixels, i, j, painted=False):
    pixels_nearby = []  # Находим список пикселей которые стоят рядом с данным пикселем
    for di in range(-1, 2):
        for dj in range(-1, 2):
            try:
                pixels_nearby.append(pixels[i + di, j + dj])
            except:
                continue
    
    total_r, total_g, total_b = 0, 0, 0
    for pixel in pixels_nearby:
        current_r, current_g, current_b = pixel
        for other_pixel in pixels_nearby:
            other_r, other_g, other_b = other_pixel  # Выполним обработку
            delta_r = delta_g = delta_b = (abs(other_r - current_r) + abs(other_g - current_g) + abs(other_b - current_b)) / 3
            total_r, total_g, total_b = total_r + delta_r, total_g + delta_g, total_b + delta_b
    n = len(pixels_nearby)
    total_r, total_g, total_b = total_r / n / n, total_g / n / n, total_b / n / n  # Разделим на n **2
    if painted:
        total_r, total_g, total_b = recolor((total_r, total_g, total_b))
    return settle(total_r), settle(total_g), settle(total_b)  # Выведем

with Image.open(im_url) as image:  # Открытие изображения
    image_processed = Image.new('RGB', (image.size[0], image.size[1] * 2))  # Создание нового пустого изображения
    pixels = image.load()  # Получение массива пикселей
    pixels_processed = image_processed.load()

    for x in range(image.size[0]):
        for y in range(image.size[1] * 2):
            if y >= image.size[1]:
                pixels_processed[x, y] = handler_black_n_white(pixels, x, y - image.size[1], True)  # Переписываем пиксели при помощи функции, написанной ранее
            else:
                pixels_processed[x, y] = pixels[x, y]  # Не меняем пиксели

image_processed.show()  # Вывод изображения

Или ссылка на colab.

4. Итоги

Обработанная картинка
Обработанная картинка

Нам удалось выполнить поставленную задачу. Картинка успешно обрабатывается и выводится. На данном этапе обработка занимает довольно много времени. Стоит подумать над способами оптимизации алгоритма.

Подумайте, где можно применить этот фильтр.

Теги:
Хабы:
Всего голосов 7: ↑6 и ↓1+6
Комментарии7

Публикации

Истории

Работа

Data Scientist
92 вакансии
Python разработчик
195 вакансий

Ближайшие события

27 августа – 7 октября
Премия digital-кейсов «Проксима»
МоскваОнлайн
28 сентября – 5 октября
О! Хакатон
Онлайн
3 – 18 октября
Kokoc Hackathon 2024
Онлайн
10 – 11 октября
HR IT & Team Lead конференция «Битва за IT-таланты»
МоскваОнлайн
25 октября
Конференция по росту продуктов EGC’24
МоскваОнлайн
7 – 8 ноября
Конференция byteoilgas_conf 2024
МоскваОнлайн