Rappresentare e risolvere un labirinto con un'immagine


271

Qual è il modo migliore per rappresentare e risolvere un labirinto dato un'immagine?

L'immagine di copertina di The Scope Numero 134

Data un'immagine JPEG (come visto sopra), qual è il modo migliore per leggerlo, analizzarlo in qualche struttura di dati e risolvere il labirinto? Il mio primo istinto è leggere l'immagine in pixel per pixel e memorizzarla in un elenco (array) di valori booleani: Trueper un pixel bianco e Falseper un pixel non bianco (i colori possono essere scartati). Il problema con questo metodo è che l'immagine potrebbe non essere "pixel perfetta". Con ciò intendo semplicemente che se c'è un pixel bianco da qualche parte su un muro, potrebbe creare un percorso non intenzionale.

Un altro metodo (che mi è venuto in mente dopo un po 'di riflessione) è quello di convertire l'immagine in un file SVG - che è un elenco di percorsi disegnati su una tela. In questo modo, i percorsi potrebbero essere letti nello stesso tipo di elenco (valori booleani) in cui Trueindica un percorso o un muro, che Falseindica uno spazio percorribile . Un problema con questo metodo sorge se la conversione non è accurata al 100% e non collega completamente tutti i muri, creando spazi vuoti.

Anche un problema con la conversione in SVG è che le linee non sono "perfettamente" diritte. Ciò si traduce in curve cubiche più bezier dei percorsi. Con un elenco (array) di valori booleani indicizzati da numeri interi, le curve non si trasferirebbero facilmente e tutti i punti che si allineano sulla curva dovrebbero essere calcolati, ma non corrisponderanno esattamente agli indici dell'elenco.

Suppongo che mentre uno di questi metodi può funzionare (anche se probabilmente non lo è) che sono terribilmente inefficienti, data un'immagine così grande, e che esiste un modo migliore. Come si fa meglio (in modo più efficiente e / o con la minima complessità)? C'è anche un modo migliore?

Poi arriva la risoluzione del labirinto. Se uso uno dei primi due metodi, finirò essenzialmente con una matrice. Secondo questa risposta , un buon modo per rappresentare un labirinto è usare un albero e un buon modo per risolverlo è usare l' algoritmo A * . Come si potrebbe creare un albero dall'immagine? Qualche idea?

TL; DR Il
modo migliore per analizzare? In quale struttura di dati? In che modo la struttura potrebbe aiutare / ostacolare la risoluzione?

AGGIORNAMENTO
Ho provato a implementare ciò che @Mikhail ha scritto in Python, usando numpy, come raccomandato da @Thomas. Sento che l'algoritmo è corretto, ma non funziona come sperato. (Codice sotto.) La libreria PNG è 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, [])

12
Vorrei convertire il labirinto in bianco e nero e utilizzare un percorso per trovare il metodo degli automi cellulari per risolverlo.
Dan D.

Devi occuparti solo di quell'immagine o di molte immagini del genere? Cioè esiste un'opzione di elaborazione manuale specifica per questa determinata immagine?
Mikhail,

1
@Whymarrh Non codifico Python, ma sono abbastanza sicuro che dovresti spostarti visited.append(s)sotto a for.ife sostituirlo con visited.append(np). Un vertice viene visitato una volta aggiunto alla coda. In effetti, questo array dovrebbe essere chiamato "in coda". Puoi anche terminare BFS una volta che hai raggiunto il traguardo.
Mikhail,

2
@Whymarrh E sembra che tu abbia saltato l'implementazione del blocco di estrazione del percorso. Senza di essa, puoi solo scoprire se il traguardo è raggiungibile o meno, ma non come.
Mikhail,

1
Per scoprire se v'è una soluzione, un UnionFind e una scansione lineare è l'algoritmo più veloce. Non ti dà il percorso, ma ti dà una serie di tessere che avranno il percorso come sottoinsieme.
via

Risposte:


236

Ecco una soluzione

  1. Converti l'immagine in scala di grigi (non ancora binaria), regolando i pesi per i colori in modo che l'immagine in scala di grigi finale sia approssimativamente uniforme. Puoi farlo semplicemente controllando i cursori in Photoshop in Immagine -> Regolazioni -> Bianco e nero.
  2. Converti l'immagine in binario impostando la soglia appropriata in Photoshop in Immagine -> Regolazioni -> Soglia.
  3. Assicurarsi che la soglia sia selezionata correttamente. Usa lo strumento bacchetta magica con tolleranza 0, punto campione, contiguo, senza anti-aliasing. Verificare che i bordi in corrispondenza dei quali le interruzioni di selezione non siano falsi vengono introdotti da una soglia errata. In effetti, tutti i punti interni di questo labirinto sono accessibili dall'inizio.
  4. Aggiungi bordi artificiali al labirinto per assicurarti che il viaggiatore virtuale non lo percorra :)
  5. Implementa la ricerca breadth-first (BFS) nella tua lingua preferita ed eseguila dall'inizio. Preferisco MATLAB per questo compito. Come già accennato da @Thomas, non è necessario incasinare la rappresentazione regolare dei grafici. Puoi lavorare direttamente con l'immagine binarizzata.

