การเป็นตัวแทนและการแก้เขาวงกตให้ภาพ


271

วิธีที่ดีที่สุดในการเป็นตัวแทนและแก้ไขเขาวงกตที่ได้รับภาพคืออะไร?

ภาพหน้าปกของ The Scope Issue 134

เมื่อพิจารณาจากรูปภาพ JPEG (ดังที่แสดงไว้ด้านบน) วิธีที่ดีที่สุดในการอ่านข้อมูลคือแยกวิเคราะห์ข้อมูลลงในโครงสร้างข้อมูลบางส่วนและแก้ปัญหาเขาวงกต สัญชาตญาณแรกของฉันคือการอ่านภาพทีละพิกเซลและเก็บไว้ในรายการ (อาร์เรย์) ของค่าบูลีน: TrueสำหรับพิกเซลสีขาวและพิกเซลFalseที่ไม่ใช่สีขาว (สีสามารถละทิ้ง) ปัญหาด้วยวิธีนี้คือภาพอาจจะไม่ "สมบูรณ์แบบพิกเซล" โดยที่ฉันเพียงแค่หมายความว่าหากมีพิกเซลสีขาวบางแห่งบนผนังก็อาจสร้างเส้นทางที่ไม่ได้ตั้งใจ

อีกวิธีหนึ่ง (ซึ่งมาหาฉันหลังจากคิดไปเล็กน้อย) ก็คือการแปลงภาพเป็นไฟล์ SVG ซึ่งเป็นรายการของเส้นทางที่วาดบนผืนผ้าใบ ด้วยวิธีนี้เส้นทางสามารถอ่านได้ในรายการประเภทเดียวกัน (ค่าบูลีน) ซึ่งTrueระบุเส้นทางหรือกำแพงเพื่อFalseระบุพื้นที่ที่สามารถเดินทางได้ ปัญหาด้วยวิธีนี้เกิดขึ้นหากการแปลงไม่ถูกต้อง 100% และไม่เชื่อมต่อกำแพงทั้งหมดอย่างเต็มที่ทำให้เกิดช่องว่าง

นอกจากนี้ปัญหาเกี่ยวกับการแปลงเป็น SVG ก็คือเส้นไม่ตรง "สมบูรณ์แบบ" ซึ่งส่งผลในเส้นทางเป็นลูกบาศก์ bezier โค้ง ด้วยรายการ (อาร์เรย์) ของค่าบูลีนที่จัดทำดัชนีโดยจำนวนเต็มเส้นโค้งจะไม่ถ่ายโอนได้อย่างง่ายดายและทุกจุดที่เส้นบนเส้นโค้งจะต้องถูกคำนวณ แต่จะไม่ตรงกับดัชนีรายการทั้งหมด

ฉันคิดว่าในขณะที่หนึ่งในวิธีการเหล่านี้อาจใช้งานได้ (แม้ว่าอาจจะไม่ใช่) ว่าพวกเขาไม่มีประสิทธิภาพมากนักเนื่องจากภาพขนาดใหญ่ดังกล่าวและมีวิธีที่ดีกว่า วิธีนี้ดีที่สุด (มีประสิทธิภาพมากที่สุดและ / หรือมีความซับซ้อนน้อยที่สุด) ทำอย่างไร มีวิธีที่ดีที่สุดหรือไม่?

จากนั้นการแก้ปัญหาของเขาวงกตมา ถ้าฉันใช้วิธีใดวิธีหนึ่งจากสองวิธีแรกฉันจะต้องใช้เมทริกซ์เป็นหลัก ตามคำตอบนี้เป็นวิธีที่ดีที่จะเป็นตัวแทนเขาวงกตใช้ต้นไม้และเป็นวิธีที่ดีที่จะแก้ปัญหาได้คือการใช้A * อัลกอริทึม เราจะสร้างต้นไม้จากภาพได้อย่างไร ความคิดใด ๆ

TL; DR
วิธีแยกวิเคราะห์ที่ดีที่สุด? โครงสร้างข้อมูลอะไร โครงสร้างดังกล่าวจะช่วย / แก้ปัญหาได้อย่างไร?

อัปเดต
ฉันได้ลองใช้สิ่งที่ @Mailail เขียนไว้ใน 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, [])

