にせねこメモ

はてなダイアリーがUTF-8じゃないので移ってきました。

このブログについて

文字・フォント・プログラム・技術・趣味などについて、Twitterでは書きづらい長い内容などをまとめるためのブログです。基本的には自分用のメモとして書いている部分が多いです。

リンク等

Pixiv http://pixiv.me/nixeneko
Tumblr http://nixeneko.tumblr.com/ 絵。Pixivアカウント持ってなくても見れます
Twitter https://twitter.com/nixeneko  
MediaMarker http://mediamarker.net/u/nixeneko/ 主に文字・言語関係の蔵書
GitHub https://github.com/nixeneko プログラム用、あまり使ってない
Amazon
欲しい物リスト
amazon.jp/registry/wishlist/1C43ZFBA4IL6Z プレゼントを下さい
ナナシスID ZhRYMnA
デレステID 421820148

同人誌(無料公開)

http://nixeneko.hatenablog.com/entry/c88_russian_alphabethttp://nixeneko.hatenablog.com/entry/c90_greek_latin_cyrillichttp://nixeneko.hatenablog.com/entry/20170811_dentyu

『電柱のスケッチ』

コミケ合わせで新刊作りました。ただし私は今回はサークル参加を申し込んでいないのでここで公開します。
電柱や電線をスケッチしたものです。

ダウンロード

PDFのダウンロードはこちらから:

本編

f:id:nixeneko:20170731065043p:plain

続きを読む

長方形で厚くて硬い万引き防止タグを分解してみた

よく電気屋などで小型商品にくっつけてある万引き防止タグ、検知方式によっていろいろな形があるのだが、長方形で立体的に厚みがある硬いタグを分解してみた。
f:id:nixeneko:20170809134741p:plain

このタグは使い捨てで、粘着テープが裏についていて、商品に貼りつけられる。このタグがゲートを通ると検知され、警報が鳴る。会計時に無効化処理を行うことで、会計を済ませた商品がゲートで反応することを防いでいるらしい。

分解

f:id:nixeneko:20170809134948p:plain
f:id:nixeneko:20170809135112p:plain
f:id:nixeneko:20170809135422p:plain
さて、分解してみると、底面に固定された金属板1枚と、金属の薄いフィルム2枚が入っている。フィルムの方は固定されていなくて、プラスチックで囲われたタグの内側の空間の中で動くことができるようになっている。タグの厚みはこのフィルムが動く空間を作るためのもので、タグが固いのも同じ理由だろう。

さて、随分シンプルな構造であるが、いったいどういう原理で動作するのだろうか。

原理

調べてみると、このタグはAcousto-magnetic systems (音響磁気方式)という方式のものらしい。Wikipediaに解説がある。

それによると、防犯ゲートの送信アンテナから送信されるパルスによって中に入っている金属フィルムが共振し、その共振によって発生する電波をゲートが受信するとアラームを起動するということらしい。

また、一番下の金属板が磁化されていると有効になるが、磁化が解除されると無効化され、ゲートに反応しなくなるとのことである。
つまり、解除機の無効化処理とは、実際には磁化の解除を行っているのだろう。

もしかして、解除されたタグについても、磁石を隣に置いておけばゲートに反応するようになるのかもしれない。


しかし、こんなに簡単な仕組みで防犯タグが実装されてるのを見て感心してしまった。解除もできる様になってるし。

イオニア数字変換TeXマクロ

イオニア数字ネイティヴではないので、イオニア数字を書く場合にはいちいち調べて書かないといけないのだけれど、実際面倒なので、(La)TeXマクロにしてLaTeXなどで書く際に簡単に変換できるようにしようというのが今回の目的。

イオニア数字というのは、古代ギリシャで使われた数値の書き方であって、ギリシャ文字を使って数値を表現するものである。一般にギリシャ数字とも呼ばれるとのこと。
詳しくはWikipediaなどを参照: ギリシアの数字 - Wikipedia

サンプル

f:id:nixeneko:20170809031334p:plain

コード

