Quelle est la meilleure façon de représenter et de résoudre un labyrinthe avec une image?
Étant donné une image JPEG (comme vu ci-dessus), quelle est la meilleure façon de la lire, de l'analyser dans une structure de données et de résoudre le labyrinthe? Mon premier réflexe est de lire l'image pixel par pixel et de la stocker dans une liste (tableau) de valeurs booléennes: True
pour un pixel blanc, et False
pour un pixel non blanc (les couleurs peuvent être supprimées). Le problème avec cette méthode, c'est que l'image peut ne pas être "pixel parfait". Par cela, je veux simplement dire que s'il y a un pixel blanc quelque part sur un mur, il peut créer un chemin involontaire.
Une autre méthode (qui m'est venue après un peu de réflexion) consiste à convertir l'image en fichier SVG - qui est une liste de chemins tracés sur une toile. De cette façon, les chemins pourraient être lus dans le même type de liste (valeurs booléennes) où True
indique un chemin ou un mur, False
indiquant un espace de voyage. Un problème avec cette méthode se produit si la conversion n'est pas précise à 100% et ne connecte pas entièrement tous les murs, créant des écarts.
Un autre problème avec la conversion en SVG est que les lignes ne sont pas "parfaitement" droites. Il en résulte que les chemins sont des courbes de Bézier cubiques. Avec une liste (tableau) de valeurs booléennes indexées par des entiers, les courbes ne seraient pas transférées facilement et tous les points qui se trouvent sur la courbe devraient être calculés, mais ne correspondraient pas exactement aux indices de liste.
Je suppose que même si l'une de ces méthodes peut fonctionner (mais probablement pas), elles sont terriblement inefficaces compte tenu d'une si grande image et qu'il existe une meilleure méthode. Comment cela se fait-il (le plus efficacement et / ou avec le moins de complexité)? Existe-t-il même un meilleur moyen?
Vient ensuite la résolution du labyrinthe. Si j'utilise l'une des deux premières méthodes, je me retrouverai essentiellement avec une matrice. Selon cette réponse , un bon moyen de représenter un labyrinthe est d'utiliser un arbre, et un bon moyen de le résoudre est d'utiliser l' algorithme A * . Comment créer un arbre à partir de l'image? Des idées?
TL; DR La
meilleure façon d'analyser? Dans quelle structure de données? Comment cette structure aiderait-elle / entraverait-elle la résolution?
MISE À JOUR
J'ai essayé de mettre en œuvre ce que @Mikhail a écrit en Python, en utilisant numpy
, comme @Thomas l'a recommandé. Je pense que l'algorithme est correct, mais il ne fonctionne pas comme espéré. (Code ci-dessous.) La bibliothèque PNG est 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, [])
visited.append(s)
sous afor.if
et le remplacer parvisited.append(np)
. Un sommet est visité une fois qu'il est ajouté à la file d'attente. En fait, ce tableau doit être nommé "en file d'attente". Vous pouvez également terminer BFS une fois que vous avez atteint l'arrivée.Réponses:
Voici une solution.
Voici le code MATLAB pour BFS:
C'est vraiment très simple et standard, il ne devrait pas y avoir de difficultés pour l'implémenter en Python ou autre.
Et voici la réponse:
la source
Cette solution est écrite en Python. Merci Mikhail pour les conseils sur la préparation de l'image.
Une première recherche animée:
Le labyrinthe terminé:
Remarque: marque un pixel blanc visité en gris. Cela supprime le besoin d'une liste visitée, mais cela nécessite un deuxième chargement du fichier image à partir du disque avant de dessiner un chemin (si vous ne voulez pas une image composite du chemin final et TOUS les chemins empruntés).
Une version vierge du labyrinthe que j'ai utilisé.
la source
Je me suis essayé à implémenter la recherche A-Star pour ce problème. Suivi de près de l'implémentation par Joseph Kern pour le framework et l'algorithme pseudocode donné ici :
Comme A-Star est un algorithme de recherche heuristique, vous devez trouver une fonction qui estime le coût restant (ici: la distance) jusqu'à ce que l'objectif soit atteint. À moins que vous ne soyez à l'aise avec une solution sous-optimale, elle ne devrait pas surestimer le coût. Un choix prudent serait ici la distance de Manhattan (ou taxi) car elle représente la distance en ligne droite entre deux points sur la grille pour le quartier de Von Neumann utilisé. (Ce qui, dans ce cas, ne surestimerait jamais le coût.)
Cependant, cela sous-estimerait considérablement le coût réel du labyrinthe en question. Par conséquent, j'ai ajouté deux autres mesures de distance au carré, la distance euclidienne et la distance de Manhattan multipliée par quatre pour comparaison. Ceux-ci pourraient cependant surestimer le coût réel et pourraient donc donner des résultats sous-optimaux.
Voici le code:
Voici quelques images pour une visualisation des résultats (inspirées de celle postée par Joseph Kern ). Les animations montrent une nouvelle image chacune après 10000 itérations de la boucle while principale.
Recherche en largeur:
Distance A-Star Manhattan:
Distance euclidienne au carré A-Star:
A-Star Manhattan Distance multipliée par quatre:
Les résultats montrent que les régions explorées du labyrinthe diffèrent considérablement pour l'heuristique utilisée. En tant que telle, la distance euclidienne au carré produit même un chemin différent (sous-optimal) comme les autres métriques.
En ce qui concerne les performances de l'algorithme A-Star en termes de durée d'exécution jusqu'à la fin, notez que beaucoup d'évaluation des fonctions de distance et de coût s'additionnent par rapport à la recherche de largeur en premier (BFS) qui n'a besoin que d'évaluer le "but" de chaque poste de candidat. Que le coût de ces évaluations de fonctions supplémentaires (A-Star) l'emporte ou non sur le coût du plus grand nombre de nœuds à vérifier (BFS) et surtout si les performances sont un problème pour votre application, c'est une question de perception individuelle. et ne peut bien sûr pas recevoir de réponse générale.
On peut dire en général si un algorithme de recherche informé (comme A-Star) pourrait être le meilleur choix par rapport à une recherche exhaustive (par exemple, BFS) est le suivant. Avec le nombre de dimensions du labyrinthe, c'est-à-dire le facteur de branchement de l'arbre de recherche, l'inconvénient d'une recherche exhaustive (pour une recherche exhaustive) croît de façon exponentielle. Avec une complexité croissante, il devient de moins en moins possible de le faire et à un moment donné, vous êtes à peu près satisfait de tout chemin de résultat, qu'il soit (approximativement) optimal ou non.
la source
La recherche dans les arbres est trop. Le labyrinthe est intrinsèquement séparable le long du ou des chemins de solution.
(Merci à rainman002 de Reddit de me l'avoir signalé.)
Pour cette raison, vous pouvez rapidement utiliser des composants connectés pour identifier les sections connectées du mur de labyrinthe. Cela itère deux fois sur les pixels.
Si vous voulez transformer cela en un joli diagramme du ou des chemins de solution, vous pouvez alors utiliser des opérations binaires avec des éléments structurants pour remplir les chemins "sans issue" pour chaque région connectée.
Le code de démonstration de MATLAB suit. Il pourrait utiliser des ajustements pour mieux nettoyer le résultat, le rendre plus généralisable et le faire fonctionner plus rapidement. (Parfois quand il n'est pas 2h30 du matin.)
la source
Utilise une file d'attente pour un remplissage continu de seuil. Pousse le pixel à gauche de l'entrée dans la file d'attente, puis démarre la boucle. Si un pixel en file d'attente est suffisamment sombre, il est de couleur gris clair (au-dessus du seuil) et tous les voisins sont poussés dans la file d'attente.
La solution est le couloir entre le mur gris et le mur coloré. Notez que ce labyrinthe a plusieurs solutions. En outre, cela semble simplement fonctionner.
la source
C'est parti: maze-solver-python (GitHub)
Je me suis amusé à jouer avec cela et j'ai étendu la réponse de Joseph Kern . Ne pas lui porter atteinte; Je viens de faire quelques ajouts mineurs pour toute autre personne qui pourrait être intéressée à jouer avec cela.
C'est un solveur basé sur python qui utilise BFS pour trouver le chemin le plus court. Mes principaux ajouts à l'époque sont les suivants:
Dans l'état actuel des choses, les points de début / fin sont codés en dur pour cet exemple de labyrinthe, mais je prévois de l'étendre de sorte que vous puissiez choisir les pixels appropriés.
la source
J'irais pour l'option matrice de bools. Si vous trouvez que les listes Python standard sont trop inefficaces pour cela, vous pouvez utiliser un
numpy.bool
tableau à la place. Le stockage pour un labyrinthe de 1 000 x 1 000 pixels ne représente alors que 1 Mo.Ne vous embêtez pas à créer des structures de données arborescentes ou graphiques. C'est juste une façon d'y penser, mais pas nécessairement une bonne façon de le représenter en mémoire; une matrice booléenne est à la fois plus facile à coder et plus efficace.
Utilisez ensuite l'algorithme A * pour le résoudre. Pour l'heuristique de distance, utilisez la distance Manhattan (
distance_x + distance_y
).Représentez les nœuds par un tuple de
(row, column)
coordonnées. Chaque fois que l'algorithme ( pseudocode Wikipedia ) appelle des "voisins", c'est une simple question de boucler sur les quatre voisins possibles (attention aux bords de l'image!).Si vous trouvez que c'est encore trop lent, vous pouvez essayer de réduire l'échelle de l'image avant de la charger. Veillez à ne pas perdre de chemins étroits dans le processus.
Il est peut-être possible de faire une réduction d'échelle 1: 2 en Python également, en vérifiant que vous ne perdez en fait aucun chemin possible. Une option intéressante, mais elle nécessite un peu plus de réflexion.
la source
boolean
valeurs, le stockage serait-il toujours comparable? La matrice est alors de 2400 * 1200. Et A * sur BFS aurait-il un impact significatif sur le temps de fonctionnement réel?Voici quelques idées.
(1. Traitement d'image :)
1.1 Chargez l'image en tant que carte de pixels RVB . En C # c'est une utilisation triviale
system.drawing.bitmap
. Dans les langues sans prise en charge simple de l'imagerie, il suffit de convertir l'image au format pixmap portable (PPM) (une représentation de texte Unix, produit de gros fichiers) ou un format de fichier binaire simple que vous pouvez facilement lire, comme BMP ou TGA . ImageMagick sous Unix ou IrfanView sous Windows.1.2 Vous pouvez, comme mentionné précédemment, simplifier les données en prenant le (R + G + B) / 3 pour chaque pixel comme indicateur de ton gris, puis seuil la valeur pour produire un tableau noir et blanc. Quelque chose près de 200 en supposant que 0 = noir et 255 = blanc supprimera les artefacts JPEG.
(2. Solutions :)
2.1 Recherche en profondeur d'abord: Initiez une pile vide avec l'emplacement de départ, collectez les mouvements de suivi disponibles, choisissez-en un au hasard et poussez sur la pile, continuez jusqu'à ce que la fin soit atteinte ou une impasse. En cas de retour en arrière en faisant sauter la pile, vous devez garder une trace des positions visitées sur la carte, donc lorsque vous collectez des mouvements disponibles, vous ne prenez jamais le même chemin deux fois. Très intéressant à animer.
2.2 Recherche étendue: mentionnée précédemment, similaire à celle ci-dessus mais utilisant uniquement des files d'attente. Aussi intéressant à animer. Cela fonctionne comme un logiciel d'édition d'images. Je pense que vous pouvez résoudre un labyrinthe dans Photoshop en utilisant cette astuce.
2.3 Mur suiveur: géométriquement parlant, un labyrinthe est un tube plié / alambiqué. Si vous gardez la main sur le mur, vous finirez par trouver la sortie;) Cela ne fonctionne pas toujours. Il existe certaines hypothèses concernant les labyrinthes parfaits, etc., par exemple, certains labyrinthes contiennent des îles. Cherchez-le; c'est fascinant.
(3. Commentaires :)
C'est le plus délicat. Il est facile de résoudre des labyrinthes s'ils sont représentés dans un simple tableau formel, chaque élément étant un type de cellule avec des murs nord, est, sud et ouest et un champ de drapeau visité. Cependant, étant donné que vous essayez de le faire étant donné un croquis dessiné à la main, cela devient désordonné. Je pense honnêtement qu'essayer de rationaliser l'esquisse vous rendra fou. Cela s'apparente à des problèmes de vision par ordinateur qui sont assez impliqués. Aller directement sur la carte d'image peut être plus facile mais plus coûteux.
la source
Voici une solution en utilisant R.
RVB à niveaux de gris, voir: https://stackoverflow.com/a/27491947/2371031
Voila!
C'est ce qui se passe si vous ne remplissez pas certains pixels de bordure (Ha!) ...
Divulgation complète: j'ai moi-même posé et répondu à une question très similaire avant de trouver celle-ci. Ensuite, grâce à la magie de SO, j'ai trouvé celui-ci comme l'une des meilleures "questions connexes". Je pensais que j'utiliserais ce labyrinthe comme cas de test supplémentaire ... J'ai été très heureux de constater que ma réponse y fonctionne également pour cette application avec très peu de modifications.
la source
la bonne solution serait qu'au lieu de trouver les voisins par pixel, ce serait fait par cellule, car un couloir peut avoir 15px donc dans le même couloir il peut prendre des actions comme à gauche ou à droite, alors que si c'était fait comme si le déplacement était un cube ce serait une action simple comme HAUT, BAS, GAUCHE OU DROITE
la source