Ecco il codice MATLAB per 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

È davvero molto semplice e standard, non dovrebbero esserci difficoltà nell'implementare questo in Python o altro.

Ed ecco la risposta:

Inserisci qui la descrizione dell'immagine


1
@Whymarrh Bene, per "Solo questa immagine" ora hai effettivamente una risposta. Hai domande specifiche? Gli articoli 1-4 dal mio elenco sono l'elaborazione manuale di cui chiedevo. L'elemento 5 è un BFS: l'algoritmo di base per i grafici, ma può essere applicato direttamente all'immagine, senza convertire i pixel in vertici e i vicini in bordi.
Mikhail,

Sento che hai coperto tutto. Sto provando a implementare ciò che hai detto in Python (usando DFS al posto di BFS, solo perché l'ho codificato una volta prima). Tornerò per aggiornare la domanda / accettare le risposte tra poco.
Whymarrh,

2
@Whymarrh DFS non ti troverà nel modo più breve, mentre BFS lo farà. Sono intrinsecamente gli stessi, l'unica differenza è la struttura sottostante. Stack (FILO) per DFS e coda (FIFO) per BFS.
Mikhail,

3
BFS è la scelta giusta qui, perché produce un percorso più breve, che fornisce un percorso "sensibile" anche quando i corridoi sono molto più larghi di 1 pixel. OTOH DFS tenderà a esplorare i corridoi e le regioni labirintiche senza compromessi con un modello di "alluvione".
j_random_hacker,

1
@JosephKern Path non si sovrappone a nessun muro. Basta rimuovere tutti i pixel rossi ed ecco fatto.
Mikhail,

160

Questa soluzione è scritta in Python. Grazie Mikhail per i suggerimenti sulla preparazione dell'immagine.

Una prima ricerca animata:

Versione animata di BFS

Il labirinto completato:

Labirinto completato

#!/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])

Nota: contrassegna un pixel bianco visitato grigio. Ciò elimina la necessità di un elenco visitato, ma richiede un secondo caricamento del file immagine dal disco prima di tracciare un percorso (se non si desidera un'immagine composita del percorso finale e TUTTI i percorsi presi).

Una versione vuota del labirinto che ho usato.


13
Dato che sei stato abbastanza fantastico da tornare e votarmi anche dopo aver risposto alla tua domanda, ho creato una gif animata di BFS, per aiutare a visualizzare meglio il processo.
Joseph Kern,

1
Bello, grazie. Per gli altri che desiderano giocare con questo, come ho fatto io, vorrei condividere i miei consigli in base alle difficoltà che ho incontrato. 1) O converti l'immagine in bianco e nero puro o modifica la tua funzione 'isWhite ()' per accettare quasi il bianco | nero. Ho scritto un metodo 'cleanImage' che ha preelaborato tutti i pixel convertendoli in bianco o nero puro, altrimenti l'algoritmo non riesce a trovare un percorso. 2) Leggere esplicitamente l'immagine come RGB [base_img = Image.open (img_in); base_img = base_img.convert ('RGB')]. Per ottenere una gif, genera diverse immagini ed esegui 'convert -delay 5 -loop 1 * .jpg bfs.gif'.
stefano,

