使用Python从图像中提取边境国家

问题描述 投票:0回答:1

我有这样的地图,有彩色国家和黑色边框。

我需要一种方法来提取地图上每个国家(红色)的所有邻国(蓝色)

结果应该是这样的:

{
    "country1":{
        "neighbours":["country2","country3","country4","country5","country6","country7","country8"]
    },
    "country2":{
        "neighbours":["country1","country8","country9","country10"]
    },
...
}

到目前为止,我已按照建议使用 potrace 和 PIL 将其转换为矢量 (.svg),以使地图更易于使用 此结果(SVG 文件的图像。我无法上传 SVG。)

import os
from PIL import Image

threshold = 620

file_in = "/Users/nils/Documents/map.png"
img = Image.open(file_in)
pixels = list(img.getdata())

newPixels = []
for pixel in pixels:
    if pixel[0]+pixel[1]+pixel[2] <= threshold:
        newPixel = (0, 0, 0)
    else:
        newPixel = (255, 255, 255)
    newPixels.append(newPixel)

newImg = Image.new(img.mode, img.size)
newImg.putdata(newPixels)

file_out = "test.bmp"
print (len(newImg.split()))  # test
if len(newImg.split()) == 4:
    print("converting")
    r, g, b, a = newImg.split()
    newImg = Image.merge("RGB", (r, g, b))
    newImg.save(file_out)
else:
    newImg.save(file_out)
    
    
os.system("potrace test.bmp --svg -o my.svg")
python opencv image-processing computer-vision maps
1个回答
0
投票

首先是源图像,其次是结果图的插图(单击它们):

enter image description here enter image description here

方法:

  1. 阈值,每个国家获得一个连接组件
  2. 连接组件标签
  3. 对于每个组件:
    1. 扩张到足以跨越边界线并与邻居重叠
    2. 相交
    3. 收集该路口的标签
    4. 收集这些关系
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt

im = cv.imread("7oPxiEBe.png")
(height, width) = im.shape[:2]

gray = cv.cvtColor(im, cv.COLOR_BGR2GRAY)

(th, mask) = cv.threshold(gray, 192, 255, cv.THRESH_BINARY)

这张图片看起来像白色背景上的黑色线条,但您应该将其视为黑色背景上的白色斑点:

raw mask

# remove small components
# near egypt, the borders enclose 1-2 pixel sized components of sea

(num_labels, labels, stats, centroids) = cv.connectedComponentsWithStats(mask, connectivity=4)
for i in range(num_labels):
    if stats[i, cv.CC_STAT_AREA] <= 2:
        mask[labels == i] = 0

cleaned up mask

# the actual labeling
(num_labels, labels, stats, centroids) = cv.connectedComponentsWithStats(mask, 8, cv.CV_32S)
# calculate a central point for each component
# that's just for visualization

# give the "sea" label a border so its central point doesn't stick to a corner of the picture
modified_mask = mask.copy()
modified_mask[:,0] = 0
modified_mask[:,-1] = 0
modified_mask[0,:] = 0
modified_mask[-1,:] = 0
dist = cv.distanceTransform(modified_mask, cv.DIST_L2, cv.DIST_MASK_PRECISE)

central_points = []
for label in range(num_labels):
    component_dist = np.where(labels == label, dist, 0)
    (minVal, maxVal, minLoc, maxLoc) = cv.minMaxLoc(component_dist)
    central_points.append(maxLoc)

central_points = np.array(central_points)

distance transform

# now let's establish neighbor relationships
# for each component mask,
#   dilate the component mask by a few pixels,
#   calculate intersection with (original mask - component mask)
#   collect the labels in the intersection
#   own label might show up, shouldn't (assertion). any collected labels are neighbors.

adjacency = np.zeros((num_labels, num_labels), dtype=bool)

# label 0 (borders): neighbor to everyone, that's useless
# label 1 (sea/background): neighbor to coastal countries
for label in range(num_labels):
    if label == 0: continue # borders themselves
    # if label == 1: continue # non-continent (sea, other continents)

    component = (labels == label)
    noncomponent = mask.astype(bool) & ~component
    dilated_component = cv.dilate(component.astype(np.uint8), kernel=None, iterations=3).astype(bool)
    # NOTE: iterations=1 gives nothing, 2 gives *something* but with obvious missing edges.
    intersection = noncomponent & dilated_component

    neighbor_labels = np.unique(labels[intersection])
    adjacency[label, neighbor_labels] = True
    print(f"neighbors of {label:2d}: {set(neighbor_labels)}")