12
ฉันจะแปลงเขาวงกตให้เป็นขาวดำและใช้เส้นทางค้นหาวิธีออโตมาตาเซลลูลาร์เพื่อแก้ปัญหา
Dan D.

คุณจำเป็นต้องจัดการกับภาพนั้นหรือด้วยภาพมากมายเช่นนั้นหรือไม่? คือมีตัวเลือกสำหรับการประมวลผลแบบแมนนวลเฉพาะสำหรับภาพบางส่วนนี้หรือไม่?
Mikhail

1
@Whymarrh ฉันทำไม่ได้รหัสหลาม แต่ผมค่อนข้างมั่นใจว่าคุณควรย้ายvisited.append(s)ใต้และแทนที่ด้วยfor.if visited.append(np)เยี่ยมชมจุดสุดยอดเมื่อมันถูกเพิ่มลงในคิว อันที่จริงแล้วอาร์เรย์นี้ควรมีชื่อว่า "อยู่ในคิว" นอกจากนี้คุณยังสามารถยกเลิก BFS เมื่อคุณทำเสร็จแล้ว
มิคาอิล

2
@ Whymarrh และคุณก็ดูเหมือนจะข้ามการใช้บล็อกแยกเส้นทาง หากไม่มีคุณจะสามารถค้นหาได้ว่าการเข้าถึงนั้นเสร็จสิ้นหรือไม่ แต่ไม่ใช่ว่าจะทำได้อย่างไร
มิคาอิล

1
หากต้องการค้นหาว่ามีเป็นวิธีการแก้ปัญหาเป็น UnionFind และเชิงเส้นสแกนขั้นตอนวิธีที่เร็วที่สุด มันไม่ได้ให้เส้นทางคุณ แต่ให้ชุดไพ่ซึ่งจะมีเส้นทางเป็นชุดย่อย
st0le

คำตอบ:


236

นี่คือทางออก

  1. แปลงภาพเป็นโทนสีเทา (ยังไม่เป็นฐานสอง) ปรับน้ำหนักสำหรับสีเพื่อให้ภาพสีเทาขั้นสุดท้ายมีความสม่ำเสมอโดยประมาณ คุณสามารถทำได้โดยควบคุมแถบเลื่อนใน Photoshop ในภาพ -> การปรับ -> ขาวดำ
  2. แปลงรูปภาพเป็นไบนารีโดยตั้งค่าขีด จำกัด ที่เหมาะสมใน Photoshop ใน Image -> Adjustments -> Threshold
  3. ตรวจสอบให้แน่ใจว่าได้เลือกเกณฑ์ถูกต้อง ใช้เครื่องมือ Magic Wand พร้อมความคลาดเคลื่อน 0 จุดตัวอย่างติดกันไม่มีรอยหยัก ตรวจสอบว่าขอบที่ตัวเลือกการแบ่งไม่ใช่ขอบปลอมที่นำมาใช้โดยขีด จำกัด ที่ไม่ถูกต้อง ที่จริงแล้วทุกจุดภายในของเขาวงกตนี้สามารถเข้าถึงได้ตั้งแต่เริ่มต้น
  4. เพิ่มเส้นขอบประดิษฐ์บนเขาวงกตเพื่อให้แน่ใจว่านักเดินทางเสมือนจริงจะไม่เดินไปรอบ ๆ :)
  5. ใช้การค้นหาแบบกว้าง (BFS) ในภาษาที่คุณชื่นชอบและเรียกใช้จากจุดเริ่มต้น ฉันชอบ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, :) = 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หรืออะไรก็ตาม

และนี่คือคำตอบ:

ป้อนคำอธิบายภาพที่นี่


1
@ Whymarrh ดีสำหรับ "เพียงแค่ภาพนี้" ตอนนี้คุณมีคำตอบจริง คุณมีคำถามเฉพาะหรือไม่? รายการที่ 1-4 จากรายการของฉันเป็นการประมวลผลด้วยตนเองที่ฉันถาม รายการที่ 5 เป็น BFS - อัลกอริทึมพื้นฐานสำหรับกราฟ แต่สามารถนำไปใช้กับภาพโดยตรงโดยไม่ต้องแปลงพิกเซลเป็นจุดยอดและเพื่อนบ้านกับขอบ
Mikhail

