¿Cuál es la mejor manera de representar y resolver un laberinto dada una imagen?
Dada una imagen JPEG (como se ve arriba), ¿cuál es la mejor manera de leerla, analizarla en alguna estructura de datos y resolver el laberinto? Mi primer instinto es leer la imagen en píxeles por píxel y almacenarla en una lista (matriz) de valores booleanos: True
para un píxel blanco y False
para un píxel no blanco (los colores se pueden descartar). El problema con este método es que la imagen puede no ser "perfecta de píxeles". Con eso simplemente quiero decir que si hay un píxel blanco en algún lugar de una pared, puede crear un camino no deseado.
Otro método (que se me ocurrió después de pensarlo un poco) es convertir la imagen en un archivo SVG, que es una lista de rutas dibujadas en un lienzo. De esta forma, las rutas podrían leerse en el mismo tipo de lista (valores booleanos) donde True
indica una ruta o muro, lo que False
indica un espacio que puede viajar. Un problema con este método surge si la conversión no es 100% precisa y no conecta completamente todas las paredes, creando espacios.
También un problema con la conversión a SVG es que las líneas no son "perfectamente" rectas. Esto hace que los caminos sean curvas cúbicas de Bezier. Con una lista (matriz) de valores booleanos indexados por enteros, las curvas no se transferirían fácilmente, y todos los puntos que se alinean en la curva tendrían que calcularse, pero no coincidirán exactamente con los índices de la lista.
Supongo que si bien uno de estos métodos puede funcionar (aunque probablemente no) que son lamentablemente ineficientes dada una imagen tan grande, y que existe una mejor manera. ¿Cómo se hace esto mejor (más eficientemente y / o con la menor complejidad)? ¿Hay incluso una mejor manera?
Luego viene la resolución del laberinto. Si uso cualquiera de los dos primeros métodos, esencialmente terminaré con una matriz. Según esta respuesta , una buena manera de representar un laberinto es usar un árbol, y una buena manera de resolverlo es usar el algoritmo A * . ¿Cómo se podría crear un árbol a partir de la imagen? ¿Algunas ideas?
TL; DR ¿La
mejor manera de analizar? ¿En qué estructura de datos? ¿Cómo ayudaría / obstaculizaría dicha estructura a la resolución?
ACTUALIZACIÓN
He intentado implementar lo que @Mikhail ha escrito en Python, utilizando numpy
, como recomendó @Thomas. Siento que el algoritmo es correcto, pero no funciona como se esperaba. (Código a continuación). La biblioteca PNG es 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)
debajo de ayfor.if
reemplazarlo porvisited.append(np)
. Se visita un vértice una vez que se agrega a la cola. De hecho, esta matriz debería llamarse "en cola". También puede terminar BFS una vez que haya llegado al final.Respuestas:
Aquí hay una solución.
Aquí está el código MATLAB para BFS:
Es realmente muy simple y estándar, no debería haber dificultades para implementar esto en Python o lo que sea.
Y aquí está la respuesta:
fuente
Esta solución está escrita en Python. Gracias Mikhail por los consejos sobre la preparación de la imagen.
Una búsqueda animada de Breadth First:
El laberinto completado:
Nota: Marca un píxel blanco visitado como gris. Esto elimina la necesidad de una lista visitada, pero esto requiere una segunda carga del archivo de imagen del disco antes de dibujar una ruta (si no desea una imagen compuesta de la ruta final y TODAS las rutas tomadas).
Una versión en blanco del laberinto que utilicé.
fuente
Intenté implementar la búsqueda de A-Star para este problema. Seguimos de cerca la implementación de Joseph Kern para el marco y el pseudocódigo del algoritmo que se proporciona aquí :
Como A-Star es un algoritmo de búsqueda heurístico, debe crear una función que calcule el costo restante (aquí: distancia) hasta alcanzar el objetivo. A menos que se sienta cómodo con una solución subóptima, no debe sobreestimar el costo. Una opción conservadora sería aquí la distancia de Manhattan (o taxi), ya que representa la distancia en línea recta entre dos puntos en la cuadrícula para el vecindario Von Neumann utilizado. (Lo cual, en este caso, nunca sobreestimaría el costo).
Sin embargo, esto subestimaría significativamente el costo real del laberinto en cuestión. Por lo tanto, he agregado otras dos métricas de distancia al cuadrado euclidiana y la distancia de Manhattan multiplicada por cuatro para comparar. Sin embargo, estos podrían sobreestimar el costo real y, por lo tanto, podrían arrojar resultados subóptimos.
Aquí está el código:
Aquí hay algunas imágenes para una visualización de los resultados (inspiradas en la publicada por Joseph Kern ). Las animaciones muestran un nuevo cuadro cada 10000 iteraciones del ciclo while principal.
Breadth-First Search:
A-Star Manhattan Distancia:
Distancia Euclidiana Cuadrada A-Star:
A-Star Manhattan Distance multiplicada por cuatro:
Los resultados muestran que las regiones exploradas del laberinto difieren considerablemente para la heurística utilizada. Como tal, la distancia euclidiana al cuadrado incluso produce una ruta diferente (subóptima) como las otras métricas.
Con respecto al rendimiento del algoritmo A-Star en términos del tiempo de ejecución hasta la finalización, tenga en cuenta que se suman muchas evaluaciones de las funciones de distancia y costo en comparación con la Búsqueda de Breadth-First (BFS) que solo necesita evaluar el "objetivo" de cada puesto candidato Si el costo de estas evaluaciones de funciones adicionales (A-Star) supera o no el costo del mayor número de nodos para verificar (BFS) y especialmente si el rendimiento es un problema para su aplicación, es una cuestión de percepción individual y, por supuesto, no se puede responder en general.
Lo que se puede decir en general acerca de si un algoritmo de búsqueda informado (como A-Star) podría ser la mejor opción en comparación con una búsqueda exhaustiva (por ejemplo, BFS) es lo siguiente. Con el número de dimensiones del laberinto, es decir, el factor de ramificación del árbol de búsqueda, la desventaja de una búsqueda exhaustiva (para buscar exhaustivamente) crece exponencialmente. Con la creciente complejidad, cada vez es menos factible hacerlo y, en algún momento, está bastante contento con cualquier camino de resultados, sea (aproximadamente) óptimo o no.
fuente
La búsqueda de árboles es demasiado. El laberinto es inherentemente separable a lo largo de la (s) ruta (s) de solución.
(Gracias a rainman002 de Reddit por señalarme esto).
Debido a esto, puede usar rápidamente componentes conectados para identificar las secciones conectadas de la pared del laberinto. Esto itera sobre los píxeles dos veces.
Si desea convertir eso en un buen diagrama de la (s) ruta (s) de solución, puede usar operaciones binarias con elementos estructurantes para completar las rutas de "callejón sin salida" para cada región conectada.
Sigue el código de demostración para MATLAB. Podría usar ajustes para limpiar mejor el resultado, hacerlo más generalizable y hacerlo correr más rápido. (En algún momento cuando no son las 2:30 a.m.)
fuente
Utiliza una cola para un umbral de llenado continuo. Empuja el píxel a la izquierda de la entrada en la cola y luego inicia el bucle. Si un píxel en cola es lo suficientemente oscuro, es de color gris claro (por encima del umbral), y todos los vecinos son empujados a la cola.
La solución es el corredor entre la pared gris y la pared de color. Tenga en cuenta que este laberinto tiene múltiples soluciones. Además, esto simplemente parece funcionar.
fuente
Aquí tienes: maze-solver-python (GitHub)
Me divertí jugando con esto y me extendí a Joseph Kern la respuesta de . Para no restarle valor; Acabo de hacer algunas adiciones menores para cualquier otra persona que pueda estar interesada en jugar con esto.
Es un solucionador basado en Python que usa BFS para encontrar la ruta más corta. Mis principales adiciones, en ese momento, son:
Tal como está, los puntos de inicio / finalización están codificados para este laberinto de muestra, pero planeo extenderlo de manera que pueda elegir los píxeles apropiados.
fuente
Yo elegiría la opción de matriz de bools. Si encuentra que las listas estándar de Python son demasiado ineficientes para esto, podría usar un
numpy.bool
matriz en su lugar. El almacenamiento para un laberinto de 1000x1000 píxeles es de solo 1 MB.No se moleste en crear estructuras de datos de árbol o gráfico. Esa es solo una forma de pensarlo, pero no necesariamente una buena forma de representarlo en la memoria; una matriz booleana es más fácil de codificar y más eficiente.
Luego usa el algoritmo A * para resolverlo. Para la heurística de distancia, use la distancia de Manhattan (
distance_x + distance_y
).Representar nodos por una tupla de
(row, column)
coordenadas. Siempre que el algoritmo ( pseudocódigo de Wikipedia ) llame a "vecinos", es una simple cuestión de recorrer los cuatro vecinos posibles (¡cuidado con los bordes de la imagen!).Si encuentra que todavía es demasiado lento, puede intentar reducir la escala de la imagen antes de cargarla. Tenga cuidado de no perder caminos estrechos en el proceso.
Tal vez también sea posible hacer una reducción de escala 1: 2 en Python, verificando que en realidad no pierda ninguna ruta posible. Una opción interesante, pero necesita un poco más de reflexión.
fuente
boolean
valores, ¿el almacenamiento aún se compararía? La matriz es entonces 2400 * 1200. ¿Y A * sobre BFS tendría un impacto significativo en el tiempo real de ejecución?Aquí hay algunas ideas.
(1. Procesamiento de imagen :)
1.1 Cargue la imagen como mapa de píxeles RGB . En C # es trivial usando
system.drawing.bitmap
. En idiomas sin soporte simple para imágenes, simplemente convierta la imagen a formato de mapa de píxeles portátil (PPM) (una representación de texto Unix, produce archivos grandes) o algún formato de archivo binario simple que pueda leer fácilmente, como BMP o TGA . ImageMagick en Unix o IrfanView en Windows.1.2 Puede, como se mencionó anteriormente, simplificar los datos al tomar (R + G + B) / 3 para cada píxel como un indicador de tono gris y luego limitar el valor para producir una tabla en blanco y negro. Algo cercano a 200 suponiendo que 0 = negro y 255 = blanco eliminará los artefactos JPEG.
(2. Soluciones :)
2.1 Búsqueda de profundidad primero: inicie una pila vacía con la ubicación inicial, recopile los movimientos de seguimiento disponibles, elija uno al azar y empuje hacia la pila, continúe hasta llegar al final o un punto muerto. En el backdend muerto haciendo estallar la pila, debe realizar un seguimiento de las posiciones que se visitaron en el mapa, de modo que cuando reúne los movimientos disponibles, nunca toma el mismo camino dos veces. Muy interesante para animar.
2.2 Breadth-First Search: mencionado anteriormente, similar al anterior pero solo usando colas. También interesante para animar. Esto funciona como un software de edición de imágenes de relleno de inundación. Creo que puedes resolver un laberinto en Photoshop usando este truco.
2.3 Seguidor de pared: Geométricamente hablando, un laberinto es un tubo doblado / contorneado. Si mantienes tu mano en la pared, eventualmente encontrarás la salida;) Esto no siempre funciona. Hay ciertos supuestos re: laberintos perfectos, etc., por ejemplo, ciertos laberintos contienen islas. Búscalo; Es fascinante.
(3. Comentarios :)
Este es el complicado. Es fácil resolver laberintos si se representa en una matriz simple formal con cada elemento como un tipo de celda con paredes norte, este, sur y oeste y un campo de bandera visitado. Sin embargo, dado que está intentando hacer esto dado un boceto dibujado a mano, se vuelve desordenado. Sinceramente, creo que intentar racionalizar el boceto te volverá loco. Esto es similar a los problemas de visión por computadora que están bastante involucrados. Quizás ir directamente al mapa de la imagen puede ser más fácil pero más derrochador.
fuente
Aquí hay una solución usando R.
RGB a escala de grises, consulte: https://stackoverflow.com/a/27491947/2371031
Voila!
Esto es lo que sucede si no rellenas algunos píxeles de borde (¡Ja!) ...
Revelación completa: pregunté y respondí una pregunta muy similar antes de encontrar esta. Luego, a través de la magia de SO, encontré esta como una de las principales "Preguntas relacionadas". Pensé que usaría este laberinto como un caso de prueba adicional ... Me complació mucho encontrar que mi respuesta allí también funciona para esta aplicación con muy poca modificación.
fuente
la buena solución sería que en lugar de encontrar a los vecinos por píxel, se haría por celda, porque un corredor puede tener 15 píxeles, por lo que en el mismo corredor puede tomar acciones como izquierda o derecha, mientras que si se hiciera como si fuera el desplazamiento era un cubo, sería una acción simple como ARRIBA, ABAJO, IZQUIERDA O DERECHA
fuente