Githubに上げた。XeLaTeXでコンパイルできる。マクロの出力結果について網羅的に確認したわけではないので不適切な部分があるかもしれない。
GitHub - nixeneko/ionicnumber: A TeX macro that does a conversion from a number into Ionic numeral system.

使用法

ionicnum.texの中で\ionicnumマクロが定義されている。これを使うには、数字を与えて

\ionicnum{1234}

のようにする。1234の部分は任意の数字が入る。

メモ

イオニア式の記数法には年代や学者等によりいくつかの変種があり、特に10000以上の書き方はさまざまであるらしい。
この実装で用いたものは次のようなものである:

  • 数字にはギリシャ文字の小文字を用いた。
  • 数字はギリシャ文字列の右肩にʹを付けることによって表現した。
  • 6にはスティグマϛを、90のコッパはϟを、900のサンピはϡを用いている。
  • 10000以上はmyriad Μにより、Μの上に一万~一千万の位の数字を書くことによって表現した。
  • 最大で対応している数は一億(100,000,000)であり、ΜΜʹで表す。

The largest number named in Ancient Greek was the myriad myriad (written MM) or hundred million.

https://en.wikipedia.org/wiki/Myriad

実装について

まず、一~千の位の数字を変換することを考える。
これは、元の数をnとすると、各位の数字は

  • 一の位:  \displaystyle n \mod 10
  • 十の位:  \displaystyle \left\lfloor \frac{n}{10} \right\rfloor \mod 10
  • 百の位:  \displaystyle \left\lfloor \frac{n}{100} \right\rfloor \mod 10
  • 千の位:  \displaystyle \left\lfloor \frac{n}{1000} \right\rfloor \mod 10

となる。次にこれを\ifcaseを使ってギリシャ数字の各文字に変換する。


次に一万以上の数値について。
元の数値を10000で割ったものについて、その値をΜの上に積み重ねて表示する。数値の表示は一~千の位の数字に変換するマクロを共用している。
また、このとき、10000がΜとなるように条件分岐を行っている。


一億以上の数に対応するために再帰っぽい感じで書いてしまったのだが、どうせ処理系が 2^{31}-1 = 2147483647までしか対応してないので、ごちゃごちゃするだけであまりメリットなかったように思う。

Chainerでアニメキャラの目からハイライトを消す

pix2pix

pix2pixというモデルがある。入力画像と、それと一対一対応する変換ターゲットの画像を用意すると、その間の変換を自動で学習してくれるというものである。
元論文はこれ:

この論文内では、モノクロ写真に着色するだとか、線画に色を付ける、航空写真から地図を生成する、ラベリング画像から写真を復元する、などが行われている。要するに、何らかの画像のペアを用意すれば、ペア間の画像変換が学習できるっぽい。


それで、入力画像としてアニメキャラの顔の画像を用意し、出力画像として入力画像から目のハイライトを消したものを用意すれば、アニメキャラの目からハイライトを消すモデルが学習できないだろうか。

幸い、pix2pixのChainer実装が公開されている:

ので、これを改変することによって作成していく。

環境

  • Windows 10 64-bit
  • Python 3.5.3 (Anaconda 64-bit)
  • Chainer 2.0.1
  • CuPy 1.0.1
  • OpenCV 3 (demo.pyの結果表示で使っている)

データセットの用意

まず、データセットがなければ話にならないので用意する。

2017年1~3月に放送されていた*1アニメのキャプチャから、OpenCVのカスケード分類器とlbpcascade_animeface.xmlを利用してアニメ顔を抜き出した。元動画が1280×720 pxとした時に256×256 px以上の解像度をもっているものだけ抽出した。

それから、切り出された画像を目で見て、誤検出やブラーがひどいものなどを取り除いた*2

さらに、そこからランダムに500枚抜き出し、それらのコピーに対してSAIを使って手でハイライトを塗りつぶした画像を用意した*3。中には塗りつぶす必要のない画像もあったが、だいたい一枚あたり1~10分程度かかった。特に、目にグラデーションがかかっていたり、タッチ線が入っていたり、ハイライトに大きなグローがかけられていたりするなどの複雑な目の処理をしている場合は難しく、時間がかかった。
用意したデータセットに含まれる画像の例は、下にある結果セクションの入力とground truth(正解画像)を参照。

