2015-11-20 30 views
5

Ho la seguente maschera di immagine:OpenCV euclidea di clustering vs findContours

mask

voglio applicare qualcosa di simile a cv::findContours, ma che l'algoritmo unisce solo i punti collegati nello stesso gruppo. Voglio farlo con una certa tolleranza, cioè, voglio aggiungere i pixel vicini l'uno all'altro entro una determinata tolleranza del raggio: questo è simile al clustering gerarchico a distanza di Euclide.

È implementato in OpenCV? O c'è un approccio veloce per l'implementazione di questo?

quello che voglio è qualcosa di simile a questo,

http://www.pointclouds.org/documentation/tutorials/cluster_extraction.php

applicata ai pixel bianchi di questa maschera.

Grazie.

+0

non è chiaro ciò che si desidera l'algoritmo da fare . Puoi mostrare un'altra immagine con il risultato atteso?A un certo livello, sembra che gli operatori morfologici ti darebbero tutto ciò di cui hai bisogno quindi sono sicuro che non può essere il caso. Abbiamo bisogno di vedere l'effetto che stai cercando di raggiungere. –

+0

@RogerRowland Nessun operatore morfologico non è un'opzione in quanto distorcerà i miei bordi. Quello che voglio è raggruppare i bordi nella mia immagine maschera per distanza euclidea tra di loro. Qualcosa di simile a http://www.pointclouds.org/documentation/tutorials/cluster_extraction.php – manatttta

+1

Penso che il suggerimento di @Humam sia buono, sebbene non esista alcuna implementazione di OpenCV. Per le attività di clustering in OpenCV non otterrai molto più di k-means o shift medio. Tuttavia, poiché hai già collegato un algoritmo di esempio, potrebbe essere più semplice portarlo su OpenCV (e presumibilmente non hai bisogno di 3D). –

risposta

10

È possibile utilizzare partition per this:

partitionsuddivide un insieme di elementi in classi di equivalenza. È possibile definire la classe di equivalenza come tutti i punti all'interno di una data distanza euclidea (tolleranza raggio)

Se si dispone di C++ 11, si può semplicemente utilizzare una funzione lambda:

int th_distance = 18; // radius tolerance 

int th2 = th_distance * th_distance; // squared radius tolerance 
vector<int> labels; 

int n_labels = partition(pts, labels, [th2](const Point& lhs, const Point& rhs) { 
    return ((lhs.x - rhs.x)*(lhs.x - rhs.x) + (lhs.y - rhs.y)*(lhs.y - rhs.y)) < th2; 
}); 

in caso contrario, puoi semplicemente costruire un funtore (vedi dettagli nel codice sotto).

Con adeguato raggio di distanza (ho trovato 18 opere buone su questa immagine), ho ottenuto:

enter image description here

codice completo:

#include <opencv2\opencv.hpp> 
#include <vector> 
#include <algorithm> 

using namespace std; 
using namespace cv; 

struct EuclideanDistanceFunctor 
{ 
    int _dist2; 
    EuclideanDistanceFunctor(int dist) : _dist2(dist*dist) {} 

    bool operator()(const Point& lhs, const Point& rhs) const 
    { 
     return ((lhs.x - rhs.x)*(lhs.x - rhs.x) + (lhs.y - rhs.y)*(lhs.y - rhs.y)) < _dist2; 
    } 
}; 

int main() 
{ 
    // Load the image (grayscale) 
    Mat1b img = imread("path_to_image", IMREAD_GRAYSCALE); 

    // Get all non black points 
    vector<Point> pts; 
    findNonZero(img, pts); 

    // Define the radius tolerance 
    int th_distance = 18; // radius tolerance 

    // Apply partition 
    // All pixels within the radius tolerance distance will belong to the same class (same label) 
    vector<int> labels; 

    // With functor 
    //int n_labels = partition(pts, labels, EuclideanDistanceFunctor(th_distance)); 

    // With lambda function (require C++11) 
    int th2 = th_distance * th_distance; 
    int n_labels = partition(pts, labels, [th2](const Point& lhs, const Point& rhs) { 
     return ((lhs.x - rhs.x)*(lhs.x - rhs.x) + (lhs.y - rhs.y)*(lhs.y - rhs.y)) < th2; 
    }); 

    // You can save all points in the same class in a vector (one for each class), just like findContours 
    vector<vector<Point>> contours(n_labels); 
    for (int i = 0; i < pts.size(); ++i) 
    { 
     contours[labels[i]].push_back(pts[i]); 
    } 

    // Draw results 

    // Build a vector of random color, one for each class (label) 
    vector<Vec3b> colors; 
    for (int i = 0; i < n_labels; ++i) 
    { 
     colors.push_back(Vec3b(rand() & 255, rand() & 255, rand() & 255)); 
    } 

    // Draw the labels 
    Mat3b lbl(img.rows, img.cols, Vec3b(0, 0, 0)); 
    for (int i = 0; i < pts.size(); ++i) 
    { 
     lbl(pts[i]) = colors[labels[i]]; 
    } 

    imshow("Labels", lbl); 
    waitKey(); 

    return 0; 
} 
+0

grazie @Miki sei fantastico! – sturkmen

+0

+1 per un approccio OpenCV. -1 per approccio non basato sulla densità. La soglia della distanza sarà un incubo. Sarei felice se condividi il tuo pensiero su un modo universale per regolare la soglia in questo problema;) –

+0

@HumamHelfawi Bene, grazie per +1; D. Riguardo a -1, rispondo: 1) DBSCAN (almeno nella formulazione originale) richiede 2 parametri, uno dei quali è esattamente la soglia di distanza (come qui). 2) La domanda è esplicitamente sul clustering all'interno di una tolleranza del raggio, non su un approccio basato sulla densità. 3) La domanda non dice di essere robusta per i valori anomali. Ecco perché è stata proposta questa soluzione. – Miki

2

Suggerisco di utilizzare l'algoritmo DBSCAN. È esattamente quello che stai cercando. Utilizzare una semplice distanza euclidea o anche la distanza di Manhattan potrebbe funzionare meglio. L'input è tutti punti bianchi (soglia). L'uscita è un gruppi di punti (il componente collegato)

Ecco un'EDIT DBSCAN C++ implenetation

: ho provato DBSCAN la mia auto ed ecco il risultato: enter image description here

Come si vede, Solo i punti realmente connessi sono considerati come un cluster.

Questo risultato è stato ottenuto utilizzando lo standerad algoritmo di DBSCAN con EPS = 3 (statica non c'è bisogno di essere sintonizzato) MinPoints = 1 (statico anche) e Manhattan Distanza