[论文阅读] SVG Vector Font Generation for Chinese Characters with Transformer

pre

title: SVG Vector Font Generation for Chinese Characters with Transformer
accepted: ICIP 2022
paper: https://arxiv.org/abs/2206.10329
code: none
亮点:矢量图、Transformer、倒角距离

针对问题

单一风格、内容参考,自动化生成中文矢量字体

注:据百度百科矢量字体(Vector font)中每一个字形是通过数学曲线来描述的,它包含了字形边界上的关键点,连线的导数信息等,字体的渲染引擎通过读取这些数学矢量,然后进行一定的数学运算来进行渲染。这类字体的优点是字体实际尺寸可以任意缩放而不变形、变色。矢量字体主要包括 Type1 、TrueType、OpenType等几类。
又叫Outline font,通常使用贝塞尔曲线,绘图指令和数学公式进行绘制。这样可以在对字体进行任意缩放的时候保持字体边缘依然光滑,字体色素不会丢失。

核心思想

本文可能是基于DeepSVG,将矢量形式的字形图片分解为 图片-路径-指令 三层,每个路径应该是一个联通的部分,而指令可理解为一条曲线,多个指令构成一个路径。

采用基于Transformer的encoder-decoder,输入输出都是指令序列,风格注入使用AdaIN,损失的计算需要将SVG指令光栅化成像素图,或者在上面采样一些点,才能跟gt计算

相关研究

raster images即光栅图像,也叫位图、点阵图、像素图。以光栅图的形式自动生成汉字已经有广泛的研究,但矢量图,即字体的原始形式,还没什么进展。

矢量图形式才支持自由缩放,是计算机图形和计算机视觉的本质任务。

SVG (Scalable Vector Graphics) 是一种广泛使用的矢量图标准格式。SVG里面一个图形由路径(path)组成,每个路径又代表一个轮廓曲线。

路径是不定长的指令序列,每个指令有代表参数化的形状基元(parametric shape primitive)

不像传统的光栅图片生成,在网格中排列像素的亮度值,矢量图生成更像NLP的序列生成。但跟NLP的关键不同是矢量图表达并不唯一,就是不同指令可以渲染出一样的结果,这也是矢量图生成的难点之一。

另一个难点是很难从指令序列里捕捉空间排列跟形状特征。从矢量图渲染成光栅图通常是不可微的,无法被放进神经网络里,因此在光栅图空间优化字形(appearance)很困难。

近来一些研究提出了可微的渲染办法,但计算效率低而且不准确。

对于向量字体生成,早先的方法只做字母表字体(alphabet fonts,指拉丁字母?),而且需要大量用户干预和拓扑约束。对于汉字矢量字体,提到有篇论文(Automatic generation of chinese vector fonts via deep layout inferring)是从大量参考字符中抽取部件去预测布局layout。

这里开始对应原论文第二节RELATED WORKS,这里略去引用的论文序号,需要再查吧。

Suveeranont and Igarashi提出自动抽取参考字符轮廓,然后混合模板字体来生成新字体。Campbell and Kautz学习多种字母字体(the manifold of alphabet fonts),并通过插值已有的字体来生成新字体。对于汉字,Gao et al.把字体生成看作部件的layout预测问题,这些部件解耦自775个参考字。

也有用RNN生成点的序列,主要是做草图(sketch)和汉字skeleton的。Zhang et al.将汉字skeleton看作点序列(点之间直线段连接),用LSTM或GRU生成。Tang et al.更进一步,同时(用预测的点?)生成了光栅图。

近来已有方法用SVG格式生成字母(alphabet)的轮廓,依靠更复杂的指令系统,但都无法直接生成汉字。SVG-VAE在光栅域训练基于CNN的VAE,然后使用抽取的风格嵌入生成直线跟贝塞尔曲线构成的SVG,用的是基于LSTM的SVG解码器。

DeepSVG着眼于SVG的层次性,即 图片 > 路径 > 指令,并使用Transformer层次地、直接地编/解码矢量图的指令。整体架构是一个编码风格特征的VAE,生成字体时字符label同时被喂入编码器跟解码器。DeepSVG对于简单的图标数据集能进行准确的重建跟插值,然后生成字母字体。

DeepVecFont生成通过在共享的特征空间同时输入输出光栅图跟矢量图,然后优化这两个模态,生成结果更加亮眼。用LSTM处理矢量图,输出图片用神经光栅化器(neural rasterizer)进行光栅化,以此在训练时计算光栅域的损失。推理时输出会用更准确的光栅化器处理,以便进一步精炼。

