[python] 이미지가 주어진 미로를 표현하고 해결하기

이미지가 주어진 미로를 표현하고 해결하는 가장 좋은 방법은 무엇입니까?

범위 문제 134의 표지 이미지

JPEG 이미지 (위에서 볼 수 있듯이)를 읽으면 그것을 읽는 가장 좋은 방법은 무엇입니까? 데이터 구조로 구문 분석하고 미로를 해결합니까? 첫 번째 본능은 이미지를 픽셀 단위로 읽고 이미지를 부울 값의 목록 (배열)에 저장하는 것입니다 : True흰색 픽셀 및 False흰색이 아닌 픽셀 (색상을 버릴 수 있음). 이 방법의 문제점은 이미지가 “픽셀 완벽”하지 않을 수 있다는 것입니다. 즉, 벽 어딘가에 흰색 픽셀이 있으면 의도하지 않은 경로가 만들어 질 수 있습니다.

또 다른 방법 (약간의 생각 후에 나에게 온)은 이미지를 캔버스에 그려진 경로 목록 인 SVG 파일로 변환하는 것입니다. 이런 식으로 경로 True는 경로 또는 벽을 False나타내는 이동 가능한 공간을 나타내는 동일한 종류의 목록 (부울 값)으로 읽을 수 있습니다 . 이 방법의 문제는 변환이 100 % 정확하지 않고 모든 벽을 완전히 연결하지 않아 간격이 생기는 경우에 발생합니다.

SVG로 변환 할 때의 문제는 선이 “완벽하게”직선적이지 않다는 것입니다. 그 결과 경로는 3 차 베 지어 곡선이됩니다. 정수로 인덱싱 된 부울 값의 목록 (배열)을 사용하면 곡선이 쉽게 전달되지 않고 곡선의 해당 선이 계산되어야하지만 목록 색인과 정확히 일치하지는 않습니다.

나는이 방법 중 하나가 효과적 일 수 있지만 그렇게 큰 이미지가 주어지면 비효율적이며 더 좋은 방법이 있다고 가정합니다. 이것이 가장 효율적이고 (또는 가장 복잡하지 않은) 어떻게 이루어 집니까? 가장 좋은 방법이 있습니까?

그런 다음 미로를 해결합니다. 처음 두 방법 중 하나를 사용하면 본질적으로 행렬로 끝납니다. 이 답변 에 따르면 , 미로를 표현하는 좋은 방법은 나무를 사용하는 것이고, 그것을 해결하는 좋은 방법은 A * 알고리즘을 사용하는 것입니다 . 이미지에서 나무를 어떻게 만들 수 있습니까? 어떤 아이디어?

TL; DR
파싱하는 가장 좋은 방법은? 어떤 데이터 구조로? 상기 구조는 어떻게 도움이되고 / 후진 해결에 도움이됩니까?

업데이트
@Thomas가 numpy권장하는 것처럼 @Mikhail이 Python으로 작성한 것을 구현하여 내 손을 사용해 보았습니다. 알고리즘이 정확하다고 생각하지만 원하는대로 작동하지 않습니다. PNG 코드는 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, [])



답변

해결책은 다음과 같습니다.

  1. 이미지를 그레이 스케일 (아직 이진 아님)로 변환하고 최종 그레이 스케일 이미지가 거의 균일하도록 색상의 가중치를 조정합니다. 이미지-> 조정-> 흑백에서 Photoshop의 슬라이더를 제어하여 간단하게 수행 할 수 있습니다.
  2. 이미지-> 조정-> 임계 값에서 Photoshop의 적절한 임계 값을 설정하여 이미지를 이진으로 변환하십시오.
  3. 임계 값이 올바르게 선택되어 있는지 확인하십시오. 공차, 포인트 샘플, 연속, 앤티 앨리어싱이없는 Magic Wand Tool을 사용하십시오. 선택이 끊어진 가장자리가 잘못된 임계 값으로 인해 잘못된 가장자리가 아닌지 확인하십시오. 실제로,이 미로의 모든 내부 지점은 처음부터 접근 할 수 있습니다.
  4. 미로에 인공 테두리를 추가하여 가상 여행자가 주변을 걷지 않도록하십시오 🙂
  5. 원하는 언어로 너비 우선 검색 (BFS)을 구현 하고 처음부터 실행하십시오. 이 작업에 MATLAB 을 선호합니다 . @Thomas가 이미 언급했듯이 정기적 인 그래프 표현을 망칠 필요가 없습니다. 이진화 된 이미지로 직접 작업 할 수 있습니다.

다음은 BFS 용 MATLAB 코드입니다.

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

실제로 매우 간단하고 표준 적이므로 Python 또는 기타로 구현하는 데 어려움이 없어야합니다 .

그리고 여기에 답이 있습니다 :

여기에 이미지 설명을 입력하십시오


답변

이 솔루션은 Python으로 작성되었습니다. 이미지 준비에 대한 조언을 준 Mikhail에게 감사합니다.

애니메이션 폭 우선 검색 :

BFS의 애니메이션 버전

완성 된 미로 :

