이미지가 주어진 미로 표현 및 해결
이미지가 주어진 미로를 표현하고 해결하는 가장 좋은 방법은 무엇일까요?
JPEG 이미지(위 그림 참조)를 읽고 데이터 구조로 해석하여 미로를 해결하는 가장 좋은 방법은 무엇입니까?의 첫 이다.True
및 "" " " " " "False
흰색 이외의 픽셀의 경우(색상은 폐기할 수 있습니다.이 방법의 문제는 이미지가 "픽셀 퍼펙트"하지 않을 수 있다는 것입니다., 벽 하지 않은경로가 될 수 .
또 다른 방법(고민 끝에 찾아낸 방법)은 이미지를 SVG 파일로 변환하는 것입니다.SVG를 사용합니다.하면 값 수 .True
.False
이동 가능한 공간을 나타냅니다.변환이 100% 정확하지 않고 모든 벽이 완전히 연결되지 않아 틈이 생길 경우 이 방법에서 문제가 발생합니다.
또한 SVG로 변환할 때 문제가 되는 것은 회선이 "완벽하게" 직선화되어 있지 않다는 것입니다.그 결과 경로는 큐빅 베지어 곡선이 됩니다.정수로 색인화된 부울 값의 리스트(배열)를 사용하면 곡선이 쉽게 전송되지 않으며, 곡선의 선이 모두 계산되어야 하지만 목록 인덱스와 정확히 일치하지는 않습니다.
이러한 방법 중 하나가 효과가 있을 수 있지만(아마 효과가 없을지도 모르지만), 그러한 큰 이미지를 고려하면 매우 비효율적이며 더 나은 방법이 있다고 생각합니다.어떻게 하면 (가장 효율적이고 복잡성이 적은) 최선의 방법을 찾을 수 있을까요?최선의 방법은 없을까?
그리고 미로의 해결이 온다.처음 두 가지 방법 중 하나를 사용하면 기본적으로 행렬이 됩니다.이 답변에 따르면 미로를 표현하는 좋은 방법은 트리를 사용하는 것이고, 이를 해결하는 좋은 방법은 A* 알고리즘을 사용하는 것입니다.이미지에서 트리는 어떻게 만들까요?좋은 생각 있어요?
TL;DR
석하하 가장? ??? ???떤떤데 이이?구조가 문제 해결에 어떻게 도움이 됩니까?
@으로 쓴 @Mikhail Python으로 numpy
@Thomas 권장대로.알고리즘은 올바른 것 같습니다만, 희망대로 동작하고 있지 않습니다.(아래 코드)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, [])
여기 해결책이 있습니다.
- 이미지를 그레이스케일(아직 바이너리 아님)로 변환하여 최종 그레이스케일 이미지가 거의 균일해지도록 색상의 가중치를 조정합니다.Photoshop의 이미지 -> 조정 -> 흑백 슬라이더를 제어하기만 하면 됩니다.
- Photoshop에서 이미지 -> 조정 -> 임계값으로 적절한 임계값을 설정하여 이미지를 바이너리로 변환합니다.
- 임계값이 올바르게 선택되었는지 확인합니다.공차 0, 점 샘플, 연속, 안티 앨리어싱이 없는 Magic Wand Tool을 사용합니다.선택 중단이 잘못된 임계값으로 인해 유입된 거짓 에지가 아닌지 확인합니다.사실, 이 미로의 모든 내부 지점은 처음부터 접근할 수 있습니다.
- 가상 여행자가 미로 주변을 걸어다니지 않도록 미로 위에 인공 테두리를 추가합니다.
- 마음에 드는 언어로 Wide-First Search(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으로 작성되어 있습니다.이미지 준비에 대한 조언 감사합니다.
애니메이션 폭 우선 검색:
완성된 메이즈:
#!/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-loop을 10,000회 반복한 후 각각 새로운 프레임을 보여줍니다.
폭 우선 검색:
A-Star 맨해튼 거리:
A-별 제곱 유클리드 거리:
A-Star Manhattan Distance에 4를 곱한 값:
그 결과 미로의 탐색된 영역은 사용 중인 휴리스틱에 따라 상당히 다르다는 것을 알 수 있다.이와 같이, 유클리드 거리의 제곱은 다른 메트릭과 다른 (최적적이지 않은) 경로를 생성한다.
종료까지의 런타임 측면에서 A-Star 알고리즘의 성능에 대해서는 각 후보 위치의 "골리티"만 평가하면 되는 BFS(Width-First Search)에 비해 거리 및 비용 함수에 대한 많은 평가가 합산된다는 점에 유의하십시오.이러한 추가 기능 평가(A-Star) 비용이 더 많은 노드 수의 확인(BFS) 비용을 초과하는지 여부, 특히 성능이 애플리케이션에 문제가 되는지 여부는 개인의 인식 문제이며 일반적으로 답변할 수 없습니다.
종합검색(예: BFS)에 비해 정보검색 알고리즘(A-Star 등)이 더 나은 선택인지 아닌지에 대해 일반적으로 말할 수 있는 것은 다음과 같다.미로의 차원수, 즉 서치 트리의 분기 계수에 따라 (완전하게 서치하는) 완전 서치의 단점은 기하급수적으로 증가한다.복잡성이 증가함에 따라 실현 가능성이 점점 낮아지고, 어느 시점에서는 최적인지 아닌지에 관계없이 어떤 결과 경로에도 만족할 수 있습니다.
트리 검색이 너무 심해요.미로는 본질적으로 솔루션 경로를 따라 분리할 수 있습니다.
(Reddit의 rainman002가 이 점을 지적해 주셔서 감사합니다.)
따라서 연결된 구성요소를 빠르게 사용하여 미로 벽의 연결된 부분을 식별할 수 있습니다.이것은 픽셀로 2회 반복됩니다.
이를 솔루션 경로의 멋진 다이어그램으로 변환하려면 구조화 요소를 포함한 이진 연산을 사용하여 연결된 각 영역의 "막다른" 경로를 채울 수 있습니다.
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);
여기 있습니다: 미로솔버피톤(깃허브)
나는 이것을 가지고 노는 것을 즐겼고 조셉 컨의 대답을 연장했다.그것을 깎아내리지 않기 위해서; 나는 단지 이것을 가지고 노는 것에 관심이 있는 다른 사람들을 위해 몇 가지 사소한 추가 사항을 만들었다.
BFS를 사용하여 최단 경로를 찾는 파이썬 기반 솔버입니다.그 당시 제가 추가한 주요 사항은 다음과 같습니다.
- 이미지는 검색 전에 클리닝됩니다(순수 흑백으로 변환).
- GIF를 자동으로 생성합니다.
- AVI 를 자동적으로 생성합니다.
현재 이 샘플 메이즈는 시작점/끝점이 하드코드 되어 있습니다만, 적절한 픽셀을 선택할 수 있도록 확장할 예정입니다.
임계값 연속 채우기에 큐를 사용합니다.입구 왼쪽의 픽셀을 큐에 밀어넣고 루프를 시작합니다.큐에 있는 픽셀이 충분히 어두운 경우 해당 픽셀은 밝은 회색(임계값 이상)으로 표시되고 모든 네이버가 큐에 푸시됩니다.
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")
해결책은 회색 벽과 컬러 벽 사이의 복도입니다.이 미로에는 여러 가지 해결책이 있습니다.또한, 이것은 단지 효과가 있는 것처럼 보인다.
Matrix-of-Bools 션션- 。 되면 Python을 할 수 .numpy.bool
대신 배열합니다..
트리 또는 그래프 데이터 구조를 만들 필요가 없습니다.이는 단순한 사고방식일 뿐 메모리에 표현하는 것은 좋은 방법은 아닙니다.부울 매트릭스는 코딩이 쉽고 효율적입니다.
그런 다음 A* 알고리즘을 사용하여 해결합니다.휴리스틱에는 Manhattan distance,distance_x + distance_y
를 참조해 주세요.
represent 음 、 음 、 음 、 타 、 타 、 represent represent represent represent represent 。(row, column)
좌표를 표시합니다.알고리즘(Wikipedia pseudocode)이 「네이버」를 호출할 때마다, 4개의 가능한 인접 라우터를 루프 하는 것은 간단합니다(이미지의 엣지에 주의해 주세요).
아직 너무 느린 경우 로드하기 전에 이미지의 다운스케일을 시도할 수 있습니다.그 과정에서 좁은 길을 잃지 않도록 주의하세요.
Python에서도 1:2 다운스케일을 실행하여 실제로 경로를 잃지 않았는지 확인할 수 있습니다.흥미로운 선택이지만, 좀 더 생각해 볼 필요가 있다.
여기 몇 가지 아이디어가 있다.
(1. 이미지 처리:)
1.1 RGB 픽셀 맵으로 이미지를 로드합니다.C#에서는 를 사용하는 것은 간단하다.system.drawing.bitmap
간단한 이미징이 지원되지 않는 언어에서는 이미지를 Portable pixmap Format(PPM; 휴대용 pixmap 형식) (유닉스 텍스트 표현, 대용량 파일 생성) 또는 읽기 쉬운 간단한 바이너리 파일 형식(BMP 또는 TGA 등)으로 변환합니다.Unix 에서는 ImageMagick 또는 Windows 에서는 IrfanView.
1.2 앞에서 설명한 바와 같이 각 픽셀의 (R+G+B)/3을 회색 톤의 표시기로 하여 값을 임계값으로 하여 흑백 테이블을 생성함으로써 데이터를 단순화할 수 있습니다.0=검은색 및 255=흰색을 가정한 200에 가까운 값이 JPEG 아티팩트를 제거합니다.
(2) 솔루션:
2.1 깊이 우선 검색: 빈 스택을 시작 위치에서 시작하여 사용 가능한 후속 조치를 수집하고 무작위로 하나를 선택하여 스택에 밀어넣습니다.끝에 도달하거나 막다른 골목에 이를 때까지 계속 진행합니다.스택을 팝핑하는 데드엔드 백트랙에서는 사용 가능한 이동을 수집할 때 동일한 경로를 두 번 사용하지 않도록 맵에서 방문한 위치를 추적해야 합니다.애니메이션을 만들기에 매우 흥미롭습니다.
2.2 폭 우선 검색: 앞서 언급한 바와 같이 큐만 사용합니다.애니메이션을 만드는 것도 재밌어요.이것은 이미지 편집 소프트웨어의 플래드 필과 같이 동작합니다.이 트릭으로 포토샵의 미로를 풀 수 있을 것 같아요.
2.3 벽 팔로어: 기하학적으로 미로는 구부러진 튜브입니다.벽에 손을 대고 있으면 결국 출구를 찾을 수 있습니다.) 이것은 항상 효과가 있는 것은 아닙니다.예를 들어 완벽한 미로 등 특정 미로에 섬이 포함되어 있다는 가정이 있습니다.꼭 찾아봐라. 신기하다.
(3) 코멘트:
이게 좀 까다롭네요.각 요소가 북쪽, 동쪽, 남쪽 및 서쪽 벽과 방문한 깃발 필드를 가진 셀 유형으로 구성된 단순한 배열로 표현되면 미로를 쉽게 해결할 수 있습니다.그러나 손으로 그린 스케치를 통해 이 작업을 수행하려고 하면 혼란스러워집니다.솔직히 말해서 스케치를 합리화하려고 하면 미쳐버릴 것 같아.이것은 상당히 관련된 컴퓨터 시력 문제와 유사합니다.이미지 맵으로 직접 이동하는 것이 더 쉬울 수 있지만 더 낭비될 수 있습니다.
여기 R을 사용한 해결책이 있습니다.
### download the image, read it into R, converting to something we can play with...
library(jpeg)
url <- "https://i.stack.imgur.com/TqKCM.jpg"
download.file(url, "./maze.jpg", mode = "wb")
jpg <- readJPEG("./maze.jpg")
### reshape array into data.frame
library(reshape2)
img3 <- melt(jpg, varnames = c("y","x","rgb"))
img3$rgb <- as.character(factor(img3$rgb, levels = c(1,2,3), labels=c("r","g","b")))
## split out rgb values into separate columns
img3 <- dcast(img3, x + y ~ rgb)
RGB에서 그레이스케일(https://stackoverflow.com/a/27491947/2371031 참조)
# convert rgb to greyscale (0, 1)
img3$v <- img3$r*.21 + img3$g*.72 + img3$b*.07
# v: values closer to 1 are white, closer to 0 are black
## strategically fill in some border pixels so the solver doesn't "go around":
img3$v2 <- img3$v
img3[(img3$x == 300 | img3$x == 500) & (img3$y %in% c(0:23,988:1002)),"v2"] = 0
# define some start/end point coordinates
pts_df <- data.frame(x = c(398, 399),
y = c(985, 26))
# set a reference value as the mean of the start and end point greyscale "v"s
ref_val <- mean(c(subset(img3, x==pts_df[1,1] & y==pts_df[1,2])$v,
subset(img3, x==pts_df[2,1] & y==pts_df[2,2])$v))
library(sp)
library(gdistance)
spdf3 <- SpatialPixelsDataFrame(points = img3[c("x","y")], data = img3["v2"])
r3 <- rasterFromXYZ(spdf3)
# transition layer defines a "conductance" function between any two points, and the number of connections (4 = Manhatten distances)
# x in the function represents the greyscale values ("v2") of two adjacent points (pixels), i.e., = (x1$v2, x2$v2)
# make function(x) encourages transitions between cells with small changes in greyscale compared to the reference values, such that:
# when v2 is closer to 0 (black) = poor conductance
# when v2 is closer to 1 (white) = good conductance
tl3 <- transition(r3, function(x) (1/max( abs( (x/ref_val)-1 ) )^2)-1, 4)
## get the shortest path between start, end points
sPath3 <- shortestPath(tl3, as.numeric(pts_df[1,]), as.numeric(pts_df[2,]), output = "SpatialLines")
## fortify for ggplot
sldf3 <- fortify(SpatialLinesDataFrame(sPath3, data = data.frame(ID = 1)))
# plot the image greyscale with start/end points (red) and shortest path (green)
ggplot(img3) +
geom_raster(aes(x, y, fill=v2)) +
scale_fill_continuous(high="white", low="black") +
scale_y_reverse() +
geom_point(data=pts_df, aes(x, y), color="red") +
geom_path(data=sldf3, aes(x=long, y=lat), color="green")
보일라!
테두리 픽셀을 채우지 않으면 이렇게 됩니다(Ha!).
완전 공개:이 질문을 찾기 전에 저도 비슷한 질문을 하고 대답했습니다.그리고 SO의 마법을 통해 이 질문을 "관련 질문"의 상위 항목 중 하나로 발견했습니다.이 미로를 추가 테스트 케이스로 사용하려고 했는데...이 어플리케이션에서도 거의 수정하지 않고 답변을 할 수 있다는 것을 알게 되어 매우 기뻤습니다.
좋은 해결책은 픽셀 단위로 이웃을 찾는 것이 아니라 셀 단위로 하는 것입니다. 왜냐하면 코리더는 같은 코리더에서 15px를 가질 수 있기 때문에 왼쪽 또는 오른쪽과 같은 액션을 취할 수 있습니다. 반면, 위, 아래, 왼쪽 또는 오른쪽과 같은 액션이 큐브인 것처럼 행해지면 위, 아래, 오른쪽과 같은 단순한 액션이 됩니다.
언급URL : https://stackoverflow.com/questions/12995434/representing-and-solving-a-maze-given-an-image
'source' 카테고리의 다른 글
여러 하위 구를 사용하여 하위 구 크기/공간 개선 (0) | 2022.12.18 |
---|---|
MySQL 테이블 컬럼 서브스트링 방법 (0) | 2022.12.18 |
JavaScript를 사용하여 부모의 하위 요소 찾기 (0) | 2022.12.18 |
Python의 상대 경로 (0) | 2022.12.18 |
PHP 네임스페이스를 자동 로드와 함께 사용하려면 어떻게 해야 합니까? (0) | 2022.12.18 |