/dev/null/onishy

どーも。

eeic走馬灯

君は今まで受けた授業の数を覚えているか?

qiita.com

気づけばもうM2で、上手く行けばもうすぐ卒業できます。eeicに入ってから今まで色々なことがありました。eeicは授業と課題が多いことで有名です。eeicで取得した単位数は実に90単位以上に登ります。惜しくも100単位に足りません…が、そのうち一体どれくらいが本当に役に立ったのか…?

M2になり、もうすぐ卒業というところで一度振り返ってみたいと思います。あくまで個人的な感想ですし、デバイス畑の人はもっと違う感想だと思います。それに無論人生は長いのであくまで「今のところ」ではあります。

B2・B3の皆さんなんかは、「こんなに授業取ってホンマに全部使うんか?W」と思っているかも知れません。差し迫った問題意識がないと人はだいたいあまり勉強しません。eeicの授業をただ惰性で受けていてもあまり身につくことはないでしょう。僕ももう少し勉強しておけばよかったと思ってこの記事を書いています。これを読んでもう少し勉強しようという気分になってもらえれば嬉しいです。

一応自己紹介をしておくと、

をしていました。

あくまで個人的な感想だったり、あまり真面目に出ていなかった授業もあるので話半分に読んでもらえると幸いです。

4学期

eeicに配属され頑張ろうという気持ちでいっぱいだった駒場時代。twitterで特定に励むオタク、1108部屋の密度にキレるオタク、うま信構文の誤用にキレるオタク、今となっては全てが懐かしい。あの頃は楽しかった…(遠い目)

生命科学概論

マジで何も覚えてない。オムニバス形式だった気はする。初っ端から本当に申し訳ないが何も覚えていない…

電気磁気学

いわゆるマクスウェル方程式などの電磁気に関する基本的な内容を学ぶ講義だったはず。僕自身は情報系のつもりでいたのであまり真剣に受けていなかったが、今は結局電磁気っぽいことをやっているのでもう少し真面目に勉強しておけばよかった。毎回課題が出て優秀なシケ対に教えてもらってた。

僕の卒論が磁界共振結合型無線電力伝送で、特に表皮効果に関係する分野だったので使っていた。あとは一時期電磁界シミュレータの研究をしようと思っていたことがあり、その時には必死にマクスウェル方程式の記憶を捻り出していた。

数学1D

オカタカシ。数学系の講義はどこで何をやったか忘れがち。変分法とか汎関数とか、微分方程式を行列を使って解いたりしたような記憶があるが定かではない。物工とかとの合同の講義で部屋が広いのでめっちゃtwitterしてた。

変分法は、一時期電磁界シミュレータの研究がしたくてサーベイをしていたときに有限要素法の勉強をしていて出てきた。微分方程式は今のところあまり研究では解いていないかも…

数理手法V

あんまり記憶にないがひたすらランダムウォークの話をしていたような気がする。今受ければもっと楽しめるのかもしれない。

情報通信理論

情報量とかエントロピーとかの話をやった気がする。

情報量は本当に色んな所に出てくる。エントロピー機械学習とかをやろうとすると出てくる。誤り訂正符号とかの話も信号処理の研究を聞いたりするとしょっちゅう出てくる。全体的によく出てくる。

電気電子計測

デシベルとか四端子法とかオシロスコープの原理とか、計測に関する話をざっくり ゆたかに やった。内容が盛りだくさんなので一つ一つを深くやる余裕があまりないのが残念だったけれども、テキストはとても良いリファレンスで実験のときにとてもお世話になった。誰かが授業終了5分前くらいに弁当を買いに行こうとして先生に咎められていた。

前期実験で一番よく使った気がするが、今の研究がネットワーク・アナライザやオシロを使ったりしているので出てくる。デシベルが分からない人はもぐりです。

信号解析基礎

ひたすらフーリエ変換を学ぶ。

この授業を含めてフーリエ変換の講義を何度受けたかわからない。でもどの授業でも違う教え方をしたりするので面白い。フーリエ変換は本当にあらゆる分野で出てくる。

電気回路理論第一

回路方程式を解いたりした。フーリエ変換/ラプラス変換とか伝達関数の行列とか影像インピーダンスとか三相交流とかとにかく電"気"回路に関する基礎をとりあえず全部やった。

無線電力伝送しているので常に使っている。F行列(海外ではABCD行列と呼ぶらしいですが)とかZ行列/Y行列とかは知らないと死んでた。Sパラメータが出てきた記憶がないが(やったのかもしれない)Sパラメータも計測をするなら必須なのでこの授業でやったほうが良いと思います(誰に向かって言ってるんだ)。

電子基礎物理

前半と後半に分かれていて、前半が量子力学で後半が統計力学だった。どちらも本当に面白い。この学期で一番好きな授業だったと言っても過言ではないが理解できたとは言ってない。前半の講義の試験がPC持ち込み可ネット利用可能で完全に情報戦だった。

前半の量子力学シュレーディンガー方程式をいじいじしていた記憶がある。これは直接的に使っていないが、ここで出てくる球面調和関数が実は3D CADの話題で出てきたことがある。

授業を受けた時は「シュレちゃんの解をなんかいい感じにパラメータを分離して2つの関数の積で表すと球面調和関数っていうのが出てきて〜」くらいの理解だったが、球面上で内積を定義してやると球面調和関数は互いに直交するので、3Dレンダリングの分野ではこれを直交基底として使うみたいな話だった気がする。結構一般的に用いられるんだという話をきいて「ほーん」となった。どこで何が出てくるか分からない。

統計力学は離散的・定性的な話からどんどん統計的・定量的な話が仕上がっていくのが凄く面白かった。ただ今の所あまり使ってはいません。

ディジタル回路

最初はブール演算とかカルノー図をやっていて余裕こいていたら、気づくとわけのわからない論理回路を組まされていた記憶がある。良い思い出。

前期実験でディジタル回路の実験がありそこで使ったが、それ以降あまり直接的には触っていない気がする。

ソフトウェア/プログラミング基礎演習

ポイインタ。どっちがどっちの講義だったかあまり覚えていないが、ロボコンをやっていたので特に苦労はしなかった。

ただリファレンスがほぼWikipediaの英語記事くらいしかないアルゴリズムを実装させられたり、任意の多項式をパースして色んな演算をできるプログラムを実装させられたりして、冬休みが飛んだ記憶がある。

実装は楽しかった。僕の頃はCだけだったが今はPythonもやると聞いて時代を感じている。

エネルギー工学

ほぼ記憶がないが、たぶん送電とかスマートグリッドとかの政治的な話とかそういうのを色々聞いた気がする(先生がそういう先生だった)。

「無効電力とはどういうものか自分の言葉で説明せよ」という課題がとても記憶に残っている。結局正しい答えというのはなくて、一種の哲学的な問いなところが面白い。後にも先にも無効電力について真剣に考えたのはあの時だけだった気がする。位相補償という意味では無線電力伝送の効率の話には深く関わっているので使っているといえば使っている。

電子デバイス基礎

pn接合とか半導体の仕組みとかをひたすらやる講義。式がやたらと出てくるのと、試験がA4のカンペ持ち込み可だったので当時はパズル感覚だったが、もう少しちゃんとやっておけばなぁという後悔がある。

FETをdigikeyとかで選定していると、色んな種類があったり授業で扱ったようなパラメータがたくさん出てきたりするので回路を触りそうな人はちゃんと勉強しておいたほうが良いです。

ただ、どの講義にも言えることだが実際に電子回路を組んだことがない人はピンと来ないとは思う。

電気電子数学演習

工科系の数学を信奉する講義。 院試勉強を先取りしている感…

微分方程式複素関数あたりがメインだったが、電気系で複素関数をめちゃめちゃ真面目に使う分野って制御くらいじゃないだろうか…。有限要素法のシミュレーションでも出てきた気はする。

5学期

憧れの本郷キャンパスに移るも実験がキツいので全てがきつくなった学期。

オタクは控室という居場所を手に入れたが先代がやらかした(?)せいか夜間は閉鎖され路頭に迷うオタクが多発した。

制御工学第一

古典制御。現代制御もちょっと出てきたがほんのちょっとだった。

RoboTechで元々使ってたりはしていても、根軌跡とかナイキスト法とかはあまり知らなかったので勉強になった。制御はもっとちゃんと勉強したい…

アルゴリズム

データ構造の話とか探索の話とかをやった気がしますがなぜかあまり記憶にない…。。競技プログラミングの本とかでぼちぼち勉強したりしてたからかもしれない。

