にせねこメモ

はてなダイアリーが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

JPEGのヘッダからクロマサブサンプリングを調べる

JPEGはクロマサブサンプリングに対応しているわけだが、画像ビューアや画像編集ソフトで開いてもその辺りの情報が得られなかったりする。
なので、JPEGのバイナリを読んでクロマサブサンプリングがどうなっているか理解できる様にしたい。

JPEGに記録されたクロマサブサンプリング情報

まず、imagemagickでクロマサブサンプリングを次のように設定して書き出した。
そしてそれをバイナリエディタで開き、対応するSOF0タグ内の構成要素のサンプリング値を調べた。ここで、数字は16進数である。

設定 Y Cb Cr
4:4:4 11 11 11
4:4:0 12 11 11
4:2:2 21 11 11
4:2:0 22 11 11
4:1:1 41 11 11
4:1:0 42 11 11

Y, Cb, Crについて、上位4ビットが水平方向、下位4ビットが垂直方向のサンプリング値を示す。
要するに、4:4:4であればY, Cb, Crそれぞれの1x1のブロック同士が対応する。4:2:0であればYの2x2ブロックとCb, Crの1x1ブロックが対応する、などといったことだろう。


JPEGのバイナリを開いて、9E番地から始まるSOF0が

FF C0 00 11 08 02 D0 05 00 03 01 22 00 02 11 01 03 11 01

となっていれば、赤がチャンネル数(3=YCbCr)、太字が各チャンネルのサンプリング値であるので、ここからYCbCr 4:2:0だとわかる。

J:a:b表記について

ところで4:2:0みたいな表記をずっと分かりづらいなと思っていたのだが、次のような事情があるらしい。

これはもともとは水平方向のY:Cr:Cbのサブサンプリングの比率を表していて、垂直方向のサブサンプリングは考慮されていなかった。そのため、人間の目の空間的な解像度が、青/黄色の方が赤/緑より小さいのを利用して、4:2:1みたいなクロマサブサンプリングも存在しているらしい(今ではほとんど使われていない)。

この後、縦方向のサブサンプリングを考慮するようになり、縦方向の色成分の解像度を半分にする場合、表記として、三番目の数字に0を指定するようになった。現在のこの書き方をJ:a:b notationという。縦方向解像度を0で示すようにしたのは、おそらくそれまでの表記法との互換性を保つためだろう。縦方向にクロマサブサンプリングをしない場合に4:2:2や4:1:1と書くのも過去の表記との整合性を考慮してのことで、過去の表記法でも現在の表記法でも同じクロマサブサンプリングを示すことになる。

同人誌向けプリントオンデマンド販売サービス

本の販売方法で、在庫をもたず、注文が入ってから本を印刷・製本し発送するものをオンデマンド販売とかプリント・オン・デマンド(POD; print on demand)という*1

同人誌通販でPODが利用できるものはあまり聞いたことがなかったのだが、いくつかあるらしいので調べたものをまとめてみる。
基本的に、初期費用がかからないものは、売り上げから印刷代や手数料分を引いた金額が著者の利益となるようである。

「POD出版」で検索すると自費出版系のサービスが引っかかる。無料で出版できるものなどをいくつか挙げたが、二次創作関係はサービスの規約で禁止されている場合があるので、同人誌向けのサービスを使った方が良さそう。

自前の販売システムを持ってるとこ

製本直送.com / どこでも印刷

http://www.seichoku.com/user_data/publish_button_std.php

  • 初期費用無料・月額利用料無料
  • 製本代・送料のほか、決済額の15%が手数料としてかかる
  • 「ディズニー(キングダムハーツを含む)、ポケットモンスターハリーポッター、サンリオ」は不可。それ「以外のキャラクターにつきましては(略)お客様ご自身で判断をお願い致します。」とのこと*2
  • 成人向け可

DLmarket / 製本販売機能

https://www.dlmarket.jp/user_data/seichoku.php

  • 製本直送.comとの連携により、ダウンロード販売に加えて製本版も販売可能
  • 製本版単体での販売は不可
  • 製本代・送料のほか、販売価格の20%が決済手数料としてかかる

ライブラ

