Darstellung und Lösung eines Labyrinths anhand eines Bildes

271

Was ist der beste Weg, um ein Labyrinth bei einem bestimmten Bild darzustellen und zu lösen?

Das Titelbild von The Scope Issue 134

Wie kann man ein JPEG-Bild (wie oben gezeigt) am besten einlesen, in eine Datenstruktur analysieren und das Labyrinth lösen? Mein erster Instinkt ist, das Bild Pixel für Pixel zu lesen und in einer Liste (Array) von Booleschen Werten zu speichern: Truefür ein weißes Pixel und Falsefür ein nicht weißes Pixel (die Farben können verworfen werden). Das Problem bei dieser Methode ist, dass das Bild möglicherweise nicht "pixelgenau" ist. Damit meine ich einfach, dass ein weißes Pixel irgendwo an einer Wand einen unbeabsichtigten Pfad erzeugen kann.

Eine andere Methode (die mir nach einigem Nachdenken einfiel) besteht darin, das Bild in eine SVG-Datei zu konvertieren - eine Liste von Pfaden, die auf einer Leinwand gezeichnet sind. Auf diese Weise könnten die Pfade in dieselbe Art von Liste (Boolesche Werte) eingelesen werden, in der Trueein Pfad oder eine Wand angegeben ist, Falsedie einen befahrbaren Raum angibt. Ein Problem bei dieser Methode tritt auf, wenn die Konvertierung nicht 100% genau ist und nicht alle Wände vollständig verbunden werden, wodurch Lücken entstehen.

Ein Problem bei der Konvertierung in SVG ist auch, dass die Linien nicht "perfekt" gerade sind. Dies führt dazu, dass die Pfade kubische Bezierkurven sind. Mit einer Liste (Array) von Booleschen Werten, die durch Ganzzahlen indiziert sind, würden die Kurven nicht einfach übertragen, und alle Punkte, die auf der Kurve liegen, müssten berechnet werden, stimmen jedoch nicht genau mit den Listenindizes überein.

Ich gehe davon aus, dass eine dieser Methoden zwar funktioniert (wenn auch wahrscheinlich nicht), dass sie bei einem so großen Bild jedoch äußerst ineffizient sind und dass es einen besseren Weg gibt. Wie wird dies am besten (am effizientesten und / oder am wenigsten komplex) durchgeführt? Gibt es überhaupt einen besten Weg?

Dann kommt das Lösen des Labyrinths. Wenn ich eine der ersten beiden Methoden verwende, erhalte ich im Wesentlichen eine Matrix. Nach dieser Antwort ist die Verwendung eines Baums eine gute Möglichkeit, ein Labyrinth darzustellen, und eine gute Möglichkeit, es zu lösen, die Verwendung des A * -Algorithmus . Wie würde man aus dem Bild einen Baum erstellen? Irgendwelche Ideen?

TL; DR
Beste Art zu analysieren? In welche Datenstruktur? Wie würde diese Struktur beim Lösen helfen / behindern?

UPDATE
Ich habe versucht, das zu implementieren, was @Mikhail in Python geschrieben hat, numpyund zwar wie von @Thomas empfohlen. Ich denke, dass der Algorithmus korrekt ist, aber er funktioniert nicht wie erhofft. (Code unten.) Die PNG-Bibliothek ist PyPNG .

import png, numpy, Queue, operator, itertools

def is_white(coord, image):
  """ Returns whether (x, y) is approx. a white pixel."""
  a = True
  for i in xrange(3):
    if not a: break
    a = image[coord[1]][coord[0] * 3 + i] > 240
  return a

def bfs(s, e, i, visited):
  """ Perform a breadth-first search. """
  frontier = Queue.Queue()
  while s != e:
    for d in [(-1, 0), (0, -1), (1, 0), (0, 1)]:
      np = tuple(map(operator.add, s, d))
      if is_white(np, i) and np not in visited:
        frontier.put(np)
    visited.append(s)
    s = frontier.get()
  return visited

def main():
  r = png.Reader(filename = "thescope-134.png")
  rows, cols, pixels, meta = r.asDirect()
  assert meta['planes'] == 3 # ensure the file is RGB
  image2d = numpy.vstack(itertools.imap(numpy.uint8, pixels))
  start, end = (402, 985), (398, 27)
  print bfs(start, end, image2d, [])
