用Python代码来解图片迷宫的方法整理


Posted in Python onApril 02, 2015

译注:原文是StackOverflow上一个如何用程序读取迷宫图片并求解的问题,几位参与者热烈地讨论并给出了自己的代码,涉及到用Python对图片的处理以及广度优先(BFS)算法等。

问题by Whymarrh:

用Python代码来解图片迷宫的方法整理

当给定上面那样一张JPEG图片,如何才能更好地将这张图转换为合适的数据结构并且解出这个迷宫?

我的第一直觉是将这张图按像素逐个读入,并存储在一个包含布尔类型元素的列表或数组中,其中True代表白色像素,False代表非白色像素(或彩色可以被处理成二值图像)。但是这种做法存在一个问题,那就是给定的图片往往并不能完美的“像素化”。考虑到如果因为图片转换的原因,某个非预期的白色像素出现在迷宫的墙上,那么就可能会创造出一一条非预期的路径。

经过思考之后,我想出了另一种方法:首先将图片转换为一个可缩放适量图形(SVG)文件,这个文件由一个画布上的矢量线条列表组成,矢量线条按照列表的顺序读取,读取出的仍是布尔值:其中True表示墙,而False表示可通过的区域。但是这种方法如果无法保证图像能够做到百分之百的精确转换,尤其是如果不能将墙完全准确的连接,那么这个迷宫就可能出现裂缝。

图像转换为SVG的另一个问题是,线条并不是完美的直线。因为SVG的线条是三次贝塞尔曲线,而使用整数索引的布尔值列表增加了曲线转换的难度,迷宫线条上的所有点在曲线上都必须经过计算,但不一定能够完美对应列表中的索引值。

假设以上方法的确可以实现(虽然很可能都不行),但当给定一张很大的图像时,它们还是不能胜任。那么是否存在一种更好地方法能够平衡效率和复杂度?

这就要讨论到如何解迷宫了。如果我使用以上两种方法中的任意一种,我最终将会得到一个矩阵。而根据这个问答(http://stackoverflow.com/questions/3097556/programming-theory-solve-a-maze/3097677#3097677),一个比较好的迷宫表示方式应该是使用树的结构,并且使用A*搜索算法来解迷宫。那么如何从迷宫图片中构造出迷宫树呢?有比较好的方法么?

以上废话太多,总结起来问题就是:如何转换迷宫图片?转换成为什么样的数据结构?采用什么样的数据结构能够帮助或阻碍解迷宫?

回答by Mikhail:

这是我的解决方案:

1. 将图片转换为灰度图像(不是直接二值),调整不同颜色的权重使得最终的灰度看起来比较统一,你可以通过简单地调节Photoshop 图像->调整->黑白 菜单中的控制条来实现。
2. 将上一步得到的灰度图片转换为二值图片,可以通过在PS 图像->调整->阈值 菜单中设定适当的阈值来实现
3. 确保正确设置了阈值。使用魔棒工具(参数设置:容差 0、取样点、连续以及消除锯齿)选择空白区域,检查所选区域的边缘不是因为错误的阈值设置而产生的假边缘。事实上,这个迷宫中从start到end应该由联通的空白区域。
4. 人为地在迷宫外部加上边界,确保迷宫漫游者^_^不会从start绕着迷宫跑到终点。:)
5. 选择语言实现广度优先搜索算法(BFS),从start处开始让程序运行。下面的代码我选择用Matlab实现。正如Thomas提到的,没必要纠结于图像的表示形式,你可以直接在二值图像上运行。

以下是用MATLAB实现的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

这是个简单的实现,应该很容易就能够改写为Python或其他语言,下面是程序的运行结果:

用Python代码来解图片迷宫的方法整理

提问者更新:

我用Python实现了一下Mikhail的方法,其中用到了numpy库,感谢Thomas推荐。我感觉这个算法是正确的,但是效果不太如预期,以下是相关代码,使用了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, [])

回答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])

动态执行效果:

用Python代码来解图片迷宫的方法整理

回答by Jim

使用树搜索太繁杂了,迷宫本身就跟解路径是可分的。正因如此,你可以使用连通区域查找算法来标记迷宫中的连通区域,这将迭代搜索两次这些像素点。如果你想要更好地解决方法,你可以对结构单元使用二元运算(binary operations)来填充每个连通区域中的死路。