https://libra.sc/

  • 現在事前登録中。2017年9月に作品登録開始予定らしい。
  • 初期投資なし。
  • 基本料金+ページ印刷料金+作家利益が販売価格となる。利益は自由に設定可能。
  • 同人誌向けっぽいので、二次創作でも使えそう
  • 成人向け可。

BCCKS

https://bccks.jp/

Amazon PODを利用したPOD出版サービス

NextPublishing著者向けPODサービス

https://open.nextpublishing.jp/author/

  • 基本無料
  • 販売手数料は販売価格の40%
  • 販売価格は100円単位で設定でき、印刷費と販売手数料を引いた額が利益として支払われるとのこと。
  • 成人向け可。
  • 「他者の著作権を侵害するものなど知的所有権を侵害する書籍は販売できません」*3とのことで二次創作は難しいかも。
  • 「製本可能なページ数は、24〜828ページです(カラーは500ページまで)」*4

MyISBN

https://myisbn.jp/

  • 自費出版向けのサービス。Amazonや(取り寄せれば)書店で購入可能。
  • Amazonのオンデマンドサービスを使ってPODが実現できる。
  • 本の登録費用が4980円で、それ以外に費用は掛からないとのこと。

BookSpace

https://bsworld.jp/pod

  • 電子書籍出版サービス。Amazon PODを使ってPODができる。
  • 無料らしい。
  • 「各配信ストアから実際に支払を受けた金額から35~43%を有料会員のアカウントに計上します*5」とのことで、6割前後が手数料として引かれるらしい。
  • 売り上げが5,000円を超えない場合、退会するまで利益の振込はされないとのこと。

*1:「必要なときに必要なだけ印刷する」という意味で、印刷方法としてのオンデマンド印刷(製版工程の要らない、レーザープリンタによる少部数印刷)のことを指すこともあるが、ここで扱うのはそれではない

*2:https://www.seichoku.com/user_data/kiyaku.php

*3:https://open.nextpublishing.jp/author/#q_6

*4:https://open.nextpublishing.jp/author/#q_9

*5:https://bsworld.jp/download

あにつく2017「デジタル作画導入スタジオが語る これで解った! デジタル作画のはじめかた」まとめ

あにつく2017での「デジタル作画導入スタジオが語る これで解った! デジタル作画のはじめかた」についてまとめておく。

登壇者、講演の概要については次のページを参照。
www.too.com

3社の使用しているデジタル作画ソフトについてまとめる。

グラフィニカ

使用ソフト

  • Clip Studio
    • レイアウト、1原
  • Stylos
    • 2原、動仕

Clip Studio

  • 書き味がよい
  • 前から使っていたStylosに加えて最近導入し試してる段階
  • 2原をClip Studioでやろうとしたこともあるが、補正の関係で描ける線がトレス向きでなく、補正がないStylosの方がなぞるのはやりやすい
  • パース定規機能……レイアウト向き
  • ペンの種類が多い、描画にタイムラグがない
  • 紙に近い
  • 自分でブラシを作れる。「見ました」みたいなブラシをつくってスタンプを押すみたいなことも
  • Clip Studio→Stylosのデータの移行はPNGを介して
    • PNG出力は制作さんがやっている
    • 人によって描き方(データの作り方)が異なるのでどうしても手でやる必要があるらしい

タイムシートについて

  • (紙で運用しているという話だったと思う)
  • (Stylosから?出力しても)AEにタイムシートを読み込ませることができない
  • 撮影会社に出すときに、紙でないと対応できない場合がある

オーエルエム

使用ソフト

  • Toon Boom Harmony

Toon Boom Harmony

  • カナダ産
  • 使う理由: 海外(特に北米)と仕事をするため(使ってるソフトが違うと共同作業が難しい)
    • 海外はペーパーレス
  • エディションが2種類ある
    • Harmony Advanced: 動仕のみ。15万
    • Harmony Premium: コンポジットもできる。30万
  • AEで撮影するためには線の2値化が必要(ベクタ画像からTGAで出力しないといけない)
  • ポケモンでは1600x900の撮影フレームを使っているが、その解像度で2値出力すると思ったところにドットが出ない
    • 現状ペイントソフトで修正している
    • 高解像度で出力しリサイズすると高品質に出力できそう
    • もともとHarmonyだけで完結させる設計になっているので、2値出力してAEでコンポジットする様な変態的な使い方は考慮されていない
  • 版権ものの抱き枕などでは一辺が一万ピクセル以上になることもあるが、ベクタデータなのでそこまでのマシンパワーは必要ない
    • 4K, 8Kなどの時代になったら紙では難しい

タイムシートについて

  • タイムシートは紙として存在し、それを見ながらデジタル作画している
  • 細かい指示がエクスポートできないため

サンジゲン

使用ソフト

  • TVPaint Animation

TVPaint Animation

  • フランス産
  • ヨーロッパのアートアニメーション向けにつくられた
  • 「ペグホールスタビライズ」機能が強力……タップ穴を合わせて自動で位置補正してくれる
  • アニメーションブラシ、カスタムブラシなどの機能
    • 設定できるパラメータが多く、様々なブラシが作成できる
  • スクリプト機能……George Scriptという化石みたいな言語
  • 欠点: 解像度(dpi情報)の保持ができない(AEと同様)
  • AEに持っていくには2値線にしないといけないが、原画以降は最初から2値(アンチエイリアスなし)で描くことで解決

タイムシートについて

Twitterのフォローが減ってることがあるので、フォローのリストをログとして記録したい

Twitterのフォローが減ってることがある。
退会や凍結によりアカウントが消えたか、あるいはブロックされたかのどちらかだろうと思うが、今まで購読していたものが購読できなくなって、それが把握できないというのは切ない。
なので、Twitterのフォローのリストを定期的に取得しておいて、フォローが減ったときなどに比較して比べて、減ったフォローを確認したい。定期的と言ってもフォローの数が変化した時に取得しておけばよい。ひょっとしたらブロックされたことがわかってひどく凹むことになるのかもしれないが……。

手法

さて、Twitterにおいて、@で始まるユーザ名("screen_name")は変更することができる。一方で、ユーザには内部で一意の番号が振られていて("id")、こちらは、ユーザ名が変わっても変わらないはずである。
なので、idの集合を定期的に記録しておけば、フォローの変化を追跡することができる。また、ユーザー名が変更された場合もそれを把握できる。

idだけの取得であればGET friends/idsでいけるが、今回はscreen_nameなども欲しいのでGET friends/listを使う。


取得したユーザのリストデータを保存する際、辞書オブジェクトであるから簡単に考えるとpickleなどで保存するということが考えられる。一方でpickleで保存するとpythonのバージョンが上がったときなどで互換性がなくなる場合があり(2->3とかだめだった気がする)、CSVJSONなどの汎用フォーマットを用いた方が良さそう。今回はJSONでダンプする。


実装にはPython Twitter APIを使った。これは pip install twitter で導入できる。

取得のためのコードを次に示す。
同じフォルダに'consumerkey'という、"<Consumer Key>[改行]<Consumer Secret>[改行]"という様なTwitter API Keyを記録したファイルを置いて実行する。Twitter API Keyはhttps://apps.twitter.com/でAppを作成すると発行できる。


自分がフォローしているユーザが数百なので、API制限など考えずに作成した。もし3000以上のユーザをフォローしている場合だとAPI制限に引っかかることになると思う。

比較

要するにユーザーのidの集合について比較し、差集合を計算すればよい。
ユーザーのidをキーとする辞書オブジェクトを作っておけば、ユーザー名などを引くことができる。

以下にコードを挙げる。

ちなみにこれをフォロワーの方で行ってフォロワーの推移を追跡することもできるが、やりたくないのでやらない。

Python+OpenCVでアニメのカット検出

はじめに

編集された映像のまとまりで一番細かい単位をカット(英語ではshot)という。カットがつなぎ合わされて一つの映像作品が作られている。
一般的にカットの切り替え時にはカメラの位置や撮影対象の位置関係が不連続になるため、画の大きな変化から目で見て判別のつく場合が多い。

なので、このカットの切り替えを、画の変化から(ある程度)自動で検出することができる。

手法

CG-ARTS協会『ディジタル画像処理[改訂新版]』に、カット検出について、次のように書かれている。

カット検出は,隣り合うフレーム間の画像の差分画像を用いて行うことができ,差の絶対値が大きいとき,その場所がカットであると判定すればよい。しかし,画面中に大きく動く物体がある場合も差の絶対値が大きくなり,カットと誤ることが多いため,精度を上げる工夫が必要である。画像をM×Nの格子(長方形領域)に区分し,長方形領域ごとの画素値の平均値を求め,その平均値の差の絶対値を用いてカットの判定を行う。16×16など比較的少ない格子数を用いることにより,物体の動きに影響されにくいカット検出ができる。

CG-ARTS協会『ディジタル画像処理[改訂新版]』(2015年) p.303

これに従って、フレーム間差分を用いてカット検出を行う。
まず、現在のフレームとそのひとつ前のフレームの差分画像を用意し、それに対してMSE (Mean Squared Error, 平均二乗誤差)やMAE (Mean Absolute Error, 平均絶対誤差)など指標となる値を計算する。その値が閾値以上であるならそのフレームでカットの切替が行われたと考える。

また、『ディジタル画像処理[改訂新版]』では精度を上げる工夫として、元画像を縮小したものに対してカット判定するという手法が紹介されているので、これについても検討する。

カット検出コードの例

Python 3.5.3, OpenCV 3.1.0, NumPy 1.13.1 (Anaconda, Windows 10 64-bit)で動作。

評価

評価するのは結構面倒である。false-positiveはカットとして検出した画像がダブってるのをみれば大体わかるが、false-negativeは検出結果からはわからない。なので、映像に対するカット切り替えタイミングの正解データを用意しておいて、それと対照して正解不正解を確認するというのが妥当だと思う。正解データは手で用意する必要がある。

ここで、ワイプやディゾルブといったカット同士の切り替えがなめらかに行われる場合、はっきりどのフレームで切り替わったということが言えないため、うまい評価法が思いつかなかった。なので、それらについては評価から除外し、カットの切替と検出されてもされなくても同じ評価になるようにした。


閾値以上の値をもつフレームをカットだと判定することから、検証にはthreshold(閾値)を変化させて、その閾値に関してprecision (精度, 適合率)とrecall (再現率)を計算した。

precisionは、カットの切れ目だ!と予測した中で実際に正しくカットの切れ目を判定できていた割合で、
 \displaystyle
P = \frac{\mbox{予測のうち正しかった数}}{\mbox{予測した総数}}
で表される。

recallは、実際に動画の中に存在するカットの切れ目をどれだけ網羅して判定できたかという割合で、
 \displaystyle
R = \frac{\mbox{予測のうち正しかった数}}{\mbox{正解の総数}}
で表される。

ここで、例えば動画全体を通してカットの切れ目を1つしか予測せず、それが正解だった場合precisionは1になるが、動画全体のほとんどのカットの切れ目を取りこぼしているためにrecallは0に近い値になる。
また、逆に全てのフレームをカットの切れ目だと判定する場合、precisionは0に近い値をとるが、recallは1になる。
このように、予測の良さを計るにはprecisionもrecallも単体ではあまり適さないと考えられるため、F-measure (F値)という尺度を導入して指標とする。

F-measureは、precisionとrecallの調和平均である。
 \displaystyle
F = \frac{2PR}{P+R}
これはprecisionとrecallのどちらもが高い値を持つ場合に高くなり、いい感じに評価に使えそうである。高いほど(1に近いほど)性能がよい。

さて、評価用に動画を用意した。会話中心で動きの少ない日常系と、激しいアクションがあるものの2種類のつもり。

各動画はCM等が含まれず、本編映像のみのものである。
これらに対し、カットの切り替わりタイミングのリストを作成した。各ファイルへのリンクを次に挙げる。

実際にはフレーム番号で計算したが、動画プレーヤでの確認のしやすさから時間表記(分:秒.ミリ秒)に直している。
カットの切り替わりタイミングを各行記載した。また、行末に"?"がついているものはカット検出の評価から除外する部分であり、ディゾルブなどで切り替わりに幅があるものについては"-"で繋いで範囲を示した。

評価結果1: ごちうさ

ご注文はうさぎですか?』1話について、いくつかの画像サイズで前フレームとのMSEとMAEを計算し、thresholdに対する検出性能の指標を計算し、グラフを描いた。

1280x720 MSE

f:id:nixeneko:20170903033850p:plain

Max F-measure 0.8344370860927152
Threshold 6947.407446108217
1280x720 MAE

f:id:nixeneko:20170903033858p:plain

Max F-measure 0.9038461538461539
Threshold 55.61545464409722
640x360 MSE

f:id:nixeneko:20170904213100p:plain

Max F-measure 0.8344370860927152
Threshold 6904.933479456018
640x360 MAE

f:id:nixeneko:20170904213115p:plain

Max F-measure 0.9020866773675763
Threshold 55.55679398148148
128x72 MSE

f:id:nixeneko:20170903033732p:plain

Max F-measure 0.8344370860927152
Threshold 6904.933479456018
128x72 MAE

f:id:nixeneko:20170903033756p:plain

Max F-measure 0.9020866773675763
Threshold 55.55679398148148
64x36 MSE

f:id:nixeneko:20170904213140p:plain

Max F-measure 0.8344370860927152
Threshold 6904.933479456018
64x36 MAE

f:id:nixeneko:20170904213148p:plain

Max F-measure 0.9020866773675763
Threshold 55.55679398148148
32x18 MSE

f:id:nixeneko:20170903033808p:plain

Max F-measure 0.8308207705192631
Threshold 5792.83275462963
32x18 MAE

f:id:nixeneko:20170903033816p:plain

Max F-measure 0.888888888888889
Threshold 48.49363425925926
16x9 MSE

f:id:nixeneko:20170903033827p:plain

Max F-measure 0.8264462809917356
Threshold 4685.99537037037
16x9 MAE

f:id:nixeneko:20170903033837p:plain

Max F-measure 0.8803827751196172
Threshold 44.44675925925926
F-measure最大値まとめ

まとめると次のようになる(数値の小数点以下桁数は適当)。

尺度 size max F-measure threshold
MSE 1280x720 0.8344 6947.4
MSE 640x360 0.8344 6904.9
MSE 128x72 0.8344 6904.9
MSE 64x36 0.8344 6904.9
MSE 32x18 0.8308 5792.8
MSE 16x9 0.8264 4686.0
MAE 1280x720 0.9039 55.6
MAE 640x360 0.9021 55.6
MAE 128x72 0.9021 55.6
MAE 64x36 0.9021 55.6
MAE 32x18 0.8889 48.5
MAE 16x9 0.8804 44.4

評価結果2: フリップフラッパーズ2話

フリップフラッパーズ』2話について、いくつかの画像サイズで前フレームとのMSEとMAEを計算し、thresholdに対する検出性能の指標を計算し、グラフを描いた。

1280x720 MSE

f:id:nixeneko:20170904213740p:plain

Max F-measure 0.7517401392111369
Threshold 4725.544186559607
1280x720 MAE

f:id:nixeneko:20170904213750p:plain

Max F-measure 0.8178528347406515
Threshold 49.28785517939815
640x360 MSE

f:id:nixeneko:20170904213759p:plain

Max F-measure 0.7522123893805309
Threshold 4287.165564236111
640x360 MAE

f:id:nixeneko:20170904213810p:plain

Max F-measure 0.8178528347406515
Threshold 49.19312789351852
256x144 MSE

f:id:nixeneko:20170904214121p:plain

Max F-measure 0.7552140504939626
Threshold 4102.424325448495
256x144 MAE

f:id:nixeneko:20170904214112p:plain

Max F-measure 0.8183962264150942
Threshold 47.06980613425926
128x72 MSE

f:id:nixeneko:20170904213833p:plain

Max F-measure 0.7603485838779955
Threshold 3877.8117042824074
128x72 MAE

f:id:nixeneko:20170904213842p:plain

Max F-measure 0.820754716981132
Threshold 46.62550636574074
64x36 MSE

f:id:nixeneko:20170904214100p:plain

Max F-measure 0.7656249999999999
Threshold 3832.808449074074
64x36 MAE

f:id:nixeneko:20170904214051p:plain

Max F-measure 0.8232502965599051
Threshold 46.094907407407405
32x18 MSE

f:id:nixeneko:20170904213856p:plain

Max F-measure 0.7734553775743707
Threshold 3645.484953703704
32x18 MAE

f:id:nixeneko:20170904213907p:plain

Max F-measure 0.8177570093457943
Threshold 43.302083333333336
16x9 MSE

f:id:nixeneko:20170904213922p:plain

Max F-measure 0.7842227378190255
Threshold 3205.7291666666665
16x9 MAE

f:id:nixeneko:20170904213931p:plain

Max F-measure 0.8189349112426034
Threshold 41.75231481481482
F-measure最大値まとめ

まとめると次のようになる(数値の小数点以下桁数は適当)。

尺度 size max F-measure threshold
MSE 1280x720 0.7517 4725.5
MSE 640x360 0.7522 4287.2
MSE 256x144 0.7552 4102.4
MSE 128x72 0.7603 3877.8
MSE 64x36 0.7656 3832.8
MSE 32x18 0.7734 3645.5
MSE 16x9 0.7842 3205.7
MAE 1280x720 0.8179 49.3
MAE 640x360 0.8179 49.2
MAE 256x144 0.8184 47.1
MAE 128x72 0.8208 46.6
MAE 64x36 0.8233 46.1
MAE 32x18 0.8178 43.3
MAE 16x9 0.8189 41.8

考察

  • 全体的にごちうさの方がフリップフラッパーズより検出性能がよい。これは、フリップフラッパーズには激しいアクションが多く、それが誤判定されがちだということが考えられる。
  • MSEよりMAEの方が検出性能がよい。
    • 画素値を[0.0, 1.0]の範囲にスケーリングしたらまた変わるのだろうか。
  • 差分画像を計算するサイズは64x36程度が一番良いようだ。
    • 差分画像はごちうさでは1280x720が最高で次に256x144, 128x72, 64x36が同じ性能で続き、解像度が大きいほど(原サイズに近いほど)性能が良いようにも見えるが、フリップフラッパーズでは64x36のパフォーマンスが一番高かった。
    • ある程度縮小することで計算速度を速くすることができる。
  • 64x36でMAEを計算した場合、最大のF-measureをたたき出したthresholdは、ごちうさ55.6、フリップフラッパーズ46.1程度とそれなりの開きがある。このため、一つのthresholdが任意の動画に対して最高の性能を出すということはない。

前のフレーム一つと比較しただけでは繋がった一続きのカットであるかどうかの判定が難しいものが必ず出てくるため、そういうものに対しても正しく判定するためには、過去や未来のフレームも参考にカット判定をしないといけないが、計算コストも高くなるということもあり、実用上そこまで重要でないような気がする。

例えば、動画エンコードで、カットの切り替わりが検出されたところをIフレームとしたい時などの場合、カットという切れ目よりは画面が大きく動いたことの方が重要であると考えられ、今回のような検出手法でも効果を発揮すると思う。

まとめ

ごちうさ1話とフリップフラッパーズ2話について検討した結果、差分画像を計算してカット検出を行うには、

  • 元画像を64x36程度に縮小
  • 尺度にMAEを用いる
  • 閾値は45~55程度(動画によって最適なものは異なる)

とするとよさそうで、0.8~0.9程度の最大F-measureが達成できるようである。
とはいえ、2つの動画作品に対してしか検証していないので、どこまで一般化できるかはわからない。

応用例: 映像のダイジェスト作成

カットの切り替わりが検出できるのであるから、カットごとに一つの画像を取り出すことで、映像のダイジェストを作成することができる。
f:id:nixeneko:20170904225533p:plain
見るとわかるが、画面のなかの大きな部分が動いたり色が変わる場面では誤検出が起きている。

他に試す価値があるかもしれないもの

  • ヒストグラムを利用したカット検出
  • SURF, ORBなどの画像特徴量の利用

*1:ウサ耳の生えたココナちゃんが齧歯類特有の前歯を削るために硬い構造物に齧りつき、唾液の糸が引くシーン、最高です。あと、色彩設計的にもポップで楽しい回。

Pillow, OpenCVなどでの画像の扱いの違い

Pythonには画像処理のために画像を読み書きするライブラリがあり、画像ファイルをnumpy.ndarrayの形で読み込んだりそれを表示・保存したりできるものがある。
一方で、各ライブラリによって画像の形式がまちまちであったりして、同じnumpy.ndarrayでも変換が必要だったりする。
ここでよく引っかかるので調べてまとめておく。

環境

  • Python 3
    • Python 3.5.3 |Anaconda custom (64-bit)| (default, May 15 2017, 10:43:23) [MSC v.1900 64 bit (AMD64)] on win32
  • NumPy: 1.13.1
  • OpenCV Python bindings (cv2): 3.1.0
  • Pillow (PIL): 4.2.1
  • SciPy: 0.19.0
  • matplotlib: 1.5.1

以下、RGBカラー画像(8 bit/channel)を読み込むことについて考える。

略記

  • w: 幅
  • h: 高さ
  • c: チャンネル数(RGB画像の場合は3)

例示するコードではnumpyはnpとしている:

import numpy as np

Pillow (PIL fork)

Imageはnumpy.ndarrayではないが、numpy.asarrayなどを使って相互変換できる。

import numpy as np
from PIL import Image

pil_img = Image.open(INFILE)
ary_img = np.asarray(pil_img)

Image.openで画像ファイルを開いて、numpy.asarrayでnumpy.ndarrayに変換する。
形は(h, w, c)。
numpy.asarrayで特に指定しないとdtype=uint8 [0, 255]で読み込まれる。また、dtype=np.floatなどと指定しても[0.0, 255.0]と値は変わらないので必要に応じて自分でスケールする必要がある。

numpy.ndarrayからPIL Imageへ

Image.fromarrayでできる。与えるのはuint8である必要があるらしい。

from PIL import Image
im = Image.fromarray(np.uint8(myarray*255))

Imageのリサイズ

画像をImageのインスタンスで保持している場合、Image.resizeでリサイズができる。
次は画像のサイズを半分にする例。

pil_img = Image.open(INFILE)
print(pil_img.size) # (w, h)
w_orig, h_orig =pil_img.size
resized_pil_img = pil_img.resize((w_orig//2, h_orig//2))

ここで、pil_img.resizeに指定するのは(w, h)の形である。


他、<Imageインスタンス>.show()で画像を表示できるっぽい。

OpenCV 3 (cv2)

OpenCVで扱うカラー画像は基本BGRである。これは歴史的な経緯があるらしい。

cv2.imread

import cv2
img_cv2 = cv2.imread(INFILE)

cv2.imreadで画像ファイルを開くとnumpy.ndarrayが得られる。
これは(h, w, c)の形ではあるが、チャンネルの並び順が他のライブラリと異なりBGRになっている。
また、何もしないとdtype=uint8 [0, 255]で読み込まれる。

同様に、cv2.imshow, cv2.imsave, cv2.VideoCapture, cv2.VideoWriterなどもBGRの順であることを前提としているため、cv2以外と組み合わせて利用する場合には、BGR-RGB間の変換をする必要がある。

cv2.imshowなどに渡す画素値の範囲

画像表示・出力用のcv2.imshow, cv2.imwriteなどの関数に与える画像の画素値の範囲は、uint8であれば[0, 255]、uint16は[0, 65535]、int16は[-32768, 32767]、floatであれば[0.0, 1.0]であるらしい(値の小さい方が黒、大きい方が白となる)。floatで[0.0, 1.0]の範囲からはみ出た値は、黒(0.0)または白(1.0)になる。

BGR画像→RGB画像への変換

img_bgrが(h, w, c=3)のBGRなnumpy.ndarrayとする。

img_rgb = img_bgr[:,:,::-1]

Pythonのスライスを使う。「第1, 2次元目はそのままで、第3次元目は逆方向に並べたもの」という風に指定している。

RGB画像→BGR画像への変換

img_rgbが(h, w, c=3)のRGBなnumpy.ndarrayのとき、

img_bgr = img_rgb[:,:,::-1]

項目を分けたが、BGR→RGBと全く同じ。

cv2.resize

一方で、画像のリサイズを行うcv2.resize関数に引数として渡すリサイズ後のサイズは(w, h)である。

resized_img = cv2.resize(img, (w, h))

これは、画像のnumpy.ndarrayの形が(h, w, c)であることを考えると、ちゃんと確認しないと引っかかりそう。
次は画像のサイズを半分にする例。

h,w,c = img.shape
resized_img = cv2.resize(img, (w//2, h//2))

cv2には空の画像を作成する関数はないっぽいので、numpy.zeros, numpy.onesなどを利用する。

matplotlib

matplotlib.image.imread

matplotlib.image.imreadが返す配列は(h, w, c)でRGB、dtype=float32、[0.0, 1.0]。

Return value is a numpy.array. For grayscale images, the return array is MxN. For RGB images, the return value is MxNx3. For RGBA images the return value is MxNx4.

https://matplotlib.org/api/image_api.html

matplotlib.pyplot.imshow

matplotlib.pyplot.imshowは、3チャンネルカラー画像であればRGB (float or uint8)を要求する。floatの場合は[0.0, 1.0]。

import matplotlib.pyplot as plt
import matplotlib.image as mpimg

img_plt = mpimg.imread(INFILE)

plt.figure()
plt.imshow(ary_img) 
plt.show()

SciPy

scipy.misc.imread

import scipy.misc
img_scp = scipy.misc.imread(INFILE)

PILを使って画像を読み込んでるっぽいのでPIL互換か。
dtype=uint8, (h, w, c), [0, 255].

scipy.misc.imsave

dtype=float32なnumpy.ndarray与えたら、[0.0, 1.0]でも[0.0, 255.0]でもどちらでもまともに出力された。
これは、uint8でないと、最小・最大の画素値を用いて正規化されるかららしい。そのため、画像の中身をちょっと見てみたい時にはいいと思うが、値が変わらないでほしい時には使えなさそう。

ChainerなどのCNNレイヤの入出力

CNN系のchainer.links.Convolution2Dは入力の形として(c, h, w)を前提とする。hとwは入れ替わっても一貫していれば問題ないが、チャンネルは最初に来る必要がある。
また、チャンネルの順番はRGBでもBGRでもなんでもいいが、一貫している必要がある。

次にあげる類のものは設計次第で何でもいいが、与えるデータを一貫して揃えておかないといけない。

  • 高さと幅の順番
  • チャンネルの並び順 (RGBかBGRか? など)
    • cv2とそれ以外を交ぜて使うと引っかかりそう
  • 画素値の範囲(とfloatにするかintにするかなど)
    • [0, 255], [-1.0, 1.0], [0.0, 1.0]、など。
(h, w, c)から(c, h, w)への変換

numpy.transposeを使う。img_hwcが(h, w, c)の形のnumpy.ndarrayだとすると、

img_chw = np.transpose(img_hwc, (2, 0, 1))
(c, h, w)から(h, w, c)への変換
img_hwc = np.transpose(img_chw, (1, 2, 0))

ミニバッチ対応

CNN系のchainer.links.Convolution2Dは入出力がミニバッチであるので、一つの画像を入力として与える場合にはnumpy.newaxisなどを使って先頭に次元を増やしてミニバッチサイズ1のミニバッチにしないといけない: (1, c=3, h, w)とか。

minibatch = img_chw[np.newaxis]

ミニバッチから取り出すのは

img_chw = minibatch[0]

とか。

値の範囲を変換

単にnumpyのブロードキャストの機能使って加減算や乗除算を行うだけ。一緒にnumpy.ndarray.astypeを使って形式の変換を行うことも多いと思う。

例: [0, 255] -> [0.0, 1.0]
a = a.astype(np.float)/255.0
例: [0, 255] -> [-1.0, 1.0)
a = a.astype(np.float)/128.0 - 1.0
例: [-1.0, 1.0] -> [0, 255]
a = ( (a + 1.0)*127.5 ).astype(np.uint8)

とか。