辞書作成・探索のアルゴリズム高速化選手権みたいなのがあった気がするがあまり良い結果が出なかった記憶がある。

電気電子基礎実験

いわゆる前期実験。電気系の分野を片っ端から体験できるので楽しいが、予習とレポートが重くてつらい日々を送っていた。実験は割と班員に任せきりでした(ごめんなさい)。ただ実際に体験したことはすごく記憶に残っているし役に立っている。

初めて真面目にFFTを実装したのがIP電話実験でとても記憶に残っている。あとはアナログフィルタの実験のテキストとかは今でもテキストを参照したりするレベルで役立っている。

コンピュータアーキテクチャ

CPUとかメモリとかの話。メモリのキャッシュの話は少し意識するだけでプログラムがだいぶ高速になったりするので面白い。あとは専門用語が本当に多いので、少し聞いたことがあるだけでもだいぶ違う気がする。

院試で出る確率が高いことが知られていて院試前にも結構勉強した。

信号処理工学

変調・復元方式の話。搬送波とかASKとかPSKとか…(たぶん)

やたらと難しかった記憶がある。この辺は今やろうとしている研究で使おうとしていたりするので、もっとちゃんと勉强しておけばよかった。

ただ電磁波は目に見えないのであまり実感が沸かないし、後期実験で無線実験を選んだりするととても良くわかりそう。1限なのであまり出席できなかった。

半導体バイス工学

4学期の電子デバイス基礎の内容と割と重複している。少し応用寄りな気がする(あまり覚えていない…)。1限なのであまり出席できなかった。と書こうとしたところで2限だったことに気づいた。

数学2G

表面上での微積分の話だったっけ…??数学3だったかもしれない。テンソルとか微分形式の話とかも出てきた気がする。数学系の講義はどこで何をやったか忘れがち(2度目)。東大工学教程のテキストがもらえた気がするが数学3だったかもしれない。

ネットワーク工学概論

TCP/IPの話とか…?結構政治的な話が多かった気もする…。1限だったのであまり出席できなかった。GNU創始者かなにかの人のインタビュー動画を見る回があった。

電気回路理論第二

フィルタの話(伝達関数、ボード線図)とか設計とか。並行して行われている前期実験でもフィルタの回があるが、実験はグループごとのローテーションなので、講義より実験を先にやる人たちは大変だが内容が身にしみて分かるようになる。

電気回路理論第一でやったF行列とかがまた出てきたような気がする。研究でフィルタ周りをやったりしたときは役立った。

電子回路I

鬼門。MOSを組み合わせてカレントミラー〜とか差動増幅回路〜とかがひたすら板書されている光景は分かりそうで分からない、さながらあやとりを見ているようで、一つ一つのステップは辛うじて理解できても全体としては難しくて何も分からなかった。

ただオペアンプのデータシートの回路図とかを見ているとマジでそんな感じなのでたぶんめっちゃ大事なんだと思う。世界観が広がった。試験は2人の先生が担当されていて前半と後半の難易度の差が本当にすごかった。やばかった。

ハードウェア設計論

Verilogで非常にシンプルなCPUのようなものを書いたりする。だいぶ実践という感じで個人的には楽しかった。Web提出の自動添削システムの穴を突いている人がいた。時代はFPGAだと思うので役立てて行きたい。

離散数学

グラフ理論寄りのアルゴリズム。ドロネー/ボロノイ図とかダイクストラ法とかプリム法とかその辺を非常に丁寧に教わった。試験が簡単すぎて優上が多発していた。

この辺りは教養として大事だなと思っている。ドロネー図は電磁界シミュレータのメッシュのアルゴリズムサーベイしていてよく出てきた。あと個人的に駅メモにハマっているのでボロノイ図にはお世話になっている。

電気電子情報工学倫理

倫理を学ぶ。法文一号館にある教室の前で倫理を司る人々から出席票を受け取って感想を書いて提出すると倫理が得られる。倫理を学ぶので不正は当然許されない。単位を取得すると倫理の化身となる。学部でバチェラーオブ倫理になっておくと大学院でもマスターオブ倫理とみなされるので再度履修する必要がなくなる。

メディコンI

毎回企業の人が来て面白い話をするので、面白い感想を書いて提出する講義。楽しい。

6学期

記憶は鮮明になってきたが、何に役に立ったとか振り返る気力が失われてきてだんだん走馬灯らしくなってきた。最後までがんばります。

この辺から講義に出る気力が減衰してくる。特に研究室振り分けに入るのか入らないのかよく分からない講義の点数はまぁいいかとなってくる。 オタクはストレスを抱え、常にキレているオタクが教室前方を占拠し通称"殺害島"を形成したり、単位切り抜刀斎が出没したりした。治安が悪すぎる。

数学3

数学2Gで書いた内容はこっちだったかもしれない。マジで忘れた。数学系の講義はどこで何をやったか忘れがち(3度目)。

国際経済学

国際経済を学ぶ。単位を取得すると国際経済の化身となる。

普通に面白かったし、金融緩和はどう効くのかとか普通に役に立ちそうな話が多かった。経済を回していこうという気持ちになった。

システム工学基礎

待ち行列の話とか故障率の話とか、システムのモデル化の話が主で面白かった。院試の数学の問題で講義で取り扱った問題と似たような出題があって進研ゼミになってしまった。3人付き合って4人目以降で一番良い相手と結婚すると幸せになれるらしい。

システム数理工学

システム工学基礎と名前が似ているがこっちは線型計画問題(シンプレックス法とか)をやる。講義にあまり出ていなかったのであまり記憶がない。

人工知能

課題提出マラソン。最後まで生き残った者が優上を得る。良い回答はテキストに載ったり翌年移行の模範解答として でんどういり するらしい。

無線通信応用工学

M先生が面白い話をしてくれる講義。授業スライドがあるが面白い話とは全く関係がないことで有名。授業の課題も当然面白い話とは関係がない。ただ課題はアンテナ系の問題が多く、意外と今も役に立っている。

計算論

あまり出ていなかったが正規表現とか正規言語とかの話は覚えている。電気系にしてはだいぶ理論よりの講義。そのうち役に立ちそうなオーラを感じる。

映像メディア工学

他の講義とごっちゃになってしまってあまり覚えていないが、画像圧縮の話とか信号処理の話とかだった気がする……

言語・音声情報処理

Hmm………….

難しかったのとあまり音声に興味がなかったので記憶が薄い。ただ言語処理はディープの重要な一分野なので最近話を聞くことが多く、うっすら聞いたことがある程度の知識でもぼちぼち役に立つことがある。

電磁波工学

だんだん人が少なくなっていく授業。裏番組で世界一のエンジニアになる授業があって、そっちを取っていた人が少なくなかった。試験1週間前くらいにテキストが発売されて生協書籍部で購入戦争が勃発した。

スミスチャートやらインピーダンス整合やら内容的には重要なはずなのだが…

ヒューマンインターフェース工学

人間の認知の話や感覚特性の話。人間の感覚は対数で効いてくるとか網膜と解像度の話とか、ユーザーテストの際に使う統計の話とか。かなり広くやってくれたのでためになる内容が多かった。

メディコンII

オムニバス形式でメディコンIよりも学術的な内容。

電子情報機器学

通称BDM。学生同士でハッカソンをやる。赤外線デバイスを作りました。

ここで初めて電子工作をやる学生も多いんじゃないだろうか。eeicのカリキュラム的に電子工作の経験があるかないかでだいぶ学びの質が変わりそうなので、もっと早めにやっておけばなぁと思った。

おわりに

なんとか書き終わりました。長くなってしまいましたがここまでお付き合い頂きありがとうございました。これで成仏できそうです。

こうして見ると、全体的にeeicのカリキュラムってよく出来ているなぁと思いました。電気と情報という深淵をいい感じに網羅している感があります。ただ最後にも書きましたが、電子工作をしたことがない人にはなかなかピンと来ない講義も多いので、適当なハッカソンに参加したりして早めに電子工作を体験しておくのが良いと思います。

最後の方は本当に走馬灯というかただのオタクの懐古みたいになってしまった。これを読んでもう少し勉強しようという気持ちが生まれるかは分かりませんが勉強しようという気持ちになってもらえれば幸いです。

Crypkoをはじめる

Crypko.aiが面白いです。

オタクなら好きそうなのに僕の周りであまりやっているという話を聞かないので、 布教も兼ねて説明していきたいと思います。

イーサリアムのウォレットを作る必要があるので、意外とハードルが高く感じますが、 まぁ5分もあれば始められると思います。

Crypkoとは

