AfterEffectsで機械学習

AEでPythonの例

機械学習Pythonが楽なのでとりあえずAEにPythonを組み込むことを考える。AEとPythonを連携させた例は以下のようなものがある。

ひとつ目は、スクリプトからbat経由でPythonスクリプトを実行するというもの。
ふたつ目は、スクリプトPythonを組み込むもの。
みっつ目の"Omino Python"は今回やりたいことの方向性と似ているけど、Cairoを扱って描写というのがちょっと扱いにくい(ピクセルをnumpyの配列で扱いたい)ので参考にしつつ自作する。

AEで実際にPython


AEプラグイン側から呼び出されるpythonのコードはこんな感じ。基本、AEプラグインと関数の命名、機能等を対応させる感じで。ParamsSetup()で、使用パラメータやチャンネルの順序を指定(OpenCVはBGRの順なため変えられるようにしてある)。SmartRender()でパラメータの実際の値や、入力画像であるnumpyの配列を受け取り色々処理した後AEプラグイン側にnumpyの配列を返して後はよしなに描写。



CC2015でOpenCV使おうとすると処理が戻ってこなくなったり、モジュールによっては落ちたりするけど、軽くフィルタを作る分には非常に楽。ひとつ前の記事の4コマの奴もこれでトライアンドエラーした。

AEで機械学習

上では紹介してないけど、トラッカーのようにある時間幅(現在フレームのみ、ワークエリア等)を指定してその間を逐次処理する機能も実装してある。最終的に何かの値をキーフレームにベイクする、連番ファイルを渡してプロジェクトに読み込む的な用途を期待してるんだけど、単純にフレームのピクセル情報を得て色々するのにも当然使えるので、それを利用して学習させる。

画像を扱うならCNNっていう感じなんだろうけど、まだあまり理解してないのと計算コストが大きいのとどういう方向でやると面白くなりそうかというのがまだ明確じゃない。なので、単純にピクセルのマッピングを学習させる。つまり、フィルタを掛ける前の画像のピクセル列を入力ピクセル、フィルタを掛けたあとの画像のピクセル列を教師ピクセルとして、そのフィルタを学習させる。


機械学習部のコードの断片。機械学習のライブラリはChainerのv1.5。別に特別なことはやっていない。入力はR, G, Bの3つ、いくつかの全結合層で繋いで、入力と同様に3つの値を出力。中間層の活性化関数はReLU。後、回帰なので誤差関数は平均二乗誤差。



左上が元画像で左下がLightroomで現像した画像、右下が学習したフィルタを適用した画像となっている。動画内では3エポック回してるが、学習が進むにつれ右下の画像の色合いが左下の画像の色合いに近付いていってるのが分かる。


f:id:kareobana:20160110043659j:plain
左が元画像、真ん中がLightroomで現像した画像、右が学習したフィルタを適用した画像である。学習したフィルタは一度3D LUTに出力してから適用してある。LUTはAEだとApply Color LUTかRed GiantのLut Buddy(Free)で読み込める。


f:id:kareobana:20160110043850j:plain
差分画像、ちょっと分かりにくいが、ヘルメットや勝負服、ブリンカー等の色鮮やかなところの再現性が少し劣っている。


f:id:kareobana:20160110045139j:plain
f:id:kareobana:20160110045144j:plain
他の画像に学習させたフィルタを適用した例。なかなか見栄えがする。

のちのち

ヴィネッティングの効果も学習させたい場合、r, g, bだけでなく位置情報も入力する必要があるけど、当然3D LUTには出力できないので可搬性に劣るのでそこらへんは何とかする必要がありそう。 後、適当なパラメータを入力に加えて、それを弄ることでトランジションさせていったりとか。
どっちにしろ、現状の1ピクセル入力、1ピクセル出力だとやれることに限度があるので、もうちょっと色々考えたい。

4コマ漫画のコマを切り抜く

esuji5.hateblo.jp

上記の記事に触発され自分も書いてみました。

4コマを切り出す

srcフォルダ以下にある画像を再帰的に処理してdstフォルダ以下に切り抜き画像を出力するようにしてあります。

まず

OpenCVにfindContoursという輪郭を抽出するメソッドがあるのでそれでコマの輪郭を抽出することを目指します。画像サンプルは手持ちで上からとった悪条件のものを。

二値化
def apply_adaptive_threshold(image, radius=15, C=5):
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    return cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 2 * radius + 1, C)

輪郭を抽出する前にまず二値化する必要があります。グレーに変換した後、Adaptive Thresholdを行います。Adaptiveの場合、画像全体で一意の閾値を適用するのではなく周囲のピクセルに応じて閾値を上下させるので影があっても綺麗に処理してくれます。
条件が悪い場合はブロックサイズを大きくしたり等パラメータを調整する必要がありますが、スキャナできちんとスキャンしたものを対象にする場合はそこまで気を使う必要はないと思います。

