it-swarm.com.de

Darstellen und Lösen eines Labyrinths anhand eines Bildes

Wie kann ein Labyrinth bei einem Bild am besten dargestellt und gelöst werden?

The cover image of The Scope Issue 134

Was ist der beste Weg, um ein JPEG-Bild (wie oben zu sehen) einzulesen, in eine Datenstruktur zu analysieren und das Labyrinth zu lösen? Mein erster Instinkt ist, das Bild Pixel für Pixel zu lesen und in einer Liste (Array) mit booleschen Werten zu speichern: True für ein weißes Pixel und False fü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ßer Pixel irgendwo an der Wand einen unbeabsichtigten Pfad erzeugen kann.

Eine andere Methode (die mir nach einiger Überlegung einfiel) ist das Konvertieren des Bildes in eine SVG-Datei - eine Liste von Pfaden, die auf einer Leinwand gezeichnet werden. Auf diese Weise könnten die Pfade in dieselbe Art von Liste (boolesche Werte) eingelesen werden, wobei True einen Pfad oder eine Wand angibt, False einen befahrbaren Platz angibt. Ein Problem bei dieser Methode tritt auf, wenn die Konvertierung nicht zu 100% genau ist und nicht alle Wände vollständig miteinander verbunden sind, wodurch Lücken entstehen.

Ein Problem bei der Konvertierung in SVG ist auch, dass die Zeilen nicht "perfekt" gerade sind. Dies führt dazu, dass die Pfade kubische Bezier-Kurven sind. Mit einer Liste (Array) von booleschen Werten, die durch Ganzzahlen indiziert sind, können die Kurven nicht einfach übertragen werden, und alle Punkte, die diese Linie in der Kurve darstellt, müssen berechnet werden, stimmen jedoch nicht genau mit Listenindizes überein.

Ich gehe davon aus, dass eine dieser Methoden zwar funktionieren kann (obwohl dies wahrscheinlich nicht der Fall ist), dass sie angesichts eines so großen Bildes jedoch äußerst ineffizient sind und dass es einen besseren Weg gibt. Wie wird dies am besten (am effizientesten und/oder mit der geringsten Komplexität) durchgeführt? Gibt es überhaupt einen besten Weg?

Dann kommt das Lösen des Labyrinths. Wenn ich eine der ersten beiden Methoden verwende, bekomme ich im Wesentlichen eine Matrix. Gemäß dieser Antwort ist ein guter Weg, ein Labyrinth darzustellen, die Verwendung eines Baums, und eine gute Lösung ist die Verwendung des A * -Algorithmus . Wie würde man aus dem Bild einen Baum erstellen? Irgendwelche Ideen?

TL; DR
Am besten zu analysieren? In welche Datenstruktur? Wie würde die besagte Struktur beim Lösen helfen?

UPDATE
Ich habe versucht, das zu implementieren, was @Mikhail in Python geschrieben hat, und numpy als @Thomas empfohlen. Ich glaube, 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, [])
241
Whymarrh

Hier ist eine Lösung.

  1. Konvertieren Sie das Bild in Graustufen (noch nicht binär), und stellen Sie die Gewichte für die Farben so ein, dass das endgültige Graustufenbild ungefähr gleichförmig ist. Sie können dies einfach tun, indem Sie die Schieberegler in Photoshop in Bild -> Anpassungen -> Schwarzweiß steuern.
  2. Konvertieren Sie das Bild in ein binäres Format, indem Sie in Photoshop unter Bild -> Anpassungen -> Schwellenwert den entsprechenden Schwellenwert einstellen.
  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. Prüfen Sie, ob Kanten, an denen Auswahlbrüche auftreten, 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 um ihn herumgeht.
  5. Implementieren Sie wide-first search (BFS) in Ihrer bevorzugten Sprache und führen Sie es von Anfang an aus. Ich bevorzuge MATLAB für diese Aufgabe. Wie bereits bei @Thomas erwähnt, besteht keine Notwendigkeit, sich mit der regelmäßigen Darstellung von Graphen zu beschäftigen. Sie können direkt mit binarisierten 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 standardisiert, es sollte keine Schwierigkeiten geben, dies in Python oder was auch immer zu implementieren.

Und hier ist die Antwort:

Enter image description here

223
Mikhail

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

Eine animierte Breitensuche:

Animated version of BFS

Das abgeschlossene Labyrinth:

Completed Maze