Whymarrh
quelle
12
Ich würde das Labyrinth in Schwarzweiß konvertieren und eine Methode zur Suche nach zellularen Automaten verwenden, um es zu lösen.
Dan D.
Müssen Sie sich nur mit diesem Bild oder mit vielen solchen Bildern befassen? Dh gibt es eine Option für eine manuelle Verarbeitung, die für dieses bestimmte Bild spezifisch ist?
Mikhail
1
@Whymarrh Ich codiere Python nicht, aber ich bin mir ziemlich sicher, dass Sie visited.append(s)unter a wechseln for.ifund es durch ersetzen sollten visited.append(np). Ein Scheitelpunkt wird besucht, sobald er der Warteschlange hinzugefügt wurde. Tatsächlich sollte dieses Array den Namen "Warteschlange" tragen. Sie können BFS auch beenden, sobald Sie das Ziel erreicht haben.
Mikhail
2
@Whymarrh Und Sie scheinen auch die Implementierung des Pfadextraktionsblocks übersprungen zu haben. Ohne sie können Sie nur herausfinden, ob das Finish erreichbar ist oder nicht, aber nicht wie.
Mikhail
1
Um herauszufinden , ob es ist eine Lösung, eine UnionFind und ein Linear - Scan ist der schnellste Algorithmus. Es gibt Ihnen nicht den Pfad, sondern eine Reihe von Kacheln, die den Pfad als Teilmenge enthalten.
st0le

Antworten:

236

Hier ist eine Lösung.

  1. Konvertieren Sie das Bild in Graustufen (noch nicht binär) und passen Sie die Gewichte für die Farben so an, dass das endgültige Graustufenbild ungefähr gleichmäßig ist. Sie können dies einfach tun, indem Sie die Schieberegler in Photoshop unter Bild -> Anpassungen -> Schwarzweiß steuern.
  2. Konvertieren Sie Bilder in Binärdateien, indem Sie in Photoshop unter Bild -> Anpassungen -> Schwellenwert den entsprechenden Schwellenwert festlegen.
  3. Stellen Sie sicher, dass der Schwellenwert richtig ausgewählt ist. Verwenden Sie das Zauberstab-Werkzeug mit 0 Toleranz, Punktmuster, zusammenhängend, ohne Anti-Aliasing. Überprüfen Sie, ob Kanten, bei denen Auswahlunterbrechungen keine falschen Kanten sind, die durch einen falschen Schwellenwert eingeführt wurden. Tatsächlich sind alle inneren Punkte dieses Labyrinths von Anfang an zugänglich.
  4. Fügen Sie dem Labyrinth künstliche Ränder hinzu, um sicherzustellen, dass der virtuelle Reisende nicht darum herumgeht :)
  5. Implementieren Sie die Breitensuche (BFS) in Ihrer Lieblingssprache und führen Sie sie von Anfang an aus. Ich bevorzuge MATLAB für diese Aufgabe. Wie @Thomas bereits erwähnt hat, besteht keine Notwendigkeit, sich mit der regelmäßigen Darstellung von Diagrammen zu beschäftigen. Sie können direkt mit binärisierten Bildern arbeiten.

Hier ist der MATLAB-Code für BFS:

function path = solve_maze(img_file)
  %% Init data
  img = imread(img_file);
  img = rgb2gray(img);
  maze = img > 0;
  start = [985 398];
  finish = [26 399];

  %% Init BFS
  n = numel(maze);
  Q = zeros(n, 2);
  M = zeros([size(maze) 2]);
  front = 0;
  back = 1;

  function push(p, d)
    q = p + d;
    if maze(q(1), q(2)) && M(q(1), q(2), 1) == 0
      front = front + 1;
      Q(front, :) = q;
      M(q(1), q(2), :) = reshape(p, [1 1 2]);
    end
  end

  push(start, [0 0]);

  d = [0 1; 0 -1; 1 0; -1 0];

  %% Run BFS
  while back <= front
    p = Q(back, :);
    back = back + 1;
    for i = 1:4
      push(p, d(i, :));
    end
  end

  %% Extracting path
  path = finish;
  while true
    q = path(end, :);
    p = reshape(M(q(1), q(2), :), 1, 2);
    path(end + 1, :) = p;
    if isequal(p, start) 
      break;
    end
  end
end

Es ist wirklich sehr einfach und Standard, es sollte keine Schwierigkeiten geben, dies in Python oder was auch immer zu implementieren .

