Stroke Based Renderingを実装してみた
Stroke Based Renderingを実装しました。 その過程などをTwitterにつぶやいていたのでそのメモをします。
ここのところ3DCGでキャラクターを作ったり背景を作ったりしていますが、これでやりたいのはゲームに使う絵を作りたいというのが目標です。どのくらいのクオリティの絵を求めるかというと神絵師の絵とまでは言わなくてもそれなりに見栄えのする絵です。問題は私の絵のスキルがそこまで高くないことです
— 折登 いつき (@MatchaChoco010) May 29, 2020
絵のスキルは高くありませんが3DCGのスキルは多少持ち合わせているので、3DCGのレンダリングした結果をベースに絵を書いていけばそれなりのものになるのではないかという期待を持っています。
— 折登 いつき (@MatchaChoco010) May 29, 2020
3DCGのレンダリングしたものをベースに筆塗りを重ねてあたかも絵で書いたように見える3DCGを用意できたら嬉しいのですが……。3DCGのレンダリング結果があるのならば、ペイントツールの筆で塗る工程をある程度自動化できてもよいのではないかと思います。
— 折登 いつき (@MatchaChoco010) May 29, 2020
そこで適当にStroke Based Renderingの論文をいくつか読みました。いろいろな論文の実装ではそれぞれ色々と工夫があるようですが、まず最初は適当に自己流でシンプルなものを作ってみようと思います。論文のをそのまま実装しようとすると面倒くさいので……。
— 折登 いつき (@MatchaChoco010) May 29, 2020
特に論文によっては割とフルオートを目指しているものがあるのですが、私の用途ではフルオートより人間が多少介入してでも良いものを作りたいという感じ。
— 折登 いつき (@MatchaChoco010) May 29, 2020
色々時間がないのでちょっとずつスキマ時間でちまちまとやっていくつもりです。とりあえずOpenGLで描画していくつもりなのでRustでOpenGLを触る方法を調べました。
— 折登 いつき (@MatchaChoco010) May 29, 2020
RustでOpenGLを使うののとても良いチュートリアルがこちらにあったので参考にして今日のところは三角形を表示するところまで。https://t.co/qaDc0sD6iV pic.twitter.com/9Aeyw3guB6
— 折登 いつき (@MatchaChoco010) May 29, 2020
OpenGLを描画するウィンドウにはsdl2クレートを利用しています。RustでOpenGLを書こうとするとunsafeが必要になって嫌な感じですが、unsafeいらずのOpenGLに対するラッパーであるgliumというクレートはメンテされていないみたいなので、素直にOpenGLをunsafeで触っていきます。
— 折登 いつき (@MatchaChoco010) May 29, 2020
Blenderからカラーとデプスとノーマルを出力しました。 pic.twitter.com/9blI1yYqc1
— 折登 いつき (@MatchaChoco010) May 30, 2020
ノーマルの出力が面倒で、CyclesのNormalPassを使えばよいかと思っていたら、ワールド法線が出力されてしまったので仕方なく別途Normal出力用のマテリアルを作成してレンダリングしています。GeometryのNormalをVector Transformノードでカメラ空間にします。 pic.twitter.com/b0XJgcluLz
— 折登 いつき (@MatchaChoco010) May 30, 2020
加えて正しいリニアなノーマルを出力するためにカラースペースのView TransformをRawにしてレンダリング後のガンマ補正を切っています。 pic.twitter.com/RwBD0uLGyn
— 折登 いつき (@MatchaChoco010) May 30, 2020
ノーマルマップをベースに方向マップを生成します。各ピクセルのノーマルと画面に垂直なベクトルの外積を取ってノーマルに垂直な方向のマップを得ます。方向をとりあえずRGに格納してみるとこんな感じ。 pic.twitter.com/DZ5FtLvUMl
— 折登 いつき (@MatchaChoco010) May 30, 2020
これがあっているのかわからなかったので適当に格納されたストローク方向に対して三角形を表示してみるとこんな感じになりました。形状に合わせてストロークが変化しているのはわかりますが正しいのかしらん? pic.twitter.com/thMoRATZpo
— 折登 いつき (@MatchaChoco010) May 30, 2020
わかりやすいように球体のシーンを作成しました。球体に沿ってストローク方向が向いているので、これで良しとします。 pic.twitter.com/44de6bfTEa
— 折登 いつき (@MatchaChoco010) May 30, 2020
ちなみに論文では勾配マップをベースにストローク方向を決めていました。要は輪郭線に沿った面の向きに沿ったストローク方向が得られれば良いので、今回は3Dをベースにしているので法線を書き出した画像をもとに適当にストローク方向を決定しました。写真からではなく3Dからやるメリットの一つですね。
— 折登 いつき (@MatchaChoco010) May 30, 2020
Blenderで出力したカラーにKritaでエッジ検出フィルタを掛けます。その後RGBのMAXを取りました。 pic.twitter.com/n1pCvDHfu2
— 折登 いつき (@MatchaChoco010) May 30, 2020
デプスからもエッジ検出を行います。 pic.twitter.com/T1kxL8oclK
— 折登 いつき (@MatchaChoco010) May 30, 2020
検出した二種類のエッジをスクリーンで重ねて、最後にグレースケールでちょっと書き足してこのような画像を得ます。これを重要度マップとします。 pic.twitter.com/mB1QDDnA6R
— 折登 いつき (@MatchaChoco010) May 30, 2020
この重要度マップを生成した画像の評価で使うので、重要度マップでより白い領域ほどもとの3DCGが再現されるようになっていくはずです。エッジだけ再現できればよいのかというとそういうことはないと思うので、評価の仕方はまた後で試行錯誤することにして、最初は単純にこの重要度を重みとしてみます。
— 折登 いつき (@MatchaChoco010) May 30, 2020
重要度マップをベースに確率をいじって点を5000個ほど表示してみました。 pic.twitter.com/dsykCAFgWk
— 折登 いつき (@MatchaChoco010) May 30, 2020
プログラムを整理してフレームバッファに書き込んだ内容をファイルに保存するコードを書きました。ばらまく点のサイズを大きくして、色を元ファイルからコピーしてくるようにしてみた結果の画像がこちらです。 pic.twitter.com/KQiolTVa3j
— 折登 いつき (@MatchaChoco010) May 30, 2020
まだ計算したストローク方向は使っていませんし、そもそもストロークになっていないただの点です。今後ストロークを描画するようにしていく予定ですが、今日のところはここまで。
— 折登 いつき (@MatchaChoco010) May 30, 2020
点の数を増やしました。また、ちょっと密度が低いところが思ったより低い気がするので、95%は出現確率が偏るように、のこり5%の点は一様分布に沿って点をばらまくようにしました。この点をシード点と呼ぶことにします。 pic.twitter.com/1PQVU7Mm5U
— 折登 いつき (@MatchaChoco010) May 31, 2020
各シード点に「太さ」のパラメータをもたせました。現状では点の大きさとして表示しています。太さのパラメータは重要度マップに基づいて適当に平均と分散を決めた正規分布から決定しています。重要度が低いやつほど太く重要度が高いほど細くなるようにしています。 pic.twitter.com/FvPk02RNQw
— 折登 いつき (@MatchaChoco010) May 31, 2020
折れ線によるストロークを実装したところ次のようになりました。 pic.twitter.com/hTL5VYHdvp
— 折登 いつき (@MatchaChoco010) May 31, 2020
ストロークの重要度の順にソートして表示したところです。どこかで方向の計算が間違っているような気がしてならないですが。 pic.twitter.com/bkmcQvihLp
— 折登 いつき (@MatchaChoco010) May 31, 2020
OpenGLの関係で画像のyを反転させていましたが、方向マップのyを反転させそこねていました。今度は方向マップもあっていると思います。 pic.twitter.com/sWiBiLPsRV
— 折登 いつき (@MatchaChoco010) May 31, 2020
折れ線をCatmull-Rom曲線ベースのストロークに変更しました。乱数で生成されているので生成するたびに違う結果になりますが、どれもしょっぱい結果です。イラストレーターの絵などはるか遠い残念な感じです。果たしてここから良くなっていくのでしょうか。 pic.twitter.com/5xwRyJVqA6
— 折登 いつき (@MatchaChoco010) May 31, 2020
色々パラメータを変えつつ計算させて、250個ほど一時に生成してからスコアの一番高いものを出力してみているのですがまあだめですね。スコアは元の画像との2乗誤差を重要度マップで重み付けして和をとったものとしています。 pic.twitter.com/xc6oBDouDM
— 折登 いつき (@MatchaChoco010) May 31, 2020
250枚レンダリングしてスコアの計算をしてとなると地味に30秒位かかります。思いの外時間がかかりますね。例えば100世代ぐらい計算しようと思うとスコアの計算だけで50分かかることになります。
— 折登 いつき (@MatchaChoco010) May 31, 2020
参考にしている2005年の論文とかでは数時間かかっていたみたいなのでそれに比べれば圧倒的に速くなってはいますが……。
— 折登 いつき (@MatchaChoco010) May 31, 2020
遺伝的アルゴリズムで探索する予定なのですが、その前にもう少しストロークの初期値の生成をいじりたいです。具体的にははみ出た塗りを削除するようにしたい。ストロークを伸ばしていく過程で色が大きく変わったら打ち切るようにしようと思います。
— 折登 いつき (@MatchaChoco010) May 31, 2020
だいぶマシになりました。顔が潰れているのは変わりませんが……。 pic.twitter.com/4OkhHSdB8Y
— 折登 いつき (@MatchaChoco010) May 31, 2020
遺伝的アルゴリズムを実装して100世代ほど探索を進めてみました。画像は1世代目、10世代目、20世代目、50世代目です。 pic.twitter.com/sM8sGUV4he
— 折登 いつき (@MatchaChoco010) May 31, 2020
そしてこちらが70世代目、80世代目、90世代目、100世代目です。 pic.twitter.com/Lko13fgc0g
— 折登 いつき (@MatchaChoco010) May 31, 2020
思った以上に収束が遅く、一応スコアの上では少しずつ良くなっているはずなのですが、パッと見てわかるほど良くなっていっているかというと微妙ですね。100世代程度ではこの単純なスコアでは大して変化がないのかもです。
— 折登 いつき (@MatchaChoco010) May 31, 2020
まずスコアが単純にrgb値のユークリッド距離を誤差としてとっているのですが、果たしてこれで良いのかというのがありますね。人間は色そのものよりも色に差のあるエッジに敏感なので、エッジ検出を元画像と生成画像で行って、その結果を比べてスコアに追加するとかそういうのも考えられそうです。
— 折登 いつき (@MatchaChoco010) May 31, 2020
あとは一様交叉でやっていますがここらへんも工夫の余地がありそうですし、突然変異の確率ももう少し上げても良いかもしれません。
— 折登 いつき (@MatchaChoco010) May 31, 2020
とりあえず突然変異の確率をちょっと上げて、ブラシサイズが小さいところが小さすぎる気がするので、ブラシサイズのパラメータもいじってもう一回100世代ほど最初からやってみます。ハイパーパラメータをどうするかというのは難しい話ですよね……。
— 折登 いつき (@MatchaChoco010) May 31, 2020
何度か実験して改善が見られなかったらスコアを見直すということで。
— 折登 いつき (@MatchaChoco010) May 31, 2020
今回の第1世代と100世代目です。ほとんど違いがわからない。前回と比べてスコアも劣っています。ちょっと突然変異の確率を上げすぎたのか、なかなか探索が進みませんでした。 pic.twitter.com/J34ALf95WL
— 折登 いつき (@MatchaChoco010) May 31, 2020
次はちょっと長めに一晩探索を回してみようと思います。これでも変化があまり見られないようならば根本的に何かを改良する必要があるでしょう。
— 折登 いつき (@MatchaChoco010) May 31, 2020
一晩遺伝的アルゴリズムの探索を回しましたが結果は微妙です。画像は1世代目と1800世代目です。スコア上は良くなっているはずですが、美的感覚としてはあまり良くなっている気がしませんね。 pic.twitter.com/wXcfOz4aPJ
— 折登 いつき (@MatchaChoco010) June 1, 2020
スコアの推移の変化の少なさを見ると局所最適解にハマりがちなのではないかと思われます。計算時間が増えることを嫌って集団サイズが少し小さめで行っていたのですが、遺伝子長がストローク数16000もあるのでそれなりに大きい集団サイズが必要なのでしょう。
— 折登 いつき (@MatchaChoco010) June 1, 2020
また今夜も集団サイズを大きくしてパラメータを微調整してから一晩探索を回してみます。
— 折登 いつき (@MatchaChoco010) June 1, 2020
はじめまして!遺伝的アルゴリズムは機械学習の一種として数えられることもあります。広い探索空間から真値に収束させるのに向いているそうです。最近主流のニューラルネットと比べるとだいぶ古い手法です。
— 折登 いつき (@MatchaChoco010) June 1, 2020
今回試しているのは3Dでレンダリングした画像を絵画調に変換するものです。無からキャラクターを創造しているわけではありません。キャラクター自体は手作業で作った3Dモデルでレンダリングしたものがベースになっているので、それを踏まえて考えると今の所あまり芳しい結果にはなっていません……。
— 折登 いつき (@MatchaChoco010) June 1, 2020
絵画調に変換するというのなら、近年のニューラルネットベースのAIベースの手法が強いのですが、今回実装しているのそそれより古い1990年代には提案されているStroke Based Renderingというものを試しています。構造のより単純な古典的な手法を試してみようとしているところです。
— 折登 いつき (@MatchaChoco010) June 1, 2020
2005年に出た論文のSalience-adaptive PainterlyRenderingusingGeneticSearchをふわっと参考にして、もっと単純化したものを実装し試しています。遺伝的アルゴリズムによって、すこしずつ生成されたペイント結果がレンダリング結果に近づいていくはずなのですがなかなかうまく行っていないところです。
— 折登 いつき (@MatchaChoco010) June 1, 2020
適当に走らせた1世代目、100世代目、200世代目、210世代目です。ちょっとクオリティをこれ以上上げるにはもう少しなにか工夫が必要そうですね。 pic.twitter.com/GsPhnu8EMs
— 折登 いつき (@MatchaChoco010) June 1, 2020
ストロークを遺伝子として持っているので、遺伝子長はストローク数の20000で、さらに言えばストローク自体にそれこそ無限のバリエーションがあり得るので、数百数千世代でごちゃごちゃ言っているのが間違っている気がしてきました。これほど遺伝子長が長いならばより大量の世代が必要なのでは?
— 折登 いつき (@MatchaChoco010) June 1, 2020
というのも、遺伝的アルゴリズムの中でも収束が早いCHCというアルゴリズムを実装してみようとして、そのアルゴリズムには遺伝子長Lに対してL/4世代だけ変化がなかったら突然変異を噛ませるという風になっていて、GAの中では収束が早いと言われるCHCでもそれだけ世代を重ねる前提なのだなと。
— 折登 いつき (@MatchaChoco010) June 1, 2020
そして、現状では集団サイズ500で1世代に付き2分ちょいの時間がかかっています。これで数万世代計算したいとなると馬鹿にならない時間がかかりますね。
— 折登 いつき (@MatchaChoco010) June 1, 2020
計算する世代数を増やすために1世代あたりの計算時間を減らすか、遺伝子表現を工夫して遺伝子長を短くするかのどちらかが必要でしょう。
— 折登 いつき (@MatchaChoco010) June 1, 2020
現状の実装では将来的に半透明ブラシに対応するために20000のストロークを後ろから順に描画しているのですが、一回描画するごとにドローコールをしなくても奥から順に三角形が並んでソートしているのだからドローコールをまとめられるのでは?
— 折登 いつき (@MatchaChoco010) June 1, 2020
ドローコールを抑えるように書き直して見てもあまり変化がなく、計測をしてみたところ、スコアの計算自体が時間かかっているようでした。4k解像度の全ピクセルについて色の差を計算して足し合わせる処理で1個体につき0.46秒ほどかかっています。これが500体で1世代となると割と時間がかかるようで。
— 折登 いつき (@MatchaChoco010) June 1, 2020
すでにrayonを使ってマルチスレッド化は済ませていて、それでもこの計算時間なので困ってしまいますね。これ以上の高速化となるとGPGPUとかに手を出すことになるのでしょうか?GPGPUで総和の計算は割と面倒だった覚えがあるのですが……。 pic.twitter.com/QAbaYGh6yl
— 折登 いつき (@MatchaChoco010) June 1, 2020
マルチスレッド化がオーバーヘッドになっていないか調べるためにrayonをオフってみたらスコアの計算に2.23秒ほどかかるようになって、rayonで並列化したときの0.46の5倍ほど遅くなっているようです?ので並列化は意味をなしているようですが……。
— 折登 いつき (@MatchaChoco010) June 1, 2020
GPGPUはちょっと本腰を入れないとできそうにないので続きは週末ですかね。色の差にCIE DE2000のDelta Eを使うようにしたので、それによってよくなることを期待して今晩は探索を回してみますが、何より世代数を稼ぐのが正義だと思うのであまり改善しなさそうな予感です。
— 折登 いつき (@MatchaChoco010) June 1, 2020
うーん。スコア計算に色の差をちゃんと計算するようにしたのと4k解像度か組み合わさって1世代あたり7分とかかかるようになっていて世代数を稼ぐのはとても無理があるという感じです。週末にGPGPUを実装して早くなってくれることを祈るのですが……。
— 折登 いつき (@MatchaChoco010) June 1, 2020
GPGPUは週末にためそうと言いつつ少しずつ触っています。
— 折登 いつき (@MatchaChoco010) June 2, 2020
RustでCudaを使うべく調べてみてaccelというのがあるらしいので使ってみることに。このクレートは一時更新が停止していたけど、最近更新が再開したっぽいです。
— 折登 いつき (@MatchaChoco010) June 2, 2020
試してみたら次のようなエラーが出ました。困ったので公式のhttps://t.co/TTOVFuDnN9をよく読んでみるとWindows and macOS are not supportedとのことで。Windows対応していないのか。Linuxをディアルブートで入れるというのはCドライブ容量的に避けたいのですがどうにかならないでしょうか。 pic.twitter.com/veskI9GUh4
— 折登 いつき (@MatchaChoco010) June 2, 2020
Dockerコンテナが用意されているようなのでそれを試してみることにします。WindowsのDockerでうまく動くのかわかりませんが、いちおうLinux的なものではあります。https://t.co/DqnVpHipas
— 折登 いつき (@MatchaChoco010) June 2, 2020
試してみたのですが、やはり同じようなエラーが出ました。エラーメッセージも謎ですね。ツールチェインがインストールされていないなんて。ツールチェイン入りのdockerイメージを使っているはずなのに。 pic.twitter.com/gyThrRjF21
— 折登 いつき (@MatchaChoco010) June 2, 2020
いろいろ調べてみたところ、公式のdockerのhttps://t.co/TTOVFuDnN9に載っているこれはバージョンが古いみたいです?バージョンの古いコンテナをインストールしていたようでツールチェインが入っていませんでした。 pic.twitter.com/vA8M7pqVGe
— 折登 いつき (@MatchaChoco010) June 2, 2020
ツールチェインのセットアップコマンドは公式で用意されているのでこちらを使います。 pic.twitter.com/IZ33q5aR96
— 折登 いつき (@MatchaChoco010) June 2, 2020
またcargo runが失敗しました。エラーメッセージはさっきと変わっています。やたらと長いエラーが出ていますが、リンク時に失敗している模様?-lcudaが見当たらないと怒っていますね。 pic.twitter.com/M9cwYhxgfm
— 折登 いつき (@MatchaChoco010) June 2, 2020
よく考えたら、そもそもGPU対応のコンテナを作っていないから問題なのではないかと思い至りました。dockerでgpuはdocker 19.03以降nvidia-dockerとかを使わずに公式で対応しているようです?
— 折登 いつき (@MatchaChoco010) June 2, 2020
gpu付きでコンテナを立ち上げようとしたらエラーが出て、その解決方法をググったらnvidia container toolkitを入れろとのことでしたが、nvidia container toolkitはWindows 10は対応していない模様。というかdockerとGPUの組み合わせがLinuxでないと無理っぽい?
— 折登 いつき (@MatchaChoco010) June 2, 2020
windows上でdockerで試すというのは残念ながら無理そうです。他に方法がないか調べてみて、wsl2が将来的にはcudaも対応するという話が出ているようですが……。私としては今使いたいのですよね。困ったなあ。
— 折登 いつき (@MatchaChoco010) June 2, 2020
もう一度Windows上でできないか試してみます。先程Windows上でやったときはfmtのエラーというのだったので。accelをローカルにコピーしてそれを使うようにしてaccel_derive/src/builders.rsをいじって様子を見ます。fmtのエラーを通過したらうまく動くようになるかもしれません。
— 折登 いつき (@MatchaChoco010) June 2, 2020
環境変数をフィルタリングして実行しているのが、フィルタリングしたことによってなぜか+nightly-2020-05-01をサブコマンドとして認識しなくなるというのが問題のようで。環境変数のフィルタリングを削除してみたところとりあえずfmtのエラーは起きなくなりました。
— 折登 いつき (@MatchaChoco010) June 2, 2020
問題が次のtargetをnvptx64-nvidia-cudaでビルドしているところで、ここでビルドがコケています。そこでrust-ptx-linkerのhttps://t.co/TTOVFuDnN9を見てみると、Windowsが非対応ということがわかりました。 pic.twitter.com/ouQvIhmycm
— 折登 いつき (@MatchaChoco010) June 2, 2020
accelをちょっといじった程度では修正できない問題とわかったのでここで終了ですね。
— 折登 いつき (@MatchaChoco010) June 2, 2020
accelは諦めて、別の方法でrustからcudaを叩けないかをまた後で調べてみることにします。別にカーネルコードをrustで書けないならそれはそれで良いのでとりあえずcudaを動かしたいなあ。
— 折登 いつき (@MatchaChoco010) June 2, 2020
GPGPUで高速化しようと言っていたのですが、そもそもスコアの計算に4K解像度使わければ一気に高速化できるので、縮小した解像度でスコアを計算して画像へのレンダリングだけ高解像度で行うようにしました。するとGPGPUで高速化する部分はなくなってしまいました。
— 折登 いつき (@MatchaChoco010) June 3, 2020
1世代あたり7分かかっていたのを4kを使わなくしたのとCPUのマルチスレッドを駆使したので、1世代あたり8秒にまで抑えることができました。世代数やストローク数を押さえればさらに高速化できます。残りの計算時間に支配的な部分はGPGPU向けのベクトル計算ではないのでGPGPUでの高速化は見込めません。
— 折登 いつき (@MatchaChoco010) June 3, 2020
そして、いろいろ試してわかってきたのは、この遺伝的アルゴリズムの手法は良い絵画の集団からより良い絵画の集団を作るのにはうまく動いても、あまり良くない絵画から良い絵画を抽出するのには向いていないのでは?ということです。
— 折登 いつき (@MatchaChoco010) June 3, 2020
ランダム生成の段階でなるべく良い絵画を出力できるようになっていれば探索範囲を狭められてよりよい絵画を探索できますが、あまり良くない絵画から良い絵画を探索するとなると探索空間が広すぎてGAといえども現実的な時間では無理がある感じがしないでもないです。
— 折登 いつき (@MatchaChoco010) June 3, 2020
ランダム生成の段階でなるべく良い絵画を作るにはGA以前の論文で提案されている経験則的なプログラムを色々組み込まないといけなさそうです。Deep Learning時代の機械学習に親しんでいたため全てを学習させればよいという富豪的な考え方でいたのですがこの時代の手法はそうは言っていられないよう?
— 折登 いつき (@MatchaChoco010) June 3, 2020
経験則的なプログラムを組み込むのですが、例えばストロークを色の差が大きいところでは打ち切るという経験則的なプログラムを作ったら、どのくらいの輝度差を「違い」とすればよいのかは画像によって違います。プログラムは対象とする画像に合わせて細かいパラメータを調整する必要がある感じですね。
— 折登 いつき (@MatchaChoco010) June 3, 2020
画像の違いとかも何もかもパラメーターは学習で吸収すれば良いというDeep LearningにあこがれてストロークベースのDeep Learningな手法はないか調べてみたのですが、ICCV2019-Learning to Paintというのがそれっぽいです?後で詳しく見てみようと思います。
— 折登 いつき (@MatchaChoco010) June 3, 2020
計算の高速化が実装できてから適当に探索を回していたのですが、スコアの計算が間違っている気がしてきました。画像は1世代目、700世代目、1500世代目、2200世代目です。探索が進むにつれて黒い領域が増えているのが謎です。間違って黒い領域にスコアを与えてしまっていないか確認が必要です。 pic.twitter.com/y75nAZUDVb
— 折登 いつき (@MatchaChoco010) June 3, 2020
今までスコアの計算が思いっきり間違っていました。OpenGLの都合でyをフリップしているのですが、スコアの計算のときだけフリップを忘れていました。yがフリップした画像に合わせようとしていたため、上の方が黒くなっていったのですね。
— 折登 いつき (@MatchaChoco010) June 4, 2020
これがスコアを修正して3500世代ほど走らせたものです。 pic.twitter.com/f7o9bIqdrj
— 折登 いつき (@MatchaChoco010) June 4, 2020
1世代目、100世代目、1000世代目、2000世代目です。 pic.twitter.com/ZtaWCQYr1E
— 折登 いつき (@MatchaChoco010) June 4, 2020
小さいストロークを積み重ねて結果を出しているのは良いのですが、もう少し長めのストロークで絵を書いてくれないか試してみたいです。
— 折登 いつき (@MatchaChoco010) June 4, 2020
今回は12000ストロークだったのですが、次はもっと少ないストローク数で描画するのを試してみます。ストローク数が少なくなればその分1ストロークが埋める面積を増やす必要がありストローク長めになってくれるんじゃないかなと。
— 折登 いつき (@MatchaChoco010) June 4, 2020
ストローク数を4000にして1700世代回しました。顔のディhチールは保つようにそこにストロークが集中するように重要度マップを与えています。まあそれなりな結果なのではないでしょうか。 pic.twitter.com/hPYllE05Nc
— 折登 いつき (@MatchaChoco010) June 4, 2020
1世代目、100世代目、500世代目、1200世代目です。 pic.twitter.com/sGBBepkbtA
— 折登 いつき (@MatchaChoco010) June 4, 2020
ストロークベースドレンダリングのプログラムを写真でも試してみます。近所の公園で撮った写真です。 pic.twitter.com/qPWIQ0gwLw
— 折登 いつき (@MatchaChoco010) June 4, 2020
絵画っぽくするために加工をします。彩度とコントラストをぐっと上げて、空の色をピンクに、地面の色を暗い青に寄せました。その後計算時間の短縮のために縮小します。 pic.twitter.com/rS4kDYW91u
— 折登 いつき (@MatchaChoco010) June 4, 2020
画像をバイラテラルフィルタで平滑化した後にエッジ検出をします。検出したエッジから勾配を求めてストローク方向を求めます。 pic.twitter.com/4mYjiJU8Sk
— 折登 いつき (@MatchaChoco010) June 4, 2020
また、検出したエッジをぼかして重要度マップとします。 pic.twitter.com/MDhOZVIxdI
— 折登 いつき (@MatchaChoco010) June 4, 2020
1世代目、500世代目、3000世代目、6500世代目です。 pic.twitter.com/lGqAzr9LCy
— 折登 いつき (@MatchaChoco010) June 4, 2020
なんか印象派の絵画っぽい気がしないでもないです。自分としてはもっとボケていないぱっきりとした絵がほしいのですが、手法的にどうしても印象派っぽい絵になるのでしょうね。
— 折登 いつき (@MatchaChoco010) June 4, 2020
印象派っぽくしたいだけならばストロークベースドレンダリングではなくてNeural Style Transferなどのニューラルネットのほうが汎用性もあって良さそうな気がしてしまいます。
— 折登 いつき (@MatchaChoco010) June 4, 2020
自分としてはもっとストロークベースでないと表現できないような、ストローク一本一本に意志のあるというか、ストローク一つ一つに意味のあるような丁寧なストロークでできた絵を生成してほしいのですが……。ランダム生成ベースではそこまで期待するのは無理がありそうです。
— 折登 いつき (@MatchaChoco010) June 4, 2020
まあ、不満がないわけではないですがこれはこれでそれなりの成果物ということで。
— 折登 いつき (@MatchaChoco010) June 4, 2020
深層強化学習のストロークベースな手法があるみたいなので今度時間ができたときにそれを調べてみるということで。
— 折登 いつき (@MatchaChoco010) June 4, 2020
というわけで出来上がったイラストです。 pic.twitter.com/ZEJ4SllkGH
— 折登 いつき (@MatchaChoco010) June 4, 2020
過程です。 pic.twitter.com/ihKnpZmr7k
— 折登 いつき (@MatchaChoco010) June 4, 2020
いろいろな写真を試して見ようと思っています。次に試すのは近所で撮ってきたボタン式横断歩道のボタンの写真です。その写真をもとにそれを色調補正したものを作成し、平滑化の後にエッジ検出したものからストローク方向マップと重要度マップを作成しプログラムに入力として食わせます。 pic.twitter.com/Sbhq5ZvxEl
— 折登 いつき (@MatchaChoco010) June 5, 2020
完成したイラストがこちらです。 pic.twitter.com/yeW7TBloLi
— 折登 いつき (@MatchaChoco010) June 5, 2020
描画の過程です。 pic.twitter.com/y2MjS0IAzl
— 折登 いつき (@MatchaChoco010) June 5, 2020
近所のカーブミラーと鉄塔の写真です。例によってプログラムに入力する色調補正した画像と、エッジ検出から求めたストローク方向の画像、重要度マップの画像です。 pic.twitter.com/7skoKQlHmH
— 折登 いつき (@MatchaChoco010) June 5, 2020
まだ探索を走らせている最中なので完成品はもう少しあとでになりますね。探索はすでに4時間半ほど走らせていて、あと30分ちょっと探索しようかな。
— 折登 いつき (@MatchaChoco010) June 5, 2020
探索初期に急速に良くなってあとの数百数千世代は本当に微々たる差しかないという状況なのでどこで切り上げてもあまり変わらないあれなのですが。キリよく3000世代まで探索することにします。
— 折登 いつき (@MatchaChoco010) June 5, 2020
カーブミラーと鉄塔のイラストの完成画像です。 pic.twitter.com/WDrWFO7Vs2
— 折登 いつき (@MatchaChoco010) June 5, 2020
過程です。 pic.twitter.com/J1J3o2DXLu
— 折登 いつき (@MatchaChoco010) June 5, 2020
探索初期と比べると鉄塔の部分がマシになっています。画像は1世代目と250世代目、3000世代目です。 pic.twitter.com/9HYZWbpV9O
— 折登 いつき (@MatchaChoco010) June 5, 2020
マシになっているとはいえ、鉄塔のところもっと綺麗にストロークを引いてほしいのですよね。細かいストロークをペタペタやってそれっぽく見えるのもそれはそれで良いのですが、鉄塔の線のような構造はストロークの線をきちんと合わせて引いてほしい。
— 折登 いつき (@MatchaChoco010) June 5, 2020
そうするためにはストローク方向マップをもっと注意深く作ったり、場合によっては手作業で作らないといけなくなりそう。
— 折登 いつき (@MatchaChoco010) June 5, 2020
https://t.co/gXEyl8pRdn
— 折登 いつき (@MatchaChoco010) June 5, 2020
こちらのICCV 2019の論文で深層強化学習でストロークベースレンダリングを行うものの実装を著者の方がGoogle Colabで公開してくれているので試してみます。
すごいです!これまでのランダムベースの探索なんてまるで勝負にならない解像度です。私の求めるストロークベースは確実にこの方向ですね。 pic.twitter.com/8oymwlOOdK
— 折登 いつき (@MatchaChoco010) June 5, 2020
生成された過程です。なんか緑色のところがちゃんと収束していない気配がしますが……。 pic.twitter.com/IEg0NHenXz
— 折登 いつき (@MatchaChoco010) June 5, 2020
高画質にするために画像を分割するようなのですが、分割サイズを変えられたので試してみました。分割サイズ3と分割サイズ25です。分割サイズが少ない方は画質が低いですが、分割サイズが高いと表現力が非常に高いです。一方でよく見るとタイル感はあります。 pic.twitter.com/ByNKacD4BQ
— 折登 いつき (@MatchaChoco010) June 5, 2020
それぞれの過程です。 pic.twitter.com/QyhM1Xfbci
— 折登 いつき (@MatchaChoco010) June 5, 2020
やっぱり緑のところが収束していない気配を感じます。 pic.twitter.com/zQyfKMyiAB
— 折登 いつき (@MatchaChoco010) June 5, 2020
気になる点は、高画質に対応するためには画像を分割する必要があるようで、その分割によってストロークが途切れているものがある点。これによるタイル感が気になりますね。それから色がぼやけているというか滲んでいるようなDeepで作った画像っぽさがあるのも気になります。
— 折登 いつき (@MatchaChoco010) June 5, 2020
ストロークの形状を変えて学習させたものとかも公開されているようで、色々カスタマイズもできるようです。
— 折登 いつき (@MatchaChoco010) June 5, 2020
この論文をベースに、高解像度をタイルに分割せずに試せないかというのと、ブラシのストロークにテクスチャを与えたものを試してみるのと、色のぼやけがなんとかならないか試してみたいです。
— 折登 いつき (@MatchaChoco010) June 5, 2020
あとは重要度マップとかで絵の中で描き込みの量に差をつけられないかも試してみたいです。現状の実装だと全部緻密に書き込むようにできているみたいなので。
— 折登 いつき (@MatchaChoco010) June 5, 2020
鉄塔を試してみました。やはり色のぼやけが気になります。ストローク方向はバッチリ決まっているようですが、ストロークが楕円形なので細い線を描画するのに向いていなさそうです。 pic.twitter.com/t6qZIpXZh7
— 折登 いつき (@MatchaChoco010) June 5, 2020
過程です。 pic.twitter.com/zWM5SaT02q
— 折登 いつき (@MatchaChoco010) June 5, 2020
divideを5から10に変えてもう一度挑戦しました。相変わらず色のにじみが気になります。「注意」の周辺が大きく滲んでいるのが気になってしまします。画像によってうまく行ったり行かなかったりするのですかね? pic.twitter.com/RYM5M8o1Ur
— 折登 いつき (@MatchaChoco010) June 5, 2020
生成された描画の過程です。 pic.twitter.com/YOpIhFO6sM
— 折登 いつき (@MatchaChoco010) June 5, 2020
この実装そのままでは色のにじみなどで私の望むクオリティではないので、やはりこの実装をもとに自分で色々カスタマイズしたものを実装する必要がありそうですね。
— 折登 いつき (@MatchaChoco010) June 5, 2020
この強化学習ベースの手法は、重要度マップに基づくランダムなストロークに比べて、大雑把に全体を書いてから書き込んでいくというような絵を描くときに近い過程が得られるのも興味深いです。
— 折登 いつき (@MatchaChoco010) June 5, 2020
こんばんは。以前実装していたのが2005年の論文で、今回試してみたのが2019年の論文の著者の方が公開してくれているものです。これだけ年数が経てば技術も進化するということですね。深層強化学習の強さを見せつけられました。時間ができたら追実装をしてみようと思っています。
— 折登 いつき (@MatchaChoco010) June 5, 2020
芸術とかの「人間的」なものがプログラムで再現できるのはなかなかSF味があって面白いですよね。人工知能と呼ばれる深層学習などの技術の発展に伴い様々な人間的な処理がコンピュータに実現できるようになってきているので、今後もコンピュータと芸術の組み合わせは面白いことになっていきそうです。
— 折登 いつき (@MatchaChoco010) June 5, 2020
この後、Learning To Paintの追実装を行ったりなど、現在進行系で深層強化学習ベースの手法を試しているところです。
Stroke Based Renderingを遺伝的アルゴリズム(GA)でやっていたプログラムのソースコードはこちらです。コメントもなければ使い方も書いていないのであれですが……。 MatchaChoco010/sbrga