ฉันรู้สึกว่าคุณได้ครอบคลุมทุกอย่าง ฉันพยายามใช้สิ่งที่คุณพูดใน Python (โดยใช้ DFS แทน BFS เพียงเพราะฉันเขียนโค้ดนั้นมาก่อน) ฉันจะกลับมาอัปเดตคำถาม / ยอมรับคำตอบในอีกสักครู่
Whymarrh

2
@Whymarrh DFS จะไม่พบคุณทางที่สั้นที่สุดในขณะที่ BFS จะ ความแตกต่างเพียงอย่างเดียวคือโครงสร้างพื้นฐาน สแต็ก (FILO) สำหรับ DFS และคิว (FIFO) สำหรับ BFS
Mikhail

3
BFS เป็นตัวเลือกที่เหมาะสมที่นี่เพราะมันสร้างเส้นทางที่สั้นที่สุดซึ่งให้เส้นทางที่ "มีเหตุผล" แม้ว่าทางเดินจะกว้างกว่า 1 พิกเซล OTOH DFS มีแนวโน้มที่จะสำรวจบริเวณทางเดินและบริเวณเขาวงกตที่ไม่มีท่าว่าจะมีรูปแบบ "การเติมน้ำท่วม"
j_random_hacker

1
@JosephKern Path ไม่ได้ซ้อนทับกำแพงใด ๆ เพียงลบพิกเซลสีแดงทั้งหมดแล้วไปเลย
Mikhail

160

โซลูชันนี้เขียนด้วย 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])

หมายเหตุ: ทำเครื่องหมายสีเทาพิกเซลที่เยี่ยมชมเป็นสีขาว สิ่งนี้จะลบความต้องการรายการที่เข้าชม แต่ต้องใช้โหลดไฟล์ภาพที่สองจากดิสก์ก่อนที่จะวาดเส้นทาง (หากคุณไม่ต้องการภาพคอมโพสิตของเส้นทางสุดท้ายและเส้นทางทั้งหมดที่ใช้)

รุ่นที่ว่างของเขาวงกตที่ฉันใช้


13
เพราะคุณยอดเยี่ยมพอที่จะกลับมาและโหวตฉันได้แม้หลังจากที่คำถามของคุณได้รับคำตอบฉันจึงสร้าง gif ที่เคลื่อนไหวได้ของ BFS เพื่อช่วยให้เห็นภาพกระบวนการได้ดีขึ้น
Joseph Kern

1
ขอขอบคุณ สำหรับคนอื่น ๆ ที่ต้องการเล่นกับสิ่งนี้เหมือนที่ฉันทำฉันต้องการแบ่งปันเคล็ดลับตามปัญหาที่ฉันเผชิญ 1) แปลงรูปภาพเป็นขาวดำบริสุทธิ์หรือปรับเปลี่ยนฟังก์ชั่น 'isWhite ()' ของคุณเพื่อยอมรับสีขาวดำ | ดำ ฉันเขียนวิธีการ 'cleanImage' ซึ่งประมวลผลพิกเซลทั้งหมดล่วงหน้าเพื่อแปลงเป็นสีขาวบริสุทธิ์หรือสีดำมิฉะนั้นอัลกอริทึมจะล้มเหลวในการค้นหาเส้นทาง 2) อ่านภาพในรูปแบบ RGB [base_img = Image.open (img_in); base_img = base_img.convert ('RGB')] หากต้องการรับ gif ให้ส่งหลายภาพแล้วเรียกใช้ 'convert -delay 5 -loop 1 * .jpg bfs.gif'
stefano

1
ไม่มีการเยื้องในบรรทัดที่ 13
เริ่ม

81

ฉันลองใช้การค้นหาแบบ A-Star เพื่อหาปัญหานี้ ติดตามการนำไปใช้อย่างใกล้ชิดโดยJoseph Kernสำหรับกรอบงานและอัลกอริธึม pseudocode ที่ให้ไว้ที่นี่ :

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 (ซึ่งในกรณีนี้จะไม่ประเมินค่าสูงไปเลย)

อย่างไรก็ตามสิ่งนี้จะประเมินค่าใช้จ่ายจริงของเขาวงกตที่ดูถูกดูแคลน ดังนั้นฉันได้เพิ่มตัวชี้วัดระยะทางอีกสองตัวนั่นคือระยะทางแบบยุคลิดกำลังสองและระยะทางแมนฮัตตันคูณด้วยสี่เพื่อการเปรียบเทียบ อย่างไรก็ตามสิ่งเหล่านี้อาจประเมินค่าสูงไปกว่าต้นทุนจริงและอาจทำให้ได้ผลลัพธ์ที่ไม่ดี

