Подтвердить что ты не робот

Удалите ложные небольшие острова шума в изображении - Python OpenCV

Я пытаюсь избавиться от фонового шума от некоторых моих изображений. Это нефильтрованное изображение.

Чтобы фильтровать, я использовал этот код для создания маски того, что должно оставаться на изображении:

 element = cv2.getStructuringElement(cv2.MORPH_RECT, (2,2))
 mask = cv2.erode(mask, element, iterations = 1)
 mask = cv2.dilate(mask, element, iterations = 1)
 mask = cv2.erode(mask, element)

С помощью этого кода, и когда я маскирую нежелательные пиксели из исходного изображения, я получаю:

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

Есть ли способ, в котором я могу применить фильтр, который находится между этими двумя крайностями?

Кроме того, может ли кто-нибудь объяснить мне, что именно делает getStructuringElement()? Что такое "структурирующий элемент"? Что он делает и как его размер (второй параметр) влияет на уровень фильтрации?

4b9b3361

Ответ 1

Многие из ваших вопросов связаны с тем, что вы не знаете, как работает морфологическая обработка изображений, но мы можем поместить ваши сомнения в покой. Вы можете интерпретировать структурирующий элемент как "базовую форму" для сравнения. 1 в элементе структурирования соответствует пикселю, который вы хотите посмотреть в этой форме, и 0 - это тот, который вы хотите игнорировать. Существуют разные формы, такие как прямоугольные (как вы выяснили с помощью MORPH_RECT), эллипс, круглый и т.д.

Таким образом, cv2.getStructuringElement возвращает для вас структурирующий элемент. Первый параметр указывает тип, который вы хотите, а второй параметр указывает размер, который вы хотите. В вашем случае вам нужен 2 x 2 "прямоугольник"... который действительно является квадратом, но это прекрасно.

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

Эрозия

Для эрозии вы исследуете все пиксели в окрестности пикселя, которые касаются элемента структурирования. Если каждый ненулевой пиксель касается пикселя элемента структурирующего элемента, равного 1, то выходной пиксель в соответствующем центральном положении относительно входа равен 1. Если есть хотя бы один ненулевой пиксель что не касается пикселя структурирования, равного 1, тогда выход равен 0.

В терминах прямоугольного элемента структурирования вам нужно убедиться, что каждый пиксель в элементе структурирования касается ненулевого пикселя вашего изображения для окрестности пикселя. Если это не так, то выход равен 0, иначе 1. Это эффективно устраняет небольшие ложные области шума, а также уменьшает площадь объектов.

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

Растяжение

Дилатация противоположна эрозии. Если есть хотя бы один ненулевой пиксель, который касается пикселя в элементе структурирования, который равен 1, тогда выход равен 1, иначе выход равен 0. Вы можете думать об этом как о незначительном увеличении областей объектов и о том, что маленькие острова больше.

Последствия с размером здесь состоят в том, что чем больше элемент структурирования, тем больше будут области объектов и чем больше изолированы острова.


То, что вы делаете, это эрозия, а затем дилатация. Это то, что известно как операция открытия. Целью этой операции является удаление небольших островков шума, в то время как (пытается) поддерживать области более крупных объектов на вашем изображении. Эрозия удаляет эти острова, в то время как дилатация увеличивает назад более крупные объекты до их первоначальных размеров.

По какой-то причине вы следуете за этим с эрозией, что я не могу понять, но это нормально.


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

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

Вместо связывания эрозии с последующей дилатацией или дилатации, сопровождаемой эрозией, используйте cv2.morphologyEx, где вы можете указать MORPH_OPEN и MORPH_CLOSE в качестве флагов.

Таким образом, я лично сделал бы это, предполагая, что ваше изображение называется spots.png:

import cv2
import numpy as np

img = cv2.imread('spots.png')
img_bw = 255*(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) > 5).astype('uint8')

se1 = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
se2 = cv2.getStructuringElement(cv2.MORPH_RECT, (2,2))
mask = cv2.morphologyEx(img_bw, cv2.MORPH_CLOSE, se1)
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, se2)

mask = np.dstack([mask, mask, mask]) / 255
out = img * mask

