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

Для начала импортируем модули для дальнейшей работы с изображениями.

import math
import random
from PIL import Image, ImageChops
  • math - модуль математических функций и констант.

  • random - модуль для работы с случайными числами.

  • PIL.Image и PIL.ImageChops - модули для работы с изображениями в формате PIL.

Определим класс Filters, который будет содержать несколько свойств и методов для работы с изображениями и фильтрами.

class Filters:
    def __init__(self, image):
        self.image = image
        self.pixels = image.load()
        self.width, self.height = image.size
        self.pixels = list(image.getdata())
        self.new_pixels = []
        self.newimg = Image.new("RGB", (self.width, self.height), "white")
  • В конструкторе __init__() происходит инициализация объекта класса Filters. Он принимает изображение в качестве аргумента и сохраняет его в свойстве self.image.

  • Метод load() используется для загрузки пикселей изображения и сохранения их в свойстве self.pixels в виде двумерного массива.

  • Свойства self.width и self.height хранят ширину и высоту изображения соответственно.

  • Пиксели изображения сохраняются в виде списка при помощи метода list(image.getdata()) и присваиваются свойству self.pixels.

  • Создается пустой список self.new_pixels, который будет использоваться для хранения обработанных пикселей.

  • Создается новое изображение self.newimg с помощью Image.new(). Оно имеет такие же размеры, как и исходное изображение, и начальный цвет «white».

Теперь перейдём к реализации самих фильтров. Выберем фотографию, чтобы увидеть как работают фильтры и какой результат у нас получится.

Точечные фильтры

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

Первым делом создадим класс SpotFilter, который будет наследовать все свойства и методы класса Filters. И сразу же добавим в него первый фильтр.

Inversion

Inversion filter
Inversion filter
class SpotFilters(Filters):
    def inversion(self):
        for pixel in self.pixels:
            self.new_pixels.append((255 - pixel[0], 
                                    255 - pixel[1], 
                                    255 - pixel[2]))

        self.newimg.putdata(self.new_pixels)
        self.newimg.save('Results/Spot_filters/Inversion.jpg', 'JPEG')
  • В цикле for каждый пиксель из self.pixels обрабатывается и добавляется в список self.new_pixels. Обработка пикселя выполняется путем вычитания значений каждого цветового канала (R, G, B) из 255, что приводит к инверсии цвета. Новые пиксели сохраняются в виде кортежей (R, G, B) в self.new_pixels.

  • После завершения цикла, метод putdata() вызывается для нового изображения self.newimg. Он принимает список self.new_pixels в качестве аргумента и устанавливает пиксели нового изображения.

  • Затем метод save() вызывается для сохранения нового изображения на диск. Он принимает два аргумента: путь и имя файла для сохранения ('Results/Spot_filters/Inversion.jpg') и формат файла ('JPEG').

Grayscale

Grayscale filter
Grayscale filter
def grayscale(self):
    for pixel in self.pixels:
        self.new_pixels.append((int((pixel[0] + pixel[1] + pixel[2]) / 3), 
                                int((pixel[0] + pixel[1] + pixel[2]) / 3), 
                                int((pixel[0] + pixel[1] + pixel[2]) / 3)))
            
    self.newimg.putdata(self.new_pixels)
    self.newimg.save('Results/Spot_filters/Grayscale.jpg', 'JPEG')
  • В цикле for каждый пиксель из self.pixels обрабатывается и добавляется в список self.new_pixels. Обработка пикселя выполняется путем вычисления среднего значения цветовых каналов (R, G, B) и присваивания этого значения каждому цветовому каналу нового пикселя. Таким образом, все цветовые каналы нового пикселя будут иметь одно и то же значение, что создаст оттенок серого.

  • После цикла for, метод putdata() вызывается для нового изображения self.newimg. Он принимает список self.new_pixels в качестве аргумента и устанавливает пиксели нового изображения.

  • Затем метод save() вызывается для сохранения нового изображения на диск. Он принимает два аргумента: путь и имя файла для сохранения ('Results/Spot_filters/Grayscale.jpg') и формат файла ('JPEG').