Und hier ist die Antwort:

Geben Sie hier die Bildbeschreibung ein

Mikhail
quelle
1
@Whymarrh Nun, für "Nur dieses Bild" hast du jetzt tatsächlich eine Antwort. Haben Sie spezielle Fragen? Die Punkte 1 bis 4 aus meiner Liste sind die manuelle Verarbeitung, nach der ich gefragt habe. Punkt 5 ist ein BFS - der grundlegende Algorithmus für Diagramme. Er kann jedoch direkt auf Bilder angewendet werden, ohne Pixel in Scheitelpunkte und Nachbarn in Kanten umzuwandeln.
Mikhail
Ich habe das Gefühl, dass Sie alles abgedeckt haben. Ich versuche, das zu implementieren, was Sie in Python gesagt haben (mit DFS anstelle von BFS, nur weil ich das schon einmal codiert habe). Ich werde zurück sein, um die Frage zu aktualisieren / Antworten in Kürze zu akzeptieren.
Whymarrh
2
@Whymarrh DFS wird Sie nicht auf dem kürzesten Weg finden, während BFS dies tun wird. Sie sind von Natur aus gleich, der einzige Unterschied ist die zugrunde liegende Struktur. Stapel (FILO) für DFS und Warteschlange (FIFO) für BFS.
Mikhail
3
BFS ist hier die richtige Wahl, da es einen kürzesten Pfad erzeugt, der einen "vernünftigen" Pfad ergibt, selbst wenn die Korridore viel breiter als 1 Pixel sind. OTOH DFS wird dazu neigen, Korridore und vielversprechende Labyrinthregionen mit einem "Flutfüllmuster" zu erkunden.
j_random_hacker
1
@JosephKern Path überlappt keine Wände. Entfernen Sie einfach alle roten Pixel und los geht's.
Mikhail
160

Diese Lösung ist in Python geschrieben. Vielen Dank an Mikhail für die Hinweise zur Bildvorbereitung.

Eine animierte Breitensuche:

Animierte Version von BFS

Das fertige Labyrinth:

Labyrinth abgeschlossen

#!/usr/bin/env python

import sys

from Queue import Queue
from PIL import Image

start = (400,984)
end = (398,25)

def iswhite(value):
    if value == (255,255,255):
        return True

def getadjacent(n):
    x,y = n
    return [(x-1,y),(x,y-1),(x+1,y),(x,y+1)]

def BFS(start, end, pixels):

    queue = Queue()
    queue.put([start]) # Wrapping the start tuple in a list

    while not queue.empty():

        path = queue.get() 
        pixel = path[-1]

        if pixel == end:
            return path

        for adjacent in getadjacent(pixel):
            x,y = adjacent
            if iswhite(pixels[x,y]):
                pixels[x,y] = (127,127,127) # see note
                new_path = list(path)
                new_path.append(adjacent)
                queue.put(new_path)

    print "Queue has been exhausted. No answer was found."


if __name__ == '__main__':

    # invoke: python mazesolver.py <mazefile> <outputfile>[.jpg|.png|etc.]
    base_img = Image.open(sys.argv[1])
    base_pixels = base_img.load()

    path = BFS(start, end, base_pixels)

    path_img = Image.open(sys.argv[1])
    path_pixels = path_img.load()

    for position in path:
        x,y = position
        path_pixels[x,y] = (255,0,0) # red

    path_img.save(sys.argv[2])

Hinweis: Markiert ein weißes besuchtes Pixelgrau. Dadurch entfällt die Notwendigkeit einer besuchten Liste, dies erfordert jedoch ein zweites Laden der Bilddatei von der Festplatte, bevor ein Pfad gezeichnet wird (wenn Sie nicht möchten, dass ein zusammengesetztes Bild des endgültigen Pfads und aller Pfade erstellt wird).

Eine leere Version des Labyrinths, das ich benutzt habe.

