2008-11-21 16 views
5

Ich habe ein 2D-Bild zufällig und spärlich mit Pixeln verstreut.
Bei einem Punkt auf dem Bild muss ich den Abstand zum nächsten Pixel finden, der nicht in der Hintergrundfarbe (schwarz) ist.
Was ist der schnellste Weg, dies zu tun?Den nächsten nicht schwarzen Pixel in einem Bild schnell finden

Die einzige Methode, die ich mir vorstellen konnte, ist der Aufbau eines kd-Baumes für die Pixel. aber ich möchte wirklich solche teure Vorverarbeitung vermeiden. Außerdem scheint mir ein kd-Baum mehr zu geben, als ich brauche. Ich brauche nur die Distanz zu etwas und mir ist es egal, was das ist.

Antwort

6

Suchen Sie, wie Pyro sagt, den Umfang eines Quadrats, das Sie immer um einen Pixel von Ihrem ursprünglichen Punkt weg bewegen (d. H. Die Breite und Höhe um zwei Pixel erhöhen). Wenn Sie ein nicht-schwarzes Pixel treffen, berechnen Sie die Entfernung (dies ist Ihre erste teure Berechnung) und fahren dann fort, nach außen zu suchen, bis die Breite Ihrer Box doppelt so groß ist wie die Entfernung zum ersten gefundenen Punkt als Ihr ursprünglich gefundener Pixel). Speichern Sie alle nicht-schwarzen Punkte, die Sie während dieses Teils finden, und berechnen Sie dann jeden ihrer Abstände, um zu sehen, ob sich einer von ihnen näher als Ihr ursprünglicher Punkt befindet.

Im Idealfall müssen Sie nur eine teure Distanzberechnung machen.

Aktualisieren: Da Sie hier Pixel-zu-Pixel-Abstände berechnen (anstelle von beliebigen Gleitkommazahlen), können Sie diesen Algorithmus erheblich beschleunigen, indem Sie eine vorberechnete Lookup-Tabelle verwenden. by-Breiten-Feld) Sie Abstand als Funktion der x und y zu geben. Ein 100x100-Array kostet Sie im Wesentlichen 40K Speicher und deckt ein 200x200-Quadrat um den ursprünglichen Punkt ab und erspart Ihnen die Kosten für eine teure Abstandsberechnung (ob Pythagoreische oder Matrix-Algebra) für jedes farbige Pixel, das Sie finden. Dieses Array könnte sogar vorberechnet und als Ressource in Ihre App eingebettet werden, um Ihnen die anfängliche Rechenzeit zu ersparen (dies ist wahrscheinlich ein ernsthafter Overkill).

Update 2: Es gibt auch Möglichkeiten, die Suche nach dem quadratischen Umfang zu optimieren. Ihre Suche sollte an den vier Punkten beginnen, die die Achsen schneiden und sich jeweils um ein Pixel in Richtung der Ecken bewegen (Sie haben 8 bewegliche Suchpunkte, was je nach den Anforderungen Ihrer Anwendung leicht zu Problemen führen kann). Sobald Sie ein farbiges Pixel gefunden haben, müssen Sie nicht mehr zu den Ecken weitergehen, da die restlichen Punkte weiter vom Ursprung entfernt sind.

Nach dem ersten gefundenen Pixel können Sie den zusätzlichen Suchbereich auf das Minimum beschränken, indem Sie die Nachschlagetabelle verwenden, um sicherzustellen, dass jeder gesuchte Punkt näher als der gefundene Punkt ist Distanzgrenze ist erreicht). Diese zweite Optimierung wäre wahrscheinlich viel zu teuer, wenn Sie jede Entfernung im laufenden Betrieb berechnen müssten. Wenn das nächstgelegene Pixel innerhalb der 200x200-Box liegt (oder eine andere Größe für Ihre Daten), werden Sie nur innerhalb eines vom Pixel begrenzten Kreises suchen, wobei nur Nachschlagevorgänge und <> Vergleiche durchgeführt werden.

+0

Dies ist eigentlich die beste Antwort noch. Vielen Dank. – shoosh

1

Suche "Suche nach dem nächsten Nachbarn", die ersten beiden Links in Google sollten Ihnen helfen.

Wenn Sie dies nur für 1 Pixel pro Bild tun, denke ich, Ihre beste Wette ist nur eine lineare Suche, 1 Pixel Breite Box zur Zeit nach außen. Sie können den ersten gefundenen Punkt nicht verwenden, wenn Ihr Suchfeld quadratisch ist. Sie müssen vorsichtig sein

