Qual é a melhor maneira de representar e resolver um labirinto com uma imagem?
Dada uma imagem JPEG (como vista acima), qual é a melhor maneira de lê-la, analisá-la em alguma estrutura de dados e resolver o labirinto? Meu primeiro instinto é ler a imagem em pixel por pixel e armazená-la em uma lista (matriz) de valores booleanos: True
para um pixel branco e False
para um pixel não branco (as cores podem ser descartadas). O problema com esse método é que a imagem pode não ser "pixel perfeita". Com isso, quero dizer simplesmente que, se houver um pixel branco em algum lugar na parede, ele poderá criar um caminho não intencional.
Outro método (que veio a mim depois de um pouco de reflexão) é converter a imagem em um arquivo SVG - que é uma lista de caminhos desenhados em uma tela. Dessa forma, os caminhos podem ser lidos no mesmo tipo de lista (valores booleanos) em que True
indica um caminho ou parede, False
indicando um espaço passível de deslocamento. Um problema com esse método surge se a conversão não for 100% precisa e não conectar totalmente todas as paredes, criando lacunas.
Também um problema com a conversão para SVG é que as linhas não são "perfeitamente" retas. Isso resulta nos caminhos sendo curvas cúbicas de bezier. Com uma lista (matriz) de valores booleanos indexados por números inteiros, as curvas não seriam transferidas facilmente e todos os pontos que alinham na curva precisariam ser calculados, mas não corresponderiam exatamente aos índices da lista.
Suponho que, embora um desses métodos possa funcionar (embora provavelmente não o seja), eles são lamentavelmente ineficientes, dada uma imagem tão grande e que existe uma maneira melhor. Como isso é feito (com mais eficiência e / ou com a menor complexidade)? Existe mesmo a melhor maneira?
Depois vem a resolução do labirinto. Se eu usar um dos dois primeiros métodos, acabarei essencialmente com uma matriz. De acordo com esta resposta , uma boa maneira de representar um labirinto é usar uma árvore, e uma boa maneira de resolvê-lo é usar o algoritmo A * . Como alguém criaria uma árvore a partir da imagem? Alguma ideia?
TL; DR
Melhor maneira de analisar? Em que estrutura de dados? Como essa estrutura ajudaria / dificultaria a solução?
ATUALIZAÇÃO
Tentei implementar o que o @Mikhail escreveu em Python, usando numpy
, como o @Thomas recomendado. Eu sinto que o algoritmo está correto, mas não está funcionando como esperado. (Código abaixo.) A biblioteca 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, [])
visited.append(s)
sob umfor.if
e substituí-lo porvisited.append(np)
. Um vértice é visitado quando é adicionado à fila. De fato, essa matriz deve ser denominada "na fila". Você também pode encerrar o BFS quando chegar ao fim.Respostas:
Aqui está uma solução.
Aqui está o código MATLAB para BFS:
É realmente muito simples e padrão, não deve haver dificuldades em implementar isso em Python ou o que seja.
E aqui está a resposta:
fonte
Esta solução está escrita em Python. Obrigado Mikhail pelas dicas sobre a preparação da imagem.
Uma pesquisa de largura em primeiro lugar animada:
O labirinto completo:
Nota: Marca um pixel visitado branco em cinza. Isso elimina a necessidade de uma lista visitada, mas requer uma segunda carga do arquivo de imagem do disco antes de desenhar um caminho (se você não quiser uma imagem composta do caminho final e TODOS os caminhos).
Uma versão em branco do labirinto que eu usei.
fonte
Eu tentei implementar a pesquisa A-Star para esse problema. Seguiu de perto a implementação por Joseph Kern para o framework e o pseudocódigo do algoritmo fornecido aqui :
Como o A-Star é um algoritmo de pesquisa heurística, é necessário criar uma função que calcule o custo restante (aqui: distância) até que a meta seja alcançada. A menos que você esteja confortável com uma solução abaixo do ideal, ela não deve superestimar o custo. Uma escolha conservadora seria aqui a distância de manhattan (ou táxi), pois isso representa a distância em linha reta entre dois pontos na grade do bairro de Von Neumann usado. (Que, nesse caso, nunca superestimaria o custo.)
No entanto, isso subestimaria significativamente o custo real do labirinto em questão. Portanto, adicionamos outras duas métricas de distância ao quadrado da distância euclidiana e a distância de manhattan multiplicada por quatro para comparação. No entanto, esses fatores podem superestimar o custo real e, portanto, produzir resultados abaixo do ideal.
Aqui está o código:
Aqui estão algumas imagens para uma visualização dos resultados (inspirada na postada por Joseph Kern ). As animações mostram um novo quadro após 10.000 iterações do loop while principal.
Primeira pesquisa de largura:
Distância A-Star Manhattan:
Distância Euclidiana Quadrada A-Star:
Distância A-Star Manhattan multiplicada por quatro:
Os resultados mostram que as regiões exploradas do labirinto diferem consideravelmente para as heurísticas utilizadas. Como tal, a distância euclidiana quadrada produz um caminho (subótimo) diferente das outras métricas.
No que diz respeito ao desempenho do algoritmo A-Star em termos de tempo de execução até o término, observe que muitas funções de distância e custo são adicionadas em comparação com a pesquisa de largura da primeira pesquisa (BFS), que precisa apenas avaliar a "objetividade" de cada posição de candidato. Se o custo dessas avaliações de funções adicionais (A-Star) supera ou não o custo do maior número de nós a verificar (BFS) e, principalmente, se o desempenho é ou não um problema para a sua aplicação, é uma questão de percepção individual e, é claro, geralmente não pode ser respondido.
Uma coisa que pode ser dito em geral sobre se um algoritmo de pesquisa informado (como o A-Star) pode ser a melhor escolha em comparação com uma pesquisa exaustiva (por exemplo, BFS) é a seguinte. Com o número de dimensões do labirinto, ou seja, o fator de ramificação da árvore de pesquisa, a desvantagem de uma pesquisa exaustiva (pesquisar exaustivamente) aumenta exponencialmente. Com a crescente complexidade, torna-se cada vez menos viável fazê-lo e, em algum momento, você fica muito feliz com qualquer caminho de resultado, seja (aproximadamente) ideal ou não.
fonte
A pesquisa em árvore é demais. O labirinto é inerentemente separável ao longo do (s) caminho (s) da solução.
(Obrigado a rainman002 do Reddit por apontar isso para mim.)
Por esse motivo, você pode usar rapidamente os componentes conectados para identificar as seções conectadas da parede do labirinto. Isso itera sobre os pixels duas vezes.
Se você quiser transformar isso em um bom diagrama do (s) caminho (s) da solução, poderá usar operações binárias com elementos estruturantes para preencher os caminhos "sem saída" para cada região conectada.
Segue o código de demonstração do MATLAB. Poderia usar ajustes para limpar melhor o resultado, torná-lo mais generalizável e executá-lo mais rapidamente. (Às vezes não são 2:30 da manhã.)
fonte
Usa uma fila para um preenchimento contínuo de limite. Empurra o pixel esquerdo da entrada para a fila e inicia o loop. Se um pixel na fila é escuro o suficiente, fica cinza claro (acima do limite) e todos os vizinhos são empurrados para a fila.
Solução é o corredor entre a parede cinza e a parede colorida. Observe que este labirinto tem várias soluções. Além disso, isso apenas parece funcionar.
fonte
Aqui está: maze-solver-python (GitHub)
Eu me diverti brincando com isso e estendi Joseph Kern resposta de . Não a depreciar; Acabei de fazer algumas pequenas adições para quem mais estiver interessado em brincar com isso.
É um solucionador baseado em python que usa o BFS para encontrar o caminho mais curto. Minhas principais adições, na época, são:
Tal como está, os pontos de partida / chegada são codificados para este labirinto de amostras, mas pretendo estendê-lo para que você possa escolher os pixels apropriados.
fonte
Eu iria para a opção matriz de bools. Se você achar que as listas Python padrão são muito ineficientes para isso, use uma
numpy.bool
matriz. O armazenamento para um labirinto de 1000x1000 pixels é de apenas 1 MB.Não se preocupe em criar estruturas de dados em árvore ou gráfico. Essa é apenas uma maneira de pensar sobre isso, mas não necessariamente uma boa maneira de representá-lo na memória; uma matriz booleana é mais fácil de codificar e mais eficiente.
Em seguida, use o algoritmo A * para resolvê-lo. Para a heurística da distância, use a distância de Manhattan (
distance_x + distance_y
).Represente nós por uma tupla de
(row, column)
coordenadas. Sempre que o algoritmo ( pseudocódigo da Wikipedia ) pede "vizinhos", é uma simples questão de percorrer os quatro vizinhos possíveis (lembre-se das bordas da imagem!).Se você achar que ainda está muito lento, tente reduzir a imagem antes de carregá-la. Cuidado para não perder nenhum caminho estreito no processo.
Talvez seja possível fazer um downscaling 1: 2 no Python também, verificando se você realmente não perde nenhum caminho possível. Uma opção interessante, mas precisa de um pouco mais de reflexão.
fonte
boolean
valores, o armazenamento ainda será comparado? A matriz é então 2400 * 1200. E A * sobre BFS teria um impacto significativo no tempo de execução real?Aqui estão algumas idéias.
(1. Processamento de imagem :)
1.1 Carregue a imagem como mapa de pixels RGB . Em C # é trivial usando
system.drawing.bitmap
. Em idiomas sem suporte simples para geração de imagens, basta converter a imagem em formato pixmap portátil (PPM) (uma representação de texto Unix, produz arquivos grandes) ou em algum formato de arquivo binário simples que você pode ler facilmente, como BMP ou TGA . ImageMagick no Unix ou IrfanView no Windows.1.2 Você pode, como mencionado anteriormente, simplificar os dados, tomando o (R + G + B) / 3 para cada pixel como um indicador de tom de cinza e, em seguida, limiar o valor para produzir uma tabela em preto e branco. Algo próximo de 200 assumindo 0 = preto e 255 = branco removerá os artefatos JPEG.
(2. Soluções :)
2.1 Busca pela profundidade: inicie uma pilha vazia com o local inicial, colete os movimentos de acompanhamento disponíveis, escolha uma aleatoriamente e empurre para a pilha, prossiga até o fim ser atingido ou um beco sem saída. No back-track sem saída, popeando a pilha, você precisa acompanhar quais posições foram visitadas no mapa; assim, quando você coletar movimentos disponíveis, nunca seguirá o mesmo caminho duas vezes. Muito interessante para animar.
2.2 Pesquisa pela primeira vez: mencionada anteriormente, semelhante à anterior, mas usando apenas filas. Também interessante para animar. Isso funciona como um software de edição de imagem para preenchimento automático. Eu acho que você pode resolver um labirinto no Photoshop usando esse truque.
2.3 Seguidor de Parede: Geometricamente falando, um labirinto é um tubo dobrado / complicado. Se você mantiver a mão na parede, encontrará a saída;) Isso nem sempre funciona. Existem certas suposições sobre labirintos perfeitos, etc., por exemplo, certos labirintos contêm ilhas. Procure; é fascinante.
(3. Comentários :)
Este é o mais complicado. É fácil resolver labirintos se representado em alguma matriz simples formal, com cada elemento sendo um tipo de célula com paredes norte, leste, sul e oeste e um campo de bandeira visitado. No entanto, dado que você está tentando fazer isso, com um esboço desenhado à mão, ele fica confuso. Sinceramente, acho que tentar racionalizar o esboço o deixará louco. Isso é semelhante a problemas de visão computacional bastante envolvidos. Talvez ir diretamente para o mapa da imagem seja mais fácil e mais esbanjador.
fonte
Aqui está uma solução usando R.
RGB para escala de cinza, consulte: https://stackoverflow.com/a/27491947/2371031
Voila!
É o que acontece se você não preencher alguns pixels de borda (Ha!) ...
Divulgação completa: fiz e respondi uma pergunta muito semelhante antes de encontrar essa. Então, através da magia do SO, encontrei este como uma das principais "perguntas relacionadas". Eu pensei em usar esse labirinto como um caso de teste adicional ... Fiquei muito satisfeito ao descobrir que minha resposta lá também funciona para esse aplicativo com poucas modificações.
fonte
a boa solução seria que, em vez de encontrar os vizinhos por pixel, fosse feito por célula, porque um corredor pode ter 15px; portanto, no mesmo corredor, ele pode executar ações como esquerda ou direita, enquanto se fosse feito como se o deslocamento era um cubo, seria uma ação simples como CIMA, BAIXO, ESQUERDA OU DIREITA
fonte