从具有正确边缘的png图像中获取轮廓(轮廓)

Get the contour (outline) from a png image with the correct edges

我有多个 png 文件,我正在尝试获取多边形轮廓坐标。 那是简化坐标,只有每个外角(不是凸包多边形)。

目前执行此操作的程序是python 和opencv。 但是另一个程序没问题我确实尝试使用 npm 包、imagemagick、potrace、Lua 来解决这个问题。 它将在 'build polygons from images' 进程中用作 shell 命令。

这是python下的最后一次测试。

现在的问题是下面示例中的一些边 'not' 是正确的。

我做了以下步骤

  1. 原始png文件包含黑色线条(保留它们)。

  1. 转换后的黑白图像(你看不到第一行,因为这个网站是白色背景)
ret, mask = cv2.threshold(img[:, :, 3], 0, 255, cv2.THRESH_BINARY)

  1. 跟踪轮廓轮廓(不是我想要的输出)
contours, hierarchy = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

问题是两个孔,左边1个像素和右边1个像素缺失。

  1. 当我在其他程序中使用该轮廓数据时,您会得到:

  1. 我想要这个轮廓轮廓多边形数据,所以外部程序显示这个:

# https://opensource.com/article/19/5/python-3-default-mac#what-to-do
# https://solarianprogrammer.com/2019/10/21/install-opencv-python-macos/
# https://docs.opencv.org/master/d4/d73/tutorial_py_contours_begin.html
# 
# https://docs.opencv.org/3.4/dd/d49/tutorial_py_contour_features.html
# 

import numpy as np
import cv2

img = cv2.imread('../temp/bord.png', cv2.IMREAD_UNCHANGED)

# make black and white
ret, mask = cv2.threshold(img[:, :, 3], 0, 255, cv2.THRESH_BINARY)

# find the external contour
contours, hierarchy = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

# at this point I want to have the correct contours to process them inside a other program
# print(contours)

# start debugging
#save image
cv2.imwrite('../temp/bord_converted.png',mask) 


#create an empty image for contours
img_contours = np.zeros(img.shape)
# draw the contours on the empty image
cv2.drawContours(img_contours, contours, -1, (0,255,0), 1)
cv2.imwrite('../temp/bord_contour.jpg',img_contours) 

编辑

我尝试的其他事情:

行进广场节目

Golang

https://github.com/zx9597446/marchingsquare/issues/1 那个给了我另一个问题,但是正确的代码

Npm https://github.com/scottglz/image-outline 那个给了我几乎和上面一样的问题

imagemagick

正在尝试将 png 转换为黑白和 return 轮廓。

convert "$IMAGE" -matte -bordercolor none -border 1 -alpha extract -edge 1 -threshold 50% -depth 8 txt: | awk -F: '/white/{print }'

potrace

但是所有的输出都有东西,所以我不能用它。

potrace --progress -b svg --blacklevel 0 --turdsize 0 --longcurve --opttolerance 0 --unit 1 --turnpolicy white --alphamax 0 --scale 1 --group --flat ../temp/bordout.bmp -o ../temp/bordout.svg
potrace --progress -b eps -c --blacklevel 0 --turdsize 0 --longcurve --opttolerance 0 --unit 1 --turnpolicy white --alphamax 0 --scale 1 --flat ../temp/bordout.bmp -o ../temp/bordout.eps
potrace --progress -b pdf -c --blacklevel 0 --turdsize 0 --longcurve --opttolerance 0 --unit 1 --turnpolicy white --alphamax 0 --scale 1 --flat ../temp/bordout.bmp -o ../temp/bordout.pdf
potrace --progress -b pdfpage --blacklevel 0 --turdsize 0 --longcurve --opttolerance 0 --unit 1 --turnpolicy white --alphamax 0 --scale 1 --flat ../temp/bordout.bmp -o ../temp/bordout.pdfpage
potrace --progress -b ps -c --blacklevel 0 --turdsize 0 --longcurve --opttolerance 0 --unit 1 --turnpolicy white --alphamax 0 --scale 1 --flat ../temp/bordout.bmp -o ../temp/bordout.ps
potrace --progress -b pgm --blacklevel 0 --turdsize 0 --longcurve --opttolerance 0 --unit 1 --turnpolicy white --alphamax 0 --scale 1 --flat ../temp/bordout.bmp -o ../temp/bordout.pgm
potrace --progress -b dxf --blacklevel 0 --turdsize 0 --longcurve --opttolerance 0 --unit 1 --turnpolicy white --alphamax 0 --scale 1 --flat ../temp/bordout.bmp -o ../temp/bordout.dxf
potrace --progress -b geojson --blacklevel 0 --turdsize 0 --longcurve --opttolerance 0 --unit 1 --turnpolicy white --alphamax 0 --scale 1 --flat ../temp/bordout.bmp -o ../temp/bordout.geojson
potrace --progress -b gimppath --blacklevel 0 --turdsize 0 --longcurve --opttolerance 0 --unit 1 --turnpolicy white --alphamax 0 --scale 1 --flat ../temp/bordout.bmp -o ../temp/bordout.gimppath
potrace --progress -b xfig --blacklevel 0 --turdsize 0 --longcurve --opttolerance 0 --unit 1 --turnpolicy white --alphamax 0 --scale 1 --flat ../temp/bordout.bmp -o ../temp/bordout.xfig

