[自分用論文メモ] Sketch Generation with Drawing Process Guided by Vector Flow

8 min read読了の目安(約7400字

Sketch Generation with Drawing Process Guided by Vector Flow and Grayscale

論文リンク:https://arxiv.org/abs/2012.09004
実装:https://github.com/TZYSJTU/Sketch-Generation-with-Drawing-Process-Guided-by-Vector-Flow-and-Grayscale
何か間違ってたら教えて下さい。

どんなもの?

高品質な写真→鉛筆風の変換手法。
従来と違い描画のプロセスも示すことができる。
以下に生成プロセスとその結果が確認できる。

先行研究と比べてどこがすごい?

従来のニューラルネットを使った手法等は構造のゆがみやアーティファクトを発生させてしまい鉛筆風描画タスクに対してあまり有効でなかった。またテクスチャレンダリングに基づいた手法では最終結果しか得ることができないので描画プロセスを示すことができない。
本報告では鉛筆のストロークを模倣するようなメカニズムを設計し、高品質かつ描画プロセスを示すことができ、既存の鉛筆画アルゴリズムと比べてテクスチャの品質、スタイル、ユーザーの評価の点で優れていることが分かった。

技術や手法のキモはどこ?

最初にざっくりと説明すると、以下のように3つのブランチに分かれていて、

  1. ストローク方向の決定
  2. ストロークの陰影の決定
  3. 細部の強調

のように分かれている。以下に全体の流れを示す。

もう少し掘り下げて本論文を読んでいきます。

Stroke Simulation

線は鉛筆デッサンの基本要素です。
鉛筆画のストロークは線なので「線」と「ストローク」を同じ概念として捉えます。
実際に画家が鉛筆でスケッチする時、拡大してよく見てみると何本かの平行な曲線でできていることが分かります。以下の画像は実際の画家が描いた鉛筆スケッチです。

この平行曲線は残り部分でも見られ、その平行曲線のグループ内の線は高い類似性を持ちます。つまり隣り合う2本の線の距離、それぞれの線の濃淡、長さ、幅がとても近しいのです。
これらの平行線に対して統計的な分析を行います。

以下の図の(a)には実際の鉛筆スケッチから平行線を1セットだけ含むパッチを切り出して、水平になるようにパッチを回転させたものです。厳密には線画わずかに湾曲しており平行ではないので水平方向の統計が難しいのですが、(a)のy方向を見て分かるように線が曲がってても垂直方向のグレー値(明度)の分布には影響がありません。

図(a)のyで示す赤い点線上の画素のグレー値は図(b)のようになります。図(b)のすべてのピーク(極小値)に赤い点を入れると、図(c)に示すように隣接する2つのピーク点間のグレー値の分布曲線がV字型のパターンとして見ることができます。

同じ統計を、すべての列のピクセルに対して行います。
n列あるとすると各ストロークはn個のVが対応します。

あるストロークに対応するn個のVについて、同じ位置にあるピクセル図(c)の黄色の点のグレー値は、独立かつ同一分布であると仮定することができ、さらにこの分布をガウス分布として仮定します。
したがって各Vの各ピクセルのグレー値の平均値と分散値を計算します。
この平均と分散は、線の中心からの距離d(中心の赤い画素からの距離)、中央グレー値(中央明度)G、線の幅Wにより以下のようにモデル化されます。

  • mean(d)=G+(255-G)×\frac{2d}{W-1}
  • variance(d)=(255-G)×cos\frac{πd}{W-1}

ここで、図3(a)の線を水平方向にまっすぐにできたとして、線の長さをLとするとこれらの線はWL列のグレー値の行列で表現することができます。
ある特定の行にある画素については、そのグレー値は独立同一ガウス分布であると考えるので、線のグレー値の分布を記録するために、(W, 2)の形をした行列Fを定義する。Fの要素(w,1)(w,2)は、それぞれ線w行すべての画素のグレー値の平均と分散を示しています。
GWが指定されていれば、線の分布行列Fを算出することができます。

Stroke Generation

直線を描画し、それを垂直方向に曲げることで自然なストロークを得ることができます。
直線を描くためには、中心となる画素のグレー値の平均値G、線幅W、線長Lの3つのパラメータを指定する必要があります。
まず、GWを使って、この直線の分布行列Fを計算します。
以下の図4(a)に示すように、幅W=7ピクセルでGが異なる線をいくつか描きましたが、これでは直線が硬すぎるので、さらに形状を調整します。

以下回りくどい説明が続きますが、図4のように直線を引いて2段階でゆがめることでストロークっぽくするよみたいなことを言いたいだけですので飛ばしても大丈夫です。

図4(d)のように、実際に鉛筆で描かれたストロークを観察すると、ストロークの両先端の部分が中央部よりも細く、軽くなっていることがわかります。これは、鉛筆の先端が紙面に触れたばかりのときや、離れようとしているときには、鉛筆の先端が紙面に与える圧力が、線の中央部を描くときよりも小さくなるためです。

絵描き的に言うと入り抜きのことです。

(参考:https://howto.clip-studio.com/library/page/view/illuststudio_zukai_pen_029)
線が完全な直線ではなく、わずかに曲がっているのは、画家が手首を振って線を描くため、紙の上での鉛筆の先端の動きは、基本的に半径の大きな円運動になるからです。
これを模倣するために、先に生成した直線を2回曲げます。

図3(d)に示すように、X軸上の黄色い点は、直線の特定の行にあるピクセルを示しています。これらのピクセルは、青い円にシフトされます。座標系を確立するために、線の中点を原点として使用します。
線上のピクセルは、Y方向へのずれの度合いが異なります。
青い円の半径をRとすると、横軸xのピクセルはY方向にδy(x)ピクセル分ずれます。半径Rとオフセットδy(x)は、R=\frac{L^2}{4W}δy(x)=\frac{x^2}{2R}と計算できる。実際にはY方向に線形補間を行います。一部のピクセルはWL列の行列を超えるので廃棄します。行列の空白部分には、真っ白なピクセルで埋めます。そうすると図4(b)に示すような曲線が得られます。最初の曲げ操作の目的は、線の両先端をシャープにすることです。2回目の曲げ操作は、1回目とほぼ同じですが、行列からそれらのピクセルを保存します。2回目の曲げの目的は、曲率をさらに高めることで図4(c)のような曲線ができます。

Guided Stroke Drawing

ここでは、キャンバスに線を描くスケッチの方法を紹介する。
ストロークを決めるには、線の幅W、長さL、中心画素のグレー値平均G、始点の座標、この線の方向を知る必要があります。線幅Wについては、ここではすべてのストロークの幅を固定値で指定します。
その他のパラメータについては、入力画像の局所的な特性を利用して決定します。

Grayscale Guidance(どうやって線を引くか)

タスクの難しさを下げるために、一定の方向にストロークを引く方法のみを示すことにする。
以下の図6(a)は、水平方向にのみストロークを描いた結果である。

以下では、その描き方を紹介する。
まず入力されたグレースケール画像のヒストグラム分布を調整して、色相を良くするために適用的ヒストグラム平坦化CLAHE(Contrast Limited Adaptive histogram Equalization)を用いて入力のコントラストを上げます。
次に画像をいくつかのグレーレベルに一様に量子化します。
そのグレーレベルの値を{G1, G2, ..., Gn}とします.以下で示すようにIは入力、Qは量子化後の結果です。

次にQを使って、水平方向にスキャンすることで、ストロークのパラメータを検索・決定します。
Qの1行目の画素を例にとります。
画素の階調値がG1以下となる1行目のすべての区間を検索します。
これらの区間の始点と長さを、描くべきストロークの始点と長さとし、ストロークの中心となる画素のグレー値の平均値GをG1とすると、水平方向に複数のストロークを描くことができます(ストロークの幅Wはあらかじめ指定されているとします)。

ここで新たな確率変数D ∼ N(W, 1)を定義し、これを用いて垂直方向のラインのピクセル距離を生成します。つまりあらかじめ指定したストロークの幅W+若干のノイズだけ離した場所に平行線を引くイメージになります。
中心となる画素の階調値が平均G=G1のストロークをすべて描くことができました。
次に、{G2, ..., Gn}についても同様にストロークを描きます。
このプロセスでは、異なるストロークのカバレッジ領域が重なります。
重なった領域のピクセルのグレー値は、最小の値(最も暗い色)に決定されます。
これで、図6(a)のような結果が得られます。
ここでは水平方向にストロークを描く方法しか紹介していませんが、異なる方向でも同じです。
水平方向にストロークを描く前に、入力画像を時計回りに回転させればよいのです。
描画が終わったら、反時計回りに回転させる。このようにして、あらゆる方向にストロークを描くことができます。

Direction Guidance(どの方向に線を引くか)

上では一定の方向に対してストロークを引く方法を紹介しました。
実際の鉛筆画を見てみると、ストロークの方向は大体エッジの接線に沿っていることが容易に分かると思います。(まあ僕は分からなかったんですけどね)
なので物体のエッジを利用して近くの領域のストロークの方向を誘導できることになります。
ここで勾配とエッジは密接に関係しており画像の勾配情報を用いることでこのストローク方向の推定ができます。
本報告ではedge tangent flow (ETF)というアルゴリズムを用いて書くピクセルのストローク方向を推定しています。
詳しくは元の論文を読むか、著者の166行程度のPython実装(https://github.com/TZYSJTU/Sketch-Generation-with-Drawing-Process-Guided-by-Vector-Flow-and-Grayscale/blob/main/ETF/edge_tangent_flow.py )を見てみることをおすすめします。図6(b)が少し見づらいと思うので拡大図を以下に持ってきました。

小さな赤い矢印はETFで計算されたベクトル方向を示しています。
方向0~2πを一様にn個の値に量子化します。位相差がπのベクトルは同一方向と見なします(つまりベクトルが真逆を向いているもの同士は同じものと見なす)。
同じ方向をもつ画素を1つの領域として分割します。
下図のArea divisionで示すように、{α_1, α_2, ... ... , α_n}は領域分割を示します(あるエリアに属する画素は白、それ以外は黒)。

各領域に異なる方向のストロークを描画した後、n個の結果を得ることができます。このストローク描画した各々の領域は{β_1, β_2, ... , β_n}で示されます。

Area Merging and Detail Enhancement(領域の合成と強調)

先の{β_1, β_2, .. . , β_n}を図6(c)のように1枚の画像に集約させます。
よく見ると異なる領域の境界に違和感があることが分かります。境界線も見えてしまっています。なので図6(d)に示すように全てのストロークの両先端に対し、2×W(Wは線の幅)拡大することで解決します。図5のAは、異なる方向のストロークの集約結果を示しており、次の式で求められます。
A = minimum(β_1, β_2, ... , β_n)
これでうまく融合できたように見えますが、図6(d)をよく見ると女性の歯と目は非常に不鮮明になっています(ストロークが長いため)。なので細部を強調してやる必要があります。ここでは図6(e)に示すように、「Combining Sketch and Tone for Pencil Drawing Production」の線形畳み込み法を採用し、エッジマップを得ることができます。
以下にその線形畳み込み法の概略図を示します。

最後にエッジマップTと描画結果Aをかけ合わせて、R=A・Tと表される最終出力Rを取得します。

Process Reconstruction(ストローク描画の順番決定)

画家がスケッチを描く時通常、最初にアウトライン(長くて暗い線、つまりはっきりとした輪郭)を描き、次にディテール(これらの線は短くてあまり暗くないことが多い)を描きます。
これを真似してSという指標を用い、それぞれの線がアウトラインとディテールのどちらになりやすいかを判定します。
S=(255-G)×\sum_{i \in D}T_i
ここでGはストロークの中心となるグレー値、Dはストロークがカバーする画素の集合、T_iは画素iにおける勾配の絶対値。

どうやって有効だと検証した?

他の手法より優れている(図7)にも関わらず、描画の順番を提示できるのはほんとに凄いと思います。

議論はある?

ディープな手法は多く提案されているが、ここまで絵を研究し統計をとり、画像処理だけで自動化している論文はそうないと思う。めちゃくちゃ感動した。

次に読むべき論文は?

Stylized Neural Painting

その他

著者が実装を公開しているので誰でも鉛筆風の変換を試すことができます。
神か?w

https://github.com/TZYSJTU/Sketch-Generation-with-Drawing-Process-Guided-by-Vector-Flow-and-Grayscale