นี่คือรหัส:

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

นี่คือภาพบางส่วนสำหรับการสร้างภาพข้อมูลผลลัพธ์ (ได้รับแรงบันดาลใจจากภาพที่โพสต์โดยโจเซฟเคอร์น ) อนิเมชั่นแสดงเฟรมใหม่แต่ละเฟรมหลังจากวนซ้ำ 10,000 รอบของเฟรมหลักขณะวนซ้ำ

ค้นหาความกว้าง - แรก:

ค้นหาความกว้าง - แรก

ระยะทาง A-Star Manhattan:

ระยะทางของแมนฮัตตัน A-Star

ระยะทางยูสคาไลด์ระดับ A-Star

ระยะห่างแบบยุคลิด A-Star

ระยะทาง A-Star Manhattan คูณด้วยสี่:

ระยะทาง A-Star Manhattan คูณด้วยสี่

ผลการวิจัยพบว่าบริเวณที่สำรวจของเขาวงกตนั้นแตกต่างกันอย่างมากสำหรับฮิวริสติกที่ใช้ ด้วยเหตุนี้ระยะทางแบบยุคลิดแบบสแควร์แม้แต่สร้างเส้นทางที่แตกต่าง (suboptimal) เป็นตัวชี้วัดอื่น

เกี่ยวกับประสิทธิภาพของอัลกอริธึม A-Star ในแง่ของรันไทม์จนกระทั่งสิ้นสุดโปรดทราบว่าการประเมินระยะทางและฟังก์ชั่นค่าใช้จ่ายเพิ่มขึ้นมากเมื่อเทียบกับการค้นหา Breadth-First Search (BFS) ซึ่งจะต้องประเมิน "เป้าหมาย" ของ ตำแหน่งผู้สมัครแต่ละคน ค่าใช้จ่ายสำหรับการประเมินฟังก์ชั่นเพิ่มเติมเหล่านี้ (A-Star) นั้นมีค่ามากกว่าค่าใช้จ่ายสำหรับจำนวนโหนดที่ต้องตรวจสอบ (BFS) มากขึ้นหรือไม่โดยเฉพาะอย่างยิ่งว่าประสิทธิภาพเป็นปัญหาสำหรับแอปพลิเคชันของคุณหรือไม่ และแน่นอนไม่สามารถตอบได้โดยทั่วไป

สิ่งที่สามารถพูดโดยทั่วไปเกี่ยวกับว่าอัลกอริทึมการค้นหาที่มีข้อมูล (เช่น A-Star) อาจเป็นตัวเลือกที่ดีกว่าเมื่อเปรียบเทียบกับการค้นหาแบบละเอียด (เช่น BFS) ดังต่อไปนี้ ด้วยจำนวนมิติของเขาวงกตคือปัจจัยการแตกกิ่งของต้นไม้การค้นหาข้อเสียของการค้นหาแบบละเอียด (เพื่อค้นหาแบบละเอียด) จะเพิ่มขึ้นแบบทวีคูณ ด้วยความซับซ้อนที่เพิ่มขึ้นมันจะน้อยลงและเป็นไปได้ที่จะทำเช่นนั้นและในบางจุดคุณมีความสุขมากกับเส้นทางผลลัพธ์ใด ๆไม่ว่าจะเป็น (โดยประมาณ) ที่ดีที่สุดหรือไม่


1
"A-Star Manhattan Distance คูณด้วยสี่"? A-Star ไม่ใช่ A-Star หากฮิวริสติกสามารถประเมินระยะทางไกลเกินไป (และไม่รับประกันว่าจะหาเส้นทางที่สั้นที่สุดเช่นกัน)
ตัวอย่างที่

@ ตัวอย่างแน่นอนถ้ามีการใช้ฟังก์ชันฮิวริสติกที่ไม่สามารถยอมรับได้อัลกอริทึมอาจล้มเหลวในการหาทางออกที่ดีที่สุด แต่ฉันจะไม่เปลี่ยนชื่ออัลกอริทึมพื้นฐานด้วยเหตุผลนั้น
moooeeeep

38

การค้นหาต้นไม้มากเกินไป เขาวงกตนั้นแยกออกจากกันได้ตามเส้นทางการแก้ปัญหา

