Обрезать короткие линейные сегменты с выхода детектора края? - PullRequest
8 голосов
/ 22 сентября 2009

Я ищу алгоритм для обрезки коротких отрезков от выхода детектора контуров. Как видно из изображения (и ссылки) ниже, обнаружено несколько небольших ребер, которые не являются «длинными» линиями. В идеале я хотел бы, чтобы после обработки отображались только четыре стороны четырехугольника, но если есть пара случайных линий, это не будет иметь большого значения ... Любые предложения?

Example

Ссылка на изображение

Ответы [ 7 ]

5 голосов
/ 22 сентября 2009

Перед нахождением краев предварительно обработайте изображение с помощью операции open или close (или обоих), то есть erode , за которой следует расширять или расширять , а затем разрушать . это должно удалить меньшие объекты, но оставить крупные примерно одинаковыми.

Я искал онлайн-примеры, и лучшее, что я мог найти, было на странице 41 этого PDF.

4 голосов
/ 20 апреля 2012

В случае, если кто-то вступит в эту цепочку, OpenCV 2.x предоставит пример с именем squares.cpp , который в основном решает эту задачу.

Я внес небольшую модификацию в приложение, чтобы улучшить обнаружение четырехугольника

enter image description here

Код

#include "highgui.h"
#include "cv.h"

#include <iostream>
#include <math.h>
#include <string.h>

using namespace cv;
using namespace std;

void help()
{
        cout <<
        "\nA program using pyramid scaling, Canny, contours, contour simpification and\n"
        "memory storage (it's got it all folks) to find\n"
        "squares in a list of images pic1-6.png\n"
        "Returns sequence of squares detected on the image.\n"
        "the sequence is stored in the specified memory storage\n"
        "Call:\n"
        "./squares\n"
    "Using OpenCV version %s\n" << CV_VERSION << "\n" << endl;
}

int thresh = 70, N = 2; 
const char* wndname = "Square Detection Demonized";

// helper function:
// finds a cosine of angle between vectors
// from pt0->pt1 and from pt0->pt2
double angle( Point pt1, Point pt2, Point pt0 )
{
    double dx1 = pt1.x - pt0.x;
    double dy1 = pt1.y - pt0.y;
    double dx2 = pt2.x - pt0.x;
    double dy2 = pt2.y - pt0.y;
    return (dx1*dx2 + dy1*dy2)/sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10);
}

// returns sequence of squares detected on the image.
// the sequence is stored in the specified memory storage
void findSquares( const Mat& image, vector<vector<Point> >& squares )
{
    squares.clear();

    Mat pyr, timg, gray0(image.size(), CV_8U), gray;

    // karlphillip: dilate the image so this technique can detect the white square,
    Mat out(image);
    dilate(out, out, Mat(), Point(-1,-1));
    // then blur it so that the ocean/sea become one big segment to avoid detecting them as 2 big squares.
    medianBlur(out, out, 3);

    // down-scale and upscale the image to filter out the noise
    pyrDown(out, pyr, Size(out.cols/2, out.rows/2));
    pyrUp(pyr, timg, out.size());
    vector<vector<Point> > contours;

    // find squares only in the first color plane
    for( int c = 0; c < 1; c++ ) // was: c < 3
    {
        int ch[] = {c, 0};
        mixChannels(&timg, 1, &gray0, 1, ch, 1);

        // try several threshold levels
        for( int l = 0; l < N; l++ )
        {
            // hack: use Canny instead of zero threshold level.
            // Canny helps to catch squares with gradient shading
            if( l == 0 )
            {
                // apply Canny. Take the upper threshold from slider
                // and set the lower to 0 (which forces edges merging)
                Canny(gray0, gray, 0, thresh, 5);
                // dilate canny output to remove potential
                // holes between edge segments
                dilate(gray, gray, Mat(), Point(-1,-1));
            }
            else
            {
                // apply threshold if l!=0:
                //     tgray(x,y) = gray(x,y) < (l+1)*255/N ? 255 : 0
                gray = gray0 >= (l+1)*255/N;
            }

            // find contours and store them all as a list
            findContours(gray, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);

            vector<Point> approx;

            // test each contour
            for( size_t i = 0; i < contours.size(); i++ )
            {
                // approximate contour with accuracy proportional
                // to the contour perimeter
                approxPolyDP(Mat(contours[i]), approx, arcLength(Mat(contours[i]), true)*0.02, true);

                // square contours should have 4 vertices after approximation
                // relatively large area (to filter out noisy contours)
                // and be convex.
                // Note: absolute value of an area is used because
                // area may be positive or negative - in accordance with the
                // contour orientation
                if( approx.size() == 4 &&
                    fabs(contourArea(Mat(approx))) > 1000 &&
                    isContourConvex(Mat(approx)) )
                {
                    double maxCosine = 0;

                    for( int j = 2; j < 5; j++ )
                    {
                        // find the maximum cosine of the angle between joint edges
                        double cosine = fabs(angle(approx[j%4], approx[j-2], approx[j-1]));
                        maxCosine = MAX(maxCosine, cosine);
                    }

                    // if cosines of all angles are small
                    // (all angles are ~90 degree) then write quandrange
                    // vertices to resultant sequence
                    if( maxCosine < 0.3 )
                        squares.push_back(approx);
                }
            }
        }
    }
}


