Pergunta

Eu estou procurando um algoritmo para segmentos de linha curta podar a partir da saída de um detector de borda. Como pode ser visto na imagem (e ligação) abaixo, há várias pequenas arestas detectado que não são "longas" linhas. Idealmente, eu gostaria apenas os 4 lados do quadrado para mostrar-se após o processamento, mas se há um par de linhas vadios, não vai ser um grande negócio ... Alguma sugestão?

Exemplo

Imagem Link

Foi útil?

Solução

Antes de encontrar as extremidades pré-processar a imagem com um aberto ou perto operação (ou ambos), ou seja, corroer seguida por dilate ou dilate seguido de corroer . Isso deve remover os objetos menores, mas deixam os maiores praticamente o mesmo.

Eu olhei para exemplos on-line, e o melhor que eu poderia encontrar era, na página 41 do este PDF .

Outras dicas

Eu duvido que isso pode ser feito com uma operação local simples. Olhe para o retângulo que você deseja manter - existem várias lacunas, portanto, realizar uma operação local para remover segmentos de linha curtos provavelmente fortemente reduzir a qualidade da saída desejada

.

Em conseqüência eu iria tentar detectar o retângulo como conteúdo importante, fechando as lacunas, encaixando um polígono, ou algo parecido, e, em seguida, em uma segunda etapa descartar o conteúdo sem importância restante. Pode ser a Hough transformar poderia ajudar.

Atualizar

Eu apenas usei este href="http://www.inf.ufrgs.br/~laffernandes/kht.html" rel="nofollow aplicação de exemplo usando um Kernel Hough Transform com sua imagem de amostra e tem quatro linhas agradáveis ??correspondentes ao seu retângulo.

Em etapas alguém caso sobre esta discussão, OpenCV 2.x traz um exemplo chamado squares.cpp que, basicamente, unhas esta tarefa.

Eu fiz uma pequena modificação no aplicativo para melhorar a detecção do quadrilátero

enter descrição da imagem aqui

Código :

#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;
}

O Hough Transform pode ser uma operação muito cara.

Uma alternativa que pode funcionar bem em o seu caso é o seguinte:

  1. executar 2 operações morfologia matemática chamado um close ( http://homepages.inf.ed.ac.uk/rbf/HIPR2/close.htm ) com uma linha horizontal e vertical (de um determinado comprimento determinado a partir de elemento de teste) estruturação respectivamente. O ponto deste é fechar todas as lacunas no retângulo grande.

  2. Executar ligado análise de componentes. Se você tiver feito a morfologia de forma eficaz, o grande retângulo vai sair como um componente conectado. Em seguida, ele permanece apenas iteração através de todos os componentes conectados e escolhendo o candidato mais provável que deveria ser o grande retângulo.

Talvez encontrar os componentes ligados, em seguida, removendo componentes com menos de pixels X (determinado empiricamente), seguida pela dilatação ao longo de linhas horizontais / verticais para voltar a ligar as lacunas dentro do rectângulo

É possível seguir duas técnicas principais:

  1. operação baseada Vector: mapear suas ilhas de pixels em clusters (blob, zonas de Voronoi, qualquer que seja). Em seguida, aplique algumas heurísticas para corrigir os segmentos, como Teh-Chin algoritmo cadeia de aproximação, e fazer a sua poda em elementos vetoriais (partida, ponto final, comprimento, orientação e assim por diante).

  2. Set operação baseada: agrupar seus dados (como acima). Para cada cluster, calcular os componentes principais e detectar linhas de círculos ou qualquer outra forma de olhar para clusters que mostram apenas 1 significativa de valores próprios (ou 2 se você olhar para os segmentos de "gordura", que poderia se assemelham a elipses). Verifique autovetores associados com valores próprios para ter informações sobre a orientação das bolhas, e fazer a sua escolha.

Ambas as formas podem ser facilmente explorada com OpenCV (o primeiro, de fato, cai sob "análise Contour" categoria de algos).

Aqui está uma solução de filtragem morfológica simples seguindo as linhas de @ Tom10:

Solução em 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

A idéia é conectar basicamente os contornos horizontais para fazer um grande componente e filtrar por um filtro de abertura de área, mais tarde, para obter o retângulo.

Resultados:

Dilatando direcionalmente os contornos (90 e 180)

abertura área usando bwareaopen, este pode precisar de alguns ajustes, mas caso contrário, a sua simples e filtro robusto

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top