You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将含1px线条的PNG精准转换为矢量图?

问题:细线条PNG转矢量时线条无法呈现直线的解决方法?

我正在寻找将带有1px线条的PNG图像转换为矢量文件的方法,但由于线条过细,现有方案效果不稳定:

  • 使用potrace工具时,得到的结果呈现模糊的blob状;
  • 初始尝试OpenCV时,无法识别全部线条。

待转换的示例文件链接:
https://geoservices.bayern.de/od/wms/alkis/v1/parzellarkarte?SERVICE=WMS&VERSION=1.3.0&REQUEST=GetMap&LAYERS=by_alkis_parzellarkarte_umr_schwarz&STYLES=&FORMAT=image/jpeg&CRS=EPSG:4326&BBOX=48.140000,11.560000,48.145000,11.565000&WIDTH=2048&HEIGHT=2048

之后我再次尝试OpenCV并取得了一定进展,编写了如下Python代码:

import sys
import cv2
import numpy as np
import svgwrite
import os

def raster_to_svg(input_path, output_path=None, simplify=False):
    img = cv2.imread(input_path, cv2.IMREAD_GRAYSCALE)
    if img is None:
        print(f"Fehler: Bild konnte nicht geladen werden: {input_path}")
        return

    inv = cv2.bitwise_not(img)

    _, thresh = cv2.threshold(inv, 127, 255, cv2.THRESH_BINARY)

    contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

    height, width = img.shape
    if not output_path:
        output_path = os.path.splitext(input_path)[0] + "_white_areas.svg"
    dwg = svgwrite.Drawing(output_path, size=(width, height))

    for contour in contours:
        if len(contour) < 3:
            continue  # Kein gültiges Polygon

        points = [(int(p[0][0]), int(p[0][1])) for p in contour]

        if simplify:
            epsilon = 1.0  # Toleranz für Polygonvereinfachung
            approx = cv2.approxPolyDP(contour, epsilon, True)
            points = [(int(p[0][0]), int(p[0][1])) for p in approx]

        dwg.add(dwg.polygon(points=points, fill='white', stroke='black', stroke_width=0.1))

    dwg.save()
    print(f"SVG gespeichert unter: {output_path}")

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("Verwendung: python raster_to_svg.py bild.png [output.svg]")
    else:
        input_file = sys.argv[1]
        output_file = sys.argv[2] if len(sys.argv) > 2 else None
        raster_to_svg(input_file, output_file)

但目前遇到了像素表示的问题:转换后的线条无法呈现为直线,请问有什么办法可以解决该问题?


解决方法

1. 优化轮廓近似的动态参数

当前固定的epsilon=1.0对1px线条适配性差,改用基于轮廓周长的动态阈值,能更精准地保留直线形态:

if simplify:
    perimeter = cv2.arcLength(contour, True)
    epsilon = 0.001 * perimeter  # 系数可在0.0005-0.002之间微调
    approx = cv2.approxPolyDP(contour, epsilon, True)
    points = [(float(p[0][0]), float(p[0][1])) for p in approx]

2. 预处理图像增强线条连续性

在阈值化前添加形态学处理,修复单像素线条的断裂和锯齿:

# 在bitwise_not之后添加
kernel = np.ones((1,1), np.uint8)
inv = cv2.morphologyEx(inv, cv2.MORPH_CLOSE, kernel)  # 闭合线条缝隙
# 若锯齿严重,可增加高斯模糊
inv = cv2.GaussianBlur(inv, (1,1), 0)

3. 针对线条轮廓直接拟合直线

区分细长线条和区域轮廓,对线条用直线拟合生成SVG直线元素,替代多边形填充:

for contour in contours:
    x, y, w, h = cv2.boundingRect(contour)
    # 判断是否为细长线条(宽高比大于10)
    if (max(w, h) / min(w, h) if min(w, h) !=0 else float('inf')) > 10:
        # 拟合直线
        [vx, vy, x0, y0] = cv2.fitLine(contour, cv2.DIST_L2, 0, 0.01, 0.01)
        # 计算图像边界内的直线端点
        lefty = int((-x0 * vy / vx) + y0)
        righty = int(((width - x0) * vy / vx) + y0)
        dwg.add(dwg.line(start=(0, lefty), end=(width, righty), stroke='black', stroke_width=0.1))
    else:
        # 原有区域轮廓处理逻辑
        if len(contour) < 3:
            continue
        points = [(float(p[0][0]), float(p[0][1])) for p in contour]
        if simplify:
            perimeter = cv2.arcLength(contour, True)
            epsilon = 0.001 * perimeter
            approx = cv2.approxPolyDP(contour, epsilon, True)
            points = [(float(p[0][0]), float(p[0][1])) for p in approx]
        dwg.add(dwg.polygon(points=points, fill='white', stroke='black', stroke_width=0.1))

4. 保留浮点坐标精度

去掉坐标转换为int的步骤,保留原始浮点坐标,避免SVG线条出现阶梯状:

# 替换原有的int转换
points = [(float(p[0][0]), float(p[0][1])) for p in contour]

内容的提问来源于stack exchange,提问作者pcace

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 01:11:17