贡献

这篇文章就是想根据单一风格参考生成汉字矢量字体,贡献如下:

  1. 发现了风格转换方式生成SVG汉字的新方法
  2. 提出了能生成复杂汉字的Transformer新框架,还有捕捉空间排列和形状的损失函数
  3. 构建了66种无衬线字体,800字符的数据集
  4. 第一次直接生成了无衬线风格的汉字矢量字体

方法(模型)流程

SVG Representation

表1 SVG绘制指令子集。起始点(x_0, y_0)是前一条指令的结束点

SVG有许多绘画指令,这里只取表1中的子集,能够跟OpenType字体相互转换。

这里结合 '##相关研究' 看,SVG图片有\(N_P\)条路径\(V = {p_1, \ldots, P_{N_P}}\),每个路径表示为\(P_i = (S_i, v_i)\),v是路径的可见性,值取0或1。

作者说没有用上DeepSVG的全部属性。每个\(S_i = (C_i^1, \ldots, C_i^{N_C})\)\(N_C\)条指令构成,指令表示为\(C_i^j = (c_i^j, X_i^j)\),其中\(c_i^j \in {<SOS>,M,L,C,Z,<EOS>}\)是指令类型,X是参数,,分别表示开始或结束token。

X是固定长度的参数列表,\(X_i^j = (x_{1}^{i,j},y_{1}^{i,j},x_{2}^{i,j},y_{2}^{i,j},x^{i,j},y^{i,j}) \in \mathrm{R}^{6},\)。没用到的参数就用-1填充。路径跟指令也是固定长度的序列,分别长\(N_P ,\; N_C\),用填充。这里的X涵盖了表1那四种指令的Arguments,因此最长3个点。

每个指令都嵌入\(e_{i}^{j} \in \mathrm{R}^{d_E}\),跟DeepSVG类似,\(e_i^j\)表达为指令类型嵌入跟控制点坐标(参数)和:\(e_{i}^{j} = e_{cmd,i}^{j} + e_{coord,i}^{j}\),嵌入序号用于网络第一层。

各种符号表达乱飞,看着累笔记做得也累,理解完会觉得其实思想是比较简单的。

Network Architecture

图1 网络架构,IE表示index embedding

看图1,从左往右看,左边是输入,编码器从风格参考\(V^{(S)} = \{P_{1}^{(S)},\ldots,P_{N_{P}}^{(S)}\}\)抽取风格特征\(z_S\),解码器再根据内容参考\(V^{(C)} = \{P_{1}^{(C)},\ldots,P_{N_{P}}^{(C)}\}\)\(z_S\)生成输出\(\hat{V} = \{\hat{P}_1,\ldots,\hat{P}_{N_P}\}\)

注意这里 \(V^{(S)}, V^{(C)}\) 应该是表示一张SVG图片,其中每个路径表示为(a)左侧的绿色圆角矩形与(b)的淡橙色圆角矩形,然后矩形里面的 \(e^{(S/V), i}_j\) 表示指令的嵌入,猜测为将不等长的指令(参数不同)嵌入为等长表示,方便transformer处理

像DeepSVG一样,用层次结构, \(E^{(1)}, D^{(1)}\) ((a)的左边跟(b)的右边)负责处理一张图的每个路径,然后 \(E^{(2)}, D^{(2)}\) 处理整张图。跟DeepSVG关键区别是内容(content)并非作为label喂入,而是作为参考图片,并且在解码器用AdaIN喂入风格特征。(这么说这篇文章就是在DeepSVG上做了小改动?)

图2 编码器解码器详细架构

每个模块的详细架构如图2所示,每个模块都有6个transformer块,每块含512维MLP且嵌入维度\(d_E 256\)

Encoder

看图1(a),跟DeepSVG的架构一样,但没用VAE。\(E^{(1)}\)独立编码每条路径,对于路径\(P^(S)_i\),输入嵌入序列 \((e^{(S), i}_j)^{N_C}_{j=1}\)跟可学习的index embeddings给\(E^{(1)}\),获得路径特征\(u_i\),它是最后一层的平均池化输出。然后逐path-wise的特征 \(\{u_i\}^{N_P}_1\) 带着index embeddings输入 \(E^{(2)}\),就能得到平均池化的输出\(z_S\)作为整张图的风格特征。

