Заполните отверстия в OpenCV

32

У меня есть карта ребер, извлеченная из модуля обнаружения границ в OpenCV (обнаружение canny edge). То, что я хочу сделать, это заполнить отверстия в карте края.

Я использую библиотеки С++ и OpenCV. В OpenCV есть функция cvFloodFill(), и она заполнит отверстия семенем (одним из мест для начала наводнения). Тем не менее, я пытаюсь заполнить все внутренние отверстия, не зная семена. (Аналогично imfill() в MATLAB)

Q1: как найти все семена, чтобы я мог применять 'cvFloodFill()'? Q2: как реализовать эквивалент 'imfill()'?

Новичок в OpenCV, и любой намек оценивается.

Теги:
opencv
image-processing
flood-fill

9 ответов

34
Лучший ответ

В соответствии с документацией imfill в MATLAB:

BW2 = imfill(BW,'holes');

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

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

Пример MATLAB:

BW = im2bw( imread('coins.png') );
subplot(121), imshow(BW)

% used here as if it was cvFloodFill
holes = imfill(BW, [1 1]);    % [1 1] is the starting location point

BW(~holes) = 1;               % fill holes
subplot(122), imshow(BW)

Изображение 6364 Изображение 6365

  • 0
    это круто, амро! Спасибо!!!
  • 0
    Очень хорошо. Я пытался понять, как это сделать, не полагаясь на флаг holes в imfill . Я хотел бы сделать это на OpenCV Python, и вы показали мне путь. Спасибо амро!
12

Функция cvDrawContours имеет возможность заполнить контуры, которые вы нарисовали.

Вот краткий пример   cvDrawContours (IplImage, контуры, цвет, цвет, -1, CV_FILLED, 8);

Вот документация

http://opencv.willowgarage.com/documentation/drawing_functions.html?highlight=cvdrawcontours#cvDrawContours

Я думаю, вы опубликовали это давным-давно, но я надеюсь, что это поможет кому-то.