Crypkoとは、かわいい女の子を取引したり融合したりしてコレクションするゲームです。

ただし、一枚として同じ女の子はいません。

crypko.ai

です。

@_aixile さんが以前出した make.girls.moe というプロジェクトを発端にしています。

make.girls.moe

これは画像を生成するディープラーニングの一手法であるGAN(Generative Adversarial Network)を用いて、 女の子のアバターを自動で生成するというものです。

この技術とブロックチェーンを組み合わせたものがCrypkoです。

各カード(=女の子)に遺伝子情報のようなものが対応していて、 2枚のカードから新しいカードを作ることができます。

この新しいカードはブロックチェーン上に記録されるため改竄が不可能であり、 同じカードは一つとして存在しない、とのことです。

実際同じ親から2枚カードを作ると違う子が生まれます。 親の遺伝子がどのように受け継がれるかは、恐らくランダムな比率に基づいていそうですが、 この辺りのアルゴリズムについては公開されていません。

ただ一つ言えるのは、この遺伝子情報から生成される女の子の画像のクオリティがものすごく高いということです。

はじめてみる

0. テストネットについて

前述のようにCrypkoはブロックチェーン上でのやり取り(トランザクション)をベースにしているので、 まずはブロックチェーンを利用する準備が必要です。

イーサリアムという仮想通貨の名前を聞いたことがある人も多いと思いますが、 Crypkoではイーサリアムのテストネットブロックチェーンテスト用のネットワーク)を使います。

テスト用のネットワークなので、トークン(通貨)は無料でもらうことができます。 ただし無尽蔵にというわけではないのでご注意ください。

テスト用のネットワークなので、基本的に実際の通貨としての価値はありません。

少なくとも、誰かが「Crypkoを使うために金を払ってでも買う」みたいに、価値を見出そうとしない限りはそうです。(それはそれで胸熱展開ですね。)

1. MetaMaskの導入

イーサリアムを利用するために、MetaMaskという便利なプラグインが存在します。 これはブラウザ上でイーサリアムを支払う際の財布(ウォレット)のようなものです。

Chromeではこちらから。 MetaMask - Chrome ウェブストア

まずはこれをインストールします。

Mnemoric Phraseはランダムな12単語の羅列で、別のアプリでウォレットを使おうとすると聞かれるのでメモしておきます。

言われたとおりにアカウントを作ると、拡張機能としてブラウザから使えるようになります。

2. トークンをもらう

トークンをもらうにはhttps://www.rinkeby.io/#faucetに書いてあるように、以下のようにします。

  1. MetaMaskの「…→Copy Address to clipboard」からウォレットのアドレスをコピペし、外部SNSに公開する(ツイートするとか)。 f:id:onishy:20180602153626p:plain:w300

  2. Rinkeby https://www.rinkeby.io/#faucetに当該ツイートやポストのURLを入力する。

  3. "Give me Ether"をクリックする。

f:id:onishy:20180602153738p:plain:w300

ここで、"Give me Ether"には"3Ether/8hrs" "7.5Ethers/day" "18Ethers/3days"の3種類のもらいかたがあることがわかります。

計算すればわかりますが、"3Ether/8hrs"が一番オトクです。ただし8時間ごとに申請をする必要があって面倒なので、 とりあえず遊んでみたい方は"18Ethers/3days"で良いと思います。

3. Crypkoへのログイン

これでCrypkoへログインする準備ができました。

MetaMaskのネットワークを「Rinkeby Test Net」にして、crypko.aiを開いてパスワードを設定してログインすれば完了です。

あそぶ

それでは遊んでみます。

お迎えする

まずは女の子がいないと始まらないので、買いますお迎えします。

18Ethersあれば大抵の女の子はお迎えできてしまいますが、とりあえず1Ether以下の好みの女の子を2人選ぶと良いでしょう。 N/R/SR/SSR/URとか書いてありますが今は気にしなくて良いです。

「マーケット→販売」を選んで、好きな女の子を選びます。

f:id:onishy:20180602154826p:plain:w300

この子を選びました。

f:id:onishy:20180602154806p:plain:w300

Buyをクリックすると、MetaMaskによる承認の画面が出てくるので、Acceptします。

f:id:onishy:20180602154800p:plain:w300

しばらくすると購入が完了し、自分のコレクションに入ります。融合するために同じようにもう一人購入しましょう。

お迎えした子を選ぶと、こんな画面になります。

f:id:onishy:20180602154833p:plain:w300

女の子の右下に四角形が2つ重なった「融合」ボタンがあるのでクリックし、融合したい他の女の子を選びます。

今回はこの2人の女の子を融合します。

f:id:onishy:20180602154948p:plain:w300

融合が完了するまでしばらく待ちます。

f:id:onishy:20180602154821p:plain:w500

完了しました。

f:id:onishy:20180602154813p:plain:w500

そうすると新しい女の子ができます。極めてシンプルです。

f:id:onishy:20180602154751p:plain:w300

自分の持っているカードは、「マイカード」から確認することができます。

f:id:onishy:20180602161240p:plain:w300

売買する

お気づきの方も多いと思いますが、さっきの女の子も誰かが売りに出していたものです。Crypkoでは人身売買が可能です。

さらにヤバいのが、女の子をレンタルできる点です。 自分のカードを有償でレンタルし、融合する権利を売買することができます。ヤバい。

先ほどの「融合」ボタンの隣に「販売」ボタンと「貸出」ボタンがありますが、これを選ぶとレートを決めることができます。

オークション形式なので、最大値と最小値を決めておくと、最大値から値段がだんだん下がっていきます。

f:id:onishy:20180602155721p:plain:w300

ざっくり言えばこれだけです。ただし、システムについて理解する必要があります。

システム

レーティング(N/R/SR/SSR/UR)とは

レーティングは、融合後に次の融合を開始するまでの時間(Cooldown)を表しています。 URなら一瞬、SSRなら15秒くらいが目安です。

先ほど融合に使った子はこんな感じで11時間のcooldownになりました。

f:id:onishy:20180602161322p:plain:w300

これはそのカードを融合した回数に依存します。 なので、最初はURでも、そのカードを親として融合していくと、だんだんSSR→SRと言ったふうにレートが下がっていきます。 これにより同じ遺伝子を持つ子供の数が制限されています。

イテレーションとは

Iterは、そのカードが第何世代なのかを表しています。

Iter0はランダムに運営側で生成されてマーケットに放出される女の子で、Iter1以上は誰かが融合して作った女の子です。

子供のIter数は親のうちIter数が大きいものに1を足した数になり、これはレーティングと違って変わることはありません。

Iter0同士の子供はIter1になりますし、Iter50とIter3の子供はIter51になります。

子供の初期状態でのレーティングはIter数に依存します。Iter1で生まれてきた子供はURになりますし、Iter100で生まれてきた子供はNになります。

親のレーティングには一切関係がないので、Iter0/N同士の子供はIter1/URになります。この辺りを知っているとちょっと有利になるかもしれません。

デリバティブ

カードを開くと、その子をベースに融合した子の一覧と親の一覧を見ることができます。

これをたどっていくと、自分が売買/レンタルに出した子が今どうなっているのか、みたいな確認もできて面白いです。

余談

この章では若干テクニカルな話をします。別に読む必要はないです。

Cryptokitties

ブロックチェーン上でカードを集めて融合して…というのはCrypkoが最初ではなく、Cryptokittiesというものがあります。 www.cryptokitties.co

これはその名の通り猫を集めるもので、カード同士を融合して別の猫を作ったり、売買したりするものです。Crypkoはこれを真似した感じですね。

こちらはMain Network上で実際にイーサリアムがないと売買できないので僕はやったことがありません。

ただGANで女の子を集めるほうがモチベーションは上がりますね。。

属性

各カードには属性が入っています。

NIPSの元論文ではDiscriminatorの最後にClassifierとして34クラス分類用の全結合層をつけています。 クラスも一致しているようなので、恐らく画像の生成後にClassifierを通した結果でしょう。

ちなみに今まで遺伝子情報と言ってきているのはGANで言うG(z)のzです。

zはうまく学習ができていると内挿が可能です。つまり足して2で割ったベクトルをネットワークに入力すると、中間的な生成画像が得られます。 子供のzは、何らかの比率で親のzを混ぜ合わせていそうです。

イーサリアムトランザクションを見てみる

「取引」メニューから各取引のトランザクションが確認できます。

トランザクションを確認すると、親のID2つと子のID1つ、それからめっちゃ長い数字が一つはいっています。

f:id:onishy:20180602160909p:plain:w500