완성 된 미로

#!/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 <mazefile> <outputfile>[.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])

참고 : 방문한 흰색 픽셀을 회색으로 표시합니다. 이렇게하면 방문한 목록이 필요하지 않지만 경로를 그리기 전에 디스크에서 이미지 파일을 다시로드해야합니다 (최종 경로와 전체 경로의 합성 이미지를 원하지 않는 경우).

내가 사용한 미로의 빈 버전.


답변

이 문제에 대해 A-Star 검색을 구현하려고했습니다. 프레임 워크와 여기에 주어진 알고리즘 의사 코드에 대한 Joseph Kern 의 구현을 면밀히 따랐 습니다 .

def AStar(start, goal, neighbor_nodes, distance, cost_estimate):
    def reconstruct_path(came_from, current_node):
        path = []
        while current_node is not None:
            path.append(current_node)
            current_node = came_from[current_node]
        return list(reversed(path))

    g_score = {start: 0}
    f_score = {start: g_score[start] + cost_estimate(start, goal)}
    openset = {start}
    closedset = set()
    came_from = {start: None}

    while openset:
        current = min(openset, key=lambda x: f_score[x])
        if current == goal:
            return reconstruct_path(came_from, goal)
        openset.remove(current)
        closedset.add(current)
        for neighbor in neighbor_nodes(current):
            if neighbor in closedset:
                continue
            if neighbor not in openset:
                openset.add(neighbor)
            tentative_g_score = g_score[current] + distance(current, neighbor)
            if tentative_g_score >= g_score.get(neighbor, float('inf')):
                continue
            came_from[neighbor] = current
            g_score[neighbor] = tentative_g_score
            f_score[neighbor] = tentative_g_score + cost_estimate(neighbor, goal)
    return []

A-Star는 휴리스틱 검색 알고리즘이므로 목표에 도달 할 때까지 남은 비용 (여기 : 거리)을 추정하는 기능이 필요합니다. 차선책 솔루션에 익숙하지 않으면 비용을 과대 평가해서는 안됩니다. 보수적 인 선택은 여기서 맨하탄 (또는 택시) 거리 입니다. 이는 사용 된 폰 노이만 (Von Neumann) 이웃에 대한 그리드상의 두 점 사이의 직선 거리 를 나타냅니다. (이 경우 비용을 과대 평가하지는 않습니다.)

그러나 이것은 주어진 미로에 대한 실제 비용을 상당히 과소 평가합니다. 따라서 비교를 위해 두 개의 다른 거리 메트릭에 제곱 유클리드 거리와 맨해튼 거리에 4를 곱한 값을 추가했습니다. 그러나 이는 실제 비용을 과대 평가하여 차선책의 결과를 산출 할 수 있습니다.

코드는 다음과 같습니다.

import sys
from PIL import Image

def is_blocked(p):
    x,y = p
    pixel = path_pixels[x,y]
    if any(c < 225 for c in pixel):
        return True
def von_neumann_neighbors(p):
    x, y = p
    neighbors = [(x-1, y), (x, y-1), (x+1, y), (x, y+1)]
    return [p for p in neighbors if not is_blocked(p)]
def manhattan(p1, p2):
    return abs(p1[0]-p2[0]) + abs(p1[1]-p2[1])
def squared_euclidean(p1, p2):
    return (p1[0]-p2[0])**2 + (p1[1]-p2[1])**2

start = (400, 984)
goal = (398, 25)

# invoke: python mazesolver.py <mazefile> <outputfile>[.jpg|.png|etc.]

path_img = Image.open(sys.argv[1])
path_pixels = path_img.load()

distance = manhattan
heuristic = manhattan

path = AStar(start, goal, von_neumann_neighbors, distance, heuristic)

for position in path:
    x,y = position
    path_pixels[x,y] = (255,0,0) # red

path_img.save(sys.argv[2])

다음은 결과를 시각화하기위한 일부 이미지입니다 ( Joseph Kern이 게시 한 이미지에서 영감을 얻음 ). 애니메이션은 기본 while 루프를 10000 회 반복 한 후 각각 새로운 프레임을 보여줍니다.

너비 우선 검색 :

너비 우선 검색

스타 맨하탄 거리 :

스타 맨하탄 거리

별 제곱 유클리드 거리 :

별 제곱 유클리드 거리

A-Star Manhattan Distance에 4를 곱한 값 :

A-Star Manhattan Distance에 4를 곱한 값

결과는 미로의 탐색 된 영역이 사용되는 휴리스틱에 대해 상당히 다르다는 것을 보여줍니다. 따라서, 제곱 유클리드 거리는 다른 메트릭과는 다른 (차선이 아닌) 경로를 생성합니다.

종료까지의 런타임 측면에서 A-Star 알고리즘의 성능과 관련하여, 거리 및 비용 함수에 대한 많은 평가는 BFS (Breadth-First Search)와 비교하여 “goaliness”만 평가하면됩니다. 각 후보자 위치. 이러한 추가 기능 평가 (A-Star)에 대한 비용이 많은 수의 노드 (BFS)에 대한 비용보다 큰지 여부, 특히 성능이 애플리케이션에 전혀 문제가되지 않는지는 개별 인식의 문제입니다. 물론 일반적으로 대답 할 수 없습니다.