(ขอบคุณrainman002จาก Reddit ที่ชี้เรื่องนี้ให้ฉัน)

ด้วยเหตุนี้คุณสามารถใช้ส่วนประกอบที่เชื่อมต่อได้อย่างรวดเร็วเพื่อระบุส่วนที่เชื่อมต่อของกำแพงเขาวงกต ซึ่งจะทำซ้ำพิกเซลมากกว่าสองครั้ง

หากคุณต้องการเปลี่ยนให้เป็นไดอะแกรมที่ดีของเส้นทางโซลูชันคุณสามารถใช้การทำงานแบบไบนารีกับองค์ประกอบโครงสร้างเพื่อกรอกเส้นทาง "เดดเอนด์" สำหรับแต่ละภูมิภาคที่เชื่อมต่อ

รหัสการสาธิตสำหรับ 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);

ผลลัพธ์ของรหัสปัจจุบัน


24

ใช้คิวสำหรับการเติมต่อเนื่องตามเกณฑ์ ดันพิกเซลทางซ้ายของทางเข้าสู่คิวแล้วเริ่มการวนซ้ำ หากพิกเซลที่อยู่ในคิวมืดพอก็จะเป็นสีเทาอ่อน (สูงกว่าขีด จำกัด ) และเพื่อนบ้านทั้งหมดจะถูกดันเข้าสู่คิว

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")

ทางออกคือทางเดินระหว่างผนังสีเทาและผนังสี หมายเหตุเขาวงกตนี้มีหลายวิธี นอกจากนี้ดูเหมือนว่าจะใช้งานได้เท่านั้น

สารละลาย


1
ความละเอียดไร้เดียงสาที่น่าสนใจขึ้นอยู่กับวิธีการส่งผ่านผนัง แน่นอนไม่ใช่คนที่ดีที่สุด แต่ฉันชอบมัน
zessx

23

ไปเลย: maze-solver-python (GitHub)

ป้อนคำอธิบายรูปภาพที่นี่

ฉันสนุกกับการเล่นกับสิ่งนี้และขยายความในโจเซฟเคอร์นคำตอบของไม่เบี่ยงเบนจากมัน ฉันเพิ่งเพิ่มเล็กน้อยสำหรับใครก็ตามที่อาจสนใจที่จะเล่นกับสิ่งนี้

มันเป็นตัวแก้ python ซึ่งใช้ BFS เพื่อค้นหาเส้นทางที่สั้นที่สุด การเพิ่มหลักของฉันในเวลานั้นคือ:

  1. ทำความสะอาดภาพก่อนการค้นหา (เช่นแปลงเป็นขาวดำ & บริสุทธิ์)
  2. สร้าง GIF โดยอัตโนมัติ
  3. สร้าง AVI โดยอัตโนมัติ

จุดเริ่มต้น / จุดสิ้นสุดนั้นมีการเข้ารหัสยากสำหรับเขาวงกตตัวอย่างนี้ แต่ฉันวางแผนที่จะขยายจุดเพื่อให้คุณสามารถเลือกพิกเซลที่เหมาะสมได้


1
เยี่ยมมากขอบคุณมันไม่ได้ทำงานบน BSD / Darwin / Mac การพึ่งพาบางอย่างและสคริปต์เชลล์ต้องการการเปลี่ยนแปลงเล็กน้อยสำหรับผู้ที่ต้องการลองใช้ Mac: [maze-solver-python]: github.com/holg/maze- solver-python
HolgT

@ HolgT: ดีใจที่คุณพบว่ามีประโยชน์ ฉันยินดีรับคำขอดึงใด ๆ สำหรับสิ่งนี้ :)
stefano

5

ฉันจะไปที่ตัวเลือก matrix-of-bools หากคุณพบว่ารายการ Python มาตรฐานไม่มีประสิทธิภาพสำหรับเรื่องนี้คุณสามารถใช้numpy.boolอาร์เรย์แทนได้ ที่เก็บข้อมูลสำหรับเขาวงกตขนาด 1,000x1000 พิกเซลมีขนาดเพียง 1 MB

อย่ากังวลกับการสร้างโครงสร้างข้อมูลต้นไม้หรือกราฟ นั่นเป็นเพียงวิธีการคิดเกี่ยวกับมัน แต่ไม่จำเป็นต้องเป็นวิธีที่ดีในการเป็นตัวแทนในหน่วยความจำ บูลีนเมทริกซ์นั้นง่ายต่อการเขียนโค้ดและมีประสิทธิภาพมากขึ้น

