Question

Je cherche un algorithme pour tailler courts segments de ligne à partir de la sortie d'un détecteur de bord. Comme on peut le voir dans l'image (et un lien) ci-dessous, il y a plusieurs petites arêtes qui ne sont pas détectés lignes « longues ». Idéalement, je voudrais juste les 4 côtés de la quadrangulaire à apparaître après le traitement, mais s'il y a un couple de lignes perdues, il ne sera pas une grosse affaire ... Des suggestions?

Exemple

image Lien

Était-ce utile?

La solution

Avant de trouver les bords pré-traitement de l'image avec un open ou close opération (ou les deux), qui est, érodent suivi par dilatent , ou dilatent suivi de érodent . cela devrait enlever les objets plus petits, mais laisser les plus grands à peu près le même.

Je l'ai cherché des exemples en ligne, et le mieux que je pouvais trouver était à la page 41 de ce PDF.

Autres conseils

Je doute que cela peut être fait avec une simple opération locale. Regardez le rectangle que vous voulez garder. - il y a plusieurs lacunes, d'où l'exécution d'une opération locale pour éliminer les courts segments de ligne réduirait probablement fortement la qualité de la sortie désirée

Par conséquent, je voudrais essayer de détecter le rectangle comme contenu important en fermant les lacunes, montage d'un polygone, ou quelque chose comme ça, et puis dans une deuxième étape jeter le contenu sans importance restant. Peut-être le transformation de Hough pourrait aider.

UPDATE

Je viens d'utiliser cette utilisant un noyau Transformée de Hough avec votre image de l'échantillon et a quatre belles lignes montage de votre rectangle.

Dans le cas où quelqu'un marche sur ce fil, OpenCV 2.x apporte un exemple nommé squares.cpp que, fondamentalement, les ongles cette tâche.

J'ai fait une légère modification à l'application pour améliorer la détection du quadrangulaire

entrer image description ici

code :

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

La transformation de Hough peut être une opération très coûteuse.

Une alternative qui peut fonctionner correctement dans votre cas est le suivant:

  1. run 2 opérations de morphologie mathématique appelé une image proche ( http://homepages.inf.ed.ac.uk/rbf/HIPR2/close.htm ) avec une ligne horizontale et verticale (d'une longueur donnée déterminée à partir d'essais) élément structurant respectivement. Le but de cela est de fermer toutes les lacunes dans le grand rectangle.

  2. analyse de composante connexe de l'exécution. Si vous avez fait la morphologie efficace, le grand rectangle sortira comme un composant connecté. Il reste alors seulement itérer tous les composants connectés et à choisir le candidat le plus probable devrait être le grand rectangle.

Peut-être que la recherche des composants connectés, puis l'élimination des composants avec moins de X pixels (déterminées de manière empirique), suivie par une dilatation le long de lignes horizontales / verticales pour reconnecter les lacunes dans le rectangle

Il est possible de suivre deux techniques principales:

  1. opération à base de vecteur: carte vos îles de pixels en grappes (blob, zones Voronoi, peu importe). Ensuite, appliquez quelques heuristiques pour corriger les segments, comme algorithme d'approximation de la chaîne Teh-Chin, et faites votre taille sur les éléments (début vectoriales, point final, la longueur, l'orientation et ainsi de suite).

  2. opération Désigner base: regrouper vos données (comme ci-dessus). Pour chaque cluster, calculer les composants principaux et détecter les lignes de cercles ou de toute autre forme en recherchant des grappes montrant seulement 1 valeur propre significative (ou 2 si vous cherchez des segments « gras », qui pourraient ressembler à des ellipses). Vérifiez vecteurs propres associés aux valeurs propres à avoir des informations sur l'orientation des blobs, et faites votre choix.

Les deux voies pourraient être explorées avec OpenCV facilement (l'ancien, en effet, relève de la catégorie « Analyse Contour » de algos).

Voici une solution simple de filtrage morphologique suivant les lignes de @ Tom10:

Solution en 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

L'idée est de relier essentiellement les contours horizontaux pour faire un grand composant et filtrer par un filtre d'ouverture aire plus tard pour obtenir le rectangle.

Résultats:

Dilatation directionnellement les contours (90 et 180)

ouverture zone à l'aide bwareaopen, cela peut avoir besoin des réglages mais sinon son filtre simple et robuste

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top