Представлення та розв’язання лабіринту даного образу


271

Який найкращий спосіб зобразити та розв’язати лабіринт із заданим зображенням?

Зображення обкладинки The Scope Issue 134

З огляду на зображення JPEG (як показано вище), який найкращий спосіб прочитати його, розібрати його в якусь структуру даних та вирішити лабіринт? Перший мій інстинкт - прочитати зображення в пікселі за пікселем і зберігати його у списку (масиві) булевих значень: Trueдля білого пікселя та Falseдля небілого пікселя (кольори можна відкинути). Проблема цього методу полягає в тому, що зображення може бути не "ідеальним для пікселів". Під цим я просто маю на увазі, що якщо є білий піксель десь на стіні, він може створити ненавмисний шлях.

Інший метод (який прийшов до мене після трохи роздумів) - це перетворити зображення у файл 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, [])

12
Я б перетворив лабіринт у чорно-біле і застосував метод пошуку шляху стільникового автомата для його вирішення.
Ден Д.

Чи потрібно мати справу лише з цим зображенням або з багатьма подібними зображеннями? Тобто є можливість певної обробки вручну, специфічної для цього певного зображення?
Михайло

1
@Whymarrh Я не кодую python, але я впевнений, що вам слід перейти visited.append(s)під a for.ifі замінити його visited.append(np). Вершина відвідується після її додавання до черги. Насправді цей масив повинен бути названий "в черзі". Ви також можете припинити BFS, як тільки доїдете до фінішу.
Михайло

2
@Whymarrh І ви також, схоже, пропустили реалізацію блоку вилучення шляху. Без нього ви можете лише дізнатись, чи є обробка доступною чи ні, але не як.
Михайло

1
Щоб дізнатися, чи є рішення, UnionFind та лінійне сканування - це найшвидший алгоритм. Він не дає вам шлях, але дає вам набір плиток, які матимуть шлях як підмножина.
st0le

Відповіді:


236

Ось рішення.

  1. Перетворіть зображення в масштаб сірого (ще не двійковий), регулюючи вагу для кольорів, щоб остаточне зображення сірого масштабу було приблизно рівномірним. Це можна зробити, просто керуючи повзунками в Photoshop в Image -> Adjustments -> Black & White.
  2. Перетворіть зображення у бінарне, встановивши відповідний поріг у Photoshop у Image -> Adjustments -> Threshold.
  3. Переконайтесь, що поріг вибрано правильно. Використовуйте інструмент "Чарівна паличка" з толерантністю 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 - самий базовий алгоритм для графіків, але його можна застосувати до зображення безпосередньо, без перетворення пікселів у вершини та сусідів у ребра.
Михайло

Я відчуваю, що ти все накрив. Я намагаюся реалізувати те, що ви сказали в Python (використовуючи DFS замість BFS, тільки тому, що я кодував це один раз раніше). Я повернусь, щоб трохи оновити питання / прийняти відповіді.
Whymarrh

2
@Whymarrh DFS не знайде вас найкоротшим шляхом, тоді як BFS. Вони за своєю суттю однакові, відмінністю є лише основна структура. Стек (FILO) для DFS та черга (FIFO) для BFS.
Михайло

3
BFS - це правильний вибір, оскільки він створює найкоротший шлях, який дає "розумний" шлях навіть тоді, коли коридори значно ширші за 1 піксель. DFS OTOH буде прагнути досліджувати коридори та неперспективні райони лабіринту з малюнком "затоплення".
j_random_hacker

1
@JosephKern Шлях не перекриває жодні стіни. Просто видаліть всі червоні пікселі і ось ви йдете.
Михайло

160

Це рішення написано в Python. Дякую Михайлу за покажчики щодо підготовки зображення.

Анімований пошук за шириною:

Анімована версія 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, щоб краще візуалізувати процес.
Джозеф Керн

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'.
стефано

1
відсутній відступ у рядку 13
sloewen

81

