用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中的并发编程实例
Jul 07 Python
Python实现PS图像抽象画风效果的方法
Jan 23 Python
python numpy和list查询其中某个数的个数及定位方法
Jun 27 Python
详解关于Django中ORM数据库迁移的配置
Oct 08 Python
使用python绘制3维正态分布图的方法
Dec 29 Python
Python实现连接MySql数据库及增删改查操作详解
Apr 16 Python
pytorch 使用加载训练好的模型做inference
Feb 20 Python
Python爬虫如何应对Cloudflare邮箱加密
Jun 24 Python
Pytorch上下采样函数--interpolate用法
Jul 07 Python
pandas to_excel 添加颜色操作
Jul 14 Python
Python中的特殊方法以及应用详解
Sep 20 Python
使用Python开发贪吃蛇游戏 SnakeGame
Apr 30 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使用数据库永久连接方式操作MySQL的是与非
2013/06/05 PHP
PHP中的一些常用函数收集
2015/05/26 PHP
golang 调用 php7详解及实例
2017/01/04 PHP
javascript下function声明一些小结
2007/12/28 Javascript
javascript的函数、创建对象、封装、属性和方法、继承
2011/03/10 Javascript
Javascript 判断是否存在函数的方法
2013/01/03 Javascript
jquery+ajax+C#实现无刷新操作数据库数据的简单实例
2014/02/08 Javascript
JavaScript仿flash遮罩动画效果
2016/06/15 Javascript
AngularJS动态绑定HTML的方法分析
2016/11/07 Javascript
基于JS组件实现拖动滑块验证功能(代码分享)
2016/11/18 Javascript
Vue+mui实现图片的本地缓存示例代码
2018/05/24 Javascript
vuex实现的简单购物车功能示例
2019/02/13 Javascript
微信小程序实现获取准确的腾讯定位地址功能示例
2019/03/27 Javascript
详解服务端预渲染之Nuxt(介绍篇)
2019/04/07 Javascript
JS实现打字游戏
2019/12/17 Javascript
OpenLayer学习之自定义测量控件
2020/09/28 Javascript
Python中的闭包详细介绍和实例
2014/11/21 Python
Python基于pygame模块播放MP3的方法示例
2017/09/30 Python
pandas数据预处理之dataframe的groupby操作方法
2018/04/13 Python
解决python多线程报错:AttributeError: Can't pickle local object问题
2020/04/08 Python
keras.utils.to_categorical和one hot格式解析
2020/07/02 Python
树莓派4B安装Tensorflow的方法步骤
2020/07/16 Python
HTML4和HTML5之间除了相似以外的10个主要不同
2012/12/13 HTML / CSS
Javascript 高级手势使用介绍
2013/04/21 HTML / CSS
上课睡觉检讨书
2014/01/28 职场文书
中年人生感言
2014/02/04 职场文书
建筑工地大门标语
2014/06/18 职场文书
会计求职自荐信
2014/06/20 职场文书
环境科学专业教师求职信
2014/07/12 职场文书
环卫工作汇报材料
2014/10/28 职场文书
任命书怎么写
2015/03/02 职场文书
素质拓展训练感想
2015/08/07 职场文书
HashMap实现保存两个key相同的数据
2021/06/30 Java/Android
Python字符串常规操作小结
2022/04/03 Python
Python OpenCV形态学运算示例详解
2022/04/07 Python
Python如何加载模型并查看网络
2022/07/15 Python