我想将包含折线的基本 SVG 文件转换为 sketch-rnn 使用的描边 3 格式。 (以及 quickdraw dataset )。
据我了解,笔画 3 格式的每个折线点将是:
- 存储为
[delta_x, delta_y, pen_up]
,其中 delta_x
、delta_y
表示相对于 上一点和pen_up
是一个位,当笔抬起时为 1 (例如move_to
操作 a-la 海龟图形)或 0(当笔处于 向下(例如line_to
操作 a-la 海龟图形)。
我尝试编写该函数并转换 SVG,但是当我渲染笔划 3 格式的测试时,我得到了一条额外的行。
我的输入 SVG 如下所示:
<?xml version="1.0" encoding="UTF-8"?><svg viewBox="0 0 900 900" xmlns="http://www.w3.org/2000/svg"><g fill="none" stroke="#00c000" stroke-linecap="round"><path d="m324.56 326.77h62.721 62.721 62.721 62.721"/><path d="m575.44 326.77 0.1772 62.891 0.1771 62.891 0.1772 62.891 0.1771 62.891"/><path d="m576.15 578.33h-63.075-63.075l-63.075-1e-4h-63.075"/><path d="m323.85 578.33 0.1772-62.891 0.1772-62.891 0.1772-62.891 0.1771-62.891"/><path d="m575.44 326.77 29.765-32.469 29.765-32.469" stroke="#c00000"/><path d="m634.97 261.83h-92.486-92.486l-92.486-1e-4h-92.486" stroke="#c00000"/><path d="m265.03 261.83 44.647 48.704 14.882 16.235" stroke="#c00000"/><path d="m323.85 578.33-15.092 13.725-30.183 27.45-15.092 13.725" stroke="#c0c000"/><path d="m263.48 633.23h93.258 93.258l93.258 1e-4h93.258" stroke="#c0c000"/><path d="m636.52 633.23-60.366-54.9" stroke="#c0c000"/><path d="m634.97 261.83 0.3863 92.851 0.3862 92.851 0.3863 92.851 0.3863 92.851" stroke="#0000c0"/><path d="m636.52 633.23h-93.258l-93.258-1e-4h-93.258-93.258" stroke="#0000c0"/><path d="m263.48 633.23 0.3863-92.851 0.3863-92.851 0.3863-92.851 0.3862-92.851" stroke="#0000c0"/></g></svg>
这是一个可视化效果,其中从 SVG 文件解析的线条以粗绿色呈现,从转换后的描边 3 格式绘制的线条以较细的红色呈现:
请注意右侧面上的对角线,它在原始 SVG 中不存在。
我一定是在某处标记了行操作而不是移动操作,但我已经盯着代码太久了,我无法发现错误。
这是一个最小的示例,展示了我使用 svg.path 的尝试:
import xml.etree.ElementTree as ET
import numpy as np
from svg.path import parse_path
from svg.path.path import Line, Move
cubeSVG = '<?xml version="1.0" encoding="UTF-8"?><svg viewBox="0 0 900 900" xmlns="http://www.w3.org/2000/svg"><g fill="none" stroke="#00c000" stroke-linecap="round"><path d="m324.56 326.77h62.721 62.721 62.721 62.721"/><path d="m575.44 326.77 0.1772 62.891 0.1771 62.891 0.1772 62.891 0.1771 62.891"/><path d="m576.15 578.33h-63.075-63.075l-63.075-1e-4h-63.075"/><path d="m323.85 578.33 0.1772-62.891 0.1772-62.891 0.1772-62.891 0.1771-62.891"/><path d="m575.44 326.77 29.765-32.469 29.765-32.469" stroke="#c00000"/><path d="m634.97 261.83h-92.486-92.486l-92.486-1e-4h-92.486" stroke="#c00000"/><path d="m265.03 261.83 44.647 48.704 14.882 16.235" stroke="#c00000"/><path d="m323.85 578.33-15.092 13.725-30.183 27.45-15.092 13.725" stroke="#c0c000"/><path d="m263.48 633.23h93.258 93.258l93.258 1e-4h93.258" stroke="#c0c000"/><path d="m636.52 633.23-60.366-54.9" stroke="#c0c000"/><path d="m634.97 261.83 0.3863 92.851 0.3862 92.851 0.3863 92.851 0.3863 92.851" stroke="#0000c0"/><path d="m636.52 633.23h-93.258l-93.258-1e-4h-93.258-93.258" stroke="#0000c0"/><path d="m263.48 633.23 0.3863-92.851 0.3863-92.851 0.3863-92.851 0.3862-92.851" stroke="#0000c0"/></g></svg>'
def svg_to_stroke3(svg_string):
# parse the doc
doc = ET.fromstring(svg_string)
# get paths
paths = doc.findall('.//{http://www.w3.org/2000/svg}path')
strokes = []
# previous x, y
px, py = 0, 0
for path_index, path in enumerate(paths):
stroke = parse_path(path.attrib['d'])
was_moving = False
for operation_index, operation in enumerate(stroke):
if isinstance(operation, Move):
mx = int(operation.start.real)
my = int(operation.start.imag)
# prep this end point for check as next line first point
was_moving = True
strokes.append([mx-px, my-py, 1])
# update previous (absolute) coordinates
px = mx
py = my
if isinstance(operation, Line):
sx = int(operation.start.real)
sy = int(operation.start.imag)
ex = int(operation.end.real)
ey = int(operation.end.imag)
if was_moving:
# append delta x, y relative to previous move operation
strokes.append([sx-px, sy-py, 0])
was_moving = False
# append delta x,y (line end relative to line start)
strokes.append([ex-sx, ey-sy, 0])
# update previous (absolute) coordinates
px = ex
py = ey
# update previous end point
strokes_np = np.array(strokes, dtype=np.int16)
return strokes_np
print(svg_to_stroke3(cubeSVG))
此外,我已将上述内容作为易于运行的内容提供 Google Colab Notebook .
最佳答案
您的转换是正确的,错误在于渲染代码中。它必须是 is_down = data[i][2] == 0
而不是 draw_lines3 中的
.is_down = data[i-1][2] == 0
此错误不会在其他路径中出现,除了两种情况外,新路径从前一个路径的末尾开始。在另一种情况下,您确实移动到新的起点,附加线与已绘制的线重合。
更新和更正:
我注意到我误解了提笔位的含义:事实上,它表明在绘制当前笔画之后要抬起笔,而不是为了当前的中风正如我一开始所想的那样。因此,您的渲染代码似乎没问题,并且错误存在于中风3文件生成中。
我想您可以通过记录每个操作的终点以及当前操作的操作码(1
= move,0
= draw)来做到更简单。转换为 numpy 数组后,我们可以通过前两列的差异轻松地将这些绝对位置转换为相对位移,然后将带有操作码的第三列向后移动一个位置:
def svg_to_stroke3(svg_string):
doc = ET.fromstring(svg_string)
paths = doc.findall('.//{http://www.w3.org/2000/svg}path')
strokes = [[0,0,0]]
for path in paths:
stroke = parse_path(path.attrib['d'])
for op in stroke:
if isinstance(op, Move):
strokes.append([op.start.real, op.start.imag, 1])
if isinstance(op, Line):
strokes.append([op.end.real, op.end.imag, 0])
strokes_np = np.array(strokes, dtype=np.int16)
# convert x,y columns to relative displacements
strokes_np[1:,:2] -= strokes_np[:-1,:2]
# shift back op codes
strokes_np[:-1,2] = strokes_np[1:,2]
# remove [0,0,0]s and set previous op to 0
m = (strokes_np == 0).all(1)
strokes_np[np.argwhere(m)-1,2] = 0
return strokes_np[~m]
这提供了一个相当紧凑的表示(示例立方体为 49 行),并使用您的原始代码和 David Ha 的 draw_lines()
正确渲染(如果您通过约定从笔开始,如 draw_lines()
所做的那样)。
关于python - 如何将 SVG 折线转换为 Quickdraw Stroke-3 numpy 格式?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65985435/