上面那个“输入嵌入序列”:e是指令的嵌入,上标(S)表示风格参考图,i表示这个图的第i个路径,下标j表示第i个路径的第j条指令,括号围起来外面j从1到\(N_C\)是序列的记法,表示路径有\(N_C\)个指令。

Decoder

看图1(b),将平均的 \((e^{(C), i}_j)^{N_C}_{j=1}\) 记为 \(\bar{e}^{(C), i}\),作为路径 \(P^{(C)}_i\) 的 path-wise 嵌入。这样每个路径的指令嵌入序列就取了个平均,一个路径只对应一个平均指令嵌入,见图(b)橙色圆角矩形,最左侧的,不是中间那俩

记号实在太多太麻烦,latex敲得累死,后面粗略讲讲流程就好

为这个路径的嵌入加上index embeddings,输给\(D^{(2)}\),然后输出path-wise特征表达 \(\{w_1,\ldots, w_{N_P}\}\) 和路径可见性\(\{\hat{v}_1,\ldots, \hat{v}_{N_P}\}\)

然后内容参考的每条路径指令嵌入序列((b)中间那些圆角矩形,它们取平均就是\(D^{(2)}\)的输入)再加上index embeddings输给\(D^{(1)}\),然后用可学习的仿射变换将 \(D^{(2)}\) 输出\(w_i\)添加到中间层,然后 \(D^{(1)}\) 用MLP头预测指令类型跟控制点坐标,就是图(b)最右侧那些绿色的小条形图、曲线。指令类型预测是分类问题,而坐标值是[0, 255]的连续值。

为了添加风格特征 \(Z_S\) ,使用STrans-G的config-c方法(看图1,图2的紫色AdaIN)。不同于原始的AdaIN在特征图之间转换风格,作者实现的AdaIN在全局向量\(Z_S\)上进行操作,看公式1,其中缩放偏移参数由\(MLP\; \gamma(\cdot),\beta(\cdot)\)转换而来:

\[AdaIN(x,z_{S}) = \gamma(z_{S}) \left(\frac{x-\mu(x)}{\sigma(x)}\right)+\beta(z_{S}), \tag{1} \]

这玩意就将风格 \(z_{S}\) 融入了x里面,实现了字体风格的转换,所以思路跟一般光栅图的方法差不多,都是从风格参考中抽取风格,然后跟内容参考提取的内容混合,并生成新字形。只是这边 \(D^{(1)}\) 相当于为每个内容参考的路径进行修改,数量不变,但指令类型跟参数可能有变化,以此实现字形的修改。

Loss Functions

损失函数由四项构成: visibility loss, command-type loss, argument loss, Chamfer loss。通过预先定义的顺序对ground-truth路径进行排序,以此定义ground-truth路径跟预测路径之间的关联(一种对齐方式?)。

可见性跟指令类型两项损失使用交叉熵,参数损失针对控制点的坐标,计算每个坐标值的 \(L_1\) 损失,因为本文的解码器不同于DeepSVG里面的,会输出连续值。ground truth里面的无用参数会在计算损失的时候遮掩掉。

除此之外用倒角损失(Chanfer loss)来捕捉空间特征,而无需将预测的路径光栅化。这四个损失中参数损失跟倒角损失是本文的创新,其余两个是DeepSVG里面的

倒角距离是一种标准度量,主要是点云生成任务里用到(总之距离越小表示曲线/点云越接近),在一对点云之间计算,对于点云 \(Q_{1},Q_{2}\subseteq\mathbb{R}^{2}\) ,可按下列公式定义倒角距离:

\[d_{\mathrm{CD}}(Q_{1},Q_{2}) = {\frac{1}{|Q_{1}|}}\sum_{\mathrm{x}\in Q_{1}}\operatorname*{min}_{\mathrm{y}\in Q_{2}}||\mathbf{x}-\mathbf{y}||_{2}^{2}+{\frac{1}{|Q_{2}|}}\sum_{\mathrm{y}\in Q_{2}}\operatorname*{min}_{\mathbf{x}\in Q_{1}}||\mathbf{x}-\mathbf{y}||_{2}^{2}. \tag{2} \]

本文以此计算SVG路径之间的距离,首先在每个SVG指令 \(C^j_i\) 定义的曲线上 采样 \(n_p\) 个点:

\[\phi_{i,j}\,=\, \left\{\begin{array}{ll}{{\{{\bf r}_{i,j}\left(k/{n_{p}}\right)\}_{k=0}^{n_{p}-1}}}&{{\left(c_{i}^{j}\,\in\,\left\{L,C\right\}\right)}}\\ {{\emptyset}}&{{\left(else \right)}} \end{array}\right., \tag{3} \]

注:这里的L, C是其中两种指令,具体可看表1,这里是挑这两种指令对应的路径去采样;同时这里的C,c应该都是一样的,表示指令;此外不同于路径数量,这里\(n_p\)都是小写字母

其中 \({\bf r}_{i,j}(t)\; (0\le t \le 1)\) 是曲线\(C^j_i\)的参数化表达,其中的参数t就是对这条曲线进行采样?真值路径\(P_i\)的点云 \(\Phi_i\)定义为

\[\Phi_{i} = \bigcup_j^{N_{C}}\phi_{i,j}. \tag{4} \]

预测路径\(\hat{P}_i\)的点云可以照此计算,并表示为\(\hat{\Phi}_i\),计算每对\(\Phi_i,\; \hat{\Phi}_i\)\(d_{CD}\)就能定义倒角损失如下:

\[{\mathcal{L}}_{\mathrm{cfr}}={\frac{1}{N_{P}}}\sum_{i}^{N_{P}}d_{\mathrm{CD}}(\Phi_{i},\hat{\Phi}_{i}). \tag{5} \]

这是一个精度跟计算开销的平衡问题,通过\(n_p\)可以调节,训练时取9。总之SVG这种路径-指令的形式似乎无法直接优化,需要对点进行采样并聚成点云,把路径之间的距离用相应的点云之间距离来近似。虽说采样的点越多越精确,性能应该越好,但并非对整条路径采样而是其中每个指令分别采样再聚成一个点云,而指令并不复杂,因此9个点应该够了。

实验

数据集

选的是日语字体里的汉字,毕竟作者都是日本人,这些字体来自日本的Fontworks公司,还有一些是网上搜集的免费字体。

作者说生成多样且复杂的汉字很困难,于是数据集仅限于66个无衬线字体(sans-serif family),一共800字,而且相对都比较简单。

随机选择59各自提训练,剩下7个评估,内容参考字体在整个学习推理过程中都是固定的。应该是一种字体固定作为内容字,训练模型从它转换到其他字体。

指标

光栅化像素距离、倒角距离

实现细节

训练batch size 96,共1500 epoch,学习率从0开始,并在500个iteration中线性增加到0.002,随后指数衰减。路径最大数量 \(N_P=12\),每条路径的指令最大数量 \(N_C=100\)

实验相比于两个SOTA:DeepSVG跟DeepVecFont。将前者的 \(N_P, N_C\)、Transformer块数量、训练epoch跟batchsize都增加到本文这样,使其支持中文字体。把后者最大训练长度设置为250,并训练100 epoch。

实验结果

图3 生成的向量字体,每列不同内容,每行不同风格

看图3是一些生成样本,本文模型确实明显好于另两个,但那俩模型的样本图怎么看都一模一样啊,咋回事呢。

表2 定量评估

表2是模型的定量比较结果,第二列光栅化的像素距离,就是把生成的SVG跟groundtruth SVG都光栅化,然后算L1距离;第三列倒角距离跟前面损失不一样,不再是每个路径对之间计算,而是整张图所有路径一起计算。

此外将每条指令的采样点数量\(n_p\)增加到99,以便评估更加精确。

总结

本文提出了个新的风格转换网络架构,使用层次Transformer、AdaIN、倒角损失,以高效捕捉矢量图里的空间结构。

矢量图生成的SOTA方法无法生成汉字这样复杂的矢量字体,本文的方法却能依据一张风格参考生成。通过在无衬线字体上与SOTA方法的比较证明了模型有效性。

评价

令人眼前一亮的方法,将SVG图片处理成统一格式并利用Transformer直接生成矢量字体。但图3似乎贴错了吧,两个用于对比的模型结果一模一样。另一方面挑选的字体、字都是比较简单的,猜测这种方法相比传统直接生成光栅图的会差一些,毕竟图3这种规矩的字体生成起来应当是很简单的。

可惜的是代码没有放出来,batchsize开到96,训练1500个epoch,可能是这种直接预测路径的方式计算开销较小,虽说数据集不大,但至少显存占用小。

待解明

posted @ 2023-02-27 14:25  NoNoe  阅读(420)  评论(0编辑  收藏  举报