256bitsあるのでこれが遺伝子情報っぽいですね。サーバーサイドで計算した結果が入ってそうです。

理論上は2^256種類のCrypkoが存在することになります。

技術の詳細

作者の方のWebサイトはこちらです。

yanghuaj.org

NIPSのWorkshopに論文も出しているので、興味があれば読んでみると良いと思います。

[1708.05509] Towards the Automatic Anime Characters Creation with Generative Adversarial Networks

おわり

GANでここまでのクオリティのキャラクターが生成できるのはすごいですね。

遊び方は色々だと思いますが、自分の知っているキャラクターに似たキャラを生成してみるみたいな遊び方もできそうです。

今後どうなっていくかが楽しみです。

ちょっとだけ良い英語を書く

eeic Advent Calendar 13日目の記事です。

インターン・就活用の書類、論文など、そろそろ英語でポエムを書かなきゃと思っている人も多いでしょう。

良い英語のためには、本当はちゃんと文法を勉強した方が良いです。

でもそんなことは言ってられないと思うので、とりあえず英文を書く上で、知っておくだけでいつもよりちょっとだけ良い英語を書けそうなテクニックをいくつか挙げます。割と当たり前のことを言っているかもしれない。

本当は例文とかも色々挙げたかったけど、あまり時間がなかったので気が向いたら。

類義語辞書を使う(重要)

同じ表現を多用するのはボキャ貧です。アカデミック界隈の礼儀はあまり知らないが、therefore連弾が来ると普通の人はボキャ貧ワロスってなると思う。

例えば英語には順接の接続詞や副詞がたくさん用意されています。therefore, thus, hence, as a resultとか。

あとはこれを良い感じに分散させます。だいたい意味は同じだけどちょっとずつ違うので、「therefore thus 違い」とかでググると出てくる英語ブログを見ながら、パズル感覚ではめていくのが良い。

他の英単語についても同じ。this paper investigates… previous work investigated… the investigation on...みたいに同じ単語が被っていると、やっぱりボキャ貧ワロスってなる。

そういう場合は類義語辞典=thesaurusを使う。

有名なのは thesaurus.com で、ここに行くと、単語について意味ごとに類義語を教えてくれる。

でもこれが絶妙に使いにくいので、日本語の類義語をWeblioで調べてから英訳してみたりするのも良いかも。

良いポエムになるかどうかは結構この辺が大事。

例えば「be interested in」を「passionate about」とか「have enthusiasm」とかにするだけでなんかそれっぽい英語になりがち。

曖昧な表現を避ける

haveとかtakeとかはあんまり使わないほうが良いです。 have, take, get, putあたりは便利すぎて意味が曖昧になりがちな単語です。

isとかも場合によってはそうで、例えば「Hemi is God」だと、Hemi means Godなのか、Hemi is a name of Godなのか分かりません。

上のボキャ貧の話ともつながりますが、できるだけ読み違えを生まないような表現を心がける。日本語でもそうですけどね。

強めの動詞を使う

thinkとかwantとかはちょっと意志が弱そうに見えます。日本語でも「〜思う」みたいな文章が避けられるのと同じ。

「〜と考えられる」と書きたい場合は、「A results from…」みたいな客観的な感じに書くのも良い。

主観的に「〜思った」みたいに書きたい場合は、「believe」とか「confirm」とかを使うと良いかもしれない。

受動態をなるべく使わない

受動態は、可能なら避けます。回りくどいので割と嫌われることが多いです。 Wordで書いていると「親に向かってなんだその受動態は!」と言われて波線を引かれます。

形容詞的な用法や、"problem was fixed"くらいの軽いものならOKですが、 能動態に変換できる限りは、能動態に変換するのが吉。

ただし、使わざるを得ない場合もあります。例えば主語をはぐらかしたい場合。 「A is considered as…」みたいなものは能動態に直せないので、受動態で書くしかないです。

そういう時は、読みやすさを意識して書きます。

parallelismを意識する

parallelismというのは、品詞や要素の並列性です。

andを付けるなら、並列されるものはできるだけ品詞や時制を一致させます。

どこが並列されているのかが分かりやすいのが大事。長い文章になってくると本当に読みにくくなります。

並列化できないなら、そもそも文章を分けたほうが良い可能性が高いです。

例えば、"I like shopping and to read a book"とか言うと、"doing"と"to do"が混じっていて、並列性を意識していないのであまりきれいじゃありません。 きれいじゃないので可読性も下がります。片方がdoingならどちらもdoingにするべき。

同様に、名詞とdoingとかも気にすると良いです。

WordのFlesch-Kincaid Grade Levelを使う

Microsoft Wordには、Flesch-Kincaid Grade Levelという機能があります。

これはある指標を基に「やーいお前の英文◯年生〜〜」と言ってくれるツールです。 Flesch–Kincaid readability tests - Wikipedia

ただの単語長や音節数を基にしたヒューリスティックなので、長い単語を使えば学年は上がります。 フランクすぎる英語は短い単語を使いがちなので、これを意識するとそこそこまともな文章になります。

これは経験上意外と有用で、12、13年生くらいを目指すと、そこそこフランクすぎない英語になります。 ちなみにアメリカは学年を小学1年から通算して言うので、高校卒業の年が12年生です。

逆に、ヤバ長い物質名を乱用する化学系とかだとあんまり参考にならないかもしれませんが、普通の文章だったら結構参考になります。

Googleを活用する

Google is God. 最近のGoogle翻訳は強いので、とりあえず日本語をGoogle翻訳に突っ込んでみると、思わぬ表現を得られたりします。 とりあえず突っ込んでみて参考にするのはオススメ。誤訳も多いのでそのまま使うのはあんまりオススメしません。

あとは、Google Ngram Viewer を活用します。

ngram viewerは、ある表現が書籍に出てくる割合とかを教えてくれます。

なので、慣用句とか専門用語とか、「これ本当にこんな言い方する?」とか「ここってinなのかonなのかどっちだ?」とか思ったら、とりあえず突っ込んでみると本質情報が得られがち。

母国語で論理構成を確認する

英語で文章を書いていると一種のハイな状態になっていて、ちゃんと読むと実は論理が破綻していたりします。

また英語を読んでいるときも、英語の理解に集中していてそういうのには気づかなかったりします。

一度和訳して読み返してみると、思わぬミスが発覚したりします。 英→日はGoogle翻訳とかで良いと思います。Google翻訳に訳せる程度のわかりやすさかどうかも一緒に確認できる。


以上、ちょっとだけ良い英語を書けそうなテクニックでした。

僕の主観的な部分もありますし、常に成り立つわけでもないので、あまり気にしすぎないほうが良いと思います。

結局は英語のできる人に読んでもらうのが一番な気もします。

Imagine how your dreams come true, EEIC.

の目覚めは、いつも通り無意識だった。

この腕に貼り付けられたウェアラブル端末によって、起床時刻に目覚まし代わりの心地よい電気刺激が与えられるのだ。まぁウェアラブルなんて言葉は今では当たり前すぎて、ほとんど聞かなくなった古い言葉だ。

メガネをかけると、壁に今日の予定が現れた。仕組みはよく分からないが、レンズ全体が透明なディスプレイになっていて、現実世界に重ねて映像が映し出されるのだ。

「1時間後にミーティング!!」

全てが適切にスケジューリングされていて、今では、時刻を意識することすら少ない。


布団を脱ぎ捨ててリビングに下りると、母がテレビ(メガネに映し出されているのだ)を見ながら朝食をとっていた。

「今年は、イチゴが安いらしいわよ」と母が言った。

農業は完全に自動化されているが、翌年の需要や収穫率が予測され(あるいはそれを制御するために)、生産量が制御されるのだ。

朝ドラを映していた僕のメガネに「母さんと同じチャンネル」と話しかける。3D映像の記者が、青森県のイチゴ工場を取材していた。


母は「行ってくるわね」と言うと自動車に乗り、音もなく出かけた。電気で動く自動車はとても静かだ。

母は車が近づくと警告を出してくれるメガネなしでは、未だに外に出るのが怖いらしい。もっとも、自動運転だから事故に遭うほうが難しいと思うのだけれど。


そういえば、最近は「電気」なんてものは意識しなくなってしまった。

無線給電でコンセントなんて危ないものはなくなってしまったし、昔は街中に張り巡らされていたらしい電信柱も、今では全部地中に埋まって、ロボットによって自動でメンテナンスが行われる。発電に用いる資源の問題も、核融合炉が実用化されてからはすっかり静かになった。

電気を意識するのは雷の日くらいだ。


僕は部屋に戻って、今日の仕事を始める。