輪郭抽出
def find_external_contours(thresh):
    _, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    external_num = hierarchy.shape[1] if hierarchy is not None else 0
    return contours[0:external_num]

findContoursで輪郭を抽出します。今回は一番外部の輪郭(コマの内部の輪郭は必要ない)のみが必要なので2つめのパラメータにcv2.RETR_EXTERNALを渡します。するとhierarchyに一番外部の輪郭のみのリストが返ってくるので、個数を数えてその分だけの輪郭データだけを返すようにしてます。
OpenCV3系では戻り値が変わって最初に画像を返すようになったので、OpenCV2系の場合は注意が必要です。

コマの輪郭だけを得、その外接矩形を取得する
def extract_rects_from_controus(contours, min_perimeter, max_perimeter):
    frames = []
    for contour in contours:
        frame = cv2.minAreaRect(contour)
        center, size, angle = frame
        # 縦・横が逆になっている場合、90度回転させる
        if angle < -45:
            size = tuple(reversed(size))
            angle = angle + 90
        w, h = size
        perimeter = 2 * (w + h)
        if min_perimeter < perimeter < max_perimeter and abs(angle) < 3.0 and 0.1 <= min(w, h) / max(w, h) <= 1.0:
            frames.append((center, (w + 2, h + 2), angle))  # パディングを加える
    return frames

輪郭の外接矩形を取得し、その矩形の、周長、角度、縦横比でフィルタを掛け、コマの輪郭だけを抽出を試みてます。

コマをソートする
def cmp_frame(tolerance):
    def _cmp(lhs, rhs):
        return (lhs > rhs) - (lhs < rhs)

    def _cmp_frame(lhs, rhs):
        if lhs[0] == rhs[0]:
            return 0
        x1, y1 = lhs[0]
        x2, y2 = rhs[0]
        if abs(x1 - x2) < tolerance:
            return _cmp(y1, y2)
        else:
            return _cmp(x2, x1)

    return _cmp_frame

コマを外接矩形の中心位置に基づいて並び替えます。傾いている場合も考慮して、x方向は許容範囲内であれば同じx位置と見て、y方向のみで比較するようにしてます。

rects = sorted(rects, key=cmp_to_key(cmp_frame(tolerance)))

python3系ではsortedにcmpを渡せなくなったのでfunctools.cmp_to_keyを用いてます。python2系の場合は直接cmpに渡せば大丈夫かと思います。

コマの部分を切り抜く
def cut_frame(image, rect):
    center, size, angle = rect
    size = int(np.round(size[0])), int(np.round(size[1]))
    box = cv2.boxPoints(rect)
    M = cv2.getAffineTransform(np.float32(box[1:4]),  np.float32([[0, 0], [size[0], 0], [size[0], size[1]]]))
    return cv2.warpAffine(image, M, size)

アフィン変換で傾きを直しつつ切り抜いてます。

かわいい。

無意識を意識するには

 惡の華を見ましたが面白いですね。去年の謎の彼女Xといい講談社は変な原作を持ってきますね。

 一話の構成は、日常を淡々と描いてこれから起こる非日常的体験に対するお膳立てといった感じですね。当然ですが非日常という起伏を描くには日常という平坦を描く必要があるわけです。非日常を続けまくったら非日常性が強化されるかといったらそうではなくて、それがその世界の日常なんだなと認識されるだけです。裏拍をずっと続けたらいつの間にか表拍として聞こえるように。また、ああいった生徒時代の帰り道での友人との会話とかあまりにも日常過ぎてわざわざ意識して覚えようとしないため記憶に残らないものですよね。それを年取って当事者ではなく第三者として観察せざるをえない状況になってどう思うか。アニメは実写より観察するようになる、と高畑勲が言ってた気がしますが、アニメの場合背景は基本変化しないので必然的に動いているモノに注目するわけですね。で、惡の華の場合細田守のように影もないので余計ちょっとした目の動きだとか手の動きだとかに目が行くわけです。関係者の方が、これをトレスしてるアニメータは勉強になるだろうなと言ってましたが、自分自身の無意識のうちにやってるちょっとした動きっていうのは当然気づくわけがないので、そういった動きを描こうと思ったら"意識的に"他人が無意識にやっている行為を観察しないといけないわけですね。しかもそういった無意識な動きが動きを自然に見せる要素ともなりうるので、自然な動きを描く(特に日常生活での)っていうのは難しいわけです。今回アニメータはトレスをする際に、必然的にそういった無意識の動きの線を引くことになって人間って存外動いてるんだなというのを思わざるを得なくて、結果として勉強になるということですね。友人の山田の髪とか小泉純一郎の切り抜き並にやりたくないですが。