例如,SVG 输出看起来像图片一样正确,但我无法将其转换为 x,y 点数组多边形。

<path d="M121 132 l-121 0 0 -66 0 -66 121 0 121 0 0 66 0 66 -121 0z m0 -1
l120 0 0 -22 0 -23 -10 0 -11 0 0 -42 0 -43 -5 0 -5 0 0 43 0 42 -89 0 -89 0
0 -42 0 -43 -5 0 -5 0 0 43 0 42 -10 0 -11 0 0 23 0 22 120 0z M121 130 l-119
0 0 -21 0 -22 11 0 10 0 0 -42 0 -43 4 0 4 0 0 43 0 42 90 0 90 0 0 -42 0 -43
4 0 4 0 0 43 0 42 11 0 10 0 0 22 0 21 -119 0z"/>
</g>

例如使用https://github.com/Phrogz/svg-path-to-polygons给我

[
  [
    [ 121, 132 ], [ 0, 132 ],
    [ 0, 66 ],    [ 0, 0 ],
    [ 121, 0 ],   [ 242, 0 ],
    [ 242, 66 ],  [ 242, 132 ],
    [ 121, 132 ], [ 121, 132 ],
    closed: true
  ],
  [
    [ 121, 131 ], [ 241, 131 ], [ 241, 109 ],
    [ 241, 86 ],  [ 231, 86 ],  [ 220, 86 ],
    [ 220, 44 ],  [ 220, 1 ],   [ 215, 1 ],
    [ 210, 1 ],   [ 210, 44 ],  [ 210, 86 ],
    [ 121, 86 ],  [ 32, 86 ],   [ 32, 44 ],
    [ 32, 1 ],    [ 27, 1 ],    [ 22, 1 ],
    [ 22, 44 ],   [ 22, 86 ],   [ 12, 86 ],
    [ 1, 86 ],    [ 1, 109 ],   [ 1, 131 ],
    [ 121, 131 ], [ 121, 131 ], closed: true
  ],
  [
    [ 121, 130 ], [ 2, 130 ],   [ 2, 109 ],
    [ 2, 87 ],    [ 13, 87 ],   [ 23, 87 ],
    [ 23, 45 ],   [ 23, 2 ],    [ 27, 2 ],
    [ 31, 2 ],    [ 31, 45 ],   [ 31, 87 ],
    [ 121, 87 ],  [ 211, 87 ],  [ 211, 45 ],
    [ 211, 2 ],   [ 215, 2 ],   [ 219, 2 ],
    [ 219, 45 ],  [ 219, 87 ],  [ 230, 87 ],
    [ 240, 87 ],  [ 240, 109 ], [ 240, 130 ],
    [ 121, 130 ], [ 121, 130 ], closed: true
  ]
]

编辑 2

当我使用 SVG 解决方案时,输出现在给我一个可读的点列表

<polygon fill="none" points="0,0 0,44 20,44 21,45 21,129 30,129 30,45 31,44 208,44 209,45 209,129 218,129 218,45 219,44 239,44 239,0" stroke="black" stroke-linecap="round" stroke-linejoin="miter" />

但是当我使用那个坐标列表时它不是 100% 正确的。 边角还是不对

Firefox 的输出(放大)将显示为:

以及我将在其中使用坐标列表(不是 SVG)的程序