「ミーティング」と呼ばれるこの仕事では、人工知能の合成音声と会話をして、たまに簡単な作業をするだけだ。合成音声と言っても人間の音声と聞き分けがつかないし、人間と会話するのと何ら変わりはない。でも強いて言うなら、「人工知能」の本体を見たことがないのは変な気持ちだ。

この会話を通して、人工知能は何かを学んで賢くなるらしい。今では、芸能人なんかを除いて、国民のほとんどがこの仕事をしている。

人工知能に認められた一部の変わった人たちは、コンピュータと一緒に研究機関で新しい技術を作っているらしい。


今日の「ミーティング」では、人工知能と他愛もない会話をしながら、昨日ドローン便で届いた立体のブロックを使ってパズルを解いた。

人工知能によれば、なんでも、これは次の宇宙探査機の形状を最適化するのに役立つらしい。

宇宙開発といえば、最近は火星の開発が活発で、中でも嫌気性細菌を送り込んで酸素を生み出すプロジェクトと、大規模な国際データセンター設置のプロジェクトをよく耳にする。まぁ案の定、環境保護団体がうるさいけれど。


4時間くらい仕事をした。 疲れたけど、今日の仕事はこれで終わり。

最近の研究によると、一日の適切な仕事時間は3時間くらいだそうだ。それ以外は、友達と遊んだり、映画を見たり、日記や物語を書いたりすることが推奨されている。

そういう経験のほうが、「ミーティング」で人工知能にとって人から学べることが増えるんだそうだ。


ベッドに寝転んで、今日見る映画を選んでいると、友人からメッセージがきた。

「勉強会しようぜ!」

僕は快諾すると、支度を始めた。 オンラインだから家を出る必要はないけれど、服を着替える。

知識でコンピュータに勝つことはできないけれど、何かを「学ぶ」のは、すごく楽しい。

参考資料

スペキュラティブ・デザイン

とは、「未来を夢想し、そこにある課題を先取りする」問題提起の方法です。

どんな未来を目指しましょうか。

VLC弄ってみた 番外編 - Waifu2x-CaffeをLinux上でコンパイルする

さて、前回はなぜフィルタの話をしたのかというと、実はWaifu2xで動画の拡大をやろうと思っていたんですね。

Waifu2xってなに

Waifu2xは少し前にtwitterで話題になった画像拡大アルゴリズムで、ざっくり言うと、ニューラルネットワークを用いて、ある画像が「別の画像の縮小である」と仮定して、モデルを基に元の画像を推定することで高画質な拡大ができるそうです。

学習済みのモデルにはいくつか種類があるようですが、アニメタッチの画像などで特に力を発揮するようです。

もっと知りたい方は、この辺の記事とかを読んでいただけると分かりやすいかと思います。

Waifu2xをVLCに組み込みたい

それで、このWaifu2xですが、いくつかバージョンがあります。 最初にtwitterで話題になったのはこれで、こちらの論文を参考にしているようです。

ただ、luaで書かれていてあまり汎用性がないため、C++で書かれたwaifu2x-converter-cppや、高速化を目指して機械学習ライブラリCaffeやcuDNNを用いたwaifu2x-caffeなどが有志によって実装され、公開されています。

僕も実際に試してみましたが、やはりCaffe版が最速のようだったので、是非こいつをVLCに組み込みたいと思ったわけです。

ただ、こいつがVisual Studioで書かれていて、Windows向けなんですね。

僕が使っているのはUbuntuなので、頑張ってLinuxコンパイルできないか…ということで、まずはCMakeでこいつをコンパイルすることにしました。

CMakeってなに

CMakeというのは、使うライブラリなどを書いておくとMakefileを自動で生成してくれるツールです。似たような用途では他にもautomakeというのがあって、VLCではこちらを使っています。

Makefile.amのamはautomakeの略ですね。

ですが、automakeは少し冗長な書き方になってしまうので、CMakeのほうがすっきりとかける傾向にあります。

この手のツールには他にもOMakeとかninjaとか、jsonで書けるgypとか色々ありますが、使ったことがあって一般的で一番手っ取り早そうだったのでCMakeにしました。

必要なもの

NVIDIAのサイトから、CUDA, cuDNNをダウンロード・インストールしておいてください。

対応GPUを搭載したマシンが必要になります。

書く

まずwaifu2x-caffeをcloneしてきます。

git clone https://github.com/lltcggie/waifu2x-caffe.git
git submodule update --init --recursive
mkdir cmake

ここにCMakeLists.txtを付け足しますが、その前にCMakeでは一般的にサポートされているOpenCV以外のライブラリを使いたい場合、自前でFind***.cmakeというファイルを用意する必要があります。こいつの役目は主にディレクトリを探してくることです。自分で書くのはめんどくさいので、他の人が書いたものを借りてきましょう。良い時代です。

まず、FindGlog.cmake, FindCUDA.cmakeというファイルが必要です。次のファイルをそれぞれcmake/FindGlog.cmake, cmake/FindCUDA.cmakeとして保存します。

そして、cuDNNをdetectするために、caffeのCuda.cmakeから、165行目〜を拝借します。これをcmake/FindcuDNN.cmakeとして保存します。

################################################################################################
# Short command for cuDNN detection. Believe it soon will be a part of CUDA toolkit distribution.
# That's why not FindcuDNN.cmake file, but just the macro
# Usage:
#   detect_cuDNN()
function(detect_cuDNN)
  set(CUDNN_ROOT "" CACHE PATH "CUDNN root folder")

  find_path(CUDNN_INCLUDE cudnn.h
            PATHS ${CUDNN_ROOT} $ENV{CUDNN_ROOT} ${CUDA_TOOLKIT_INCLUDE}
            DOC "Path to cuDNN include directory." )

  get_filename_component(__libpath_hist ${CUDA_CUDART_LIBRARY} PATH)
  find_library(CUDNN_LIBRARY NAMES libcudnn.so # libcudnn_static.a
                             PATHS ${CUDNN_ROOT} $ENV{CUDNN_ROOT} ${CUDNN_INCLUDE} ${__libpath_hist}
                             DOC "Path to cuDNN library.")

  if(CUDNN_INCLUDE AND CUDNN_LIBRARY)
    set(HAVE_CUDNN  TRUE PARENT_SCOPE)
    set(CUDNN_FOUND TRUE PARENT_SCOPE)

    mark_as_advanced(CUDNN_INCLUDE CUDNN_LIBRARY CUDNN_ROOT)
    message(STATUS "Found cuDNN (include: ${CUDNN_INCLUDE}, library: ${CUDNN_LIBRARY})")
  endif()
    message(STATUS "Found cuDNN (include: ${CUDNN_INCLUDE}, library: ${CUDNN_LIBRARY})")
endfunction()

そして、waifu2x-caffe/に次のCMakeLists.txtを保存します。

cuDNNをインストールしたディレクトリをexportしておきます。

export CUDNN_ROOT=/usr/lib/cuda/include

次のコマンドを実行し、コンパイルできることを確かめましょう。

mkdir build; cd build
cmake ..
make

著作権上微妙なFindcuDNN.cmake以外のファイルをgithubに上げてあります。

https://github.com/onishy/Waifu2x-Caffe-Linux.git

結果

waifu2x-caffeは無事にコンパイルできました。ただ、こいつをVLCに移植する段階で少し挫折してしまいました。

まず処理時間的にリアルタイム処理は難しかったので、適当に数フレームごとに適用する処理を書かなければいけなかったのが少し面倒だったというのがあります。

またautomakeでのライブラリのリンクなんかが面倒すぎて、あまり時間もなくなってきたのでやめてしまいました。ごめんなさい。。

VLC弄ってみた#3 - フィルタを追加する

第3回目の今回は、VLCのフィルタ機能について解説します。

VLCには色々な機能がありますが、その中でも1、2位を争うレベルで知られていない機能と言っても過言ではないのが、フィルタ機能です。

フィルタ機能とは?

普通に再生した動画は以下。

f:id:onishy:20151104004758p:plain

試しに、次のコマンドをつけてVLCを起動してみましょう。

vlc --video-filter rotate

このウィンドウで適当な動画を再生すると、次のような実行結果になります。

f:id:onishy:20151104004802p:plain

その名の通り、動画を45度回転させて再生する機能のようです。

一体どういう時に使うのか、サッパリ見当が付きません…

次に、このようなコマンドをつけて起動してみます。

vlc --video-filter ball

先ほど同様に動画を再生すると…?

f:id:onishy:20151104004807p:plain

!?!?!?

どうやら、動画から輪郭を抽出して、それを壁にボールが跳ねる仕様っぽい…??

