Jaki jest najlepszy sposób na przedstawienie i rozwiązanie labiryntu na podstawie obrazu?
Biorąc pod uwagę obraz JPEG (jak pokazano powyżej), jaki jest najlepszy sposób, aby go odczytać, przeanalizować w jakiejś strukturze danych i rozwiązać labirynt? Moim pierwszym instynktem jest odczytanie obrazu piksel po pikselu i zapisanie go na liście (tablicy) wartości boolowskich: True
dla białego piksela i False
dla innego niż biały piksel (kolory można odrzucić). Problem z tą metodą polega na tym, że obraz może nie być „w pikselach idealny”. Rozumiem przez to, że jeśli gdzieś na ścianie jest biały piksel, może on stworzyć niezamierzoną ścieżkę.
Inną metodą (która przyszła mi do głowy po namyśle) jest konwersja obrazu do pliku SVG - który jest listą ścieżek narysowanych na płótnie. W ten sposób ścieżki mogą zostać wczytane do tego samego rodzaju listy (wartości boolowskie), gdzie True
wskazuje ścieżkę lub ścianę, False
wskazując przestrzeń, którą można przejechać. Problem z tą metodą powstaje, jeśli konwersja nie jest w 100% dokładna i nie łączy w pełni wszystkich ścian, tworząc luki.
Problemem przy konwersji do SVG jest również to, że linie nie są „idealnie” proste. Powoduje to, że ścieżki są sześciennymi krzywymi Beziera. Z listą (tablicą) wartości boolowskich indeksowanych liczbami całkowitymi krzywe nie byłyby łatwe do przeniesienia, a wszystkie punkty na linii musiałyby zostać obliczone, ale nie pasowałyby dokładnie do indeksów listy.
Zakładam, że chociaż jedna z tych metod może działać (choć prawdopodobnie nie), to są one niestety nieefektywne, biorąc pod uwagę tak duży obraz i że istnieje lepszy sposób. Jak to się robi najlepiej (najbardziej wydajnie i / lub przy najmniejszej złożoności)? Czy jest jakiś najlepszy sposób?
Potem przychodzi rozwiązanie labiryntu. Jeśli użyję jednej z dwóch pierwszych metod, zasadniczo skończę na matrycy. Zgodnie z tą odpowiedzią dobrym sposobem na przedstawienie labiryntu jest użycie drzewa, a dobrym sposobem jego rozwiązania jest użycie algorytmu A * . Jak stworzyć drzewo z obrazu? Jakieś pomysły?
TL; DR
Najlepszy sposób na parsowanie? W jakiej strukturze danych? W jaki sposób wspomniana struktura pomaga / utrudnia rozwiązywanie?
AKTUALIZACJA
Próbowałem swoich sił we wdrażaniu tego, co @Mikhail napisał w Pythonie, używając numpy
zgodnie z zaleceniami @Thomas. Wydaje mi się, że algorytm jest poprawny, ale nie działa zgodnie z oczekiwaniami. (Kod poniżej.) Biblioteka PNG to 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)
pod afor.if
i zastąpić govisited.append(np)
. Wierzchołek jest odwiedzany po dodaniu do kolejki. W rzeczywistości tablica ta powinna mieć nazwę „w kolejce”. Możesz także zakończyć BFS, gdy dotrzesz do mety.Odpowiedzi:
Oto rozwiązanie.
Oto kod MATLAB dla BFS:
Jest to naprawdę bardzo proste i standardowe, nie powinno być trudności z implementacją tego w Pythonie lub czymkolwiek.
A oto odpowiedź:
źródło
To rozwiązanie zostało napisane w języku Python. Dziękuję Michaiłowi za wskazówki dotyczące przygotowania obrazu.
Animowane wyszukiwanie szerokości:
The Completed Maze:
Uwaga: oznacza biały piksel odwiedzany na szaro. Eliminuje to potrzebę odwiedzania listy, ale wymaga to drugiego załadowania pliku obrazu z dysku przed narysowaniem ścieżki (jeśli nie chcesz, aby złożony obraz końcowej ścieżki i WSZYSTKIE ścieżki zostały podjęte).
Pusta wersja labiryntu, którego użyłem.
źródło
Próbowałem samodzielnie wdrożyć wyszukiwanie A-Star dla tego problemu. Dokładnie śledził implementację Josepha Kerna dla frameworka i pseudokodu algorytmu podanego tutaj :
Ponieważ A-Star jest heurystycznym algorytmem wyszukiwania, musisz wymyślić funkcję, która szacuje pozostały koszt (tutaj: odległość), aż do osiągnięcia celu. O ile nie czujesz się dobrze z rozwiązaniem nieoptymalnym, nie powinno ono przeceniać kosztów. Konserwatywnym wyborem byłaby tutaj odległość manhattan (lub taksówka), ponieważ reprezentuje ona odległość w linii prostej między dwoma punktami na siatce dla używanej dzielnicy von Neumanna. (Co w tym przypadku nigdy nie zawyżałoby kosztów).
To jednak znacznie nie docenia faktycznego kosztu danego labiryntu. Dlatego dodałem dwie inne miary odległości do kwadratu odległość euklidesowa i manhattan pomnożone przez cztery dla porównania. Mogą one jednak zawyżać faktyczny koszt, a zatem mogą dawać wyniki nieoptymalne.
Oto kod:
Oto kilka zdjęć do wizualizacji wyników (zainspirowanych tym, które opublikował Joseph Kern ). Animacje pokazują nową ramkę po 10000 iteracjach głównej pętli while.
Wyszukiwanie szerokości:
A-Star Manhattan Odległość:
Odległość do kwadratu euklidesowego z gwiazdką:
A-Star Manhattan Odległość pomnożona przez cztery:
Wyniki pokazują, że badane obszary labiryntu różnią się znacznie w zależności od zastosowanej heurystyki. Jako taka, kwadratowa odległość euklidesowa wytwarza nawet inną (nieoptymalną) ścieżkę niż inne metryki.
Jeśli chodzi o wydajność algorytmu A-Star pod względem czasu działania do czasu zakończenia, należy zauważyć, że wiele ocen funkcji odległości i kosztów sumuje się w porównaniu z rozszerzeniem pierwszego wyszukiwania (BFS), które musi jedynie ocenić „celność” każde stanowisko kandydujące. To, czy koszt tych dodatkowych ocen funkcji (A-Star) przeważa nad kosztem większej liczby węzłów do sprawdzenia (BFS), a zwłaszcza czy wydajność w ogóle stanowi problem dla Twojej aplikacji, jest kwestią indywidualnego postrzegania i oczywiście nie można na ogół odpowiedzieć.
Rzecz, którą można ogólnie powiedzieć o tym, czy algorytm świadomego wyszukiwania (taki jak A-Star) może być lepszym wyborem niż wyczerpujące wyszukiwanie (np. BFS), jest następujący. Wraz z liczbą wymiarów labiryntu, tj. Współczynnikiem rozgałęzienia drzewa wyszukiwania, wada wyczerpującego wyszukiwania (w celu wyczerpującego wyszukiwania) rośnie wykładniczo. Z rosnącą złożonością staje się to coraz mniej wykonalne, a w pewnym momencie jesteś całkiem zadowolony z dowolnej ścieżki wyników, czy to (w przybliżeniu) optymalnej, czy nie.
źródło
Wyszukiwanie drzew to za dużo. Labirynt można z natury oddzielić wzdłuż ścieżki (ścieżek) rozwiązania.
(Podziękowania dla rainman002 z Reddit za wskazanie mi tego.)
Z tego powodu możesz szybko korzystać z podłączonych komponentów aby zidentyfikować połączone odcinki ściany labiryntu. Powtarza to dwukrotnie piksele.
Jeśli chcesz zmienić to w ładny schemat ścieżek rozwiązania, możesz następnie użyć operacji binarnych z elementami strukturyzującymi, aby wypełnić ścieżki „ślepego zaułka” dla każdego połączonego regionu.
Poniżej znajduje się kod demonstracyjny dla MATLAB. Przydałoby się ulepszenie, aby lepiej wyczyścić wynik, uczynić go bardziej ogólnym i przyspieszyć. (Kiedyś nie jest 2:30.)
źródło
Wykorzystuje kolejkę do ciągłego wypełniania progu. Przesuwa piksel w lewo od wejścia do kolejki, a następnie uruchamia pętlę. Jeśli piksel w kolejce jest wystarczająco ciemny, ma kolor jasnoszary (powyżej progu), a wszyscy sąsiedzi są wypychani do kolejki.
Rozwiązaniem jest korytarz między szarą ścianą a kolorową ścianą. Uwaga: ten labirynt ma wiele rozwiązań. Wydaje się, że to po prostu działa.
źródło
Proszę bardzo: maze-solver-python (GitHub)
Bawiłem się dobrze z tym i poszerzyłem odpowiedź Josepha Kerna . Nie umniejszać tego; Właśnie dodałem kilka drobnych dodatków dla każdego, kto może być zainteresowany zabawą.
Jest to solver oparty na pythonie, który używa BFS do znalezienia najkrótszej ścieżki. Moje główne dodatki w tym czasie to:
Na obecnym etapie punkty początkowe / końcowe są zakodowane na stałe dla tego przykładowego labiryntu, ale planuję rozszerzyć go tak, aby można było wybrać odpowiednie piksele.
źródło
Wybrałbym opcję matrycy booli. Jeśli okaże się, że standardowe listy Pythona są zbyt nieefektywne, możesz
numpy.bool
zamiast tego użyć tablicy. Miejsce na labirynt 1000 x 1000 pikseli wynosi wtedy zaledwie 1 MB.Nie przejmuj się tworzeniem struktur danych drzewa lub wykresu. To tylko sposób myślenia o tym, ale niekoniecznie dobry sposób na przedstawienie go w pamięci; macierz boolowska jest łatwiejsza do kodowania i bardziej wydajna.
Następnie użyj algorytmu A *, aby go rozwiązać. Dla heurystyki odległości używaj odległości Manhattan (
distance_x + distance_y
).Reprezentuj węzły przez krotkę
(row, column)
współrzędnych. Ilekroć algorytm ( pseudokod Wikipedii ) woła o „sąsiadów”, jest to prosta kwestia zapętlenia czterech możliwych sąsiadów (pamiętaj o krawędziach obrazu!).Jeśli uznasz, że wciąż jest zbyt wolny, możesz spróbować przeskalować obraz przed załadowaniem. Uważaj, aby nie zgubić wąskich ścieżek.
Być może możliwe jest również wykonanie skalowania w dół 1: 2 w Pythonie, sprawdzając, czy faktycznie nie straciłeś żadnych możliwych ścieżek. Ciekawa opcja, ale wymaga nieco więcej przemyślenia.
źródło
boolean
wartości, czy pamięć nadal będzie się porównywać? Matryca ma wtedy 2400 * 1200. Czy A * w porównaniu z BFS miałby znaczący wpływ na rzeczywisty czas działania?Oto kilka pomysłów.
(1. Przetwarzanie obrazu :)
1.1 Załaduj obraz jako mapę pikseli RGB . W języku C # jest to banalne
system.drawing.bitmap
. W językach bez prostej obsługi obrazowania wystarczy przekonwertować obraz na przenośny format pixmap (PPM) (uniksowa reprezentacja tekstowa, produkuje duże pliki) lub jakiś prosty format pliku binarnego, który można łatwo odczytać, taki jak BMP lub TGA . ImageMagick w systemie Unix lub IrfanView w systemie Windows.1.2 Możesz, jak wspomniano wcześniej, uprościć dane, przyjmując (R + G + B) / 3 dla każdego piksela jako wskaźnika odcienia szarości, a następnie próg wartości, aby utworzyć tabelę czarno-białą. Coś bliskiego 200 przy założeniu 0 = czarny i 255 = biały usunie artefakty JPEG.
(2. Rozwiązania :)
2.1 Przeszukiwanie w głąb: Rozpocznij pusty stos z początkową lokalizacją, zbierz dostępne ruchy kontrolne, wybierz jeden losowo i pchnij na stos, kontynuuj, aż do osiągnięcia końca lub martwego punktu. Po powrocie do martwego punktu przez przerzucenie stosu musisz śledzić, które pozycje odwiedzono na mapie, więc kiedy zbierasz dostępne ruchy, nigdy nie podążasz tą samą ścieżką dwa razy. Bardzo interesujące do animowania.
2.2 Wyszukiwanie według szerokości: wcześniej wspomniane, podobnie jak powyżej, ale tylko przy użyciu kolejek. Również interesujące do animowania. Działa to podobnie jak oprogramowanie do edycji obrazu w trybie wypełniania. Myślę, że przy pomocy tej sztuczki możesz rozwiązać labirynt w Photoshopie.
2.3 Zwolennik ściany: Geometrycznie labirynt jest złożoną / zwiniętą rurką. Jeśli trzymasz rękę na ścianie, w końcu znajdziesz wyjście;) To nie zawsze działa. Istnieją pewne założenia dotyczące: doskonałych labiryntów itp., Na przykład niektóre labirynty zawierają wyspy. Spójrz na to; to jest fascynujące.
(3. Komentarze :)
To jest trudne. Łatwo jest rozwiązywać labirynty, jeśli są reprezentowane w prostym układzie tablic, przy czym każdy element jest typem komórki ze ścianami północną, wschodnią, południową i zachodnią i odwiedzanym polem flagi. Jednak biorąc pod uwagę, że próbujesz to zrobić, biorąc pod uwagę ręcznie narysowany szkic, robi się bałagan. Szczerze sądzę, że próba racjonalizacji szkicu doprowadzi cię do szału. Jest to podobne do problemów z widzeniem komputerowym, które są dość zaangażowane. Być może przejście bezpośrednio na mapę obrazu może być łatwiejsze, ale bardziej marnotrawne.
źródło
Oto rozwiązanie wykorzystujące R.
RGB na skalę szarości, patrz: https://stackoverflow.com/a/27491947/2371031
Voila!
Dzieje się tak, jeśli nie wypełnisz niektórych pikseli granicznych (Ha!) ...
Pełne ujawnienie: sam zadałem bardzo podobne pytanie, zanim je znalazłem. Następnie dzięki magii SO znalazłem to jedno z najważniejszych „Powiązanych pytań”. Pomyślałem, że użyję tego labiryntu jako dodatkowego przypadku testowego ... Byłem bardzo zadowolony, że moja odpowiedź tam działa również dla tej aplikacji z niewielkimi modyfikacjami.
źródło
dobrym rozwiązaniem byłoby to, że zamiast znajdować sąsiadów po pikselach, można to zrobić za pomocą komórki, ponieważ korytarz może mieć 15 pikseli, więc w tym samym korytarzu może wykonywać działania takie jak w lewo lub w prawo, a jeśli zrobiłby to tak, jakby przemieszczenie był sześcianem, byłoby to proste działanie takie jak GÓRA, DÓŁ, LEWO LUB PRAWO
źródło