Pull to refresh

Цветовая сегментация для чайников

Reading time 3 min
Views 35K
Это статья рассчитана на новичков, которые только начинают осваивать методы обработки изображений. Сама я часто сталкиваюсь с отсутствием легких примеров, особенно на русском языке, поэтому надеюсь данный материал окажется полезным.

Как-то встала передо мной следующая задача. У меня было много фотографий болгарских перцев и необходимо было отделить растение от фона. На примере этой задачи я покажу один из самых примитивных способов как это можно сделать при помощи openCV 2.4.

Суть задачи: закрасить белым все что не является растением.


Исходная фотография (слева) и то что должно получиться (справа).

Для начала загрузим изображение:

Mat src = imread("1.jpg"); //Исходное изображение

По умолчанию в opencv цветное изображение хранится палитре BGR. Определять цвет в BGR не очень удобно, поэтому для начала переведем изображение в формат HSV.
HSV (или HSB) означает Hue, Saturation, Value (Brightness), где:
Hue — цветовой тон, т.е. оттенок цвета.

Saturation — насыщенность. Чем выше этот параметр, тем «чище» будет цвет, а чем ниже, тем ближе он будет к серому.

Value (Brightness) — значение (яркость) цвета. Чем выше значение, тем ярче будет цвет (но не белее). А чем ниже, тем темнее (0% — черный)

image

Так как искать растение мы будем именно по цвету, то больше всего нас интересует именно тон.

Преобразуем изображение в палитру HSV и разобьем на три составляющие Hue Saturation Value соответственно.

//Переводим в формат HSV
Mat hsv = Mat(src.cols, src.rows, 8, 3); //
vector<Mat> splitedHsv = vector<Mat>();
cvtColor(src, hsv, CV_BGR2HSV);
split(hsv, splitedHsv);

Зададим диапазон значений тона. В OpenCV зеленый находится в диапазоне от 34 до 72. Перец на фотографиях не полностью зеленый. Поэтому опытным путем был подобран диапазон от 21 до 110.

const int GREEN_MIN = 21;
const int GREEN_MAX = 110;

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

for (int y = 0; y < hsv.cols; y++) {
	for (int x = 0; x < hsv.rows; x++) {
		// получаем HSV-компоненты пикселя
		int H = static_cast<int>(splitedHsv[0].at<uchar>(x, y));        // Тон
		int S = static_cast<int>(splitedHsv[1].at<uchar>(x, y));        // Интенсивность
		int V = static_cast<int>(splitedHsv[2].at<uchar>(x, y));        // Яркость
		
		//Если яркость слишком низкая либо Тон не попадает у заданный диапазон, то закрашиваем белым
		if ((V < 20) || (H < GREEN_MIN) || (H > GREEN_MAX)) {
			src.at<Vec3b>(x, y)[0] = 255;
			src.at<Vec3b>(x, y)[1] = 255;
			src.at<Vec3b>(x, y)[2] = 255;
		}
	}
}

В результате у нас получится такое изображение:



В целом фон удалился, но остались непонятные шумы в левом углу.

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

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

Эрозия (морфологическое сужение) – обратная операция. Действие эрозии подобно дилатации, разница лишь в том, что используется оператор поиска локального минимума серым цветом залиты пиксели, которые станут черными в результате эрозии.

Подробнее про это дело можно почитать тут. Применим морфологические операции к нашим картинкам. В качестве структурного элемента возьмем эллипс.

int an = 5;
//Морфологическое замыкание для удаления остаточных шумов.
Mat element = getStructuringElement(MORPH_ELLIPSE, Size(an * 2 + 1, an * 2 + 1), Point(an, an));
dilate(src, tmp, element);
erode(tmp, tmp, element);


Результат морфологической обработки.

Большинство шумов убрались, но и само изображение размылось, а это не совсем то что мы хотели. Поэтому мы будем использовать преобразованное изображение как маску, чтобы удалить ненужный шум.

Mat grayscaleMat;
cvtColor(tmp, grayscaleMat, CV_BGR2GRAY);

//Делаем бинарную маску
Mat mask(grayscaleMat.size(), grayscaleMat.type());
Mat out(src.size(), src.type());
threshold(grayscaleMat, mask, 200, 255, THRESH_BINARY_INV);

//Финальное изображение предварительно красим в белый цвет
out = Scalar::all(255);
//Копируем зашумленное изображение через маску
src.copyTo(out, mask);


Слева маска, справа результат применения маски.

Вот таким способом можно примитивно выделить объект из фона.

Полный код примера

UPD: исправлена битая ссылка.
Tags:
Hubs:
+40
Comments 19
Comments Comments 19

Articles