こんなものがVLCの正式版に入っているんですから奇妙です。

フィルタ機能の実装

され、これらの謎のフィルタですが、どこに実装されているかというと、modules/video_filterの中です。これらのファイル一つ一つ、その数58個!!!そしてその大半が実用性の見いだせないおもちゃフィルタです。暇人かよ。

psychedelicとかも割とマジキチで面白いです。結果は試してみてください。

さてフィルタですが、既存のフィルタを踏襲すれば、比較的簡単に実装することができます。

ここでは、ballフィルタをパクって、ボールの代わりに画像を表示できるようにします。

モジュールの登録

ball.cの122行目、vlc_module_begin()から始まる部分が、vlcのライブラリにフィルタをモジュールとして登録しています。

vlc_module_begin ()
    set_description( N_("Ball video filter") )
    set_shortname( N_( "Ball" ))
    set_help(BALL_HELP)
    set_capability( "video filter2", 0 )
    set_category( CAT_VIDEO )
    set_subcategory( SUBCAT_VIDEO_VFILTER )

    add_string( FILTER_PREFIX "color", "red",
                BALL_COLOR_TEXT, BALL_COLOR_TEXT, false )
    change_string_list( mode_list, mode_list_text )

    add_integer_with_range( FILTER_PREFIX "speed", 4, 1, 15,
                            BALL_SPEED_TEXT, BALL_SPEED_LONGTEXT, false )

    add_integer_with_range( FILTER_PREFIX "size", 10, 5, 30,
                            BALL_SIZE_TEXT, BALL_SIZE_LONGTEXT, false )

    add_integer_with_range( FILTER_PREFIX "gradient-threshold", 40, 1, 200,
                            GRAD_THRESH_TEXT, GRAD_THRESH_LONGTEXT, false )

    add_bool( FILTER_PREFIX "edge-visible", true,
              EDGE_VISIBLE_TEXT, EDGE_VISIBLE_LONGTEXT, true )

    add_shortcut( "ball" )
    set_callbacks( Create, Destroy )
vlc_module_end ()

ballフィルタに関する情報がずらずらと並べられています。まずはこれを適当に変更します。

vlc_module_begin ()
    set_description( N_("Hemi video filter") )
    set_shortname( N_( "Hemi" ))
    set_help(BALL_HELP)
    set_capability( "video filter2", 0 )
    set_category( CAT_VIDEO )
    set_subcategory( SUBCAT_VIDEO_VFILTER )

    add_string( FILTER_PREFIX "color", "red",
                BALL_COLOR_TEXT, BALL_COLOR_TEXT, false )
    change_string_list( mode_list, mode_list_text )

    add_integer_with_range( FILTER_PREFIX "speed", 4, 1, 15,
                            BALL_SPEED_TEXT, BALL_SPEED_LONGTEXT, false )

    add_integer_with_range( FILTER_PREFIX "size", 10, 5, 30,
                            BALL_SIZE_TEXT, BALL_SIZE_LONGTEXT, false )

    add_integer_with_range( FILTER_PREFIX "gradient-threshold", 40, 1, 200,
                            GRAD_THRESH_TEXT, GRAD_THRESH_LONGTEXT, false )

    add_bool( FILTER_PREFIX "edge-visible", true,
              EDGE_VISIBLE_TEXT, EDGE_VISIBLE_LONGTEXT, true )

    add_shortcut( "hemi" )
    set_callbacks( Create, Destroy )
vlc_module_end ()

hemiというのは、僕の中でhogeと同義語の単語です。あまり気にしないでください。

BMP画像の読み込み

画像を表示するので、まずはbmpを読み込む関数を作らなければいけません。VLCには画像系の関数は色々充実していそうなものですが、bmpに出力する関数はあっても、読み込む関数はないようです。

とりあえず動けばいいので、今回はアルファチャンネルなどは考慮しません。GIMPで40x40に縮小した正方形の画像をbmpに出力した、どーもくんのアイコンを用います。 f:id:onishy:20151105015953j:plain

なのでここは、検索すると出てくる中でも一番シンプルな実装をされている、物理のかぎしっぽさんのソースコードを拝借させて頂きます。

ただ、こちらのソースコードには少し読み込みのバグがあり、そのまま使うと数ピクセル分黒いピクセルが現れた後、RGBが入れ替わった画像が不思議な周期で現れるというおかしな挙動を示してしまいます。

少しデバッグをすると分かるのですが、これはヘッダを読み込んだ際にバッファを先送りしていないためなので、56行目あたりに2行だけソースコードを追加します。

//68bytes捨てる
char null_buf[128];
fread(null_buf, sizeof(unsigned char), 68, fp); //Throw away

これで読み込み関数はOKです。

描画と基底変換

次に、ボールの代わりにこの画像を描画します。

読み込まれた画像は2次元配列になっているので、大して難しくはありません。元々円が描画されていた部分(drawBall)を、次のように変更するだけです。

static void drawHemi( filter_sys_t *p_sys, picture_t *p_outpic )
{
    Image *img = Read_Bmp(hemi_filename);

    int x = p_sys->i_hemi_x;
    int y = p_sys->i_hemi_y;
    int size = p_sys->i_hemiSize;

    const int i_width = p_outpic->p[0].i_visible_pitch;
    const int i_height = p_outpic->p[0].i_visible_lines;

    for( int j = y - img->height/2; j < y + img->height/2; j++ )
    {
        bool b_skip = ( x - size ) % 2;
        int m_y = j - (y-img->height/2);
        for( int i = x - img->width/2; i < x + img->width/2; i++ )
        {
            /* Draw the pixel if it is inside the disk
               and check we don't write out the frame. */
            {
                int m_x = i - (x-img->width/2);
                ( *p_sys->drawingPixelFunction )( p_sys, p_outpic,
                                    get_color_func(img->data[m_y * img->width + m_x]).comp1,
                                    get_color_func(img->data[m_y * img->width + m_x]).comp2,
                                    get_color_func(img->data[m_y * img->width + m_x]).comp3,
                                    i, j, b_skip );
                b_skip = !b_skip;
            }
        }
    }   
}

少しややこしいのは、VLCが必ずしもRGBで画像を描画するわけではないところです。

ball.cの冒頭、48行目付近を見てみます。

#define COLORS_RGB \
    p_filter->p_sys->colorList[RED].comp1 = 255; p_filter->p_sys->colorList[RED].comp2 = 0;        \
                                p_filter->p_sys->colorList[RED].comp3 = 0;        \
    p_filter->p_sys->colorList[GREEN].comp1 = 0; p_filter->p_sys->colorList[GREEN].comp2 = 255;    \
                               p_filter->p_sys->colorList[GREEN].comp3 = 0;       \
    p_filter->p_sys->colorList[BLUE].comp1 = 0; p_filter->p_sys->colorList[BLUE].comp2 = 0;        \
                               p_filter->p_sys->colorList[BLUE].comp3 = 255;      \
    p_filter->p_sys->colorList[WHITE].comp1 = 255; p_filter->p_sys->colorList[WHITE].comp2 = 255;  \
                                  p_filter->p_sys->colorList[WHITE].comp3 = 255;

#define COLORS_YUV \
    p_filter->p_sys->colorList[RED].comp1 = 82; p_filter->p_sys->colorList[RED].comp2 = 240;        \
                                p_filter->p_sys->colorList[RED].comp3 = 90;        \
    p_filter->p_sys->colorList[GREEN].comp1 = 145; p_filter->p_sys->colorList[GREEN].comp2 = 34;    \
                               p_filter->p_sys->colorList[GREEN].comp3 = 54 ;      \
    p_filter->p_sys->colorList[BLUE].comp1 = 41; p_filter->p_sys->colorList[BLUE].comp2 = 146;      \
                               p_filter->p_sys->colorList[BLUE].comp3 = 240;       \
    p_filter->p_sys->colorList[WHITE].comp1 = 255; p_filter->p_sys->colorList[WHITE].comp2 = 128;   \
                                  p_filter->p_sys->colorList[WHITE].comp3 = 128;

ここでYUVという文字列が出てきました。

YUV(Wikipedia)というのは、輝度信号Yと色差信号2つを用いて色を表現する方法です。

これを、231行目からのswitch文で次のように呼び出しています。