Black and white

Black and white filter
Black and white filter
def blackwhite(self):
    for pixel in self.pixels:
        if (pixel[0] + pixel[1] + pixel[2]) / 3 > 128:
            self.new_pixels.append((255, 255, 255))
        else:
            self.new_pixels.append((0, 0, 0))

    self.newimg.putdata(self.new_pixels)
    self.newimg.save('Results/Spot_filters/Black_white.jpg', 'JPEG')
  • В цикле for каждый пиксель из self.pixels обрабатывается.

  • Вычисляется среднее значение цветовых каналов (R, G, B) пикселя путем деления суммы значений на 3.

  • Если среднее значение больше 128 (половины максимального значения цветового канала), то пиксель считается более светлым и добавляется в список self.new_pixels с цветом (255, 255, 255) (белый цвет).

  • Если среднее значение меньше или равно 128, то пиксель считается более темным и добавляется в список self.new_pixels с цветом (0, 0, 0) (черный цвет).

  • После завершения цикла, метод putdata() вызывается для нового изображения self.newimg. Он принимает список self.new_pixels в качестве аргумента и устанавливает пиксели нового изображения.

  • Затем метод save() вызывается для сохранения нового изображения на диск. Он принимает два аргумента: путь и имя файла для сохранения ('Results/Spot_filters/Black_white.jpg') и формат файла ('JPEG').

Если кратко, то мы преобразовываем изображение в черно-белое (бинарное) изображение, где пиксели со средним значением яркости больше 128 становятся белыми, а пиксели со значением меньше или равным 128 становятся черными.

Sepia

Sepia filter
Sepia filter
def sepia(self):
    for pixel in self.pixels:
        self.new_pixels.append((int((pixel[0] * 0.393) + (pixel[1] * 0.769) + (pixel[2] * 0.189)),
                                int((pixel[0] * 0.349) + (pixel[1] * 0.686) + (pixel[2] * 0.168)),
                                int((pixel[0] * 0.272) + (pixel[1] * 0.534) + (pixel[2] * 0.131))))
            
    self.newimg.putdata(self.new_pixels)
    self.newimg.save('Results/Spot_filters/Sepia.jpg', 'JPEG')
  • В цикле for каждый пиксель из self.pixels обрабатывается.

  • Для каждого пикселя вычисляются новые значения цветовых каналов (R, G, B) с помощью заданных коэффициентов для каждого канала. Коэффициенты используются для создания эффекта сепии. Вычисленные значения округляются до целых чисел с помощью int().

  • Вычисленные значения цветовых каналов добавляются в список self.new_pixels.

  • После завершения цикла, метод putdata() вызывается для нового изображения self.newimg. Он принимает список self.new_pixels в качестве аргумента и устанавливает пиксели нового изображения.

  • Затем метод save() вызывается для сохранения нового изображения на диск. Он принимает два аргумента: путь и имя файла для сохранения ('Results/Spot_filters/Sepia.jpg') и формат файла ('JPEG').

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

В данном коде используются следующие значения коэффициентов:

  • Для красного канала (R): 0.393, 0.349, 0.272.

  • Для зеленого канала (G): 0.769, 0.686, 0.534.

  • Для синего канала (B): 0.189, 0.168, 0.131.

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

Brightness

Brightness filter
Brightness filter
def brightness(self, value=50):
    for pixel in self.pixels:
        self.new_pixels.append((int(pixel[0] + value), 
                                int(pixel[1] + value), 
                                int(pixel[2] + value)))
            
    self.newimg.putdata(self.new_pixels)
    self.newimg.save('Results/Spot_filters/Brightness.jpg', 'JPEG')
  • Метод brightness() принимает необязательный аргумент value, который по умолчанию равен 50. Значение value указывает на величину изменения яркости изображения.

  • В цикле for каждый пиксель из self.pixels обрабатывается.

  • Для каждого цветового канала (R, G, B) каждого пикселя производится сдвиг на значение value. Это осуществляется путем прибавления значения value к текущему значению цветового канала.

  • Вычисленные значения цветовых каналов добавляются в список self.new_pixels.

  • После завершения цикла, метод putdata() вызывается для нового изображения self.newimg. Он принимает список self.new_pixels в качестве аргумента и устанавливает пиксели нового изображения.

  • Затем метод save() вызывается для сохранения нового изображения на диск. Он принимает два аргумента: путь и имя файла для сохранения ('Results/Spot_filters/Brightness.jpg') и формат файла ('JPEG').

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