1
rientro mancante nella riga 13
sloewen,

81

Ho provato a implementare la ricerca A-Star per questo problema. Seguito da vicino l'implementazione di Joseph Kern per il framework e l'algoritmo pseudocodice fornito qui :

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 []

Poiché A-Star è un algoritmo di ricerca euristica, è necessario elaborare una funzione che stima il costo rimanente (qui: distanza) fino al raggiungimento dell'obiettivo. A meno che non ti trovi a tuo agio con una soluzione non ottimale, non dovrebbe sopravvalutare il costo. Una scelta conservativa sarebbe qui la distanza di Manhattan (o taxi) in quanto rappresenta la distanza in linea retta tra due punti della griglia per il quartiere Von Neumann usato. (Che, in questo caso, non sopravvaluterebbe mai il costo.)

Ciò tuttavia sottovaluterebbe in modo significativo il costo effettivo per il labirinto dato a portata di mano. Pertanto ho aggiunto altre due metriche di distanza al quadrato della distanza euclidea e la distanza di Manhattan moltiplicata per quattro per il confronto. Questi tuttavia potrebbero sovrastimare il costo effettivo e potrebbero quindi produrre risultati non ottimali.

Ecco il codice:

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])

Ecco alcune immagini per una visualizzazione dei risultati (ispirata a quella pubblicata da Joseph Kern ). Le animazioni mostrano un nuovo fotogramma dopo ogni 10000 iterazioni del ciclo di ciclo principale.

Prima ricerca:

Ampia ricerca

A-Star Manhattan Distanza:

A-Star Manhattan Distance

Distanza euclidea quadrata a stella:

Distanza euclidea quadrata a stella

A-Star Manhattan Distance moltiplicata per quattro:

A-Star Manhattan Distance moltiplicata per quattro

I risultati mostrano che le regioni esplorate del labirinto differiscono considerevolmente per l'euristica utilizzata. Pertanto, la distanza euclidea quadrata produce anche un percorso diverso (non ottimale) rispetto alle altre metriche.

Per quanto riguarda le prestazioni dell'algoritmo A-Star in termini di tempo di esecuzione fino al termine, si noti che molta valutazione delle funzioni di distanza e costo si sommano rispetto alla Breadth-First Search (BFS) che deve solo valutare il "goaliness" di ogni posizione candidata. Indipendentemente dal fatto che il costo di queste valutazioni delle funzioni aggiuntive (A-Star) superi il costo per il maggior numero di nodi da controllare (BFS) e in particolare se le prestazioni siano o meno un problema per l'applicazione, è una questione di percezione individuale e ovviamente non si può rispondere in generale.

Una cosa che si può dire in generale sul fatto che un algoritmo di ricerca informato (come A-Star) potrebbe essere la scelta migliore rispetto a una ricerca esaustiva (ad esempio, BFS) è la seguente. Con il numero di dimensioni del labirinto, ovvero il fattore di ramificazione dell'albero di ricerca, lo svantaggio di una ricerca esaustiva (da cercare in modo esaustivo) cresce esponenzialmente. Con crescente complessità diventa sempre meno fattibile farlo e ad un certo punto sei praticamente soddisfatto di qualsiasi percorso di risultato, sia esso (approssimativamente) ottimale o meno.


1
"A-Star Manhattan Distance moltiplicato per quattro"? A-Star non è A-Star se l'euristico può sopravvalutare la distanza. (E quindi non garantisce nemmeno di trovare un percorso più breve)
esempio

@esempio Naturalmente, se si applica una funzione euristica non ammissibile, l'algoritmo potrebbe non riuscire a trovare la soluzione ottimale (come ho indicato nella mia risposta). Ma non vorrei spingermi fino a rinominare l'algoritmo di base per questo motivo.
Moooeeeep,

38

La ricerca dell'albero è troppo. Il labirinto è intrinsecamente separabile lungo i percorsi della soluzione.

(Grazie a rainman002 di Reddit per avermelo segnalato.)