switch( p_filter->fmt_in.video.i_chroma )
{
    case VLC_CODEC_I420:
    case VLC_CODEC_J420:
        p_filter->p_sys->drawingPixelFunction = drawPixelI420;
        COLORS_YUV
        break;
    CASE_PACKED_YUV_422
        p_filter->p_sys->drawingPixelFunction = drawPixelPacked;
        COLORS_YUV
        GetPackedYuvOffsets( p_filter->fmt_in.video.i_chroma,
                             &p_filter->p_sys->i_y_offset,
                             &p_filter->p_sys->i_u_offset,
                             &p_filter->p_sys->i_v_offset );
        break;
    case VLC_CODEC_RGB24:
        p_filter->p_sys->drawingPixelFunction = drawPixelRGB24;
        COLORS_RGB
        break;
    default:
        msg_Err( p_filter, "Unsupported input chroma (%4.4s)",
                 (char*)&(p_filter->fmt_in.video.i_chroma) );
        return VLC_EGENERIC;
}

コーデックによって、RGBを用いるか、YUVを用いるかを決めています。

そして、この処理結果を395行目で次のように利用しています。

( *p_sys->drawingPixelFunction )( p_sys, p_outpic,
                    p_sys->colorList[ p_sys->ballColor ].comp1,
                    p_sys->colorList[ p_sys->ballColor ].comp2,
                    p_sys->colorList[ p_sys->ballColor ].comp3,
                    i, j, b_skip );

見ての通り、特定の色のみをサポートしていて(ボールの色は単色で良い)、かつ単色のためよほど変な色でない限り問題ありません。

BMPから取得した色はRGBであるため、これをYUV空間の値に変換する必要があります。この辺によると、次の変換公式を使うことができます。

Y = 0.299R + 0.587G + 0.114B
U = -0.169R - 0.331G + 0.500B
V = 0.500R - 0.419G - 0.081B

Y = (0.257 * R) + (0.504 * G) + (0.098 * B) + 16
Cr = V = (0.439 * R) - (0.368 * G) - (0.071 * B) + 128
Cb = U = -(0.148 * R) - (0.291 * G) + (0.439 * B) + 128

これを基に、先ほどのマクロがあった部分を次のように書き換えます。

typedef struct{
    unsigned char b;
    unsigned char g;
    unsigned char r;
}Rgb;

typedef struct{
    char comp1;
    char comp2;
    char comp3;
}color_t;

color_t (*get_color_func)(Rgb color);

color_t get_rgb_color(Rgb color) {
    color_t c = {color.b, color.g, color.r};
    return c;
}

// Y =  0.299R + 0.587G + 0.114B
// U = -0.169R - 0.331G + 0.500B
// V =  0.500R - 0.419G - 0.081B

// Y  =      (0.257 * R) + (0.504 * G) + (0.098 * B) + 16
// Cr = V =  (0.439 * R) - (0.368 * G) - (0.071 * B) + 128
// Cb = U = -(0.148 * R) - (0.291 * G) + (0.439 * B) + 128

color_t get_yuv_color(Rgb color) {
    color_t c = {
        (int)( 0.257 * color.r + 0.504 * color.g + 0.098 * color.b)+16,
        (int)( 0.439 * color.r - 0.368 * color.g - 0.071 * color.b)+128,
        (int)(-0.148 * color.r - 0.291 * color.g + 0.439 * color.b)+128};

    return c;
}

#define COLORS_RGB \
    p_filter->p_sys->colorList[RED].comp1 = 255; p_filter->p_sys->colorList[RED].comp2 = 0;        \
                                p_filter->p_sys->colorList[RED].comp3 = 0;        \
    p_filter->p_sys->colorList[GREEN].comp1 = 0; p_filter->p_sys->colorList[GREEN].comp2 = 255;    \
                               p_filter->p_sys->colorList[GREEN].comp3 = 0;       \
    p_filter->p_sys->colorList[BLUE].comp1 = 0; p_filter->p_sys->colorList[BLUE].comp2 = 0;        \
                               p_filter->p_sys->colorList[BLUE].comp3 = 255;      \
    p_filter->p_sys->colorList[WHITE].comp1 = 255; p_filter->p_sys->colorList[WHITE].comp2 = 255;  \
                                  p_filter->p_sys->colorList[WHITE].comp3 = 255;   \
    get_color_func = get_rgb_color; printf("using rgb mode\n");

#define COLORS_YUV \
    p_filter->p_sys->colorList[RED].comp1 = 82; p_filter->p_sys->colorList[RED].comp2 = 240;        \
                                p_filter->p_sys->colorList[RED].comp3 = 90;        \
    p_filter->p_sys->colorList[GREEN].comp1 = 145; p_filter->p_sys->colorList[GREEN].comp2 = 34;    \
                               p_filter->p_sys->colorList[GREEN].comp3 = 54 ;      \
    p_filter->p_sys->colorList[BLUE].comp1 = 41; p_filter->p_sys->colorList[BLUE].comp2 = 146;      \
                               p_filter->p_sys->colorList[BLUE].comp3 = 240;       \
    p_filter->p_sys->colorList[WHITE].comp1 = 255; p_filter->p_sys->colorList[WHITE].comp2 = 128;   \
                                  p_filter->p_sys->colorList[WHITE].comp3 = 128;   \
    get_color_func = get_yuv_color; printf("using yuv mode\n");

こうすることで、get_color_funcが、色情報によって正しい色を返してくれるため、画像をYUVに変換することができました。

コード全体像:

そして最後に、Makefileをいじっておきます。modules/video_filter/Makefile.amの中に、次の段落を追加します。

libhemi_plugin_la_SOURCES = $(SOURCES_hemi)
libhemi_plugin_la_CPPFLAGS = $(AM_CPPFLAGS) $(CPPFLAGS_hemi)    -DMODULE_NAME_IS_hemi
libhemi_plugin_la_CFLAGS = $(AM_CFLAGS) $(CFLAGS_hemi)
libhemi_plugin_la_CXXFLAGS = $(AM_CXXFLAGS) $(CXXFLAGS_hemi)
libhemi_plugin_la_OBJCFLAGS = $(AM_OBJCFLAGS) $(OBJCFLAGS_hemi)
libhemi_plugin_la_LIBADD = $(LIBS_hemi)
libhemi_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(video_filterdir)' $(LDFLAGS_hemi)

プロジェクトのディレクトリでconfigureとmakeを実行して完成です。

実行結果:

f:id:onishy:20151105015733p:plain

おまけ: VLC+OpenCV

さて、フィルタと言えばOpenCVですが、以前も少しだけ触れたとおり、VLCOpenCVを使うのはちょっとテクニックが必要です。

というのも、VLCのpicture_tには色空間がYUVの場合が存在するわけですが、このために少し変換がややこしいわけです。

実は、VLCのフィルタの中にopencv_example.cppというのがあります。OpenCVを使うからには、IplImageなりcv::Matなりにpicture_tを変換する必要があるわけですが、何と、こいつによると次のように変換できるらしいんですね。

158行目

//(hack) cast the picture_t to array of IplImage*
p_img = (IplImage**) p_pic;

これはさすがにまさか嘘だろ、と思いますが、真っ赤な嘘です。

実際こいつでコンパイルするとセグフォを吐きやがります。

少しググると、StackOverflowでこのような記事を見つけることができます。こちらの記事によれば、正しいコードは以下の通りです。

//picture_t to IplImage without segmentation fault
    p_img = cvCreateImageHeader( cvSize( p_pic->p[0].i_pitch, p_pic->p[0].i_visible_lines ),
        IPL_DEPTH_8U, 1 );
    cvSetData( p_img, p_pic->p[0].p_pixels, p_pic->p[0].i_pitch );

随分まともな雰囲気が漂っていますね。IplImageを(コンストラクタのない時代なので)初期化関数によって初期化し、次にpicture_tの色情報を書き込んでいますが、いずれもp[0]のみを利用しています。

p[0]というのは、picture_tのYUVのうちYのことで、これには輝度情報が含まれています。つまり、この変換によって、p_imgにはpicture_tのもつ画像情報のうち、モノクロのデータのみがコピーされます。他の色情報(色差)は完全に欠落してしまいます。実際にimShowとかで表示させてみると分かるでしょう。

ただ、このopencv_exampleフィルタは、どうやら顔認識を行うだけのフィルタなようなので、これだけでも問題がないわけです。(僕の手持ちの動画では残念ながら本当に顔認識をしているのかは分かりませんでしたが…)

OpenCVの色認識などのフィルターを使いたければ、YUVの3レイヤー全ての情報を使わなければいけません。ただ、残念ながらIplImageは古いので、RGBしかサポートしません。cv::MatならYUVでもサポートしてくれます。正直できそうなもんですが、今回は学生実験であまり時間がないので、またの機会にすることにします。

VLC弄ってみた#2 - 時間をミリ秒にしてみる