จากนั้นใช้อัลกอริทึม A * เพื่อแก้ปัญหา สำหรับการแก้ปัญหาระยะห่างให้ใช้ระยะทางแมนฮัตตัน ( distance_x + distance_y)

แทนโหนดด้วย tuple ของ(row, column)พิกัด เมื่อใดก็ตามที่อัลกอริธึม ( Wikipedia pseudocode ) เรียกร้องให้ "เพื่อนบ้าน" มันเป็นเรื่องง่าย ๆ ที่จะวนลูปเหนือเพื่อนบ้านทั้งสี่ที่เป็นไปได้ (คำนึงถึงขอบของภาพ!)

หากคุณพบว่ามันยังช้าเกินไปคุณสามารถลองลดขนาดภาพก่อนที่จะโหลด ระวังอย่าให้เส้นทางที่แคบ ๆ หายไปในกระบวนการ

อาจเป็นไปได้ที่จะทำการลดขนาด 1: 2 ใน Python เช่นกันตรวจสอบว่าคุณไม่สูญเสียเส้นทางที่เป็นไปได้ ตัวเลือกที่น่าสนใจ แต่ต้องการความคิดอีกเล็กน้อย


โพสต์บล็อกที่ยอดเยี่ยมนี้แสดงวิธีแก้ปัญหาเขาวงกตในวิชาคณิตศาสตร์ การแปลวิธีการเป็นงูเหลือมไม่น่าจะมีปัญหา
Boris Gorelik

ฉันได้อัปเดตคำถามแล้ว หากฉันเลือกใช้ RGB อเนกประสงค์แทนbooleanค่าที่เก็บจะยังคงเปรียบเทียบหรือไม่ เมทริกซ์นั้นคือ 2,400 * 1200 และ A * ใน BFS จะมีผลกระทบอย่างมีนัยสำคัญต่อเวลาทำงานจริงหรือไม่?
Whymarrh

@ Whymarrh ความลึกของบิตสามารถลดขนาดลงเพื่อชดเชย 2 บิตต่อพิกเซลควรเพียงพอสำหรับทุกคน
Brian Cain

5

นี่คือแนวคิดบางส่วน

(1. การประมวลผลภาพ :)

1.1 โหลดภาพเป็นแผนที่พิกเซลRGB ในC #มันเป็นเรื่องเล็ก ๆ system.drawing.bitmapน้อยใช้ ในภาษาที่มีไม่มีการสนับสนุนที่ง่ายสำหรับการถ่ายภาพเพียงแปลงภาพไปยัง รูปแบบพกพา pixmap (PPM) (การแสดงข้อความ Unix, ผลิตไฟล์ขนาดใหญ่) หรือบางรูปแบบไฟล์ไบนารีง่ายๆที่คุณสามารถอ่านได้อย่างง่ายดายเช่นBMPหรือTGA ImageMagickใน Unix หรือIrfanViewใน Windows

1.2 คุณอาจทำให้ข้อมูลง่ายขึ้นโดยการใช้ (R + G + B) / 3 สำหรับแต่ละพิกเซลเป็นตัวบ่งชี้ของโทนสีเทาแล้วกำหนดค่าเพื่อสร้างตารางขาวดำ บางสิ่งที่ใกล้เคียงกับ 200 สมมติว่า 0 = ดำและ 255 = สีขาวจะนำสิ่งประดิษฐ์ JPEG

(2. โซลูชั่น :)

2.1 การค้นหาความลึกครั้งแรก: เริ่มต้นสแต็กเปล่าที่มีตำแหน่งเริ่มต้นรวบรวมการติดตามผลที่มีอยู่เลือกหนึ่งโดยการสุ่มและกดลงบนสแต็คดำเนินการต่อจนกว่าจะถึงจุดสิ้นสุดหรือสิ้นสุด ใน backend dead โดย popping สแต็คคุณต้องติดตามตำแหน่งที่เข้าเยี่ยมชมบนแผนที่ดังนั้นเมื่อคุณรวบรวมการเคลื่อนไหวที่มีอยู่คุณจะไม่ใช้เส้นทางเดียวกันสองครั้ง น่าสนใจมากที่จะสร้างภาพเคลื่อนไหว

