Как рассчитать среднее значение окружающих пикселей пикселя?

Я пытаюсь создать средний фильтр 5x5, чтобы удалить из изображения немного шума соли и перца. Я прочитал изображение в массиве numpy. И попытался внести некоторые изменения, чтобы вычислить среднее значение соседей пикселя. Результат, который я получил, довольно плохой, и я не могу понять, почему в моем изображении есть пробелы.

from PIL import Image
import numpy

image1 = 'noisy.jpg'
save1 = 'filtered.jpg'

def average(path, name):
    temp=Image.open(path)
    image_array = numpy.array(temp)
    new_image = []
    for i in range(0, len(image_array)):
        new_image.append([])
    n = 0
    average_sum = 0
    for i in range(0, len(image_array)):
        for j in range(0, len(image_array[i])):
            for k in range(-2, 3):
                for l in range(-2, 3):
                    if (len(image_array) > (i + k) >= 0) and (len(image_array[i]) > (j + l) >= 0):
                        average_sum += image_array[i+k][j+l]
                        n += 1

            new_image[i].append(int(round(average_sum/n)))
            average_sum = 0
            n = 0

    x = Image.fromarray(numpy.array(new_image), 'L')
    x.save(name)
    print("done")

average(image1, save1)

---------------------Введите изображение-----------------

Входное изображение

---------------------Выходное изображение-----------------

введите здесь описание изображения


person Dikshant Adhikari    schedule 27.02.2018    source источник


Ответы (2)


Вместо создания списка для нового изображения просто сделайте копию исходного изображения (или создайте массив того же размера, что и у исходного изображения) и измените его значение на новые усредненные значения пикселей. Как это

def average(path, name):
    temp=Image.open(path)
    image_array = numpy.array(temp)
    new_image = image_array.copy()
    n = 0
    average_sum = 0
    for i in range(0, len(image_array)):
        for j in range(0, len(image_array[i])):
            for k in range(-2, 3):
                for l in range(-2, 3):
                    if (len(image_array) > (i + k) >= 0) and (len(image_array[i]) > (j + l) >= 0):
                        average_sum += image_array[i+k][j+l]
                        n += 1

            new_image[i][j] = (int(round(average_sum/n)))
            average_sum = 0
            n = 0

    x = Image.fromarray(numpy.array(new_image), 'L')
    x.save(name)
    print("done")

Это дало мне следующий результат:

Вывод

person janu777    schedule 27.02.2018
comment
Чрезвычайно медленная упаковка и распаковка данных numpy (перевод в/из переменных python и собственной памяти), все эти циклы с попиксельным чтением являются смертью в numpy и злоупотреблением numpy. Ваш код чрезвычайно медленный. Numpy НЕ предназначен для хранения случайной ручной математики. Numpy предназначен для внутренней математики с использованием собственных функций C++. Что касается приведенного выше примера: вместо этого просто используйте OpenCV с cv.blur() для сверхвысокой производительности в 1 строке кода. И OpenCV также имеет много других фильтров, таких как размытие по Гауссу, медианное размытие, пользовательские ядра и т. д. - person Mitch McMabers; 05.10.2019
comment
Вопрос был не в том, как быстрее реализовать алгоритм или какие еще библиотеки можно использовать. В коде была ошибка, и я ее исправил (пожалуйста, прочитайте вопрос). Если вы новичок и пытаетесь реализовать алгоритм, можно сначала придумать алгоритм грубой силы, а затем повысить его эффективность. - person janu777; 05.10.2019

Я просто хочу предупредить всех, кто найдет эту страницу. По сути, никто никогда не должен делать somenumpyarray[y,x] для прямого доступа к значениям пикселей по одному. Каждый раз, когда вы вводите что-то подобное, Numpy должен создавать 4 новых объекта Python (объект tuple, содержащий значения RGB, и три отдельных объекта int для каждого значения R/G/B). Это связано с тем, что в Python все является объектом (даже числа являются объектами), а это означает, что данные нельзя «просто прочитать непосредственно из Numpy». Вместо этого должны быть созданы фактические объекты Python, а данные Numpy (такие как числа) копируются в эти объекты каждый раз, когда вы пытаетесь прочитать что-нибудь из Numpy в Python.

Это 4 создания объекта Python на пиксель, который вы пытаетесь прочитать из массива. Для изображения 1080p, если вы ТОЛЬКО читаете каждый пиксель ОДИН РАЗ, это 8 294 400 объектов. Но приведенный выше код проверяет матрицу 5x5 (25 пикселей) вокруг каждого пикселя, так что получается 207 360 000 объектов! Безумие!

Это создание объекта известно как упаковка (взятие собственных данных Numpy и их упаковка/упаковка в структуру данных объекта Python). А также распаковка (сбор данных Python, извлечение фактического значения, которое они содержат (например, число), и упаковка их в собственный массив Numpy). Чтение/запись значений в массиве Numpy из Python всегда включает в себя упаковку и распаковку, поэтому это чрезвычайно медленно, и вместо этого вы должны всегда использовать собственные методы Numpy для работы с вашими данными. . Numpy НЕ является «массивом» общего назначения, который мы должны рассматривать как любой список Python с произвольным доступом. Numpy предназначен для векторных/матричных операций с использованием собственных встроенных функций! Фактически, вы никогда не должны даже делать for X in some_ndarray, потому что итерация вызывает такой же медленный процесс упаковки (каждый элемент X в таком цикле извлекается из Numpy и упаковывается).

В любом случае... то, что вы пытаетесь реализовать, представляет собой «размытие коробки» 5x5, что означает среднее значение всех соседних пикселей в пределах квадратного радиуса 5x5.

Поэтому вам следует использовать нативную библиотеку C++, которая выполняет все это в чистой, чистой оперативной памяти, вообще не задействуя Python. Одной из таких библиотек является OpenCV, которая принимает ndarray (пиксели вашего изображения) и внутренне считывает непосредственно из ОЗУ, принадлежащего ndarray, и изначально работает непосредственно с каждым пикселем.

Вот код:

import cv2

path = "noisy.jpg"
img = cv2.imread(path)
img = cv2.blur(img, (5,5)) # This is now your box-blurred image.

Тесты на изображении 1920x1080:

  • Ваш исходный код (который до смерти злоупотребляет массивом Numpy и создает более 200 миллионов новых объектов Python): 87,80000 секунд (и это даже не считая злоупотребления ОЗУ, которое происходит при создании более 200 миллионов объектов)
  • Вместо этого используется OpenCV: 0,02992 секунды (в 2935 раз быстрее)

Никогда, никогда не обращайтесь к элементам массива Numpy напрямую. Он не для этого предназначен.

Удачи всем будущим читателям.


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

Ввод:

вход

Размытие прямоугольника 5x5 (также известное как среднее/среднее размытие):

img = cv2.blur(img, (5,5))

размытие

Среднее размытие 3 x 3:

img = cv2.medianBlur(img, 3)

медиана

person Mitch McMabers    schedule 05.10.2019