1

Ja, die Suche nach dem nächsten Nachbarn ist gut, aber garantiert nicht, dass Sie das "nächste" finden. Wenn Sie jedes Mal ein Pixel herausziehen, entsteht eine quadratische Suche - die Diagonalen sind weiter entfernt als die horizontale/vertikale. Wenn dies wichtig ist, sollten Sie Folgendes überprüfen: Fahren Sie fort, bis die absolute Horizontale einen größeren Abstand als das gefundene Pixel aufweist, und berechnen Sie anschließend die Entfernungen für alle nicht schwarzen Pixel, die sich dort befanden.

2

Sie haben nicht angegeben, wie Sie die Entfernung messen möchten. Ich nehme L1 (geradlinig) an, weil es einfacher ist; möglicherweise könnten diese Ideen für L2 (euklidisch) modifiziert werden.

Wenn Sie dies nur für relativ wenige Pixel tun, dann suchen Sie einfach vom Quellpixel in einer Spirale nach außen, bis Sie einen nicht-schwarzen treffen.

Wenn Sie dies für viele/alle tun, wie wäre es damit: Erstellen Sie ein 2D-Array der Größe des Bildes, wobei jede Zelle die Entfernung zum nächsten nichtschwarzen Pixel speichert (und falls erforderlich, die Koordinaten dieses Pixels). Führen Sie vier Zeilen durch: von links nach rechts, von rechts nach links, von unten nach oben und von oben nach unten. Betrachten Sie den Sweep von links nach rechts; Während Sie fegen, behalten Sie eine 1-D-Spalte mit dem letzten nicht-schwarzen Pixel in jeder Zeile und markieren Sie jede Zelle im 2-D-Array mit dem Abstand zu und/oder den Koordinaten dieses Pixels. O (n^2).

Alternativ ist ein k-d-Baum Overkill; Du könntest einen Quadtree benutzen. Nur ein bisschen schwieriger zu programmieren als mein Zeilen-Sweep, etwas mehr Speicher (aber weniger als doppelt so viel) und möglicherweise schneller.

+0

Ich sehe nicht, wie der Sweep-Algorithmus korrekt ist. Wenn das nächste Pixel in einer diagonalen Richtung liegt, würde diese Methode es nicht finden. Es würde nur die Pixel auf den zwei Achsen finden, die von dem Punkt ausgehen. – shoosh

-1

Ich würde eine einfache Lookup-Tabelle tun - für jedes Pixel, vorberechnen Entfernung zum nächsten nicht-schwarzen Pixel und speichere den Wert in dem als die entsprechenden Pixel-Offset gleich. Natürlich brauchen Sie auf diese Weise mehr Speicher.

+0

Der Punkt ist * zu vermeiden * langwierige Verarbeitungsoperationen, die 0 sind (Anzahl der Punkte) – shoosh

+0

Ok, ich dachte, Vorverarbeitung würde viel seltener im Vergleich zu der Suche nach der Entfernung (s) durchgeführt werden. – Geee

5

Persönlich würde ich MusiGenesis' Vorschlag einer Lookup-Tabelle ignorieren.

Die Berechnung der Entfernung zwischen Pixeln ist nicht teuer, besonders da für diesen ersten Test Sie nicht die tatsächliche Entfernung benötigen, so dass es keine Notwendigkeit gibt, die Quadratwurzel zu nehmen. Sie können mit der Entfernung arbeiten^2, das heißt:

r^2 = dx^2 + dy^2 

Auch wenn du gehst nach außen ein Pixel in einer Zeit, daran erinnern, dass:

(n + 1)^2 = n^2 + 2n + 1 

oder wenn nx ist der aktuelle Wert und ox ist der vorherige Wert:

nx^2 = ox^2 + 2ox + 1 
      = ox^2 + 2(nx - 1) + 1 
      = ox^2 + 2nx - 1 
=> nx^2 += 2nx - 1 

Es ist leicht zu sehen, wie das funktioniert:

012.351.
1^2 = 0 + 2*1 - 1 = 1 
2^2 = 1 + 2*2 - 1 = 4 
3^2 = 4 + 2*3 - 1 = 9 
4^2 = 9 + 2*4 - 1 = 16 
5^2 = 16 + 2*5 - 1 = 25 
etc... 