Per questo motivo, è possibile utilizzare rapidamente i componenti collegati per identificare le sezioni connesse della parete del labirinto. Ciò scorre due volte sui pixel.

Se vuoi trasformarlo in un bel diagramma dei percorsi della soluzione, puoi quindi utilizzare le operazioni binarie con elementi di strutturazione per riempire i percorsi "vicolo cieco" per ogni regione connessa.

Segue il codice demo per MATLAB. Potrebbe utilizzare la messa a punto per ripulire meglio il risultato, renderlo più generalizzabile e farlo funzionare più velocemente. (A volte quando non sono le 2:30 del mattino.)

% 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);

risultato del codice corrente


24

Utilizza una coda per un riempimento continuo di soglia. Spinge il pixel a sinistra dell'ingresso sulla coda e quindi avvia il loop. Se un pixel in coda è abbastanza scuro, è di colore grigio chiaro (sopra la soglia) e tutti i vicini vengono inseriti nella coda.

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")

La soluzione è il corridoio tra muro grigio e muro colorato. Nota che questo labirinto ha più soluzioni. Inoltre, questo sembra semplicemente funzionare.

Soluzione


1
Interessante risoluzione ingenua, basata sul metodo hand-on-wall. Anzi, non il migliore, ma mi piace.
zessx,

23

Ecco qua: maze-solver-python (GitHub)

inserisci qui la descrizione dell'immagine

Mi sono divertito a giocarci e ho esteso la risposta di Joseph Kern . Per non toglierlo; Ho appena fatto alcune aggiunte minori per chiunque possa essere interessato a giocare con questo.

È un solutore basato su Python che utilizza BFS per trovare il percorso più breve. Le mie aggiunte principali, al momento, sono:

  1. L'immagine viene pulita prima della ricerca (ad es. Conversione in bianco e nero puro)
  2. Genera automaticamente una GIF.
  3. Genera automaticamente un AVI.

Allo stato attuale, i punti di inizio / fine sono codificati per questo labirinto di esempio, ma ho intenzione di estenderlo in modo da poter scegliere i pixel appropriati.


1
Fantastico, grazie, non funzionava su BSD / Darwin / Mac, alcune dipendenze e lo script della shell avevano bisogno di piccoli cambiamenti, per chi volesse provare su Mac: [maze-solver-python]: github.com/holg/maze- solver-python
HolgT

@HolgT: Sono contento che l'abbia trovato utile. Accolgo con favore qualsiasi richiesta pull per questo. :)
stefano,

5

Vorrei scegliere l'opzione matrix of bools. Se trovi che gli elenchi Python standard sono troppo inefficienti per questo, puoi numpy.boolinvece usare un array. Lo spazio di archiviazione per un labirinto di 1000x1000 pixel è quindi solo 1 MB.

Non preoccuparti di creare strutture di dati ad albero o grafici. Questo è solo un modo di pensarci, ma non necessariamente un buon modo per rappresentarlo in memoria; una matrice booleana è sia più semplice da codificare sia più efficiente.

Quindi utilizzare l'algoritmo A * per risolverlo. Per l'euristica della distanza, usa la distanza di Manhattan ( distance_x + distance_y).

Rappresenta i nodi con una tupla di (row, column)coordinate. Ogni volta che l'algoritmo ( pseudocodice di Wikipedia ) richiede "vicini", è una semplice questione di passare in rassegna i quattro possibili vicini (attenzione ai bordi dell'immagine!).

Se scopri che è ancora troppo lento, puoi provare a ridimensionare l'immagine prima di caricarla. Fare attenzione a non perdere percorsi stretti nel processo.

Forse è possibile eseguire un downscaling 1: 2 anche in Python, verificando che non si perda effettivamente alcun percorso. Un'opzione interessante, ma ha bisogno di un po 'più di pensiero.


Questo eccellente post sul blog mostra come risolvere un labirinto in matematica. Tradurre il metodo in pitone non dovrebbe essere un problema
Boris Gorelik,

Ho aggiornato la domanda. Se scelgo di usare le triple RGB al posto dei booleanvalori, la memoria comparerebbe comunque? La matrice è quindi 2400 * 1200. E A * su BFS avrebbe un impatto significativo sul tempo di esecuzione reale?
Whymarrh,

