将PNG图形转换为KML或GeoJson格式

3

我有成千上万个形状存储为PNG文件,并且每个形状都有其边界坐标。边界坐标是形状最小外接矩形的四个角点的坐标(如下所示)。

目标是使用PNG图像及其边界坐标将它们转换为多边形(KML或GeoJSON格式)。

我甚至不确定可以使用哪些技术来实现这个结果,因此我会感激任何建议。

输入数据(PNG):

Alt text

  • 形状最小外接矩形的4个角的坐标: 8.348236, 44.66804, 8.305321, 44.66829, 8.348579, 44.63507, 8.305492, 44.63507

期望输出:

我如何想象整个过程:

  • 第一步:我们有一张PNG图片和4个点。这让我们可以将PNG图片放置在地图上的正确位置并适当缩放。
  • 第二步:我们识别出形状关键点的位置。
  • 第三步:我们将一组已识别的点提取成多边形。

Alt text

我将简单的PNG作为例子,但形状可能更加复杂:

Alt text


1
我不清楚你实际拥有的是什么!你称一件东西为“PNG形状”,而我只知道“PNG”是一种图像,然后它是一个包含其他URL链接的Markdown文档的链接。然后你说你有一些坐标,但它显示为图片,但在下面是JSON。所以我不知道是否需要解析Markdown,解释JSON或查找PNG中特定颜色的区域来回答你。 - Mark Setchell
@MarkSetchell,感谢您的回复!您说得对,我表述不够清晰。我已经编辑了问题并添加了新的细节。请问还有什么可以让它更加清晰明了吗? - Val
我已经尝试在您的问题中包含所有必要的部分,而不需要参考其他网站。我仍然不明白您想做什么。您在PNG中有一个6角形状,还有一个JSON中的4个点。您希望以某种方式生成一个新的具有6个点的JSON?那么我为什么需要其中的4个点的JSON呢?我的意思是,“PNG中的6个顶点与第一个JSON中的4个顶点之间的关系是什么?” - Mark Setchell
@MarkSetchell 我尝试用图形解释来说明:https://take.ms/ODnvU 步骤1:我们有一张PNG图片和4个点。这让我们可以将PNG图片放置在地图上的正确位置并适当缩放。步骤2:我们识别出形状关键点的位置。步骤3:我们将一组识别出的点提取到多边形中。如果这样可以使描述更清晰,我可以直接将此说明添加到问题中。 - Val
@MarkSetchell 是的,两个问题都是如此!但它并不总是一个简单的形状。还有更复杂的情况:https://take.ms/i459hG 你认为将我在评论中添加的所有内容都包含在原始问题中有意义吗? - Val
显示剩余3条评论
1个回答

2

好的,我将您的图像保存为"shape.png",将包围矩形的GeoJSON保存为"boundaries.json"。然后我的方法如下:

  • 以纬度和经度为单位获取北、东、南和西的限制
  • 加载并修剪形状图像以摆脱所有黑色边框,阈值为纯黑色和白色
  • 通过查看像素和度数的图像宽度和高度来计算从像素到度数的X和Y缩放比例
  • 使用OpenCVfindContours()在形状图像中找到顶点
  • 将我找到的所有顶点从图像坐标翻译成纬度和经度
  • 将这些点写入JSON结果文件。

#!/usr/bin/env python3

import cv2
import json
import geojson
import numpy as np
from geojson import Feature, Point, FeatureCollection, Polygon, dump

def getNESWextents(GeoJSONfile):

    # Load the enclosing rectangle JSON
    with open('boundaries.json','r') as datafile:
        data = json.load(datafile)
    feature_collection = FeatureCollection(data['features'])

    lats = []
    lons = []
    for feature in data['features']:
        coords = feature['geometry']['coordinates']
        lons.append(coords[0])
        lats.append(coords[1])

    # Work out N, E, S, W extents of boundaries
    Nextent = max(lats)
    Sextent = min(lats)
    Wextent = min(lons)
    Eextent = max(lons)
    return Nextent, Eextent, Sextent, Wextent