Joseph Kern
quelle
13
Da Sie großartig genug waren, um zurück zu kommen und mich zu bewerten, selbst nachdem Ihre Frage beantwortet wurde, habe ich ein animiertes GIF des BFS erstellt, um den Prozess besser zu visualisieren.
Joseph Kern
1
Schön, danke. Für andere, die wie ich damit herumspielen möchten, möchte ich meine Tipps anhand der Schwierigkeiten teilen, mit denen ich konfrontiert war. 1) Konvertieren Sie das Bild entweder in reines Schwarzweiß oder ändern Sie die Funktion 'isWhite ()' so, dass nahezu Weiß | Schwarz akzeptiert wird. Ich habe eine 'cleanImage'-Methode geschrieben, bei der alle Pixel vorverarbeitet wurden, um sie entweder in reines Weiß oder Schwarz umzuwandeln. Andernfalls findet der Algorithmus keinen Pfad. 2) Lesen Sie das Bild explizit als RGB ein [base_img = Image.open (img_in); base_img = base_img.convert ('RGB')]. Um ein GIF zu erhalten, geben Sie mehrere Bilder aus und führen Sie dann 'convert -delay 5 -loop 1 * .jpg bfs.gif' aus.
Stefano
1
fehlender Einzug in Zeile 13
sloewen
81

Ich habe versucht, die A-Star-Suche für dieses Problem zu implementieren. Dicht gefolgt die Umsetzung von Joseph Kern für den Rahmen und der Algorithmus Pseudo - Code angegeben hier :

def AStar(start, goal, neighbor_nodes, distance, cost_estimate):
    def reconstruct_path(came_from, current_node):
        path = []
        while current_node is not None:
            path.append(current_node)
            current_node = came_from[current_node]
        return list(reversed(path))

    g_score = {start: 0}
    f_score = {start: g_score[start] + cost_estimate(start, goal)}
    openset = {start}
    closedset = set()
    came_from = {start: None}

    while openset:
        current = min(openset, key=lambda x: f_score[x])
        if current == goal:
            return reconstruct_path(came_from, goal)
        openset.remove(current)
        closedset.add(current)
        for neighbor in neighbor_nodes(current):
            if neighbor in closedset:
                continue
            if neighbor not in openset:
                openset.add(neighbor)
            tentative_g_score = g_score[current] + distance(current, neighbor)
            if tentative_g_score >= g_score.get(neighbor, float('inf')):
                continue
            came_from[neighbor] = current
            g_score[neighbor] = tentative_g_score
            f_score[neighbor] = tentative_g_score + cost_estimate(neighbor, goal)
    return []

Da A-Star ein heuristischer Suchalgorithmus ist, müssen Sie eine Funktion entwickeln, die die verbleibenden Kosten (hier: Entfernung) schätzt, bis das Ziel erreicht ist. Wenn Sie mit einer suboptimalen Lösung nicht vertraut sind, sollten die Kosten nicht überschätzt werden. Eine konservative Wahl wäre hier die Entfernung nach Manhattan (oder Taxi), da dies die geradlinige Entfernung zwischen zwei Punkten im Raster für das verwendete Viertel Von Neumann darstellt. (Was in diesem Fall die Kosten niemals überschätzen würde.)

Dies würde jedoch die tatsächlichen Kosten für das jeweilige Labyrinth erheblich unterschätzen. Daher habe ich zum Vergleich zwei weitere Entfernungsmetriken im Quadrat der euklidischen Entfernung und die Manhattan-Entfernung multipliziert mit vier hinzugefügt. Diese können jedoch die tatsächlichen Kosten überschätzen und daher zu suboptimalen Ergebnissen führen.

Hier ist der Code:

import sys
from PIL import Image

def is_blocked(p):
    x,y = p
    pixel = path_pixels[x,y]
    if any(c < 225 for c in pixel):
        return True
def von_neumann_neighbors(p):
    x, y = p
    neighbors = [(x-1, y), (x, y-1), (x+1, y), (x, y+1)]
    return [p for p in neighbors if not is_blocked(p)]
def manhattan(p1, p2):
    return abs(p1[0]-p2[0]) + abs(p1[1]-p2[1])
def squared_euclidean(p1, p2):
    return (p1[0]-p2[0])**2 + (p1[1]-p2[1])**2

start = (400, 984)
goal = (398, 25)

# invoke: python mazesolver.py <mazefile> <outputfile>[.jpg|.png|etc.]

path_img = Image.open(sys.argv[1])
path_pixels = path_img.load()

distance = manhattan
heuristic = manhattan

path = AStar(start, goal, von_neumann_neighbors, distance, heuristic)

for position in path:
    x,y = position
    path_pixels[x,y] = (255,0,0) # red

path_img.save(sys.argv[2])