@Whymarrh, la profondità del bit può ridursi per compensare. 2 bit per pixel dovrebbero essere sufficienti per chiunque.
Brian Cain,

5

Ecco alcune idee.

(1. Elaborazione delle immagini :)

1.1 Caricare l'immagine come pixel map RGB . In C # è banale usare system.drawing.bitmap. In lingue senza un semplice supporto per l'imaging, converti l'immagine in un formato pixmap portatile (PPM) (una rappresentazione di testo Unix, produce file di grandi dimensioni) o in un semplice formato di file binario che puoi facilmente leggere, come BMP o TGA . ImageMagick in Unix o IrfanView in Windows.

1.2 È possibile, come accennato in precedenza, semplificare i dati prendendo (R + G + B) / 3 per ciascun pixel come indicatore di tono grigio e quindi soglia il valore per produrre una tabella in bianco e nero. Qualcosa vicino a 200 assumendo 0 = nero e 255 = bianco eliminerà i manufatti JPEG.

(2. Soluzioni :)

2.1 Ricerca prima profondità: avvia una pila vuota con la posizione iniziale, raccogli le mosse di follow-up disponibili, scegline una a caso e spingi sulla pila, procedi fino a raggiungere la fine o un deadend. Sul vicolo cieco spuntando la pila, è necessario tenere traccia delle posizioni visitate sulla mappa, quindi quando si raccolgono le mosse disponibili non si prende mai lo stesso percorso due volte. Molto interessante da animare.

2.2 Ricerca breadth-first: menzionata in precedenza, simile alla precedente ma che utilizza solo le code. Interessante anche da animare. Funziona come un software di editing delle immagini. Penso che potresti essere in grado di risolvere un labirinto in Photoshop usando questo trucco.

2.3 Wall Follower: geometricamente parlando, un labirinto è un tubo piegato / contorto. Se tieni la mano sul muro alla fine troverai l'uscita;) Questo non sempre funziona. Ci sono alcuni presupposti: labirinti perfetti, ecc., Ad esempio, alcuni labirinti contengono isole. Guardalo in alto; è affascinante.

(3. Commenti :)

Questa è quella difficile. È facile risolvere i labirinti se rappresentato in una semplice matrice formale con ogni elemento che è un tipo di cella con pareti nord, est, sud e ovest e un campo bandiera visitato. Tuttavia, dato che stai provando a farlo, dato uno schizzo disegnato a mano, diventa disordinato. Sinceramente penso che provare a razionalizzare lo schizzo ti farà impazzire. Questo è simile ai problemi di visione del computer che sono abbastanza coinvolti. Forse andare direttamente sulla mappa dell'immagine potrebbe essere più facile ma più dispendioso.


2

Ecco una soluzione che utilizza 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 in scala di grigi, consultare: 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")

Ecco!

soluzione che trova correttamente il percorso più breve

Questo è ciò che accade se non riempi alcuni pixel del bordo (Ah!) ...

versione della soluzione in cui il risolutore gira intorno al labirinto

Divulgazione completa: ho posto e risposto a una domanda molto simile prima di trovarla. Quindi attraverso la magia di SO, ho trovato questo come una delle principali "Domande correlate". Ho pensato di usare questo labirinto come ulteriore test case ... Sono stato molto contento di scoprire che la mia risposta lì funziona anche per questa applicazione con pochissime modifiche.


0

la buona soluzione sarebbe che invece di trovare i vicini per pixel, sarebbe fatto per cella, perché un corridoio può avere 15px quindi nello stesso corridoio può compiere azioni come sinistra o destra, mentre se fosse fatto come se lo spostamento era un cubo sarebbe una semplice azione come SU, GIÙ, SINISTRA O DESTRA


Puoi aggiungere il grafico della soluzione e l'algoritmo come il resto della risposta per convalidare il tuo punto? Sarà meglio se puoi aggiungerli per aggiungere più peso alla tua risposta in modo che altri possano effettivamente comprendere meglio la tua risposta.
Himanshu Bansal
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.