在 RGB 图像中查找 Point 之间的路径

Finding a path between Point in a RGB image

我有一张 RGB 图像,它是一张包含道路和车站的地图。

我正在尝试找到彼此最近的车站,它们之间有一条路。 道路显示为黑色,车站显示为红色。我添加了名称,这样会更容易理解。

O -> M
A -> B
B -> C
E -> F
G -> H
G -> C
M -> N
.
. 
.

两个车站之间的距离是根据它们之间的道路长度计算的。

关于解决这个问题的一些想法: 我在考虑从道路上移除车站,我们已经断开了道路,然后使用等高线来计算道路的长度(以像素为单位)。但在某些情况下(如 F 或 E),车站不会完全覆盖道路,也不会将道路分成三个不同的部分。

请告诉我你的想法,你是如何解决的。

去掉车站道路会变成这样:

这是没有标签的主图。

如果可能的话,跟踪两个站之间的路径并创建路径图像。例如从A站到C站:

哇,解决这个问题很有趣! :-)

我用我的想法扩大红色车站斑点以确保它们接触到它们相关的道路,并且 @Micka 的“波前”解算器的想法计算出沿路线的距离形成距离图。

计算完这些距离图后,查找从 A 到 B 的距离只需读取 B 地图中 A 位置的值即可(反之亦然)。

代码

诚然,代码有点长,但应该添加注释以便您了解发生了什么。 :)

您可以找到包含额外 Matplotlib 内容的代码来生成诊断和图像 over here at GitHub

from itertools import count, combinations

import cv2
import numpy as np


def inrange_thresh(image, color, thresh, binarize_thresh=None):
    """
    Apply cv.inRange with a threshold near the given color, optionally threshold the final image.
    """
    min_color = tuple(c - thresh for c in color)
    max_color = tuple(c + thresh for c in color)
    image = cv2.inRange(image, min_color, max_color)
    if binarize_thresh is not None:
        t, image = cv2.threshold(image, binarize_thresh, 255, cv2.THRESH_BINARY)
    return image


def find_feature_bboxes(image):
    """
    Find contours in the image and return their bounding boxes.
    :return: Iterable of (x, y, w, h)
    """
    cnts, *_ = cv2.findContours(image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    for c in cnts:
        yield cv2.boundingRect(c)


def distance_from_point(input_mask, start_point):
    """
    Build a distance map following truthy paths in the input mask image, starting from start_point.
    :return: Tuple of distance map matrix and infinity value for the matrix
    """
    binary_mask = (input_mask > 127)
    # Figure out a suitably large number to serve as "infinity" for the mask.
    infinite_distance = max(binary_mask.shape) * 2

    # Generate a distance map with unreachable points, then seed it with our start point.
    dist_map = np.full_like(input_mask, infinite_distance, dtype="uint32")
    dist_map[start_point[::-1]] = 0

    # Precompute a structuring element we can use to dilate the "wavefront" to walk along the route with.
    struct_el = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
    for step in count(1):
        # Compute a zero map for new neighboring pixels.
        neighbor_map = np.full_like(dist_map, 0, dtype="uint8")
        # Mask in all of the pixels that were filled in by the last step.
        neighbor_map[dist_map == (step - 1)] = 255
        # Dilate the map with the structuring element so new neighboring pixels would be included.
        neighbor_map = cv2.dilate(neighbor_map, struct_el)

        # Figure out which pixels in the dist map should be filled
        new_dist_mask = (
            (dist_map > step) &  # must be more distant than we've already filled
            (neighbor_map > 0) &  # must be one of these new neighbors
            binary_mask  # must be walkable
        )
        if not np.any(new_dist_mask):
            # If there are no new pixels, we're done.
            break
        dist_map[new_dist_mask] = step
    return (dist_map, infinite_distance)


def main():
    image = cv2.imread("hHwyu.png", cv2.IMREAD_COLOR)

    marker_color = (239, 30, 40)[::-1]  # RGB -> BGR
    route_color = (0, 0, 0)[::-1]

    # Grab a grayscale image of the markers only
    markers = (inrange_thresh(image, marker_color, 5, 5) > 0).astype(np.uint8)
    # Use the center of their bounding boxes as a station location
    station_positions = [(int(x + w / 2), int(y + h / 2)) for x, y, w, h in find_feature_bboxes(markers)]
    station_positions.sort(key=lambda pair: pair[1])

    # Dilate the markers a bit so they'll always sit on the roads, then splat them on.
    # We'll use this as the base map for the contour-walking algorithm so it's all connected.
    markers_dilated = cv2.dilate(markers, cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5)))
    routes_binary = inrange_thresh(image, route_color, 25, 0)
    routes_binary[markers_dilated > 0] = 255

    station_infos = []
    for station_id, start_point in enumerate(station_positions, 1):
        print(f"Computing distance map for station {station_id} at {start_point}")
        distmap, inf = distance_from_point(routes_binary, start_point)
        station_infos.append((station_id, start_point, distmap, inf))

    for (sa_id, sa_point, sa_map, sa_inf), (sb_id, sb_point, sb_map, sb_inf) in combinations(station_infos, 2):
        distance = sa_map[sb_point[::-1]]
        if distance >= sa_inf:
            distance = np.inf
        print(f"Distance between {sa_id} ({sa_point}) and {sb_id} ({sb_point}): {distance}")


if __name__ == '__main__':
    main()

输出

输出(格式化为距离矩阵)是

B / A  |    #1    #2    #3    #4    #5    #6    #7    #8    #9   #10   #11
    #1 |      -   356   288   370   212   inf   574   304   inf   455   inf
    #2 |    356     -    68   232   495   inf   436   587   inf   317   inf
    #3 |    288    68     -   164   427   inf   368   519   inf   249   inf
    #4 |    370   232   164     -   509   inf   379   601   inf   260   inf
    #5 |    212   495   427   509     -   inf   713   176   inf   594   inf
    #6 |    inf   inf   inf   inf   inf     -   inf   inf   inf   inf   inf
    #7 |    574   436   368   379   713   inf     -   805   inf   212   inf
    #8 |    304   587   519   601   176   inf   805     -   inf   686   inf
    #9 |    inf   inf   inf   inf   inf   inf   inf   inf     -   inf   114
   #10 |    455   317   249   260   594   inf   212   686   inf     -   inf
   #11 |    inf   inf   inf   inf   inf   inf   inf   inf   114   inf     -

给定距离图和站点,如下面的诊断图像所示。

基于 6、9 和 11 与网络的其余部分断开连接(并且 6 与所有内容断开连接!)并且它们往往在输出中获得 inf 距离这一事实,我会说这个有效。 :-)

插图

每个站点的距离图

漂亮的动画

这些加速的 gif 说明了距离地图和邻居地图“波前”如何为左上站工作

距离图构建

邻居图构建