in jeder Iteration Also, Sie daher brauchen nur einige Zwischengrößen so behalten:

int dx2 = 0, dy2, r2; 
for (dx = 1; dx < w; ++dx) { // ignoring bounds checks 
    dx2 += (dx << 1) - 1; 
    dy2 = 0; 
    for (dy = 1; dy < h; ++dy) { 
     dy2 += (dy << 1) - 1; 
     r2 = dx2 + dy2; 
     // do tests here 
    } 
} 

Tada!r^2 Berechnung mit nur wenig verschiebt, addiert und subtrahiert :)

Natürlich auf jedem anständigen modernen CPU Berechnung r^2 = dx * dx + dy * dy könnte genauso schnell wie das sein ...

1

Ok, das hört sich interessant an. Ich habe eine C++ - Version einer Soulution gemacht, ich weiß nicht, ob dir das hilft. Ich denke, es funktioniert schnell genug, da es fast sofort auf einer 800 * 600-Matrix ist. Wenn Sie Fragen haben, fragen Sie einfach.

Entschuldigung für alle Fehler, die ich gemacht habe, es ist ein 10min Code ... Dies ist eine iterative Version (Ich wollte auch eine rekursive Version erstellen, aber ich habe meine Meinung geändert). Der Algorithmus könnte verbessert werden, indem kein Punkt zum Punktarray hinzugefügt wird, der eine größere Entfernung vom Startpunkt als min_dist hat, aber dies beinhaltet die Berechnung für jeden Pixel (trotz seiner Farbe) die Entfernung vom Startpunkt.

Hoffnung, die

//(c++ version) 
#include<iostream> 
#include<cmath> 
#include<ctime> 
using namespace std; 
//ITERATIVE VERSION 

//picture witdh&height 
#define width 800 
#define height 600 
//indexex 
int i,j; 

//initial point coordinates 
int x,y; 
//variables to work with the array 
int p,u; 
//minimum dist 
double min_dist=2000000000; 
//array for memorising the points added 
struct point{ 
    int x; 
    int y; 
} points[width*height]; 
double dist; 
bool viz[width][height]; 
// direction vectors, used for adding adjacent points in the "points" array. 
int dx[8]={1,1,0,-1,-1,-1,0,1}; 
int dy[8]={0,1,1,1,0,-1,-1,-1}; 
int k,nX,nY; 
//we will generate an image with white&black pixels (0&1) 
bool image[width-1][height-1]; 
int main(){ 
    srand(time(0)); 
    //generate the random pic 
    for(i=1;i<=width-1;i++) 
     for(j=1;j<=height-1;j++) 
      if(rand()%10001<=9999) //9999/10000 chances of generating a black pixel 
      image[i][j]=0; 
      else image[i][j]=1; 
    //random coordinates for starting x&y 
    x=rand()%width; 
    y=rand()%height; 
    p=1;u=1; 
    points[1].x=x; 
    points[1].y=y; 
    while(p<=u){ 
     for(k=0;k<=7;k++){ 
      nX=points[p].x+dx[k]; 
      nY=points[p].y+dy[k]; 
      //nX&nY are the coordinates for the next point 
      //if we haven't added the point yet 
      //also check if the point is valid 
      if(nX>0&&nY>0&&nX<width&&nY<height) 
      if(viz[nX][nY] == 0){ 
       //mark it as added 
       viz[nX][nY]=1; 
       //add it in the array 
       u++; 
       points[u].x=nX; 
       points[u].y=nY; 
       //if it's not black 
       if(image[nX][nY]!=0){ 
       //calculate the distance 
       dist=(x-nX)*(x-nX) + (y-nY)*(y-nY); 
       dist=sqrt(dist); 
       //if the dist is shorter than the minimum, we save it 
       if(dist<min_dist) 
        min_dist=dist; 
        //you could save the coordinates of the point that has 
        //the minimum distance too, like sX=nX;, sY=nY; 
       } 
      } 
     } 
     p++; 
} 
    cout<<"Minimum dist:"<<min_dist<<"\n"; 
return 0; 
} 
0