love.graphics.polygon("line",0,0,0,44,20,44,21,45,21,129,30,129,30,45,31,44,208,44,209,45,209,129,218,129,218,45,219,44,239,44,239,0)

将输出:

编辑 3

使用最后一个 python 脚本不生成多边形。

所需输出:矢量图形

矢量图中的点、线和曲线可以按比例放大或缩小到任何分辨率,而不会出现锯齿。因此,您不会看到破角。假设输出是 SVG 格式的矢量图。通过将每个轮廓转换为 SVG 多边形,可以很好地可视化角落。您可以参考here 三种渲染角的选择。我还添加了一个功能add_pixel_fillers来调整足够接近的点。

import cv2
import svgwrite

img = cv2.imread("WFVso.png", cv2.IMREAD_UNCHANGED)
ret, mask = cv2.threshold(img[:, :, 3], 0, 255, cv2.THRESH_BINARY)

def add_pixel_fillers(img, cnt):
    n_points = len(cnt)
    for idx in range(n_points):
        prev_pt = cnt[(idx+n_points+1) % n_points]
        next_pt = cnt[(idx+1) % n_points]
        if abs(cnt[idx][0]-next_pt[0])==1 and abs(cnt[idx][1]-next_pt[1])==1:
            temp_x, temp_y = max(cnt[idx][0], next_pt[0]), min(cnt[idx][1], next_pt[1])
            if img[temp_y, temp_x] == 255:
                cnt[idx][0] = temp_x
                cnt[idx][1] = temp_y
            else:
                temp_x, temp_y = min(cnt[idx][0], next_pt[0]), max(cnt[idx][1], next_pt[1])
                if img[temp_y, temp_x] == 255:
                    cnt[idx][0] = temp_x
                    cnt[idx][1] = temp_y
    return cnt

contours, hierarchy = cv2.findContours(mask, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)

h, w = width=img.shape[0], img.shape[1]
dwg = svgwrite.Drawing('test.svg', height=h, width=w, viewBox=(f'-10 -10 {h} {w}'))
for cnt in contours:
    cnt = add_pixel_fillers(mask, cnt.squeeze().tolist())
    dwg.add(dwg.polygon(
        points=cnt,
        stroke_linecap='round',
        stroke='black',
        fill='none',
        stroke_linejoin='miter'
        ))
dwg.save()

示例输入的 SVG 输出为

<?xml version="1.0" encoding="utf-8" ?>
<svg baseProfile="full" height="100%" version="1.1" viewBox="-10 -10 130 240" width="100%" xmlns="http://www.w3.org/2000/svg" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs />
<polygon fill="none" points="0,0 0,44 21,44 21,45 21,129 30,129 31,44 31,44 209,44 209,45 209,129 218,129 219,44 219,44 239,44 239,0" stroke="black" stroke-linecap="round" stroke-linejoin="miter" />
</svg>

所需输出:光栅图形

如何填充角落缺失的像素点?假设在给定的情况下您只需要处理 90 度角,那么您想要填充缺失像素的像素模式数量有限。鉴于这些模式,您可以使用 scipy 包中的 ndimage.correlate 函数来找出填充像素的位置。

示例代码

import numpy as np
from scipy import ndimage
# Assume this is the contour you obtained in step 3
img = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                [1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
                [0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
                [0, 0, 1, 0, 0, 0, 0, 1, 1, 1],
                [0, 0, 1, 0, 0, 0, 1, 0, 0, 0],
                [0, 0, 1, 0, 0, 0, 1, 0, 0, 0]])

# You can add the patterns here
patterns = [
    np.array([[0,0,0,0,0],
              [0,0,0,0,0],
              [1,1,0,0,0],
              [0,0,1,0,0],
              [0,0,1,0,0]]),
    np.array([[0,0,0,0,0],
              [0,0,0,0,0],
              [0,0,0,1,1],
              [0,0,1,0,0],
              [0,0,1,0,0]])]

missing_corners = np.zeros_like(img) #Result will be stored here
for patt in patterns:
    result = ndimage.correlate(img, patt, mode="constant")
    corners = np.floor(result/np.sum(patt)).astype(int)
    missing_corners = (missing_corners + corners) % 2 #Can use binary OR

查看结果

print(missing_corners)

将显示缺失角的位置:

array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
       [0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])

对于更复杂的形状,我建议您在导出轮廓时使用 drawSvg 等包生成矢量图(例如 SVG)。