Оглавление Этот урок покажет вам, как сегментировать несколько соединенных или перекрывающихся объектов, а также подсчитать их количество. Мы будем использовать алгоритм Watershed и функцию Distance transform.

Сегментация изображения в OpenCV с помощью алгоритмов Watershed и Distance Transform

OpenCV/Обработка и распознавание изображений

Сегментация изображения в OpenCV с помощью алгоритмов Watershed и Distance Transform

Оглавление

Этот урок покажет вам, как сегментировать несколько соединенных или перекрывающихся объектов, а также подсчитать их количество. Мы будем использовать алгоритм Watershed и функцию Distance transform.

Подсчитать простые и несвязанные объекты относительно легко. Можно получить их контуры из бинарного изображения. Но для соединенных или перекрывающихся объектов это сделать довольно трудно.

Рассмотрим пересекающиеся игральные карты, как показано на рисунке 1а. Изображение полученное в результате порогового преобразования 1b, в результате мы получаем один контур а нам нужно два. Чтобы получить правильный результат, мы должны использовать другой алгоритм обработки изображения, чтобы сегментировать две карты. Дальше мы познакомимся с алгоритмами Watershed и Distance transform.

Изображение 1. (a) Входное изображение (b) Бинарное изображение полученное из (a).

Алгоритм Watershed

Алгоритм Watershed обработки изображений предназначен для отделения объектов на изображении от фона. Алгоритм принимает изображение в градациях серого и изображения маркера. Маркеры являются изображением, где вы указываете алгоритму Watershed объекты переднего плана и фон. Смотрите рисунок ниже.

Рисунок 2а входное изображение с маркерами, нарисованными вручную, чтобы отделить дорогу от остальной сцены. Учитывая входное изображение и маркер, алгоритм Watershed выполняет свою магию сегментации выделяя дорогу на рис 2b.

Изображение 2. (a) Входное изображение с маркерами (b) Сегментированная дорога с помощью алгоритма Watershed.

OpenCV функция алгоритма Watershed выглядит следующим образом:

cv::watershed(
    InputArray image,         // Input 8 bit 3-channel image
    InputOutputArray markers, // Input/output 32-bit single-channel image _
                              // of markers. it should have the same size _
                              // as the input
)

Distance transform

Distance transform представляет собой бинарное изображение, где значение каждого пикселя заменяется его расстоянием до ближайшего пикселя фона. Смотрите пример ниже.

Изображение 3. (a) Входное изображение (b) Результат алгоритма Distance transform изображения (a).

В OpenCV функция алгоритма Distance transform выглядит следующим образом:

cv::distanceTransform(
    InputArray src,   // 8-bit single-channel image
    OutputArray dst,  // 32-bit floating point single channel image
    int distanceType, // One of: CV_DIST_L1, CV_DIST_L2, CV_DIST_L3
    int maskSize      // Size of the distance transform mask
)

Этот алгоритм является очень полезным для автоматической генерации маркерного изображения для алгоритма Watershed, описанного выше.

Пишем код

Давайте попробуем посчитать количество монет на изображении ниже.

Загружаем изображение и преобразуем в бинарный вид.

cv::Mat src = cv::imread("coins.jpg");
if (!src.data)
    return -1;
// Create binary image from source image
cv::Mat bw;
cv::cvtColor(src, bw, CV_BGR2GRAY);
cv::threshold(bw, bw, 40, 255, CV_THRESH_BINARY);

Выполняем алгоритм Distance transform и нормализуем результат [0,1].

cv::Mat dist;
cv::distanceTransform(bw, dist, CV_DIST_L2, 3);
cv::normalize(dist, dist, 0, 1., cv::NORM_MINMAX);

Выполняем пороговое преобразование для получения пиков. Это будут маркеры для объектов переднего плана.

cv::threshold(dist, dist, .5, 1., CV_THRESH_BINARY);

Ищем контуры.

// Create the CV_8U version of the distance image
// It is needed for cv::findContours()
cv::Mat dist_8u;
dist.convertTo(dist_8u, CV_8U);
// Find total markers
std::vector<std::vector<cv::Point> > contours;
cv::findContours(dist_8u, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE)

И мы сразу можем получить число объектов.

// Total objects
int ncomp = contours.size();

Далее мы будем сегментировать объекты переднего плана. Создадим маркерное изображение для алгоритма Watershed.

cv::Mat markers = cv::Mat::zeros(dist.size(), CV_32SC1);
for (int i = 0; i < ncomp; i++)
    cv::drawContours(markers, contours, i, cv::Scalar::all(i+1), -1);

Мы также должны нарисовать фоновый маркер. Если предположить, что объекты, расположены в центре изображения, модно просто нарисовать маркер у края.

cv::circle(markers, cv::Point(5,5), 3, CV_RGB(255,255,255), -1);

Выполняем алгоритм Watershed.

cv::watershed(src, markers);

Записываем результат в финальное изображение.

// Generate random colors
std::vector<cv::Vec3b> colors;
for (int i = 0; i < ncomp; i++)
{
    int b = cv::theRNG().uniform(0, 255);
    int g = cv::theRNG().uniform(0, 255);
    int r = cv::theRNG().uniform(0, 255);
    colors.push_back(cv::Vec3b((uchar)b, (uchar)g, (uchar)r));
}
// Create the result image
cv::Mat dst = cv::Mat::zeros(markers.size(), CV_8UC3);
// Fill labeled objects with random colors
for (int i = 0; i < markers.rows; i++)
{
    for (int j = 0; j < markers.cols; j++)
    {
        int index = markers.at<int>(i,j);
        if (index > 0 && index <= ncomp)
            dst.at<cv::Vec3b>(i,j) = colors[index-1];
        else
            dst.at<cv::Vec3b>(i,j) = cv::Vec3b(0,0,0);
    }
}
cv::imshow("dst", dst);

Полный исходный код доступен на Github.

Видео процесса

Оглавление

19:09
30670
RSS
Гость
13:29
Здравствуйте. Не могли бы вы подсказать как переписать данный код на openCvSharp? Путаница получается с переделкой аргументов многих функций

Авторизация

Войдите, используя Ваш аккаунт

Войти с помощью

Пользователи

Skyeng
GeekBrains
Lingualeo