hilft Ich bin sicher, dass dies besser getan werden könnte, aber hier ist einige Code, der den Umfang eines Quadrats um das Mittelpixel sucht, zunächst das zentrale Prüfung und zu den Ecken zu bewegen. Wenn ein Pixel nicht gefunden wird, wird der Umfang (Radius) erweitert, bis entweder die Radiusgrenze erreicht ist oder ein Pixel gefunden wurde. Die erste Implementierung war eine Schleife, die eine einfache Spirale um den Mittelpunkt machte, aber wie erwähnt, findet sie nicht das absolut nächste Pixel. Die Erstellung von SomeBigObjCStruct innerhalb der Schleife war sehr langsam - das Entfernen aus der Schleife machte es gut genug und der spiralförmige Ansatz wurde verwendet. Aber hier ist diese Implementierung sowieso - Vorsicht, wenig bis gar keine Tests durchgeführt.

Es ist alles mit ganzzahligen Addition und Subtraktion getan.

- (SomeBigObjCStruct *)nearestWalkablePoint:(SomeBigObjCStruct)point {  

typedef struct _testPoint { // using the IYMapPoint object here is very slow 
    int x; 
    int y; 
} testPoint; 

// see if the point supplied is walkable 
testPoint centre; 
centre.x = point.x; 
centre.y = point.y; 

NSMutableData *map = [self getWalkingMapDataForLevelId:point.levelId]; 

// check point for walkable (case radius = 0) 
if(testThePoint(centre.x, centre.y, map) != 0) // bullseye 
    return point; 

// radius is the distance from the location of point. A square is checked on each iteration, radius units from point. 
// The point with y=0 or x=0 distance is checked first, i.e. the centre of the side of the square. A cursor variable 
// is used to move along the side of the square looking for a walkable point. This proceeds until a walkable point 
// is found or the side is exhausted. Sides are checked until radius is exhausted at which point the search fails. 
int radius = 1; 

BOOL leftWithinMap = YES, rightWithinMap = YES, upWithinMap = YES, downWithinMap = YES; 

testPoint leftCentre, upCentre, rightCentre, downCentre; 
testPoint leftUp, leftDown, rightUp, rightDown; 
testPoint upLeft, upRight, downLeft, downRight; 

leftCentre = rightCentre = upCentre = downCentre = centre; 

int foundX = -1; 
int foundY = -1; 

while(radius < 1000) { 

    // radius increases. move centres outward 
    if(leftWithinMap == YES) { 

     leftCentre.x -= 1; // move left 

     if(leftCentre.x < 0) { 

      leftWithinMap = NO; 
     } 
    } 

    if(rightWithinMap == YES) { 

     rightCentre.x += 1; // move right 

     if(!(rightCentre.x < kIYMapWidth)) { 

      rightWithinMap = NO; 
     } 
    } 

    if(upWithinMap == YES) { 

     upCentre.y -= 1; // move up 

     if(upCentre.y < 0) { 

      upWithinMap = NO; 
     } 
    } 

    if(downWithinMap == YES) { 

     downCentre.y += 1; // move down 

     if(!(downCentre.y < kIYMapHeight)) { 

      downWithinMap = NO; 
     } 
    } 

    // set up cursor values for checking along the sides of the square 
    leftUp = leftDown = leftCentre; 
    leftUp.y -= 1; 
    leftDown.y += 1; 
    rightUp = rightDown = rightCentre; 
    rightUp.y -= 1; 
    rightDown.y += 1; 
    upRight = upLeft = upCentre; 
    upRight.x += 1; 
    upLeft.x -= 1; 
    downRight = downLeft = downCentre; 
    downRight.x += 1; 
    downLeft.x -= 1; 

    // check centres 
    if(testThePoint(leftCentre.x, leftCentre.y, map) != 0) { 

     foundX = leftCentre.x; 
     foundY = leftCentre.y; 
     break; 
    } 
    if(testThePoint(rightCentre.x, rightCentre.y, map) != 0) { 

     foundX = rightCentre.x; 
     foundY = rightCentre.y; 
     break; 
    } 
    if(testThePoint(upCentre.x, upCentre.y, map) != 0) { 

     foundX = upCentre.x; 
     foundY = upCentre.y; 
     break; 
    } 
    if(testThePoint(downCentre.x, downCentre.y, map) != 0) { 

     foundX = downCentre.x; 
     foundY = downCentre.y; 
     break; 
    } 

    int i; 

    for(i = 0; i < radius; i++) { 

     if(leftWithinMap == YES) { 
      // LEFT Side - stop short of top/bottom rows because up/down horizontal cursors check that line 
      // if cursor position is within map 
      if(i < radius - 1) { 

       if(leftUp.y > 0) { 
        // check it 
        if(testThePoint(leftUp.x, leftUp.y, map) != 0) { 
         foundX = leftUp.x; 
         foundY = leftUp.y; 
         break; 
        } 
        leftUp.y -= 1; // moving up 
       } 
       if(leftDown.y < kIYMapHeight) { 
        // check it 
        if(testThePoint(leftDown.x, leftDown.y, map) != 0) { 
         foundX = leftDown.x; 
         foundY = leftDown.y; 
         break; 
        } 
        leftDown.y += 1; // moving down 
       } 
      } 
     } 

     if(rightWithinMap == YES) { 
      // RIGHT Side 
      if(i < radius - 1) { 

       if(rightUp.y > 0) { 

        if(testThePoint(rightUp.x, rightUp.y, map) != 0) { 
         foundX = rightUp.x; 
         foundY = rightUp.y; 
         break; 
        } 
        rightUp.y -= 1; // moving up 
       } 
       if(rightDown.y < kIYMapHeight) { 

        if(testThePoint(rightDown.x, rightDown.y, map) != 0) { 
         foundX = rightDown.x; 
         foundY = rightDown.y; 
         break; 
        } 
        rightDown.y += 1; // moving down 
       } 
      } 
     } 

     if(upWithinMap == YES) { 
      // UP Side 
      if(upRight.x < kIYMapWidth) { 

       if(testThePoint(upRight.x, upRight.y, map) != 0) { 
        foundX = upRight.x; 
        foundY = upRight.y; 
        break; 
       } 
       upRight.x += 1; // moving right 
      } 
      if(upLeft.x > 0) { 

       if(testThePoint(upLeft.x, upLeft.y, map) != 0) { 
        foundX = upLeft.x; 
        foundY = upLeft.y; 
        break; 
       } 
       upLeft.y -= 1; // moving left 
      } 
     } 

     if(downWithinMap == YES) { 
      // DOWN Side 
      if(downRight.x < kIYMapWidth) { 

       if(testThePoint(downRight.x, downRight.y, map) != 0) { 
        foundX = downRight.x; 
        foundY = downRight.y; 
        break; 
       } 
       downRight.x += 1; // moving right 
      } 
      if(downLeft.x > 0) { 

       if(testThePoint(upLeft.x, upLeft.y, map) != 0) { 
        foundX = downLeft.x; 
        foundY = downLeft.y; 
        break; 
       } 
       downLeft.y -= 1; // moving left 
      } 
     } 
    } 

    if(foundX != -1 && foundY != -1) { 
     break; 
    } 

    radius++; 
} 

// build the return object 
if(foundX != -1 && foundY != -1) { 

    SomeBigObjCStruct *foundPoint = [SomeBigObjCStruct mapPointWithX:foundX Y:foundY levelId:point.levelId]; 
    foundPoint.z = [self zWithLevelId:point.levelId]; 
    return foundPoint; 
} 
return nil; 

}