塔のある風景

http://www.flickr.com/photos/kareobana/sets/72157631651892208/

 街中から見える塔って惹かれませんか。特に八坂通から見る法観寺の五重塔は格別ですね。ということで、塔とその周りの風景を題材に何か撮ろうと前々から考えていて、それと同時に写真の写真を撮る表現も何かしたいなとも考えていて(こういうのは例として、Dear Photographのような昔の写真と現代の風景との対比させているものや、PENCIL VS CAMERAみたいな虚実が一体化したようなものがありますね)結果としてああいう感じになりました。標準域で撮る、周りの景色が十分映るよう塔が全体の1/4にすっぽりはまるぐらいの大きさに撮ると決めていたので、撮影範囲は自ずと限られてその中でいい構図を探す必要性があったり、上下左右方向だけでなく奥行き方向も合わせる必要性があったり、塔が右上ならバランス保つために左下に何か副主題がほしいなと考えながら撮ったり、なかなかに面白かったです。これからも適当にテーマを見繕っては撮って行きたい。

iOS 6の新Map登場によりAndroidの敗北が確実になった件について

 今回のiOS 6の新MAPを受けて、これでAndroidの勝利は確実になったなと思ったAndroid原理主義者は多いように思う。しかし、これは非常に甘い願望であり理知的な分析を欠いていると言わざるを得ない。
 まず、この新搭載のMapの最大の特徴は「ないものがある」という点、そしてこのMapにはiOS 6搭載機を所持する者しかアクセスできないという点にある。察しのいい方はもう気づいたかもしれない。つまり、このMapはiOS 6を使用できる者のみが理解できる暗号代わりとなる可能性があるのである。例えばこういうことが想定される。

「次の日曜空いてる?」
「うん、空いてるけど何で?」
「○○と☓☓と遊ぶ予定やねんけどかれをばなも来いひん?」
「行く行く」
「じゃあ、1時にパチンコガンダム駅で集合ってことで」
「えっ、何処て?」
「それじゃあ、日曜にまた!」
「えっ…ちょっと」
「ツーツー」
--こういうことが何度かあって---
「かれをばなって何度誘ってもトンズラするし、もう誘うのやめるわ」
「えっ…」

 このような陰湿な同調圧力に屈せずにいられる猛者は少数派であろうし、特に同じ物を所有することで仲間意識を確認しあう風習のある(私を含む)女子中高生にこういった事例は多く見られるようになるであろう。

 以上の分析から、残念ながらAndroidの敗北は(特に陰湿な同調圧力の強い日本において)決定的であると言わざるをえない。

ケからハレへのトランジション

 カーブミラーというのは基本的に交差車線の車の存在の有無を確認するためにあるため、当然カーブミラーの中に映る世界というのは道路が中心となり空は見えても占める割合は小さい。従って、アスファルトのねずみ色が支配的な世界、これがカーブミラーの中の日常的な世界となるわけである。しかし、カーブミラーに近づき仰角が増すと、空の占める面積は広がって空の色が支配的となり、同時にカーブミラーの機能性は損なわれる。カーブミラーはただの鏡と化し、その中にただ非日常的な空間を映すだけとなる(歩いてる時にもカーブミラーをよく見る人にとってはその光景は日常的かもしれないが……)。

 カーブミラーと似たものにバックミラーがある。カーブミラーの場合機能を果たすかどうかは見る側の位置に依存するが、バックミラーの場合運転手に対するバックミラーの角度に依存する。バックミラーを普段とは異なり、ただ上向きにして空を映るようにして(危険だが)走ってみるだけでも先程と同様、今まで経験したことのない非日常的な体験をすることができるはずである。

 普段見慣れて日常的だなと思ってる空間も見方を変えたり少しの工夫をすれば忽ち非日常的空間に変化する。

 

差異の認識

 男女の違いなんかはちんこの有無もあるし学校等の社会的な場で区別して扱われるので否が応にも認識するし、表面的な運動能力、学力も体育やテストだったりで自分と他人とは違うんだというのは認識しますよね。でも、高次脳機能障害みたいな例を出さなくても記憶力だったり、価値観だったり考え方だったりの内面的な部分というのは、交流したり議論をしたりすれば自分と比較することできて差異や同一性を認識できるけど、外面的には当然出てくるものではないので比較できてないことの方が多くなる。で、その比較できていない状態において、多分人は他人と自分は同一であると仮定すると思うんですよね。で、その同一性という意識はその状態が普通であるという意識に繋がって、例えば差異のある人物が出てきた場合にこの人は普通ではないと認識して相手を否定して自分と同じ方向に向けようとする、なぜならそれが自分にとって普通であるから。