#!/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 __== '__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ß besuchtes Pixel grau. Dadurch entfällt die Notwendigkeit einer besuchten Liste. Dies erfordert jedoch ein zweites Laden der Image-Datei von der Festplatte, bevor Sie einen Pfad zeichnen (wenn Sie nicht möchten, dass ein zusammengesetztes Image des endgültigen Pfads und ALLE Pfade verwendet wird).

Eine leere Version des Labyrinths, das ich verwendet habe.

152
Joseph Kern

Ich habe versucht, die A-Star-Suche für dieses Problem zu implementieren. Verfolgte genau die Implementierung von Joseph Kern für das Framework und den angegebenen Algorithmus-Pseudocode 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 es sich bei A-Star um einen heuristischen Suchalgorithmus handelt, müssen Sie eine Funktion entwickeln, mit der die verbleibenden Kosten (hier: Entfernung) geschätzt werden, bis das Ziel erreicht ist. Sofern Sie nicht mit einer suboptimalen Lösung zufrieden sind, sollten Sie die Kosten nicht überschätzen. Eine konservative Wahl wäre hier die Manhattan- (oder Taxi-) Entfernung , da dies die geradlinige Entfernung zwischen zwei Punkten auf dem Gitter für das verwendete Von Neumann-Viertel darstellt. (Was in diesem Fall die Kosten niemals überschätzen würde.)

Dies würde jedoch die tatsächlichen Kosten für das vorliegende Labyrinth erheblich unterschätzen. Aus diesem Grund habe ich zum Vergleich zwei weitere Entfernungsmetriken zum 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 ein neues Bild.

Breitensuche:

Breadth-First Search

A-Star Manhattan Entfernung:

A-Star Manhattan Distance

A-Star Squared Euclidean Entfernung:

A-Star Squared Euclidean Distance

A-Star Manhattan Entfernung multipliziert mit vier:

A-Star Manhattan Distance multiplied by four

Die Ergebnisse zeigen, dass sich die untersuchten Bereiche des Labyrinths in Bezug auf die verwendeten Heuristiken erheblich unterscheiden. Der quadratische euklidische Abstand erzeugt als solcher 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 Breitensuche (BFS), bei der nur die "Zielgenauigkeit" von bewertet werden muss, eine Vielzahl von Entfernungs- und Kostenfunktionen summieren jede Kandidatenposition. Ob die Kosten für diese zusätzlichen Funktionsauswertungen (A-Star) die Kosten für die größere Anzahl zu überprüfender Knoten (BFS) überwiegen und 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, kann im Allgemeinen darüber gesagt werden, ob ein informierter Suchalgorithmus (wie A-Star) die bessere Wahl sein könnte als eine umfassende Suche (z. B. BFS). ist das Folgende. Mit der Anzahl der Dimensionen des Labyrinths, d. H. Dem Verzweigungsfaktor des Suchbaums, wächst der Nachteil einer erschöpfenden Suche (um erschöpfend zu suchen) exponentiell. Mit zunehmender Komplexität wird es immer weniger machbar, und irgendwann sind Sie ziemlich zufrieden mit dem Ergebnispfad any , sei es (ungefähr) optimal oder nicht.

78
moooeeeep

Baumsuche ist zu viel. Das Labyrinth ist an sich entlang der Lösungspfade trennbar.

(Danke an rainman002 von Reddit für den Hinweis auf mich.)

Aus diesem Grund können Sie schnell mit verbundene Komponenten die verbundenen Abschnitte der Labyrinthwand identifizieren. Dies wiederholt sich zweimal über die Pixel.

Wenn Sie daraus ein Nice-Diagramm der Lösungspfade erstellen möchten, können Sie binäre Operationen mit strukturierenden Elementen verwenden, um die "Sackgassen" -Pfade für jede verbundene Region einzugeben.

Demo-Code für MATLAB folgt. Es könnte das Tweaking verwenden, um das Ergebnis besser zu bereinigen, es allgemeiner zu machen und es schneller zu machen. (Manchmal, 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);

result of current code

34
Jim Gray

Verwendet eine Warteschlange für eine kontinuierliche Füllung mit Schwellenwert. Schiebt das Pixel links vom Eingang in die Warteschlange und startet die Schleife. Wenn ein Pixel in der Warteschlange dunkel genug ist, wird 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 bietet. Dies scheint auch nur zu funktionieren.

Solution

23
kylefinn

Hier gehts: Labyrinth-Solver-Python (GitHub)

enter image description here