下面是相关的MATLAB代码及运行结果:
 

% 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);

用Python代码来解图片迷宫的方法整理

回答by Stefano

stefano童鞋给出了生成搜索过程GIF及AVI文件的代码 maze-solver-python (GitHub)

用Python代码来解图片迷宫的方法整理

Python 相关文章推荐
基于Python的身份证号码自动生成程序
Aug 15 Python
Python实现的手机号归属地相关信息查询功能示例
Jun 08 Python
python通过getopt模块如何获取执行的命令参数详解
Dec 29 Python
python url 参数修改方法
Dec 26 Python
linux下安装python3和对应的pip环境教程详解
Jul 01 Python
后端开发使用pycharm的技巧(推荐)
Mar 27 Python
基于Python实现2种反转链表方法代码实例
Jul 06 Python
python json.dumps() json.dump()的区别详解
Jul 14 Python
Python进行特征提取的示例代码
Oct 15 Python
举例讲解Python装饰器
Dec 24 Python
python基于OpenCV模板匹配识别图片中的数字
Mar 31 Python
Python操作CSV格式文件的方法大全
Jul 15 Python
在Python3中使用asyncio库进行快速数据抓取的教程
Apr 02 #Python
Python中的Classes和Metaclasses详解
Apr 02 #Python
详解Python中的装饰器、闭包和functools的教程
Apr 02 #Python
详解Python的迭代器、生成器以及相关的itertools包
Apr 02 #Python
用Python实现通过哈希算法检测图片重复的教程
Apr 02 #Python
仅用500行Python代码实现一个英文解析器的教程
Apr 02 #Python
python下载文件时显示下载进度的方法
Apr 02 #Python
You might like
PHP内核介绍及扩展开发指南―基础知识
2011/09/11 PHP
深入php中var_dump方法的使用详解
2013/06/24 PHP
基于PHP+Ajax实现表单验证的详解
2013/06/25 PHP
php语言中使用json的技巧及json的实现代码详解
2015/10/27 PHP
PHP实现将标点符号正则替换为空格的方法
2017/08/09 PHP
PHP基于rabbitmq操作类的生产者和消费者功能示例
2018/06/16 PHP
JS继承 笔记
2011/07/13 Javascript
jQuery的each终止或跳过示例代码
2013/12/12 Javascript
Javascript Objects详解
2014/09/04 Javascript
TypeError document.getElementById(...) is null错误原因
2015/05/18 Javascript
创建一个类Person的简单实例
2016/05/17 Javascript
BootStrap实现带有增删改查功能的表格(DEMO详解)
2016/10/26 Javascript
VueJs监听window.resize方法示例
2018/01/17 Javascript
Angular5给组件本身的标签添加样式class的方法
2018/04/07 Javascript
微信公众平台获取access_token的方法步骤
2019/03/29 Javascript
JS获取本地地址及天气的方法实例小结
2019/05/10 Javascript
11个并不被常用但对开发非常有帮助的Python库
2015/03/31 Python
详谈python3中用for循环删除列表中元素的坑
2018/04/19 Python
python对list中的每个元素进行某种操作的方法
2018/06/29 Python
python实现烟花小程序
2019/01/30 Python
对PyQt5中树结构的实现方法详解
2019/06/17 Python
python实现电子书翻页小程序
2019/07/23 Python
浅谈Python中文件夹和python package包的区别
2020/06/01 Python
python和node.js生成当前时间戳的示例
2020/09/29 Python
45个非常奇妙的CSS3 特性应用示例
2012/01/01 HTML / CSS
购买限量版收藏品、珠宝和礼品:Bradford Exchange
2016/09/23 全球购物
巴西女装购物网站:Eclectic
2018/04/24 全球购物
文字自荐书范文
2014/02/10 职场文书
小学校长先进事迹材料
2014/05/13 职场文书
党的群众路线教育实践活动制度建设计划
2014/11/03 职场文书
合作合同协议书范本
2015/01/27 职场文书
陪护人员误工证明
2015/06/24 职场文书
中学教师读书笔记
2015/07/01 职场文书
大学生支教感言
2015/08/01 职场文书
企业财务管理制度范本
2015/08/04 职场文书