コードの変更

先ほど挙げたpix2pixの実装においては、Facade datasetから、入力用のラベリング画像を12 chとして、変換ターゲットの画像を3 chのnumpy.arrayとして読み込み、これらによってトレーニングを行っている。これを変更し、入力・出力ともに3chのカラー画像を扱えるものにする。

画像をデータセットとして読み込むプログラムをimg_dataset.py、このデータセットの上で訓練するコードをtrain_dehighlight.pyとして用意した。

ここで、先ほどデータセットとして用意した画像500組のうち、9割(450枚)を訓練用として、残り1割(50枚)を評価用として分割している。

コード

GitHubに上げた。
github.com

デモ

python demo.py -g <GPU番号> -i <画像ファイルのパス>

トレーニング

python train_dehighlight.py --epoch 400 --gpu <GPU番号> 

などとして実行する。GTX 1060使って18時間とかだった気がする。GPU使用しないと凄く時間がかかる。実際トレーニングをしてみて感じたことであるが、50,000 iter以上繰り返してもほとんど結果が改善しているようには見えなかった。

結果

176,000 iter目のトレーニング結果を次に挙げる。

入力

f:id:nixeneko:20170801195435p:plain

出力 (変換結果)

f:id:nixeneko:20170801195606p:plain

Ground truth (正解画像)

f:id:nixeneko:20170801195542p:plain
上手くいっているところを選んだとはいえ、割といい感じにハイライトが消えてる気がする(たのしい)。

その他変換結果

訓練セットにも評価セットにも含まれない画像についての変換結果を載せる。トレーニング176,000 iter目のモデルを用いた。左が入力、右が出力である。絵は自作なので絵柄にバリエーションがないのはご愛敬。
f:id:nixeneko:20170801205608p:plain
f:id:nixeneko:20170801210236p:plain
f:id:nixeneko:20170801205943p:plain
何となく塗りつぶした跡が見えるが、そこそこうまく動いてる様に思う。

改善点など

現状、入力画像のうち顔の領域の大きさが256×256 px程度である場合にはうまくいくが、大きさが大きかったり小さかったりするとあまりうまくいかない。つまり、スケール不変性がない。
これは、カスケード分類器で切り出した顔画像を256×256に変形してそれを用いてトレーニングしているためであると考えられ、data augmentationなどで訓練画像として様々なスケールの入力が与えられるようにすることである程度は対応できるのではないかと思う。

また、元画像で目のハイライトが小さい場合にはあまり効果が感じられない。これについてもdata augmentationで様々にスケールを変更したものを入力とすることで多少改善するとは思うが、どうなんだろう。

更に、訓練画像を抽出したカスケード分類器の特徴からか斜めや横向きの顔の画像が少なく、正面向きではうまく働くが、横向くとハイライトが消えなかったりする様である。

後、訓練されたモデルはほとんど顔しか知らないので、画面に文字などが被さってる場合も文字のあたりを目のハイライトとして勘違いするらしく、暗くなったりした。

他にも、訓練に使った画像の作品以外の画像だとハイライトが消えづらいということがあった。目の描き方は作品によって千差万別で、様々な絵柄に対応するためには訓練画像のバリエーションを増やすしかないように思う。何にせよ訓練データセットの作り方次第で改善の余地は大きそうな気がする。

*1:一部再放送を含む。

*2:つらい

*3:とてもつらい

ChainerCVでFaster R-CNNを使って顔検出してみる

(この記事は手探りで書いてるので大いに勘違いを含んでいる可能性があります。ご了承ください。)

VGG16をベースとしたFaster R-CNNを使って顔検出を行うというテクニカルリポートがあった。

ここでは、WIDER FACEという顔検出用のデータセットの上でトレーニングを行っている。

これを再現してみたい*1

手法

ChainerCVにFasterRCNNVGG16というそのものズバリのものがあるので、これを使ってトレーニングをしてみて、顔検出をやってみる。
今回はChainerCVの、Faster RCNNのサンプルコードchainercv/examples/faster_rcnn/を改変することによって行った。サンプルコードでは、PASCAL VOCデータセットに従ってトレーニングをし、20種類の物体が認識できる様だった。

