用Python代碼來(lái)解圖片迷宮的方法整理
譯注:原文是StackOverflow上一個(gè)如何用程序讀取迷宮圖片并求解的問(wèn)題,幾位參與者熱烈地討論并給出了自己的代碼,涉及到用Python對(duì)圖片的處理以及廣度優(yōu)先(BFS)算法等。
問(wèn)題by Whymarrh:

當(dāng)給定上面那樣一張JPEG圖片,如何才能更好地將這張圖轉(zhuǎn)換為合適的數(shù)據(jù)結(jié)構(gòu)并且解出這個(gè)迷宮?
我的第一直覺(jué)是將這張圖按像素逐個(gè)讀入,并存儲(chǔ)在一個(gè)包含布爾類(lèi)型元素的列表或數(shù)組中,其中True代表白色像素,F(xiàn)alse代表非白色像素(或彩色可以被處理成二值圖像)。但是這種做法存在一個(gè)問(wèn)題,那就是給定的圖片往往并不能完美的“像素化”??紤]到如果因?yàn)閳D片轉(zhuǎn)換的原因,某個(gè)非預(yù)期的白色像素出現(xiàn)在迷宮的墻上,那么就可能會(huì)創(chuàng)造出一一條非預(yù)期的路徑。
經(jīng)過(guò)思考之后,我想出了另一種方法:首先將圖片轉(zhuǎn)換為一個(gè)可縮放適量圖形(SVG)文件,這個(gè)文件由一個(gè)畫(huà)布上的矢量線條列表組成,矢量線條按照列表的順序讀取,讀取出的仍是布爾值:其中True表示墻,而False表示可通過(guò)的區(qū)域。但是這種方法如果無(wú)法保證圖像能夠做到百分之百的精確轉(zhuǎn)換,尤其是如果不能將墻完全準(zhǔn)確的連接,那么這個(gè)迷宮就可能出現(xiàn)裂縫。
圖像轉(zhuǎn)換為SVG的另一個(gè)問(wèn)題是,線條并不是完美的直線。因?yàn)镾VG的線條是三次貝塞爾曲線,而使用整數(shù)索引的布爾值列表增加了曲線轉(zhuǎn)換的難度,迷宮線條上的所有點(diǎn)在曲線上都必須經(jīng)過(guò)計(jì)算,但不一定能夠完美對(duì)應(yīng)列表中的索引值。
假設(shè)以上方法的確可以實(shí)現(xiàn)(雖然很可能都不行),但當(dāng)給定一張很大的圖像時(shí),它們還是不能勝任。那么是否存在一種更好地方法能夠平衡效率和復(fù)雜度?
這就要討論到如何解迷宮了。如果我使用以上兩種方法中的任意一種,我最終將會(huì)得到一個(gè)矩陣。而根據(jù)這個(gè)問(wèn)答(http://stackoverflow.com/questions/3097556/programming-theory-solve-a-maze/3097677#3097677),一個(gè)比較好的迷宮表示方式應(yīng)該是使用樹(shù)的結(jié)構(gòu),并且使用A*搜索算法來(lái)解迷宮。那么如何從迷宮圖片中構(gòu)造出迷宮樹(shù)呢?有比較好的方法么?
以上廢話太多,總結(jié)起來(lái)問(wèn)題就是:如何轉(zhuǎn)換迷宮圖片?轉(zhuǎn)換成為什么樣的數(shù)據(jù)結(jié)構(gòu)?采用什么樣的數(shù)據(jù)結(jié)構(gòu)能夠幫助或阻礙解迷宮?
回答by Mikhail:
這是我的解決方案:
1. 將圖片轉(zhuǎn)換為灰度圖像(不是直接二值),調(diào)整不同顏色的權(quán)重使得最終的灰度看起來(lái)比較統(tǒng)一,你可以通過(guò)簡(jiǎn)單地調(diào)節(jié)Photoshop 圖像->調(diào)整->黑白 菜單中的控制條來(lái)實(shí)現(xiàn)。
2. 將上一步得到的灰度圖片轉(zhuǎn)換為二值圖片,可以通過(guò)在PS 圖像->調(diào)整->閾值 菜單中設(shè)定適當(dāng)?shù)拈撝祦?lái)實(shí)現(xiàn)
3. 確保正確設(shè)置了閾值。使用魔棒工具(參數(shù)設(shè)置:容差 0、取樣點(diǎn)、連續(xù)以及消除鋸齒)選擇空白區(qū)域,檢查所選區(qū)域的邊緣不是因?yàn)殄e(cuò)誤的閾值設(shè)置而產(chǎn)生的假邊緣。事實(shí)上,這個(gè)迷宮中從start到end應(yīng)該由聯(lián)通的空白區(qū)域。
4. 人為地在迷宮外部加上邊界,確保迷宮漫游者^(guò)_^不會(huì)從start繞著迷宮跑到終點(diǎn)。:)
5. 選擇語(yǔ)言實(shí)現(xiàn)廣度優(yōu)先搜索算法(BFS),從start處開(kāi)始讓程序運(yùn)行。下面的代碼我選擇用Matlab實(shí)現(xiàn)。正如Thomas提到的,沒(méi)必要糾結(jié)于圖像的表示形式,你可以直接在二值圖像上運(yùn)行。
以下是用MATLAB實(shí)現(xiàn)的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, <img src="http://python.jobbole.com/wp-includes/images/smilies/icon_smile.gif" alt=":)" class="wp-smiley"> = q; M(q(1), q(2), <img src="http://python.jobbole.com/wp-includes/images/smilies/icon_smile.gif" alt=":)" class="wp-smiley"> = 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, <img src="http://python.jobbole.com/wp-includes/images/smilies/icon_smile.gif" alt=":)" class="wp-smiley"> ; back = back + 1; for i = 1:4 push(p, d(i, <img src="http://python.jobbole.com/wp-includes/images/smilies/icon_smile.gif" alt=":)" class="wp-smiley"> ); end end %% Extracting path path = finish; while true q = path(end, <img src="http://python.jobbole.com/wp-includes/images/smilies/icon_smile.gif" alt=":)" class="wp-smiley"> ; p = reshape(M(q(1), q(2), <img src="http://python.jobbole.com/wp-includes/images/smilies/icon_smile.gif" alt=":)" class="wp-smiley"> , 1, 2); path(end + 1, <img src="http://python.jobbole.com/wp-includes/images/smilies/icon_smile.gif" alt=":)" class="wp-smiley"> = p; if isequal(p, start) break; end end end
這是個(gè)簡(jiǎn)單的實(shí)現(xiàn),應(yīng)該很容易就能夠改寫(xiě)為Python或其他語(yǔ)言,下面是程序的運(yùn)行結(jié)果:

提問(wèn)者更新:
我用Python實(shí)現(xiàn)了一下Mikhail的方法,其中用到了numpy庫(kù),感謝Thomas推薦。我感覺(jué)這個(gè)算法是正確的,但是效果不太如預(yù)期,以下是相關(guān)代碼,使用了PyPNG庫(kù)處理圖片。
譯注:很遺憾,我用提問(wèn)者提供的代碼并沒(méi)有跑通程序,并且似乎代碼縮進(jìn)有點(diǎn)問(wèn)題,而下面其他參與者的代碼能夠執(zhí)行通過(guò),并且效果很好。
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, [])
回答by Joseph Kern:
#!/usr/bin/env python
import sys
from Queue import Queue
from PIL import Image
start = (400,984)
end = (398,25)
def iswhite(value):
if value == (255,255,255):
return True
def getadjacent(n):
x,y = n
return [(x-1,y),(x,y-1),(x+1,y),(x,y+1)]
def BFS(start, end, pixels):
queue = Queue()
queue.put([start]) # Wrapping the start tuple in a list
while not queue.empty():
path = queue.get()
pixel = path[-1]
if pixel == end:
return path
for adjacent in getadjacent(pixel):
x,y = adjacent
if iswhite(pixels[x,y]):
pixels[x,y] = (127,127,127) # see note
new_path = list(path)
new_path.append(adjacent)
queue.put(new_path)
print "Queue has been exhausted. No answer was found."
if __name__ == '__main__':
# invoke: python mazesolver.py [.jpg|.png|etc.]
base_img = Image.open(sys.argv[1])
base_pixels = base_img.load()
path = BFS(start, end, base_pixels)
path_img = Image.open(sys.argv[1])
path_pixels = path_img.load()
for position in path:
x,y = position
path_pixels[x,y] = (255,0,0) # red
path_img.save(sys.argv[2])
動(dòng)態(tài)執(zhí)行效果:

回答by Jim
使用樹(shù)搜索太繁雜了,迷宮本身就跟解路徑是可分的。正因如此,你可以使用連通區(qū)域查找算法來(lái)標(biāo)記迷宮中的連通區(qū)域,這將迭代搜索兩次這些像素點(diǎn)。如果你想要更好地解決方法,你可以對(duì)結(jié)構(gòu)單元使用二元運(yùn)算(binary operations)來(lái)填充每個(gè)連通區(qū)域中的死路。
下面是相關(guān)的MATLAB代碼及運(yùn)行結(jié)果:
% read in and invert the image
im = 255 - imread('maze.jpg');
% sharpen it to address small fuzzy channels
% threshold to binary 15%
% run connected components
result = bwlabel(im2bw(imfilter(im,fspecial('unsharp')),0.15));
% purge small components (e.g. letters)
for i = 1:max(reshape(result,1,1002*800))
[count,~] = size(find(result==i));
if count < 500
result(result==i) = 0;
end
end
% close dead-end channels
closed = zeros(1002,800);
for i = 1:max(reshape(result,1,1002*800))
k = zeros(1002,800);
k(result==i) = 1; k = imclose(k,strel('square',8));
closed(k==1) = i;
end
% do output
out = 255 - im;
for x = 1:1002
for y = 1:800
if closed(x,y) == 0
out(x,y,:) = 0;
end
end
end
imshow(out);

回答by Stefano
stefano童鞋給出了生成搜索過(guò)程GIF及AVI文件的代碼 maze-solver-python (GitHub)

- python實(shí)現(xiàn)的生成隨機(jī)迷宮算法核心代碼分享(含游戲完整代碼)
- Python解決走迷宮問(wèn)題算法示例
- Python基于遞歸算法實(shí)現(xiàn)的走迷宮問(wèn)題
- Python深度優(yōu)先算法生成迷宮
- Python使用Tkinter實(shí)現(xiàn)機(jī)器人走迷宮
- Python使用回溯法子集樹(shù)模板解決迷宮問(wèn)題示例
- 一道python走迷宮算法題
- Python基于分水嶺算法解決走迷宮游戲示例
- Python 實(shí)現(xiàn)遞歸法解決迷宮問(wèn)題的示例代碼
- python實(shí)現(xiàn)地牢迷宮生成的完整步驟
相關(guān)文章
python 實(shí)現(xiàn)判斷ip連通性的方法總結(jié)
下面小編就為大家分享一篇python 實(shí)現(xiàn)判斷ip連通性的方法總結(jié),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-04-04
Perl中著名的Schwartzian轉(zhuǎn)換問(wèn)題解決實(shí)現(xiàn)
這篇文章主要介紹了Perl中著名的Schwartzian轉(zhuǎn)換問(wèn)題解決實(shí)現(xiàn),本文詳解講解了Schwartzian轉(zhuǎn)換涉及的排序問(wèn)題,并同時(shí)給出實(shí)現(xiàn)代碼,需要的朋友可以參考下2015-06-06
python打包壓縮、讀取指定目錄下的指定類(lèi)型文件
這篇文章主要介紹了python打包壓縮、讀取指定目錄下的指定類(lèi)型文件,需要的朋友可以參考下2018-04-04
python基于plotly實(shí)現(xiàn)畫(huà)餅狀圖代碼實(shí)例
這篇文章主要介紹了python基于plotly實(shí)現(xiàn)畫(huà)餅狀圖代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12
對(duì)python mayavi三維繪圖的實(shí)現(xiàn)詳解
今天小編就為大家分享一篇對(duì)python mayavi三維繪圖的實(shí)現(xiàn)詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-01-01
python實(shí)現(xiàn)的分層隨機(jī)抽樣案例
這篇文章主要介紹了python實(shí)現(xiàn)的分層隨機(jī)抽樣案例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-02-02