Hier sind einige Bilder zur Visualisierung der Ergebnisse (inspiriert von dem von Joseph Kern ). Die Animationen zeigen nach jeweils 10000 Iterationen der Haupt-while-Schleife jeweils einen neuen Frame.

Breitensuche:

Breitensuche

A-Star Manhattan Entfernung:

A-Star Manhattan Entfernung

A-Stern im Quadrat euklidischer Abstand:

A-Stern im Quadrat euklidischer Abstand

A-Star Manhattan Entfernung multipliziert mit vier:

A-Star Manhattan Distance multipliziert mit vier

Die Ergebnisse zeigen, dass sich die untersuchten Regionen des Labyrinths hinsichtlich der verwendeten Heuristik erheblich unterscheiden. Daher erzeugt der euklidische Quadratabstand sogar einen anderen (suboptimalen) Pfad als die anderen Metriken.

In Bezug auf die Leistung des A-Star-Algorithmus in Bezug auf die Laufzeit bis zur Beendigung ist zu beachten, dass sich im Vergleich zur Breadth-First Search (BFS), bei der nur die "Zielgenauigkeit" von bewertet werden muss, viele Bewertungen der Entfernungs- und Kostenfunktionen ergeben jede Kandidatenposition. Ob die Kosten für diese zusätzlichen Funktionsbewertungen (A-Star) die Kosten für die größere Anzahl zu überprüfender Knoten (BFS) überwiegen und insbesondere, ob die Leistung für Ihre Anwendung überhaupt ein Problem darstellt oder nicht, ist eine Frage der individuellen Wahrnehmung und kann natürlich nicht allgemein beantwortet werden.

Eine Sache , die kann im allgemeinen gesagt werden , ob oder nicht ein informierter Suchalgorithmus (wie A-Star) im Vergleich die bessere Wahl sein könnte zu einer erschöpfenden Suche (zB BFS) ist die folgende. Mit der Anzahl der Dimensionen des Labyrinths, dh dem Verzweigungsfaktor des Suchbaums, wächst der Nachteil einer erschöpfenden Suche (erschöpfende Suche) exponentiell. Mit zunehmender Komplexität wird es immer weniger machbar, und irgendwann sind Sie mit jedem Ergebnispfad ziemlich zufrieden , ob (ungefähr) optimal oder nicht.

moooeeeep
quelle
1
"A-Star Manhattan Distance multipliziert mit vier"? A-Star ist kein A-Star, wenn die Heuristik die Entfernung überschätzen kann. (Und garantiert daher auch nicht, einen kürzesten Weg zu finden)
Beispiel
@example Wenn man eine nicht zulässige heuristische Funktion anwendet, kann der Algorithmus natürlich möglicherweise nicht die optimale Lösung finden (wie ich in meiner Antwort ausgeführt habe). Aber ich würde aus diesem Grund nicht so weit gehen, den grundlegenden Algorithmus umzubenennen.
Moooeeeep
38

Baumsuche ist zu viel. Das Labyrinth ist entlang der Lösungswege von Natur aus trennbar.

(Vielen Dank an rainman002 von Reddit für den Hinweis.)

Aus diesem Grund können Sie schnell verbundene Komponenten verwenden , um die verbundenen Abschnitte der Labyrinthwand zu identifizieren. Dies iteriert zweimal über die Pixel.

Wenn Sie daraus ein schönes Diagramm der Lösungspfade machen möchten, können Sie binäre Operationen mit Strukturierungselementen verwenden, um die "Sackgasse" -Pfade für jede verbundene Region auszufüllen.

Demo-Code für MATLAB folgt. Es könnte Optimierungen verwenden, um das Ergebnis besser zu bereinigen, verallgemeinerbar zu machen und es schneller laufen zu lassen. (Irgendwann, wenn es nicht 2:30 Uhr ist.)

% read in and invert the image
im = 255 - imread('maze.jpg');

% sharpen it to address small fuzzy channels
% threshold to binary 15%
% run connected components
result = bwlabel(im2bw(imfilter(im,fspecial('unsharp')),0.15));

% purge small components (e.g. letters)
for i = 1:max(reshape(result,1,1002*800))
    [count,~] = size(find(result==i));
    if count < 500
        result(result==i) = 0;
    end
end