Я спробував себе здійснити пошук A-Star для цієї проблеми. Уважно слідкували за виконанням Джозефа Керна за рамками та алгоритмом псевдокоду, наведеними тут :

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 - це евристичний алгоритм пошуку, вам потрібно придумати функцію, яка оцінює решту витрат (тут: відстань) до досягнення мети. Якщо вам не подобається неоптимальне рішення, воно не повинно завищувати вартість. Консервативним вибором тут була б відстань на Манхеттені (або таксі), оскільки це являє собою прямолінійну відстань між двома точками на сітці для використовуваного мікрорайону Фон Неймана. (Що в цьому випадку ніколи не переоцінить вартість.)

Це, однак, суттєво занизить фактичну вартість даного лабіринту. Тому я додав ще дві показники відстані в квадрат евклідової відстані та відстань Манхеттена, помножені на чотири для порівняння. Однак вони можуть завищити фактичну вартість і, отже, можуть дати неоптимальні результати.

Ось код:

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

Ось декілька зображень для візуалізації результатів (натхненних тим, який опублікував Джозеф Керн ). Анімація показує новий кадр через 10000 ітерацій основного циклу.

Перший пошук:

Перший пошук

Відстань до Манхеттена зірки:

Манхеттенська відстань до зірки

Відстань евклідового квадрата зірки:

Евклідова відстань зірки в квадраті

Відстань на Манхеттені зірки, помножене на чотири:

Відстань на Манхеттені зірки, помножене на чотири

Результати показують, що досліджувані ділянки лабіринту значно відрізняються від евристики, що використовується. Таким чином, евклідова відстань у квадраті навіть створює інший (субоптимальний) шлях, як і інші показники.

Щодо продуктивності алгоритму A-Star з точки зору часу виконання до завершення, зауважте, що багато оцінки функцій відстані та вартості складаються порівняно з результатом пошуку за першим шириною (BFS), якому потрібно лише оцінити "цілеспрямованість" кожна кандидатська посада. Незалежно від того, чи вартості цих додаткових оцінок функцій (A-Star) переважають витрати на більшу кількість перевірених вузлів (BFS) і, особливо, чи продуктивність взагалі є проблемою для вашої програми, - це питання індивідуального сприйняття і звичайно не можна відповісти на них.

Річ, яка може бути сказано в загальному про те , чи не інформував алгоритм пошуку (наприклад, A-Star) може бути кращим вибором по порівнянні з перебором (наприклад, BFS) полягає в наступному. З кількістю розмірів лабіринту, тобто коефіцієнта розгалуження дерева пошуку, дефіцит вичерпного пошуку (пошуку вичерпно) зростає експоненціально. Зі збільшенням складності це стає все менш і менш можливим, і в якийсь момент ви майже задоволені будь-яким результатом, будь то (приблизно) оптимальним чи ні.


1
"Відстань на Манхеттені зірки, помножене на чотири"? A-Star не є зіркою, якщо евристика може завищити відстань. (І, таким чином, не гарантує знайти найкоротший шлях)
наприклад,

@example Звичайно, якщо застосовувати неприйнятну евристичну функцію, алгоритм може не знайти оптимального рішення (як я зазначив у своїй відповіді). Але я б не пішов так далеко, щоб перейменувати основний алгоритм з цієї причини.
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

Ось вам: лабіринт-пітон (GitHub)

введіть тут опис зображення

Мені було весело грати з цим і продовжували відповідь Джозефа Керна . Не відволікати від цього; Я лише зробив деякі незначні доповнення для всіх, хто може зацікавитись із цим.

Це вирішувач на основі пітона, який використовує BFS для пошуку найкоротшого шляху. Мої основні доповнення на той час:

  1. Зображення очищається перед пошуком (тобто перетворюється в чистий чорно-білий)
  2. Автоматично створювати GIF.
  3. Автоматично генерувати AVI.

На даний момент, стартові / кінцеві точки жорстко закодовані для цього зразка лабіринту, але я планую розширити його таким чином, щоб ви могли вибрати відповідні пікселі.


1
Чудово, дякую, він не працював на BSD / Darwin / Mac, деякі залежності та сценарій оболонки потребували незначних змін для тих, хто хоче спробувати Mac: [maze- solver
solver

@HolgT: Радий, що ти вважаєш його корисним. Я вітаю будь-які прохання про це. :)
stefano

5

Я б пішов на вибір матриці булів. Якщо ви виявите, що стандартні списки Python для цього занадто неефективні, ви можете numpy.boolзамість цього використати масив. Зберігання для лабіринту 1000x1000 пікселів становить лише 1 Мб.