2.2 การค้นหาแบบกว้างก่อน: พูดถึงก่อนหน้านี้คล้ายกับด้านบน แต่ใช้คิวเท่านั้น ยังน่าสนใจที่จะเคลื่อนไหว ใช้งานได้เหมือนซอฟต์แวร์แก้ไขรูปภาพ ฉันคิดว่าคุณอาจสามารถแก้ปัญหาเขาวงกตใน Photoshop ได้โดยใช้กลอุบายนี้

2.3 ผู้ติดตามกำแพง: เขาพูดตามเรขาคณิตเขาวงกตเป็นท่อที่พับ / ซับซ้อน หากคุณวางมือบนกำแพงคุณจะพบทางออกในที่สุด) สิ่งนี้อาจไม่ได้ผลเสมอไป มีข้อสันนิษฐานบางอย่างเช่นเขาวงกตที่สมบูรณ์แบบเป็นต้นเช่นเขาวงกตบางแห่งมีเกาะต่างๆ อย่ามองมัน มันน่าสนใจ

(3. ความเห็น :)

นี่คือสิ่งที่ยุ่งยาก มันง่ายที่จะแก้ปัญหาเขาวงกตถ้าเป็นตัวแทนในอาร์เรย์อย่างเป็นทางการที่มีองค์ประกอบแต่ละอย่างเป็นประเภทเซลล์ที่มีผนังเหนือ, ตะวันออก, ใต้และตะวันตกและสนามธงที่เยี่ยมชม อย่างไรก็ตามเนื่องจากว่าคุณกำลังพยายามทำสิ่งนี้เนื่องจากภาพร่างที่วาดด้วยมือมันจะเลอะเทอะ ฉันคิดอย่างสุจริตว่าการพยายามหาเหตุผลเข้าข้างตนเองให้ร่างจะผลักดันคุณถั่ว นี่คล้ายกับปัญหาการมองเห็นด้วยคอมพิวเตอร์ที่เกี่ยวข้อง บางทีการเข้าไปยังแผนที่รูปภาพโดยตรงอาจทำได้ง่ายและสิ้นเปลืองมากกว่า


2

นี่คือวิธีการแก้ปัญหาโดยใช้อาร์

### 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")

Voila!

ทางออกที่ถูกต้องค้นหาเส้นทางที่สั้นที่สุด

นี่คือสิ่งที่จะเกิดขึ้นหากคุณไม่เติมพิกเซลขอบ (ฮา!) ...

รุ่นแก้ปัญหาที่นักแก้ปัญหาไปรอบเขาวงกต

การเปิดเผยอย่างสมบูรณ์: ฉันถามและตอบคำถามที่คล้ายกันมากก่อนที่ฉันจะพบคำถามนี้ จากนั้นด้วยเวทมนตร์ของ SO พบว่าหนึ่งในนี้เป็นหนึ่งใน "คำถามที่เกี่ยวข้อง" ด้านบน ฉันคิดว่าฉันใช้เขาวงกตนี้เป็นกรณีทดสอบเพิ่มเติม ... ฉันยินดีมากที่พบว่าคำตอบของฉันที่นั่นยังใช้ได้กับแอปพลิเคชันนี้ที่มีการปรับเปลี่ยนเพียงเล็กน้อย


0

ทางออกที่ดีก็คือแทนที่จะหาเพื่อนบ้านทีละพิกเซลมันจะทำโดยเซลล์เพราะทางเดินสามารถมี 15px ดังนั้นในทางเดินเดียวกันมันสามารถดำเนินการเหมือนซ้ายหรือขวาในขณะที่มันทำราวกับว่าการเคลื่อนที่ เป็นคิวบ์มันจะเป็นการกระทำที่ง่าย ๆ เช่นขึ้นลงซ้ายหรือขวา


คุณสามารถเพิ่มกราฟโซลูชันและอัลกอริทึมเช่นคำตอบที่เหลือเพื่อตรวจสอบจุดของคุณได้หรือไม่? จะดีกว่าถ้าคุณสามารถเพิ่มสิ่งเหล่านั้นเพื่อเพิ่มน้ำหนักให้กับคำตอบของคุณเพื่อให้ผู้อื่นสามารถเข้าใจคำตอบของคุณได้มากขึ้น
Himanshu Bansal
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.