% close dead-end channels
closed = zeros(1002,800);
for i = 1:max(reshape(result,1,1002*800))
    k = zeros(1002,800);
    k(result==i) = 1; k = imclose(k,strel('square',8));
    closed(k==1) = i;
end

% do output
out = 255 - im;
for x = 1:1002
    for y = 1:800
        if closed(x,y) == 0
            out(x,y,:) = 0;
        end
    end
end
imshow(out);

Ergebnis des aktuellen Codes

Jim Gray
quelle
24

Verwendet eine Warteschlange für eine kontinuierliche Füllung mit Schwellenwert. Schiebt das Pixel links vom Eingang in die Warteschlange und startet dann die Schleife. Wenn ein Pixel in der Warteschlange dunkel genug ist, ist es hellgrau (über dem Schwellenwert) und alle Nachbarn werden in die Warteschlange verschoben.

from PIL import Image
img = Image.open("/tmp/in.jpg")
(w,h) = img.size
scan = [(394,23)]
while(len(scan) > 0):
    (i,j) = scan.pop()
    (r,g,b) = img.getpixel((i,j))
    if(r*g*b < 9000000):
        img.putpixel((i,j),(210,210,210))
        for x in [i-1,i,i+1]:
            for y in [j-1,j,j+1]:
                scan.append((x,y))
img.save("/tmp/out.png")

Lösung ist der Korridor zwischen grauer Wand und farbiger Wand. Beachten Sie, dass dieses Labyrinth mehrere Lösungen hat. Auch dies scheint nur zu funktionieren.

Lösung

Kylefinn
quelle
1
Interessante naive Auflösung, basierend auf der Hand-on-Wall-Methode. In der Tat nicht der beste, aber ich mag es.
Zessx
23

Los geht's: Labyrinth-Löser-Python (GitHub)

Geben Sie hier die Bildbeschreibung ein

Ich hatte Spaß daran herumzuspielen und erweiterte Joseph Kerns Antwort. Nicht davon abzulenken; Ich habe nur ein paar kleinere Ergänzungen für alle anderen gemacht, die daran interessiert sein könnten, damit herumzuspielen.

Es ist ein Python-basierter Solver, der BFS verwendet, um den kürzesten Weg zu finden. Meine wichtigsten Ergänzungen zu der Zeit sind:

  1. Das Bild wird vor der Suche bereinigt (dh in reines Schwarzweiß konvertieren)
  2. Generieren Sie automatisch ein GIF.
  3. Generieren Sie automatisch eine AVI.

Derzeit sind die Start- / Endpunkte für dieses Beispiellabyrinth fest codiert, aber ich plane, es so zu erweitern, dass Sie die entsprechenden Pixel auswählen können.

stefano
quelle
1
Großartig, danke, es lief nicht auf BSD / Darwin / Mac, einige Abhängigkeiten und das Shell-Skript erforderten geringfügige Änderungen für diejenigen, die Mac ausprobieren möchten: [maze-solver-python]: github.com/holg/maze- Solver-Python
HolgT
@ HolgT: Schön, dass Sie es nützlich fanden. Ich freue mich über Pull-Anfragen. :)
Stefano
5

Ich würde mich für die Matrix-of-Bools-Option entscheiden. Wenn Sie feststellen, dass Standard-Python-Listen dafür zu ineffizient sind, können Sie numpy.boolstattdessen ein Array verwenden. Der Speicher für ein Labyrinth mit 1000 x 1000 Pixeln beträgt dann nur 1 MB.

Erstellen Sie keine Baum- oder Diagrammdatenstrukturen. Das ist nur eine Art, darüber nachzudenken, aber nicht unbedingt eine gute Art, es im Gedächtnis darzustellen. Eine boolesche Matrix ist sowohl einfacher zu codieren als auch effizienter.

Verwenden Sie dann den A * -Algorithmus, um das Problem zu lösen. Verwenden Sie für die Entfernungsheuristik die Manhattan-Entfernung ( distance_x + distance_y).

Stellen Sie Knoten durch ein Tupel von (row, column)Koordinaten dar. Immer wenn der Algorithmus ( Wikipedia-Pseudocode ) "Nachbarn" fordert, ist es eine einfache Sache, die vier möglichen Nachbarn zu durchlaufen (beachten Sie die Bildränder!).

Wenn Sie feststellen, dass es immer noch zu langsam ist, können Sie versuchen, das Bild vor dem Laden zu verkleinern. Achten Sie darauf, dabei keine engen Pfade zu verlieren.