// the function draws all the squares in the image
void drawSquares( Mat& image, const vector<vector<Point> >& squares )
{
    for( size_t i = 1; i < squares.size(); i++ )
    {
        const Point* p = &squares[i][0];
        int n = (int)squares[i].size();
        polylines(image, &p, &n, 1, true, Scalar(0,255,0), 3, CV_AA);
    }

    imshow(wndname, image);
}


int main(int argc, char** argv)
{
    if (argc < 2)
    {
        cout << "Usage: ./program <file>" << endl;
        return -1;
    }

    static const char* names[] = { argv[1], 0 };

    help();
    namedWindow( wndname, 1 );
    vector<vector<Point> > squares;

    for( int i = 0; names[i] != 0; i++ )
    {
        Mat image = imread(names[i], 1);
        if( image.empty() )
        {
            cout << "Couldn't load " << names[i] << endl;
            continue;
        }

        findSquares(image, squares);
        drawSquares(image, squares);
        imwrite("out.jpg", image);

        int c = waitKey();
        if( (char)c == 27 )
            break;
    }

    return 0;
}
4 голосов
/ 22 сентября 2009

Я сомневаюсь, что это можно сделать с помощью простой локальной операции. Посмотрите на прямоугольник, который вы хотите сохранить - есть несколько пробелов, поэтому выполнение локальной операции по удалению коротких отрезков может значительно снизить качество желаемого вывода.

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

UPDATE

Я только что использовал это пример приложения с использованием преобразования ядра Хафа с вашим образцом изображения и получил четыре красивые линии, подходящие к вашему прямоугольнику.

2 голосов
/ 23 сентября 2009

Возможно, поиск подключенных компонентов, затем удаление компонентов с размером менее X пикселей (эмпирически определено) с последующим расширением по горизонтальным / вертикальным линиям для повторного соединения промежутков внутри прямоугольника

2 голосов
/ 23 сентября 2009

Преобразование Хафа может быть очень дорогой операцией.

Альтернатива, которая может хорошо работать в вашем случае , следующая:

  1. запустить 2 операции математической морфологии, называемые закрытием изображения (http://homepages.inf.ed.ac.uk/rbf/HIPR2/close.htm) с горизонтальной и вертикальной линией (заданной длины, определенной из тестирования) соответственно структурирующего элемента. Смысл этого состоит в том, чтобы закрыть все пробелы в большом прямоугольнике.

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

1 голос
/ 30 сентября 2009

Можно следовать двум основным приемам:

  1. Операция на векторной основе: отобразите ваши пиксельные острова в кластеры (blob, voronoi зоны, что угодно). Затем примените некоторые эвристические методы для исправления сегментов, например алгоритм аппроксимации цепочки Тех-Чина, и обрежьте векторные элементы (начало, конечная точка, длина, ориентация и т. Д.).

  2. Установка на основе операции: кластеризация данных (как указано выше). Для каждого кластера вычислите главные компоненты и определите линии из окружностей или любой другой формы, ища кластеры, показывающие только 1 значимое собственное значение (или 2, если вы ищете «толстые» сегменты, которые могут напоминать эллипсы). Проверьте собственные векторы, связанные с собственными значениями, чтобы получить информацию об ориентации больших двоичных объектов, и сделайте свой выбор.

Оба способа могут быть легко изучены с помощью OpenCV (первый действительно относится к категории "Анализ контуров" из алгоритмов).

0 голосов
/ 30 октября 2013

Вот простое морфологическое фильтрующее решение, следующее за @ Tom10:

Решение в Matlab:

se1 = strel('line',5,180);            % linear horizontal structuring element 
se2 = strel('line',5,90);             % linear vertical structuring element 
I = rgb2gray(imread('test.jpg'))>80;  % threshold (since i had a grayscale version of the image)
Idil = imdilate(imdilate(I,se1),se2); % dilate contours so that they connect
Idil_area = bwareaopen(Idil,1200);    % area filter them to remove the small components

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

Результаты:

Dilating directionally the contours (90 and 180)

Area opening using bwareaopen, This may need some tuning but otherwise its simple and robust filter

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...