Сontrast

Сontrast filter
Сontrast filter
def contrast(self, value=2):
    for pixel in self.pixels:
        self.new_pixels.append((int(pixel[0] * value), 
                                int(pixel[1] * value), 
                                int(pixel[2] * value)))
            
    self.newimg.putdata(self.new_pixels)
    self.newimg.save('Results/Spot_filters/Contrast.jpg', 'JPEG')
  • Метод contrast() принимает необязательный аргумент value, который по умолчанию равен 2. Значение value указывает на множитель изменения контрастности изображения.

  • В цикле for каждый пиксель из self.pixels обрабатывается.

  • Для каждого цветового канала (R, G, B) каждого пикселя производится умножение на значение value. Это осуществляется путем умножения текущего значения цветового канала на value.

  • Вычисленные значения цветовых каналов добавляются в список self.new_pixels.

  • После завершения цикла, метод putdata() вызывается для нового изображения self.newimg. Он принимает список self.new_pixels в качестве аргумента и устанавливает пиксели нового изображения.

  • Затем метод save() вызывается для сохранения нового изображения на диск. Он принимает два аргумента: путь и имя файла для сохранения ('Results/Spot_filters/Contrast.jpg') и формат файла ('JPEG').

Изменения контрастности изображения достигаются путем масштабирования значений цветовых каналов каждого пикселя на указанное значение value. Значение value определяет множитель контрастности, где значение больше 1 увеличивает контрастность, а значение меньше 1 уменьшает контрастность.

Gamma

Gamma filter
Gamma filter
 def gamma(self, value=2):
    for pixel in self.pixels:
        self.new_pixels.append((int(pixel[0] ** value), 
                                int(pixel[1] ** value), 
                                int(pixel[2] ** value)))
            
    self.newimg.putdata(self.new_pixels)
    self.newimg.save('Results/Spot_filters/Gamma.jpg', 'JPEG')
  • Метод gamma() принимает необязательный аргумент value, который по умолчанию равен 2. Значение value указывает на значение гаммы для коррекции изображения.

  • В цикле for каждый пиксель из self.pixels обрабатывается.

  • Для каждого цветового канала (R, G, B) каждого пикселя производится возведение в степень value. Это осуществляется путем возведения текущего значения цветового канала в степень value.

  • Вычисленные значения цветовых каналов добавляются в список self.new_pixels.

  • После завершения цикла, метод putdata() вызывается для нового изображения self.newimg. Он принимает список self.new_pixels в качестве аргумента и устанавливает пиксели нового изображения.

  • Затем метод save() вызывается для сохранения нового изображения на диск. Он принимает два аргумента: путь и имя файла для сохранения ('Results/Spot_filters/Gamma.jpg') и формат файла ('JPEG').

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

Blackout

Blackout filter- Метод blackout() определен в классе SpotFilters. Он принимает необязательный аргумент value, который по умолчанию равен 5. Значение value указывает на величину затемнения изображения.
Blackout filter
  • Метод blackout() принимает необязательный аргумент value, который по умолчанию равен 5. Значение value указывает на величину затемнения изображения.

  • В цикле for каждый пиксель из self.pixels обрабатывается.

  • Для каждого цветового канала (R, G, B) каждого пикселя производится деление на значение value. Это осуществляется путем деления текущего значения цветового канала на value.

  • Вычисленные значения цветовых каналов добавляются в список self.new_pixels.

  • После завершения цикла, метод putdata() вызывается для нового изображения self.newimg. Он принимает список self.new_pixels в качестве аргумента и устанавливает пиксели нового изображения.

  • Затем метод save() вызывается для сохранения нового изображения на диск. Он принимает два аргумента: путь и имя файла для сохранения ('Results/Spot_filters/Blackout.jpg') и формат файла ('JPEG').