cv2.imshow('Output', out)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.imwrite('output.png', out)

Вышеприведенный код довольно понятен. Сначала я читаю изображение, а затем конвертирую изображение в оттенки серого и порог с интенсивностью 5, чтобы создать маску того, что считается объектным пикселем. Это довольно чистый образ, и, похоже, что-то большее, чем 5, сработало. Для подпрограмм морфологии мне нужно преобразовать изображение в uint8 и масштабировать маску до 255. Затем мы создаем два элемента структурирования - один из которых представляет собой прямоугольник 5 × 5 для операции закрытия, а другой - 2 x 2 для операция открытия. Я запускаю cv2.morphologyEx дважды для операций открытия и закрытия соответственно на пороговом изображении.

Как только я это сделаю, я складываю маску так, чтобы она превратилась в трехмерную матрицу и разделилась на 255, чтобы она стала маской [0,1], а затем мы умножаем эту маску на исходное изображение, чтобы мы могли захватить оригинал пиксели изображения и поддержание того, что считается истинным объектом из вывода маски.

Остальное просто для иллюстрации. Я показываю изображение в окне, и я также сохраняю изображение в файл под названием output.png, и его цель - показать вам, как выглядит изображение в этом сообщении.

Я получаю это:

enter image description here

Имейте в виду, что это не идеально, но это намного лучше, чем у вас раньше. Вам придется поиграть с размерами структурирующих элементов, чтобы получить то, что вы считаете хорошим выходом, но этого, безусловно, достаточно, чтобы вы начали. Удачи!


Версия на С++

Были некоторые запросы на перевод кода, который я написал выше, в версию на С++ с использованием OpenCV. Я, наконец, начал писать С++-версию кода, и это было проверено на OpenCV 3.1.0. Код для этого ниже. Как вы можете видеть, код очень похож на код, представленный в версии Python. Тем не менее, я использовал cv::Mat::setTo на копии исходного изображения и установил все, что не было частью окончательной маски, равным 0. Это то же самое как выполнение умножения по элементам в Python.

#include <opencv2/opencv.hpp>

using namespace cv;

int main(int argc, char *argv[])
{
    // Read in the image
    Mat img = imread("spots.png", CV_LOAD_IMAGE_COLOR);

    // Convert to black and white
    Mat img_bw;
    cvtColor(img, img_bw, COLOR_BGR2GRAY);
    img_bw = img_bw > 5;

    // Define the structuring elements
    Mat se1 = getStructuringElement(MORPH_RECT, Size(5, 5));
    Mat se2 = getStructuringElement(MORPH_RECT, Size(2, 2));

    // Perform closing then opening
    Mat mask;
    morphologyEx(img_bw, mask, MORPH_CLOSE, se1);
    morphologyEx(mask, mask, MORPH_OPEN, se2);

    // Filter the output
    Mat out = img.clone();
    out.setTo(Scalar(0), mask == 0);

    // Show image and save
    namedWindow("Output", WINDOW_NORMAL);
    imshow("Output", out);
    waitKey(0);
    destroyWindow("Output");
    imwrite("output.png", out);
}

Результаты должны быть такими же, как и в версии Python.

Ответ 2

Можно также удалить небольшие кластеры пикселов, используя remove_small_objects функцию в skimage:

import matplotlib.pyplot as plt
from skimage import morphology
import numpy as np
import skimage

# read the image, grayscale it, binarize it, then remove small pixel clusters
im = plt.imread('spots.png')
grayscale = skimage.color.rgb2gray(im)
binarized = np.where(grayscale>0.1, 1, 0)
processed = morphology.remove_small_objects(binarized.astype(bool), min_size=2, connectivity=2).astype(int)

# black out pixels
mask_x, mask_y = np.where(processed == 0)
im[mask_x, mask_y, :3] = 0

# plot the result
plt.figure(figsize=(10,10))
plt.imshow(im)

Это отображает:

enter image description here

Чтобы сохранить только более крупные кластеры, попробуйте увеличить min_size (наименьший размер min_size кластеров) и уменьшить connectivity (размер окрестности пикселя при формировании кластеров). Используя только эти два параметра, можно сохранить только кластеры пикселей соответствующего размера.