第2回目の今回は、VLCにちょっとした改造を加えてみたいと思います。

VLCでは再生時間が秒単位でしか表示されませんが、ミリ秒単位でのシークがしたいことってありますよね。ありますよね?

そういう需要に応えるため、VLCの再生時間をミリ秒で表示させてみます。

環境はUbuntu 15.04/14.04です。MacWindowsではGUIの描画が若干異なるため、恐らくこれだと変わりません。

どこをいじれば良いのかを探す

まずは、どこをいじれば良いのかを探す必要があります。

とりあえず描画部分のパーツを見てみることにしましょう。今回いじりたいのはシーク中の再生時間ですが、これは2ヵ所に表示されています。一つはシークバーの左端、現在の再生時間が表示されている場所で、もう一つはカーソルを合わせた際に出てくるツールチップです。

描画部分がmodule/gui/qt4あたりに入っていることは何となく分かるので、とりあえずこの中をのぞいてみると、utilの中に"timetooltip.cpp" "timetooltip.hpp"というファイルがありました。これがツールチップ情報を持った描画クラスのようです。

/* timetooltip.hpp */
class TimeTooltip : public QWidget
{
Q_OBJECT
public:
explicit TimeTooltip( QWidget *parent = 0 );
void setTip( const QPoint& pos, const QString& time, const QString& text );
virtual void show();

protected:
virtual void paintEvent( QPaintEvent * );

private:
void adjustPosition();
void buildPath();
QPoint mTarget;
QString mTime;
QString mText;
QString mDisplayedText;
QFont mFont;
QRect mBox;
QPainterPath mPainterPath;
QBitmap mMask;
int mTipX;
};

mTextというのが表示されているテキストで、setTip関数を通じてセットされていることが容易に想像できます。

ただこのクラスはパーツを定義しているだけなので、実際にsetTipしている場所を探さなければいけないですね。"timetooltip.hpp"をincludeしているファイルを検索すると、今度は同じディレクトリに"input_slider.cpp" "input_slider.hpp"というファイルが見つかりました。103行目で

/* Tooltip bubble */
mTimeTooltip = new TimeTooltip( this );
mTimeTooltip->setMouseTracking( true );

とあり、ビンゴ!ですね。

さて、実際にsetTipをしている場所を探すと、331行目に

if( likely( size().width() > handleLength() ) ) {
secstotimestr( psz_length, ( ( posX - margin ) * inputLength ) / ( size().width() - handleLength() ) );
mTimeTooltip->setTip( target, psz_length, chapterLabel );
}

という場所がありました。 secstotimestrという関数がキモのようですが、この関数はsrc/misc/mtime.cで次のように定義されています。

/**
* Convert seconds to a time in the format h:mm:ss.
*
* This function is provided for any interface function which need to print a
* time string in the format h:mm:ss
* date.
* \param secs the date to be converted
* \param psz_buffer should be a buffer at least MSTRTIME_MAX_SIZE characters
* \return psz_buffer is returned so this can be used as printf parameter.
*/
char *secstotimestr( char *psz_buffer, int32_t i_seconds )
{
if( unlikely(i_seconds < 0) )
{
secstotimestr( psz_buffer + 1, -i_seconds );
*psz_buffer = '-';
return psz_buffer;
}

div_t d;

d = div( i_seconds, 60 );
i_seconds = d.rem;
d = div( d.quot, 60 );

if( d.quot )
snprintf( psz_buffer, MSTRTIME_MAX_SIZE, "%u:%02u:%02u",
d.quot, d.rem, i_seconds );
else
snprintf( psz_buffer, MSTRTIME_MAX_SIZE, "%02u:%02u",
d.rem, i_seconds );
return psz_buffer;
}

見ての通り、秒数を受け取ってh:mm:ssのフォーマットで文字列を吐き出す関数です。なので、こいつをコピーして、millisecstotimestrという関数を作り、こちらを呼び出すようにしましょう。

実際にいじってみる

次のように関数を定義し、ヘッダファイルも編集しておきます。

char *millisecstotimestr( char *psz_buffer, int32_t i_milliseconds )
{
if( unlikely(i_seconds < 0) )
{
millisecstotimestr( psz_buffer + 1, -i_milliseconds );
*psz_buffer = '-';
return psz_buffer;
}

div_t d;
unsigned short millisec, sec;
d = div( i_milliseconds, 1000);
millisec = d.rem;

d = div( d.quot, 60 );
sec = d.rem;
d = div( d.quot, 60 );

if( d.quot )
snprintf( psz_buffer, MSTRTIME_MAX_SIZE, "%u:%02u:%02u.%03u",
d.quot, d.rem, sec, millisec );
else
snprintf( psz_buffer, MSTRTIME_MAX_SIZE, "%02u:%02u.%03u",
d.rem, sec, millisec );
return psz_buffer;
}

とりあえず、現在位置を秒数で受け取っている分にはどうしようもないので、これをミリ秒で受け取るようにします。受け取る秒は次のように計算されているのでした。

( ( posX - margin ) * inputLength ) / ( size().width() - handleLength() )

ハンドルの位置と動画の長さから秒数を計算し、これがintにキャストされています。なので、ミリ秒を取得するためにはこれを1000倍するだけで良いでしょう。

if( likely( size().width() > handleLength() ) ) {
millisecstotimestr( psz_length, ( ( posX - margin ) * inputLength * 1000) / ( size().width() - handleLength() ) );
mTimeTooltip->setTip( target, psz_length, chapterLabel );
}

動かない??

これでコンパイルを行うと、コンパイルは通りますが起動時に次のようなエラーが出ます。

VLC media player 2.2.1 Terry Pratchett (Weatherwax)
Command Line Interface initialized. Type `help' for help.
> [0000000001229228] [cli] lua interface error: Error loading script [vlc]/share/lua/intf/cli.luac: lua/intf/modules/host.lua:279: Interrupted.
[00000000011224f8] core libvlc: Running vlc with the default interface. Use 'cvlc' to use vlc without interface.
[0000000001229228] core interface error: corrupt module: [vlc dir]/modules/gui/qt4/.libs/libqt4_plugin.so
[00007f194c01f458] core generic error: corrupt module: [vlc dir]/modules/gui/qt4/.libs/libqt4_plugin.so
[0000000001229228] skins2 interface error: no suitable dialogs provider found (hint: compile the qt4 plugin, and make sure it is loaded properly)
[0000000001229228] skins2 interface error: cannot instantiate qt4 dialogs provider
[0000000001229228] [cli] lua interface: Listening on host 

つまり、libqt4がライブラリとして不完全ということです。この原因を探るため、secstotimestrでgrepをかけ、millisecstotimestrとの差を見てみます。すると、なにやら怪しげなファイルにヒットします。

[vlc dir]/src/libvlccore.sym:

358 sdp_AddAttribute
359 sdp_AddMedia
360 secstotimestr
361 services_discovery_AddItem
362 services_discovery_EventManager

どうやらこのlibvlccore.symというファイルは、ライブラリに含まれるシンボルが集まったファイルのようです。 libvlccore.symで再度プロジェクト全体に対してgrepをかけてみます。すると、/src/Makefile.am内に

libvlccore_la_SOURCES = $(SOURCES_libvlc)
libvlccore_la_LDFLAGS = \
$(LDFLAGS_libvlccore) \
-no-undefined \
-export-symbols $(srcdir)/libvlccore.sym \
-version-info 8:0:0
libvlccore_la_LIBADD = $(LIBS_libvlccore) \
../compat/libcompat.la \
$(LTLIBINTL) $(LTLIBICONV) \
$(IDN_LIBS) $(LIBPTHREAD) $(SOCKET_LIBS) $(LIBDL) $(LIBM)
libvlccore_la_DEPENDENCIES = libvlccore.sym
if HAVE_WIN32

とあります。-export-symbolsオプションについてGNU libtoolのドキュメントを見てみると、確かに

-export-symbols symfile

Tells the linker to export only the symbols listed in symfile. The symbol file should end in .sym and must contain the name of one > symbol per line. This option has no effect on some platforms. By default all symbols are exported.

ということらしいです。

つまり、shared libraryでは、望まないシンボルをライブラリに含めないよう、シンボルの指定ができるということです。そのため、ライブラリに含まれないmillisecstotimestrを呼び出そうとするとエラーが起きていたのでした。なるほどなるほど。

ということで、ここにmillisecstotimestrを一行追加して再automake・コンパイルします。これでOKです。

実行結果

f:id:onishy:20151104002409p:plain

ヨッシャヨッシャ!!