Это исходный код (на С#):

        Image<Gray, byte> image = new Image<Gray, byte>(@"D:\final.bmp");
        CvInvoke.cvShowImage("image 1", image);

        var contours = image.FindContours();
        while (contours != null)
        {
            CvInvoke.cvDrawContours(image, contours, new Gray(255).MCvScalar, new Gray (255).MCvScalar, 0, -1, Emgu.CV.CvEnum.LINE_TYPE.CV_AA, new DPoint(0, 0));
            contours = contours.HNext;
        }
        CvInvoke.cvShowImage("image 2", image);

Изображение 6366

7

Я просматривал интернет, чтобы найти правильную imfill функцию (как в Matlab), но работающую на С++ с OpenCV. После некоторых реасиксов я наконец придумал решение:

IplImage* imfill(IplImage* src)
{
    CvScalar white = CV_RGB( 255, 255, 255 );

    IplImage* dst = cvCreateImage( cvGetSize(src), 8, 3);
    CvMemStorage* storage = cvCreateMemStorage(0);
    CvSeq* contour = 0;

    cvFindContours(src, storage, &contour, sizeof(CvContour), CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE );
    cvZero( dst );

    for( ; contour != 0; contour = contour->h_next )
    {
        cvDrawContours( dst, contour, white, white, 0, CV_FILLED);
    }

    IplImage* bin_imgFilled = cvCreateImage(cvGetSize(src), 8, 1);
    cvInRangeS(dst, white, white, bin_imgFilled);

    return bin_imgFilled;
}

Для этого: Оригинальное двоичное изображение

Результат: Конечное двоичное изображение

Фокус в настройках параметров функции cvDrawContours: cvDrawContours (dst, контур, белый, белый, 0, CV_FILLED);

  • dst = образ назначения
  • contour = указатель на первый контур
  • белый = цвет, используемый для заполнения контура
  • 0 = Максимальный уровень для контуров. Если 0, выводится только контур
  • CV_FILLED = Толщина линий, на которые нарисованы контуры. Если он отрицательный (например, = CV_FILLED), нарисованы контурные интерьеры.

Дополнительная информация в документации openCV.

Вероятно, есть способ получить "dst" непосредственно как двоичное изображение, но я не мог найти, как использовать функцию cvDrawContours с двоичными значениями.

  • 2
    Если быть точным, то здесь используется не API C ++, а старый интерфейс C. Кроме того, это то же самое, что NotNamedDwayne показал в своем ответе
  • 0
    @ Амро: ты прав, я отредактировал свой ответ. Спасибо!
4

Я сделал простую функцию, эквивалентную matlab imfill ('hole'). Я не тестировал его во многих случаях, но он работал до сих пор. Я использую его на изображениях края, но он принимает любое двоичное изображение, например, из операции порога.

Отверстие - это не более чем набор пикселей, которые не могут быть "достигнуты", когда фон заполнен, поэтому

void fillEdgeImage(cv::Mat edgesIn, cv::Mat& filledEdgesOut) const
{
    cv::Mat edgesNeg = edgesIn.clone();

    cv::floodFill(edgesNeg, cv::Point(0,0), CV_RGB(255,255,255));
    bitwise_not(edgesNeg, edgesNeg);
    filledEdgesOut = (edgesNeg | edgesIn);

    return;
}

Вот пример результата

Изображение 6367

4

Здесь быстрый и грязный подход:

  • Выполните на своем входном изображении canny, чтобы новое двоичное изображение имело 1 по краям, а 0 в противном случае
  • Найдите первое 0 вдоль стороны вашего краевого изображения и запустите заливку с 1 в этой точке на пустом изображении, используя ваше краевое изображение в качестве маски. (Мы надеемся, что нам не повезло, и семя это первое заполнение внутри формы, которая находится на половину экрана)
  • Это новое залитое изображение - это "фон". Любой пиксель здесь, который имеет 1, является фоном, и любой пиксель, который имеет 0, является передним планом.
  • Прокрутите изображение и найдите любые пиксели переднего плана. Залейте наводнение на любом, что вы найдете.
  • ИЛИ это новое залитое изображение с изображением Canny с шага 1, и все готово.
3

Просто приложение для Ответ Amro.

void cvFillHoles(cv::Mat &input)
{
    //assume input is uint8 B & W (0 or 1)
    //this function imitates imfill(image,'hole')
    cv::Mat holes=input.clone();
    cv::floodFill(holes,cv::Point2i(0,0),cv::Scalar(1));
    for(int i=0;i<input.rows*input.cols;i++)
    {
        if(holes.data[i]==0)
            input.data[i]=1;
    }
}
  • 3
    вместо зацикливания вы могли бы вычесть из 255, чтобы дополнить двоичное изображение CV_8U, что эквивалентно: hole = 255 - hole
2

Недавно я также нашел решение этой проблемы. Здесь я реализовал идею Amro следующим образом:

#include <iostream>
using namespace std;
#include <cv.h>
#include <cxcore.h>
#include <highgui.h>
using namespace cv;

int main()
{
    IplImage *im = cvLoadImage("coin.png",CV_LOAD_IMAGE_ANYDEPTH);
    IplImage *hole = cvCreateImage(cvSize(im->width,im->height),8,1);
    cvShowImage("Original",im);

    cvCopyImage(im,hole);
    cvFloodFill(hole,cvPoint(0,0),cvScalar(255));
    cvShowImage("Hole",hole);
    cvSaveImage("hole.png",hole);

    cvNot(hole,hole);
    cvAdd(im,hole,im);
    cvShowImage("FillHole",im);
    cvSaveImage("fillHole.png",im);

    cvWaitKey(0);
    system("pause");
    return 0;
} 

Надеюсь, это будет полезно.

  • 0
    Это, к сожалению, использует также C API OpenCV, который скоро будет устаревшим. Не используйте его больше, кроме случаев , когда у вас есть некоторые очень конкретные причины для этого.
2

Вы пробовали ContourFinding над Cannyied Image?

cvFindContours создает своеобразное дерево, в котором внешние счетчики являются родителями для внутренних контуров ( "дыры" ). См. Образец contours.py. Из контуров вы можете извлекать семена

  • 0
    Я пытаюсь именно это, это не работает: FindContours идентифицирует много поддельных контуров, где есть резкий поворот краев, так что вы не можете точно их пометить.
0

Если у вас есть точки из ребер, вы можете использовать fillConvexPoly() или fillPoly() (если поли не выпуклый).

Один из способов получить точки из ребер - сделать findContours() → approxPolyDP().

Ещё вопросы

Сообщество Overcoder
Наверх
Меню