Реализовываем фильтр затемнения изображения путем уменьшения яркости каждого пикселя на указанную величину value. Чем больше значение value, тем сильнее будет затемнение изображения.

Red

Red filter
Red filter
def green(self):
    for pixel in self.pixels:
        self.new_pixels.append((0, pixel[1], 0))
            
    self.newimg.putdata(self.new_pixels)
    self.newimg.save('Results/Spot_filters/Green.jpg', 'JPEG')
  • В цикле for каждый пиксель из self.pixels обрабатывается.

  • Для каждого пикселя создается новый пиксель с составляющей красного цвета pixel[0], а остальные составляющие устанавливаются в ноль (R=pixel[0], G=0, B=0). Таким образом, все пиксели получают красный оттенок, где значение красного цвета остается неизменным, а остальные цветовые каналы устанавливаются в ноль.

  • Сформированный новый пиксель добавляется в список self.new_pixels.

  • После завершения цикла, метод putdata() вызывается для нового изображения self.newimg. Он принимает список self.new_pixels в качестве аргумента и устанавливает пиксели нового изображения.

  • Затем метод save() вызывается для сохранения нового изображения на диск. Он принимает два аргумента: путь и имя файла для сохранения ('Results/Spot_filters/Red.jpg') и формат файла ('JPEG').

В фильтре красного оттенка изображения все пиксели получают красный цвет, а остальные цветовые каналы устанавливаются в ноль. В зелёном и синем фильтрах используется тот же способ, только меняются значения (R=0, G=pixel[1], B=0) и (R=0, G=0, B=pixel[2]).

Green

Green filter
Green filter
def green(self):
    for pixel in self.pixels:
        self.new_pixels.append((0, pixel[1], 0))
            
    self.newimg.putdata(self.new_pixels)
    self.newimg.save('Results/Spot_filters/Green.jpg', 'JPEG')

Blue

Blue filter
Blue filter
def blue(self):
    for pixel in self.pixels:
        self.new_pixels.append((0, 0, pixel[2]))
            
    self.newimg.putdata(self.new_pixels)
    self.newimg.save('Results/Spot_filters/Blue.jpg', 'JPEG')

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

Ссылочки: GitHub, Telegram

Комментарии (3)


  1. Videoman
    16.05.2023 08:49
    +3

    Вроде фильтры совсем базовые и простые но есть одно замечание и один совет.
    Замечание - gamma у вас работает неправильно. В нем граничные значения динамического диапазона [0, 255] (или [0.0, 1.0], если удобно) должны переходить сами в себя. Поэтому лучше сначала отнормировать значение яркости к [0.0, 1.0], а потом уже возводит в степень, затем обратно.
    Совет - так как фильтры точечные и каждый канал обрабатывается независимо, лучше считать их в таблице для всех значений динамического диапазона, [0, 255] например, и уже потом брать оттуда. Во-первых, это сделает скорость любого фильтра независимой от размера изображения и от сложности самого фильтра. Во-вторых, можно применять сразу несколько фильтров к таблице, а потом обсчитывать уже каналы изображения по ней. Это еще увеличит скорость обработки.


    1. kath_ml Автор
      16.05.2023 08:49
      +2

      Хорошо, спасибо за совет, учту!
      Я только начала этим заниматься, т.к. была лаб. работа в университете на это тему, решила осветить пока работу точечных фильтров (самое простое и базовое), а так ещё есть фильтры , основанные на матрицах, и мат. морфология, что работают чуть посложнее :)


  1. imageman
    16.05.2023 08:49
    -1

    А что же вы не сказали про скорость обработки? Про требования к памяти? Возьмите картинку с мобильного телефона (сколько там сейчас мегапикселей? 12-16?) замерьте скорость обработки и сколько памяти откушало, дополните статью.

    Выбранный вами подход будет крайне не оптимальный. Мало того что вы активно используете питоновские циклы (питон довольно медленный), так вы еще всё складываете в list. Может стоит посмотреть в сторону NumPy?