如何将含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
相关产品推荐
相关产品推荐