Möglicherweise ist es auch in Python möglich, eine 1: 2-Verkleinerung durchzuführen, um sicherzustellen, dass Sie keine möglichen Pfade verlieren. Eine interessante Option, aber es bedarf etwas mehr Überlegungen.

Thomas
quelle
Dieser ausgezeichnete Blog-Beitrag zeigt, wie man ein Labyrinth in Mathematica löst. Die Übersetzung der Methode in Python sollte kein Problem sein
Boris Gorelik
Ich habe die Frage aktualisiert. Wenn ich RGB-Tripel anstelle von booleanWerten verwende, wird der Speicher dann immer noch verglichen? Die Matrix ist dann 2400 * 1200. Und würde A * über BFS einen signifikanten Einfluss auf die tatsächliche Laufzeit haben?
Whymarrh
@Whymarrh, die Bittiefe kann zur Kompensation schrumpfen. 2 Bits pro Pixel sollten für jeden ausreichen.
Brian Cain
5

Hier sind ein paar Ideen.

(1. Bildverarbeitung :)

1.1 Laden Sie das Bild als RGB- Pixelkarte. In C # ist die Verwendung trivial system.drawing.bitmap. In Sprachen ohne einfache Unterstützung für das Imaging konvertieren Sie das Bild einfach in das tragbare Pixmap-Format (PPM) (eine Unix-Textdarstellung, erzeugt große Dateien) oder ein einfaches Binärdateiformat, das Sie leicht lesen können, wie z. B. BMP oder TGA . ImageMagick unter Unix oder IrfanView unter Windows.

1.2 Sie können, wie bereits erwähnt, die Daten vereinfachen, indem Sie (R + G + B) / 3 für jedes Pixel als Indikator für den Grauton verwenden und dann den Wert für die Erstellung einer Schwarzweißtabelle als Schwellenwert festlegen. Etwas nahe 200 unter der Annahme von 0 = Schwarz und 255 = Weiß entfernt die JPEG-Artefakte.

(2. Lösungen :)

2.1 Tiefensuche: Initiieren Sie einen leeren Stapel mit Startposition, sammeln Sie verfügbare Folgebewegungen, wählen Sie eine nach dem Zufallsprinzip aus und schieben Sie sie auf den Stapel, fahren Sie fort, bis das Ende oder eine Sackgasse erreicht ist. Wenn Sie beim Stoppen des Stacks den Stapel öffnen, müssen Sie nachverfolgen, welche Positionen auf der Karte besucht wurden. Wenn Sie also verfügbare Züge sammeln, nehmen Sie nie zweimal denselben Weg. Sehr interessant zu animieren.

2.2 Breitensuche: Zuvor erwähnt, ähnlich wie oben, jedoch nur unter Verwendung von Warteschlangen. Auch interessant zu animieren. Dies funktioniert wie das Ausfüllen von Bildbearbeitungssoftware. Ich denke, Sie können möglicherweise ein Labyrinth in Photoshop mit diesem Trick lösen.

2.3 Wandfolger: Geometrisch gesehen ist ein Labyrinth eine gefaltete / gewundene Röhre. Wenn Sie Ihre Hand an der Wand halten, finden Sie schließlich den Ausgang;) Dies funktioniert nicht immer. Es gibt bestimmte Annahmen bezüglich perfekter Labyrinthe usw., zum Beispiel enthalten bestimmte Labyrinthe Inseln. Schauen Sie doch mal nach; es ist faszinierend.

(3. Kommentare :)

Dies ist die schwierige Frage. Es ist leicht, Labyrinthe zu lösen, wenn sie in einem einfachen formalen Array dargestellt werden, wobei jedes Element ein Zelltyp mit Nord-, Ost-, Süd- und Westwänden und einem besuchten Flaggenfeld ist. Wenn Sie dies jedoch anhand einer handgezeichneten Skizze versuchen, wird es chaotisch. Ich denke ehrlich, dass der Versuch, die Skizze zu rationalisieren, Sie verrückt machen wird. Dies ist vergleichbar mit Computer-Vision-Problemen, die ziemlich kompliziert sind. Vielleicht ist es einfacher und doch verschwenderischer, direkt auf die Imagemap zuzugreifen.

Linoleum
quelle
2

Hier ist eine Lösung mit R.

