Это статья рассчитана на новичков, которые только начинают осваивать методы обработки изображений. Сама я часто сталкиваюсь с отсутствием легких примеров, особенно на русском языке, поэтому надеюсь данный материал окажется полезным.
Как-то встала передо мной следующая задача. У меня было много фотографий болгарских перцев и необходимо было отделить растение от фона. На примере этой задачи я покажу один из самых примитивных способов как это можно сделать при помощи openCV 2.4.
Суть задачи: закрасить белым все что не является растением.
Исходная фотография (слева) и то что должно получиться (справа).
Для начала загрузим изображение:
Mat src = imread("1.jpg"); //Исходное изображение
По умолчанию в opencv цветное изображение хранится палитре BGR. Определять цвет в BGR не очень удобно, поэтому для начала переведем изображение в формат HSV.
HSV (или HSB) означает Hue, Saturation, Value (Brightness), где:
Hue — цветовой тон, т.е. оттенок цвета.Saturation — насыщенность. Чем выше этот параметр, тем «чище» будет цвет, а чем ниже, тем ближе он будет к серому.
Value (Brightness) — значение (яркость) цвета. Чем выше значение, тем ярче будет цвет (но не белее). А чем ниже, тем темнее (0% — черный)
Так как искать растение мы будем именно по цвету, то больше всего нас интересует именно тон.
Преобразуем изображение в палитру 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);
Слева маска, справа результат применения маски.
Вот таким способом можно примитивно выделить объект из фона.
Автор: greenroach