Ответ 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
, и его цель - показать вам, как выглядит изображение в этом сообщении.
Я получаю это:
Имейте в виду, что это не идеально, но это намного лучше, чем у вас раньше. Вам придется поиграть с размерами структурирующих элементов, чтобы получить то, что вы считаете хорошим выходом, но этого, безусловно, достаточно, чтобы вы начали. Удачи!
Версия на С++
Были некоторые запросы на перевод кода, который я написал выше, в версию на С++ с использованием 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.