Не заважайте створювати будь-які структури даних про дерево або графік. Це лише спосіб його думати, але не обов'язково хороший спосіб представити його в пам'яті; булева матриця є і простішою в кодуванні, і ефективнішою.

Потім використовуйте алгоритм A * для його вирішення. Для евристики відстані використовуйте відстань на Манхеттені ( distance_x + distance_y).

Представити вузли за допомогою кордону (row, column)координат. Всякий раз, коли алгоритм ( псевдокод Вікіпедії ) вимагає "сусідів", це проста проблема перекидання чотирьох можливих сусідів (майте на увазі краї зображення!).

Якщо ви виявите, що вона все ще занадто повільна, ви можете спробувати зменшити масштаб зображення перед завантаженням. Будьте уважні, щоб не втратити в процесі вузькі шляхи.

Можливо, можна також зменшити масштаб 1: 2 і в Python, перевіривши, що ви фактично не втрачаєте жодних можливих шляхів. Цікавий варіант, але йому потрібно трохи більше продумати.


Ця відмінна публікація в блозі показує, як вирішити лабіринт з математики. Переклад методу на python не повинен бути проблемою
Борис Горелік

Я оновив питання. Якби я вирішив використати RGB-трійки замість booleanзначень, чи зберігатиметься сховище? Тоді матриця становить 2400 * 1200. А чи матиме A * над BFS істотний вплив на реальний час роботи?
Whymarrh

@Whymarrh, глибина біта може зменшитися, щоб компенсувати. 2 біта на піксель нікому не вистачить.
Брайан Каїн

5

Ось кілька ідей.

(1. Обробка зображень :)

1.1 Завантажте зображення як піксельну карту RGB . У C # це тривіально використання system.drawing.bitmap. Мови, що не мають простої підтримки для зображень, просто перетворіть зображення у портативний формат піксельних зображень (PPM) (представлення тексту Unix, створює великі файли) або у якийсь простий формат бінарних файлів, який ви легко можете прочитати, наприклад BMP або TGA . ImageMagick в Unix або IrfanView в Windows.

1.2 Ви можете, як було сказано раніше, спростити дані, взявши (R + G + B) / 3 для кожного пікселя як індикатор сірого тону, а потім порогове значення для отримання чорно-білої таблиці. Щось близько 200 припускаючи, що 0 = чорний і 255 = білий, вийдуть артефакти JPEG.

(2. Рішення :)

2.1 Поглиблений пошук: запустити порожній стек із початковим розташуванням, зібрати доступні подальші рухи, вибрати навмання і натиснути на стек, продовжувати, поки не буде досягнуто кінця чи не буде. На зворотному треку, що просувається, вискакуючи стек, вам потрібно відслідковувати, які позиції були відвідані на карті, щоб, збираючи доступні рухи, ви ніколи не проходили двічі той самий шлях. Дуже цікаво оживити.

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

Вуаля!

рішення, яке правильно знаходить найкоротший шлях

Це те, що відбувається, якщо ви не заповнюєте деякі прикордонні пікселі (Ha!) ...

версія рішення, де розв’язувач обходить лабіринт

Повне розкриття: я запитав і відповів на дуже подібне запитання сам, перш ніж знайшов це. Потім через магію SO знайшов це як одне з найкращих "Пов'язаних питань". Я думав, що використовую цей лабіринт як додатковий тестовий випадок ... Мені було дуже приємно виявити, що моя відповідь також працює для цього додатка з дуже малою модифікацією.


0

хорошим рішенням буде те, що замість того, щоб шукати сусідів за пікселем, це було б зроблено по комірці, оскільки коридор може мати 15 пікселів, тому в тому ж коридорі він може вживати дій, як ліворуч, так і праворуч, тоді як якщо це робиться так, ніби переміщення був кубом, це була б така проста дія, як Вгору, Вниз, Вліво АБО Вправо


Чи можете ви додати графік та алгоритм рішення, як і решту відповідей, щоб підтвердити свою точку? Буде краще, якщо ви зможете додати ті, які додадуть більше зважування до вашої відповіді, щоб інші могли насправді зрозуміти вашу відповідь.
Хіманшу Бансал
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.