정보 검색 알고리즘 (예 : A-Star)이 전체 검색 (예 : BFS)에 비해 더 나은 선택이 될 수 있는지에 대해 일반적으로 말할 있는 것은 다음과 같습니다. 미로의 차원의 수, 즉 검색 트리의 분기 인자에 따라, 철저한 검색 (완전히 검색)의 단점이 기하 급수적으로 증가한다. 복잡성 증가로 당신이 꽤 많이 행복과 그래서 어떤 시점에서 할 적게 가능하게 어떤 결과로, 그것은 (약) 최적의 여부를합니다.


답변

나무 검색이 너무 많습니다. 미로는 본질적으로 용액 경로 ​​(들)를 따라 분리 가능하다.

( Redit의 rainman002 에게 감사의 말을 합니다.)

이로 인해 연결된 구성 요소 를 빠르게 사용 하여 미로 벽의 연결된 섹션을 식별 할 수 있습니다 . 픽셀을 두 번 반복합니다.

이를 솔루션 경로의 멋진 다이어그램으로 바꾸려면, 구조화 요소와 함께 이진 연산을 사용하여 연결된 각 영역의 “데드 엔드”경로를 채울 수 있습니다.

MATLAB의 데모 코드는 다음과 같습니다. 조정을 사용하여 결과를 더 잘 정리하고 더 일반화 할 수 있으며 더 빠르게 실행할 수 있습니다. (오전 2:30이 아닌 경우도 있습니다.)

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

현재 코드의 결과


답변

임계 값 연속 채우기에 큐를 사용합니다. 입구의 왼쪽 픽셀을 큐로 밀고 루프를 시작합니다. 대기중인 픽셀이 충분히 어두우면 밝은 회색 (임계 값 위)으로 표시되고 모든 인접 항목이 대기열로 푸시됩니다.

from PIL import Image
img = Image.open("/tmp/in.jpg")
(w,h) = img.size
scan = [(394,23)]
while(len(scan) > 0):
    (i,j) = scan.pop()
    (r,g,b) = img.getpixel((i,j))
    if(r*g*b < 9000000):
        img.putpixel((i,j),(210,210,210))
        for x in [i-1,i,i+1]:
            for y in [j-1,j,j+1]:
                scan.append((x,y))
img.save("/tmp/out.png")

해결책은 회색 벽과 컬러 벽 사이의 복도입니다. 이 미로는 여러 가지 해결책이 있습니다. 또한 이것은 단지 작동하는 것처럼 보입니다.

해결책


답변

여기 있습니다 : maze-solver-python (GitHub)

여기에 이미지 설명을 입력하십시오

나는 이것을 가지고 놀았고 Joseph Kern 의 대답을 확장했습니다 . 그것을 방해하지 마십시오. 나는 이것으로 놀고 싶어하는 다른 사람들을 위해 약간의 추가를했습니다.

BFS를 사용하여 최단 경로를 찾는 파이썬 기반 솔버입니다. 당시의 주요 추가 사항은 다음과 같습니다.

  1. 검색하기 전에 이미지가 정리됩니다 (즉, 순수한 흑백으로 변환)
  2. GIF를 자동으로 생성합니다.
  3. AVI를 자동으로 생성합니다.

그대로, 시작 / 종료 지점 이이 샘플 미로에 대해 하드 코딩되어 있지만 적절한 픽셀을 선택할 수 있도록 확장 할 계획입니다.


답변

부울 매트릭스 옵션으로갑니다. 표준 파이썬 목록이 너무 비효율적이라는 것을 알게되면 numpy.bool대신 배열을 사용할 수 있습니다 . 1000×1000 픽셀 미로의 저장 용량은 1MB에 불과합니다.

트리 또는 그래프 데이터 구조를 만드는 데 신경 쓰지 마십시오. 그것은 단지 그것에 대해 생각하는 방법 일 뿐이지 만 반드시 그것을 기억에 표현하는 좋은 방법은 아닙니다. 부울 행렬은 코딩하기 쉽고 효율적입니다.

그런 다음 A * 알고리즘을 사용하여 해결하십시오. 거리 휴리스틱의 경우 맨해튼 거리 ( distance_x + distance_y)를 사용하십시오 .

튜플 (row, column)좌표로 노드를 나타냅니다 . 알고리즘 ( 위키피디아 의사 코드 )이 “이웃”을 요구할 때마다 네 개의 가능한 이웃을 반복하는 것은 간단합니다 (이미지의 가장자리를 생각하십시오!).

여전히 너무 느리다면 이미지를로드하기 전에 다운 스케일링을 시도 할 수 있습니다. 프로세스에서 좁은 경로를 잃지 않도록주의하십시오.

파이썬에서 1 : 2 다운 스케일링을 수행하여 실제로 가능한 경로를 잃지 않는지 확인할 수 있습니다. 흥미로운 옵션이지만 조금 더 생각해야합니다.