neighbors of  1: {2, 3, 4, 5, 6, 8, 13, 14, 15, 18, 19, 20, 21, 22, 23, 24, 26, 27, 28, 29, 31, 32, 33, 34, 35, 37, 38, 39, 40, 43, 44, 47, 48, 50, 52}
neighbors of  2: {1, 3, 5}
neighbors of  3: {1, 2, 4, 5, 8, 10, 11}
neighbors of  4: {1, 3, 7, 8, 9}
neighbors of  5: {1, 2, 3, 6, 11, 12, 13}
neighbors of  6: {1, 13, 5}
neighbors of  7: {8, 4}
neighbors of  8: {1, 3, 4, 7, 9, 10, 15}
neighbors of  9: {8, 4}
neighbors of 10: {3, 8, 11, 15, 16, 22, 31}
neighbors of 11: {3, 5, 10, 12, 16, 19, 20, 24}
neighbors of 12: {5, 11, 13, 20, 30}
neighbors of 13: {1, 5, 6, 12, 14, 17, 25, 30}
neighbors of 14: {1, 13, 17, 23}
neighbors of 15: {1, 8, 10, 18, 21, 22}
neighbors of 16: {10, 11, 24, 28, 29, 31}
neighbors of 17: {34, 13, 14, 23, 25, 26, 27}
neighbors of 18: {1, 15}
neighbors of 19: {24, 1, 11, 20}
neighbors of 20: {1, 37, 38, 39, 11, 12, 19, 30}
neighbors of 21: {1, 22, 15}
neighbors of 22: {32, 1, 33, 10, 15, 21, 31}
neighbors of 23: {1, 27, 14, 17}
neighbors of 24: {1, 11, 16, 19, 29}
neighbors of 25: {34, 35, 36, 13, 17, 30}
neighbors of 26: {1, 34, 27, 17}
neighbors of 27: {1, 26, 17, 23}
neighbors of 28: {16, 1, 29, 31}
neighbors of 29: {16, 1, 28, 24}
neighbors of 30: {35, 37, 12, 13, 20, 25}
neighbors of 31: {1, 33, 10, 16, 22, 28}
neighbors of 32: {1, 22, 33}
neighbors of 33: {32, 1, 22, 31}
neighbors of 34: {1, 36, 40, 17, 25, 26}
neighbors of 35: {1, 36, 37, 40, 41, 42, 43, 44, 45, 25, 30}
neighbors of 36: {34, 35, 40, 41, 25}
neighbors of 37: {1, 35, 39, 43, 20, 30}
neighbors of 38: {1, 20, 39}
neighbors of 39: {1, 20, 37, 38}
neighbors of 40: {1, 34, 35, 36, 41, 42, 45, 46, 47}
neighbors of 41: {40, 42, 35, 36}
neighbors of 42: {40, 41, 35}
neighbors of 43: {1, 35, 37}
neighbors of 44: {1, 50, 35, 45}
neighbors of 45: {35, 40, 44, 46, 47, 49, 50, 51}
neighbors of 46: {40, 45, 47}
neighbors of 47: {1, 40, 45, 46, 49, 52, 53}
neighbors of 48: {1}
neighbors of 49: {51, 52, 45, 47}
neighbors of 50: {1, 44, 45, 51, 52}
neighbors of 51: {49, 50, 52, 45}
neighbors of 52: {1, 47, 49, 50, 51, 53, 54}
neighbors of 53: {52, 47}
neighbors of 54: {52}
# illustration
scale = 4
canvas = cv.resize((im >> 1), dsize=None, fx=scale, fy=scale, interpolation=cv.INTER_NEAREST)
for i in range(2, num_labels):
    for j in range(i+1, num_labels):
        if adjacency[i, j]:
            #cv.line(canvas, tuple(central_points[i]), tuple(central_points[j]), (0, 255, 0), 1)
            cv.line(
                img=canvas,
                pt1=((central_points[i]+0.5) * scale).astype(int),
                pt2=((central_points[j]+0.5) * scale).astype(int),
                #color=(186, 186, 186),
                color=(255, 255, 255),
                thickness=1,
                lineType=cv.LINE_AA
            )
for i in range(2, num_labels):
    cv.putText(
        img=canvas,
        text=f"{i}",
        org=((central_points[i]+0.5) * scale).astype(int),
        fontFace=cv.FONT_HERSHEY_SIMPLEX, fontScale=0.5 * scale, color=(255, 255, 255), thickness=round(1 * scale)
    )

enter image description here

© www.soinside.com 2019 - 2024. All rights reserved.