0

Sie können viele Möglichkeiten kombinieren sie zu beschleunigen.

  • Eine Möglichkeit, die Pixel-Suche zu beschleunigen, ist die Verwendung einer so genannten räumlichen Lookup-Map. Es ist im Grunde eine heruntergerechnet Karte (sagen wir von 8x8 Pixel, aber es ist ein Kompromiss) der Pixel in diesem Block. Werte können "keine Pixel gesetzt" sein "Teilpixel gesetzt" "alle Pixel gesetzt". Auf diese Weise kann ein Lesevorgang feststellen, ob ein Block/eine Zelle entweder voll, teilweise voll oder leer ist.
  • Scannen einer Box/Rechteck um das Zentrum möglicherweise nicht ideal, weil es viele Pixel/Zellen gibt, die weit entfernt sind. Ich verwende einen Kreiszeichnungsalgorithmus (Bresenham), um den Overhead zu reduzieren.
  • Das Lesen der Rohpixelwerte kann in horizontalen Stapeln erfolgen, z. B. ein Byte (für eine Zellengröße von 8x8 oder ein Vielfaches davon), dword oder long. Dies sollte Ihnen wieder eine ernsthafte Beschleunigung geben.
  • Sie können auch mehrere Ebenen von "räumlichen Lookup Maps" verwenden, es ist wieder ein Kompromiss.
  • Für die Entfernungsberechnung kann die erwähnte Nachschlagetabelle verwendet werden, aber es ist eine (Cache) Bandbreite vs Berechnung Geschwindigkeit Kompromiss (ich weiß nicht, wie es auf einer GPU zum Beispiel).