Обработка изображений на Python
1. Введение
В процессе этой статьи будет разработан фильтр для изображения, который позволит показать контуры фигур на изображение.
Для этого будем рассматривать разности значений между соседями данного пикселя (серые на картинке).
Если пиксель находится на границе какой-либо фигуры, то одни соседи будут иметь значение
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. Итоги
Нам удалось выполнить поставленную задачу. Картинка успешно обрабатывается и выводится. На данном этапе обработка занимает довольно много времени. Стоит подумать над способами оптимизации алгоритма.
Подумайте, где можно применить этот фильтр.