ここでは、要するに、顔だけを検出するので、物体認識において検出できるクラスの数を1とすれば、とりあえずは動くはずである。
あんまりよくわかってないので、ハイパーパラメータはサンプルコードのものをほぼそのまま利用している。

環境

依存ライブラリ

  • Chainer 2.0.0
  • CuPy 1.0.0.1
  • ChainerCV 0.5.1
  • SciPy

ソース

github.com
なお、トレーニング済みのモデルはデータ容量が大きいのでリポジトリに含んでいないので、以下のリンクからダウンロードして trained_model/snapshot_model.npz に置くか、あるいは demo.py などを実行時に自動でダウンロードされるようになっている。

デモ

python demo.py [--gpu <gpu>] [--pretrained_model <model_path>] <image>.jpg

などとすれば、次のように顔と予測された矩形領域とスコアが表示される。
f:id:nixeneko:20170724160758p:plain
なお、faceと表示されてるのはPASCAL VOCの多クラス検出のコードを流用したためで、冗長になっている。

トレーニング手順

まず、顔検出用のWIDER FACEデータセットをダウンロードする。

ここから

  • Wider Face Training Images
  • Wider Face Validation Images
  • Face annotations

をダウンロードしてきて展開する。

そして、次のようにトレーニング向け/評価向けデータセットの、画像ファイルの含まれるフォルダとアノテーションデータのパスを指定して train.py を実行する。

python train.py --gpu=0 --train_data_dir="WIDER_train" --train_annotation="wider_face_split/wider_face_train.mat" --val_data_dir="WIDER_val" --val_annotation="wider_face_split/wider_face_val.mat"

やや細かい説明

chainercv/examples/faster_rcnn/のサンプルでは、Pascal VOCデータセットで物体認識をトレーニングしているので、ここで使われいてるVOCDetectionDatasetと互換性のある出力をするデータセットを、WIDER FACEデータセットを読み込ませるに際して用意している。
これが wider_face_dataset.py である。chainercv/chainercv/datasets/voc/voc_detection_dataset.pyを改変して作成した。


さて、このデータセットの上でトレーニングしたところ、RuntimeErrorが出たので、エラーが出た画像ファイルをトレーニング時に使用しない様に抜くようにしている。エラーがでたファイルは blacklist.txt にリストアップしてある。抜かない場合、トレーニング途中でlossがnanになってしまい失敗した。

なお、ライブラリのchainercv/links/model/faster_rcnn/utils/bbox2loc.pyの

    dh = xp.log(base_height / height)
    dw = xp.log(base_width / width)

を、

    _dh = xp.fmax(base_height / height, xp.finfo(xp.float32).eps)
    _dw = xp.fmax(base_width / width,  xp.finfo(xp.float32).eps)
    dh = xp.log(_dh)
    dw = xp.log(_dw)

などと変更して、logに与える値が0以下にならないようにしたらnanにならなくなった。

検出結果

緑がground truth、青が検出結果である。スコアが低いほど色が薄くなっている。

割とうまく行った例

f:id:nixeneko:20170724162340p:plain

うまく行かない例

f:id:nixeneko:20170724162432p:plain
ブラーのかかった顔、小さい顔などは2重に検出されていたり、検出されなかったりする。また、検出されてもずれがおおきかったりしているようだ。
見たところ、検出できる領域の最小があまり小さくなく(あまり小さい領域は検出できない)、動かす幅にも制限がある気がする。パラメータの設定次第では改善するのかもしれない? 割と大きなサイズをもった顔はちゃんと検出されているようだ。

*1:再現できているとは言っていない。

WindowsでChainerCVのサンプルを動かしFaster R-CNNをトレーニングしてみる

先日のChainer Meetupにて、ChainerCVというライブラリを知った。

Chainerの上で動作する、コンピュータビジョンタスクのためのディープラーニングライブラリとのことである。

このChainerCVで、Faster R-CNNによる物体検出が簡単に利用できるらしく、exampleを動かすことで試してみた。

環境