def loadAndTrimImage(imagefilename):
    """Loads the named image and trims it to the extent of its content"""
    # Open shape image and extract alpha channel
    im = cv2.imread(imagefilename,cv2.IMREAD_UNCHANGED)
    alpha = im[...,3]
    # Find where non-zero, i.e. not black
    y_nonzero, x_nonzero = np.nonzero(alpha)
    # Crop to extent of non-black pixels and return
    res = alpha[np.min(y_nonzero):np.max(y_nonzero), np.min(x_nonzero):np.max(x_nonzero)]

    # Threshold to pure white on black
    _, res = cv2.threshold(res, 64, 255, cv2.THRESH_BINARY)
    return res

def getVertices(im):
    """Gets the vertices of the shape in im"""

    _, contours, *_ = cv2.findContours(im, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

    # Should probably sort by contour area here - and take contour with largest area
    perim = cv2.arcLength(contours[0], True)
    approx = cv2.approxPolyDP(contours[0], 0.01 * perim, True)

    print(f'DEBUG: Found shape with {approx.shape[0]} vertices')
    return approx

if __name__ == "__main__":

    # Get N, E, S, W extents from JSON file
    Nextent, Eextent, Sextent, Wextent = getNESWextents('boundaries.json')
    print(f'DEBUG: Nextent={Nextent}, Eextent={Eextent}, Sextent={Sextent}, Wextent={Wextent}')

    # Load the image and crop to contents
    im = loadAndTrimImage('shape.png')
    print('DEBUG: Trimmed image is "trimmed.png"')
    cv2.imwrite('trimmed.png', im)

    # Get width and height in pixels
    Hpx, Wpx = im.shape
    # Get width and height in degrees
    Hdeg, Wdeg = Nextent-Sextent, Eextent-Wextent
    # Calculate degrees per pixel in East-West and North-South direction
    degppEW = Wdeg/Wpx
    degppNS = Hdeg/Hpx
    print(f'DEBUG: degppEW={degppEW}, degppNS={degppNS}')

    # Get vertices of shape and stuff into list of features
    features = []
    vertices = getVertices(im)
    for i in range(vertices.shape[0]):
       x, y = vertices[i,0]
       lon = Wextent + x*degppEW
       lat = Nextent - y*degppNS
       print(f'DEBUG: Vertex {i}: imageX={x}, imageY={y}, lon={lon}, lat={lat}')
       point = Point((lon,lat))
       features.append(Feature(geometry=point, properties={"key":"value"}))

    # Convert list of features into a FeatureCollection and write to disk
    featureCol = FeatureCollection(features)
    with open ('result.json', 'w') as f:
        dump(featureCol, f)

这里是裁剪后的图片:

图片描述在此输入

这里是调试输出:

DEBUG: Nextent=44.66828662253787, Eextent=8.348579406738281, Sextent=44.63507036301143, Wextent=8.305320739746094
DEBUG: Trimmed image is "trimmed.png"
DEBUG: degppEW=8.634464469498503e-05, degppNS=6.0503204966194347e-05
DEBUG: Found shape with 6 vertices
DEBUG: Vertex 0: imageX=211, imageY=2, lon=8.323539459776736, lat=44.668165616127936
DEBUG: Vertex 1: imageX=2, imageY=224, lon=8.305493429035483, lat=44.654733904625445
DEBUG: Vertex 2: imageX=81, imageY=472, lon=8.312314655966388, lat=44.63972910979383
DEBUG: Vertex 3: imageX=374, imageY=548, lon=8.337613636862018, lat=44.63513086621639
DEBUG: Vertex 4: imageX=500, imageY=392, lon=8.348493062093587, lat=44.64456936619112
DEBUG: Vertex 5: imageX=484, imageY=155, lon=8.347111547778466, lat=44.65890862576811

1
我现在已经将结果的输出添加到一个JSON文件中了。 - Mark Setchell
我对代码进行了小改动,以便在更新的cv2上运行,但其他所有内容都像魔术一样顺利。谢谢! - Val

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接