### download the image, read it into R, converting to something we can play with...
library(jpeg)
url <- "https://i.stack.imgur.com/TqKCM.jpg"
download.file(url, "./maze.jpg", mode = "wb")
jpg <- readJPEG("./maze.jpg")

### reshape array into data.frame
library(reshape2)
img3 <- melt(jpg, varnames = c("y","x","rgb"))
img3$rgb <- as.character(factor(img3$rgb, levels = c(1,2,3), labels=c("r","g","b")))

## split out rgb values into separate columns
img3 <- dcast(img3, x + y ~ rgb)

RGB zu Graustufen, siehe: https://stackoverflow.com/a/27491947/2371031

# convert rgb to greyscale (0, 1)
img3$v <- img3$r*.21 + img3$g*.72 + img3$b*.07
# v: values closer to 1 are white, closer to 0 are black

## strategically fill in some border pixels so the solver doesn't "go around":
img3$v2 <- img3$v
img3[(img3$x == 300 | img3$x == 500) & (img3$y %in% c(0:23,988:1002)),"v2"]  = 0

# define some start/end point coordinates
pts_df <- data.frame(x = c(398, 399),
                     y = c(985, 26))

# set a reference value as the mean of the start and end point greyscale "v"s
ref_val <- mean(c(subset(img3, x==pts_df[1,1] & y==pts_df[1,2])$v,
                  subset(img3, x==pts_df[2,1] & y==pts_df[2,2])$v))

library(sp)
library(gdistance)
spdf3 <- SpatialPixelsDataFrame(points = img3[c("x","y")], data = img3["v2"])
r3 <- rasterFromXYZ(spdf3)

# transition layer defines a "conductance" function between any two points, and the number of connections (4 = Manhatten distances)
# x in the function represents the greyscale values ("v2") of two adjacent points (pixels), i.e., = (x1$v2, x2$v2)
# make function(x) encourages transitions between cells with small changes in greyscale compared to the reference values, such that: 
# when v2 is closer to 0 (black) = poor conductance
# when v2 is closer to 1 (white) = good conductance
tl3 <- transition(r3, function(x) (1/max( abs( (x/ref_val)-1 ) )^2)-1, 4) 

## get the shortest path between start, end points
sPath3 <- shortestPath(tl3, as.numeric(pts_df[1,]), as.numeric(pts_df[2,]), output = "SpatialLines")

## fortify for ggplot
sldf3 <- fortify(SpatialLinesDataFrame(sPath3, data = data.frame(ID = 1)))

# plot the image greyscale with start/end points (red) and shortest path (green)
ggplot(img3) +
  geom_raster(aes(x, y, fill=v2)) +
  scale_fill_continuous(high="white", low="black") +
  scale_y_reverse() +
  geom_point(data=pts_df, aes(x, y), color="red") +
  geom_path(data=sldf3, aes(x=long, y=lat), color="green")

Voila!

Lösung, die den kürzesten Weg richtig findet

Dies passiert, wenn Sie einige Randpixel nicht ausfüllen (Ha!) ...

Lösungsversion, bei der der Löser das Labyrinth umrundet

Vollständige Offenlegung: Ich habe selbst eine sehr ähnliche Frage gestellt und beantwortet, bevor ich diese gefunden habe. Dann durch die Magie von SO, fand diese als eine der Top "Related Questions". Ich dachte, ich würde dieses Labyrinth als zusätzlichen Testfall verwenden ... Ich war sehr erfreut festzustellen, dass meine Antwort dort auch für diese Anwendung mit sehr geringen Änderungen funktioniert.

Brian D.
quelle
0

Die gute Lösung wäre, dass anstatt die Nachbarn nach Pixeln zu finden, dies nach Zellen erfolgt, da ein Korridor 15 Pixel haben kann, sodass er im selben Korridor Aktionen wie links oder rechts ausführen kann, während dies so geschieht, als ob die Verschiebung erfolgt Wäre ein Würfel, wäre es eine einfache Aktion wie AUF, AB, LINKS ODER RECHTS

black_john
quelle
Können Sie das Lösungsdiagramm und den Algorithmus wie den Rest der Antwort hinzufügen, um Ihren Punkt zu validieren? Es ist besser, wenn Sie diese hinzufügen können, um Ihrer Antwort mehr Gewicht zu verleihen, damit andere Ihre Antwort tatsächlich besser verstehen können.
Himanshu Bansal