記事時点でのChainer, ChainerCVのバージョンは以下の通り。

  • Chainer: 2.0.0
  • ChainerCV: 0.5.1

ディープラーニングWindows使うとつまらない所でひっかかって割とつらいのでLinux使うべきだと思う。

インストー

  1. Chainer (Version 2.0.0)とCuPyをインストールする。
  2. 必要なライブラリが入ってなければpipで入れる
    • 必須: Cython, Pillow
    • 任意: Matplotlib, OpenCV
  3. ChainerCV (Version 0.5.1)をインストールする
    • Anacondaプロンプトを開き、次を実行する:
pip install chainercv

これでインストールは完了。


あと、(v0.5.1の) examplesを動かしてる時に、データをダウンロードするところでZeroDivisionErrorで止まったので、
chainercv/utils/download.py (C:\Anaconda3\Lib\site-packages\chainercv\utils\download.py などにある)の25行目

    speed = int(progress_size / (1024 * duration))

がdurationが0での時でも動くように書き換える:

    try:
        speed = int(progress_size / (1024 * duration))
    except ZeroDivisionError:
        speed = float('inf')

これは最新のリポジトリでは直ってるので、新しいバージョンがリリースされれば解決される問題であると思う。

examplesを動かしてみる

ソースコードをとってくる

git clone https://github.com/chainer/chainercv.git
cd chainercv
git checkout v0.5.1
cd examples/faster_rcnn

demo.py

demo.pyは物体検出を行い、結果を表示する。デフォルトでは、トレーニング済みのモデルをネットからダウンロードしてきてそれを利用する。

適当な画像hoge.jpgを用意して

python demo.py hoge.jpg

とすると、検出結果が表示される🐱
f:id:nixeneko:20170623002835p:plain

train.py

これはモデルのトレーニング用プログラムで、次のようにして実行する。0番目のGPUを使用する設定にしている*1

python train.py --gpu 0

しかし、Windowsでは実行すると次のようなエラーが発生する。

AttributeError: Can't pickle local object 'main.<locals>.transform'

このサイトによると、multiprocessingがサブプロセスを生成する方法がUnixWindowsで異なっていることがエラーの原因らしい。

Unixではデフォルトでforkによりサブプロセスを作るが、Windowsではforkは使えず、spawnによってサブプロセスを生成する。
このspawnでサブプロセスを生成する際、targetはpickableである(pickleでシリアライズできる)必要があるとのこと。特に、関数内に定義された関数はpickableでないので、モジュールのトップレベルに持ってくる必要がある。

エラーメッセージを見ると、train.pyのmain関数内に定義されたtransform関数が引っかかっているようである。なので、transform関数をトップレベルまで持ってくれば良さそうだ。

train.pyのfaster_rcnnの定義とtransform関数の定義をmain関数の定義の前に持ってくると、とりあえず動くようになった。faster_rcnnはtransformの中で参照されているため、これもトップレベルにもってくる。

faster_rcnn = FasterRCNNVGG16(n_fg_class=len(voc_detection_label_names),
                              pretrained_model='imagenet')
def transform(in_data):
    img, bbox, label = in_data
    _, H, W = img.shape
    img = faster_rcnn.prepare(img)
    _, o_H, o_W = img.shape
    scale = o_H / H
    bbox = transforms.resize_bbox(bbox, (H, W), (o_H, o_W))

    # horizontally flip
    img, params = transforms.random_flip(
        img, x_random=True, return_param=True)
    bbox = transforms.flip_bbox(
        bbox, (o_H, o_W), x_flip=params['x_flip'])

    return img, bbox, label, scale
    
def main():
    #(略)

この様に変更した後に、

python train.py --gpu 0

を実行し、トレーニングループを回し、終了するまで待つ。

トレーニングが終わると、デフォルトではresult/snapshot_model.npzにトレーニング後のモデルが保存される。
なので、それを指定してdemo.pyを実行する:

python demo.py --pretrained_model "result/snapshot_model.npz" hoge.jpg

すると、先ほどと同様の結果が出力された。
f:id:nixeneko:20170624093754p:plain

*1:GTX 1060で10時間ほど、CPUだと60日程度かかるらしい。