Ich hatte Spaß daran, herumzuspielen und erweiterte die Antwort von Joseph Kern . Nicht davon ablenken; Ich habe nur ein paar kleine Ergänzungen für alle anderen gemacht, die daran interessiert sind, mit diesem zu spielen.

Es ist ein Python-basierter Löser, der BFS verwendet, um den kürzesten Pfad zu finden. Meine wichtigsten Ergänzungen zu dieser Zeit sind:

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

So wie es aussieht, sind die Start-/Endpunkte für dieses Beispiellabyrinth hartcodiert. Ich plane jedoch, es so zu erweitern, dass Sie die geeigneten Pixel auswählen können.

22
stefano

Hier sind ein paar Ideen.

(1. Bildverarbeitung :)

1.1 Laden Sie das Bild als RGB pixel map. In C # ist es trivial mit system.drawing.bitmap. In Sprachen ohne einfache Bildunterstützung konvertieren Sie das Bild einfach in das Portable Pixmap-Format (PPM) (eine Unix-Textdarstellung, das große Dateien erzeugt) oder ein einfaches Binärdateiformat, das Sie leicht lesen können, z. B. BMP oder TGA . ImageMagick in Unix oder IrfanView in 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 Grautöne verwenden und dann den Wert für die Erstellung einer Schwarzweißtabelle festlegen. Etwas in der Nähe von 200, bei dem von 0 = Schwarz und 255 = Weiß ausgegangen wird, werden die JPEG-Artefakte gelöscht.

(2. Lösungen :)

2.1 Tiefensuche: Starten Sie einen leeren Stapel mit Startposition, sammeln Sie die verfügbaren Folgezüge, wählen Sie eine zufällig aus und drücken Sie auf den Stapel, fahren Sie fort, bis das Ende erreicht ist oder ein Deadend. Bei einem Deadend-Backtrack durch Aufstapeln des Stapels müssen Sie nachverfolgen, welche Positionen auf der Karte besucht wurden. Wenn Sie also verfügbare Züge sammeln, gehen Sie niemals zweimal denselben Weg. Sehr interessant zu animieren.

2.2 Breitensuche: Vorgenannt, ähnlich wie oben, jedoch nur mit Warteschlangen. Auch interessant zu animieren. Dies funktioniert wie Flutungsfüllung in Bildbearbeitungssoftware. Ich denke, Sie können mit diesem Trick möglicherweise ein Labyrinth in Photoshop lösen.

2.3 Wandnachfolger: Ein Labyrinth ist geometrisch eine gefaltete/gewundene Röhre. Wenn Sie Ihre Hand an der Wand lassen, werden Sie eventuell den Ausgang finden;) Dies funktioniert nicht immer. Es gibt bestimmte Annahmen bezüglich: Perfekte Labyrinthe usw. Zum Beispiel enthalten bestimmte Labyrinthe Inseln. Schau es nach; es ist faszinierend.

(3. Kommentare :)

Dies ist der knifflige. 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. In Anbetracht dessen, dass Sie dies mit einer von Hand gezeichneten Skizze versuchen, wird diese unordentlich. Ich glaube wirklich, dass der Versuch, die Skizze zu rationalisieren, Sie verrückt machen wird. Dies ist vergleichbar mit Computersichtproblemen, die ziemlich involviert sind. Möglicherweise ist es einfacher, direkt auf die Imagemap zuzugreifen, aber es ist umständlicher.

5
lino

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

Machen Sie sich keine Gedanken um das Erstellen von 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 programmieren als auch effizienter.

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

Stellen Sie Knoten durch einen Tupel mit (row, column)-Koordinaten dar. Immer wenn der Algorithmus ( Wikipedia Pseudocode ) nach "Nachbarn" ruft, ist es einfach, die vier möglichen Nachbarn zu durchlaufen (beachten Sie die Ränder des Bildes!).

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

Möglicherweise ist es auch möglich, eine 1: 2-Herunterskalierung in Python durchzuführen, um zu überprüfen, dass Sie keine möglichen Pfade verlieren. Eine interessante Option, aber es muss etwas mehr darüber nachgedacht werden.

5
Thomas

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!

solution that correctly finds shortest path

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

solution version where the solver goes around the maze

Vollständige Offenlegung: Ich habe eine sehr ähnliche Frage selbst gestellt und beantwortet, bevor ich diese gefunden habe. Dann durch die Magie von SO, fand dieser als einer 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 nur sehr geringen Änderungen funktioniert.

0
Brian D