Whymarrh - 1 year ago 62

Python Question

What is the best way to represent and solve a maze given an image?

Given an JPEG image (as seen above), what's the best way to read it in, parse it into some data structure and solve the maze? My first instinct is to read the image in pixel by pixel and store it in a list (array) of boolean values:

`True`

`False`

Another method (which came to me after a bit of thought) is to convert the image to an SVG file - which is a list of paths drawn on a canvas. This way, the paths could be read into the same sort of list (boolean values) where

`True`

`False`

Also an issue with converting to SVG is that the lines are not "perfectly" straight. This results in the paths being cubic bezier curves. With a list (array) of boolean values indexed by integers, the curves would not transfer easily, and all the points that line on the curve would have to be calculated, but won't exactly match to list indices.

I assume that while one of these methods may work (though probably not) that they are woefully inefficient given such a large image, and that there exists a better way. How is this best (most efficiently and/or with the least complexity) done? Is there even a best way?

Then comes the solving of the maze. If I use either of the first two methods, I will essentially end up with a matrix. According to this answer, a good way to represent a maze is using a tree, and a good way to solve it is using the A* algorithm. How would one create a tree from the image? Any ideas?

Best way to parse? Into what data structure? How would said structure help/hinder solving?

I've tried my hand at implementing what @Mikhail has written in Python, using

`numpy`

`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, [])

Answer Source

Here is a solution.

- Convert image to grayscale (not yet binary), adjusting weights for the colors so that final grayscale image is approximately uniform. You can do it simply by controlling sliders in Photoshop in Image -> Adjustments -> Black & White.
- Convert image to binary by setting appropriate threshold in Photoshop in Image -> Adjustments -> Threshold.
- Make sure threshold is selected right. Use the Magic Wand Tool with 0 tolerance, point sample, contiguous, no anti-aliasing. Check that edges at which selection breaks are not false edges introduced by wrong threshold. In fact, all interior points of this maze are accessible from the start.
- Add artificial borders on the maze to make sure virtual traveler will not walk around it :)
- Implement breadth-first search (BFS) in your favorite language and run it from the start. I prefer MATLAB for this task. As @Thomas already mentioned, there is no need to mess with regular representation of graphs. You can work with binarized image directly.

Here is the MATLAB code for 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
```

It is really very simple and standard, there should not be difficulties on implementing this in Python or whatever.

And here is the answer: