イラストレーターの絵から学ぶ画像認識 - 基礎からGANを用いた画像生成まで -

本記事では、画像認識(image recognition)の概要を紹介していきます。

画像認識は、手書き文字の認識、ガン診断、自動運転技術などその応用が非常に多岐にわたることで知られています。しかしながら、画像認識は画像の情報量の多さ、また3次元の情報を2次元に射影していることによる幾何学的な縮退などのため、多くの課題を抱えており、とっつきづらい分野でもあります。そこで、本記事では、できるだけ基本的なアプローチを紹介して、そういった敷居を下げることを目的としています。

本記事では、イラストをもとに様々な画像認識のトピックを紹介していきます。
今回は、Twitterやpixivで活躍されているイラストレーターの花かんざらしさん(@kore099)から許可を頂き、絵を使用させて頂くこととなりました。
実際に使用させて頂く絵は以下になります。

f:id:hiyoko9t:20170921021259j:plain
図1. しぶりん

画像認識の基本

画像認識と一口に言っても、その目的によって様々な方法に分かれます。例として、写っている物体に対して適切なラベルを与える物体認識、複数の物体から意味のある状況を読み取るシーン認識、さらに目標となる対象が存在する領域を検出する物体検出などが挙げられます(図2)。

f:id:hiyoko9t:20170924153308j:plain
図2. 画像認識の分類

本記事では、(その他の手法とも共通する部分が多いため)主にクラス認識を対象として、画像認識の基本を説明していきます。例として、図1が入力画像として与えられたとき、人というラベルを付与することが目的となります。

画像認識といえば畳み込みニューラルネットワーク(Convolutional Neural Network; CNN)だ!という方もいるかもしれません。実際、CNNの利点として、局所特徴抽出などに詳しくなくても分類ができるという点もあり、あながち間違いではないと思います。しかし、伝統的な画像処理手法を知っておくことは、柔軟な画像処理を行い、無駄な計算を減らすことやネットワークの適切な構成方法を構築することに繋がります。

クラス認識の手順

クラス認識の手順は主に以下の3つとなります。([5]では主に2. と3. が取り上げられています)

  1. 前処理
  2. 特徴抽出
    1. サンプリング
    2. 局所記述
    3. 統計的特徴抽出
    4. コーディング
    5. プーリング
  3. 分類

以降では、これらのトピックを一つづつ紹介していきます。また、実装にはopencvを利用していますが、こちらの導入方法などは公式ドキュメントの方でお願いします。

前処理

機械学習においてデータの前処理は重要ですが、画像認識においても重要となります。
データの前処理では、主にノイズの除去やコントラスト、輝度の標準化を行います[11]。

まず、ノイズの載った画像を考えるため、図1にガウシアンノイズを加えた画像を見てみましょう。 ガウシアンノイズは、以下の確率密度関数$p$から得られます。 $$ p(x) = \frac{1}{\sqrt{2 \pi v^2}} exp \left( - \frac{(x-m)^2}{2v^2} \right) $$ ここで, $m, v^2$はそれぞれ平均、分散をあらわします。

実際にガウシアンノイズを乗せてみます。

# coding:utf-8
import cv2
from matplotlib import pyplot

# 画像の読み込み
img = cv2.imread('001.jpg')

# BGRからRBGへ
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

# ガウシアンノイズの付与
img_gaussian_noise = img
row, col, d = img.shape

# ノイズの作成
gaussian_noise = np.zeros((row, col, d), np.int8)
cv2.randn(gaussian_noise, np.zeros(3), np.ones(3)*10)

# ノイズの追加
img_gaussian_noise = cv2.add(img_gaussian_noise, gaussian_noise, dtype=cv2.CV_8UC3)


#  描画の設定
pyplot.figure(figsize=(16,9))
pyplot.xticks([]), pyplot.yticks([])

# 描画
pyplot.imshow(img_gaussian_noise)

# 保存
filename = "001_gauss.png"
pyplot.savefig(filename)

pyplot.show()

今回は、視覚的にわかりやすいようにノイズの標準偏差の値を大きくしました。標準偏差の値が大きい方が、ノイズが大きいことがわかると思います。ちなみに、わざわざ画像にノイズを乗せることなんてあるのか?と疑問に思われる方もいるかもしれません。これに関しては、学習データが少ない時に学習データの水増しのため、ノイズを乗せた画像を別の画像として利用することがあります。その他にも平行移動や回転で画像の枚数を増やす方法もあります。

f:id:hiyoko9t:20170925202334p:plain
図3. 標準偏差10のガウシアンノイズ

f:id:hiyoko9t:20170925202410p:plain
図4. 標準偏差30のガウシアンノイズ

さて、話がそれましたが、このようなノイズを除去する方法として、平滑化という手法がよく知られています。 代表的なものとして、平均値フィルタが挙げられます。$3\times 3$の平均値フィルタは以下のようにあらわされます。 $$ \frac{1}{9} \begin{pmatrix} 1 & 1 & 1 \\ 1 & 1 & 1 \\ 1 & 1 & 1 \end{pmatrix} $$ 式から、この平均値フィルタは注目画素の周囲8マスとの画素の和をとって、それを画素数で割っていることがわかります。これにより、ノイズを低減させることができます。
また、平均値フィルタでは注目画素とその周囲の画素が同じ重みですが、実際には注目画素から近ければ近いほど重要であると考えられます。それを実現したのがガウシアンフィルタとなります。 $$ G_v(x, y) = \frac{1}{2 \pi v^2} exp \left( - \frac{x^2 + y^2}{2v^2} \right) $$ 実際に適用してみます。今回は、視覚的にわかりやすくするためにカーネルサイズを$15\times 15$と非常に大きくとっていますが、通常は$3\times 3$, $5\times 5$程度だと思います。

# 平滑化
img_smoothing = cv2.GaussianBlur(img_gaussian_noise, (15, 15), 0)

f:id:hiyoko9t:20170925202455p:plain
図5. 図3に対して平滑化

f:id:hiyoko9t:20170925202528p:plain
図6. 図4に対して平滑化

図5, 6から、画像全体がぼけたようになっており、ノイズが小さくなったことがわかります。

特徴抽出

ここでは、簡単に特徴抽出の手法とそれぞれの役割を見ていきます。

サンプリング

画像は情報量が非常に大きいので、それそのものを扱うのは計算コストが高く、また(分類に)必要のない点も考慮しなければなりません。そこで、画像から特徴的な点を抽出するサンプリングを行います。(サンプリングに関して詳しく知りたい方は[8]をどうぞ)

まず、簡単に思いつく方法として、一定間隔ごとに画像の代表点を取得する方法が挙げられます。以下の図7に$10\times 10$の画像のサンプリングの例を示します。図中の次元は、今回だとRGBなので3次元となります。パッチサイズは注目画素のまわりをどれだけ考えるかを示しています。

f:id:hiyoko9t:20170925011928j:plain
図7. サンプリングの例

また、一定ではなく画像の特徴的な点、例えば人と背景の境界など、をサンプリングする方法も提案されています。

最も代表的なエッジ検出を紹介します。

今、画像における$(x, y)$の画素を$I(x, y)$としたとき、その$x$軸方向の微分は、離散画像なので次のような差分であらわされます。 $$ \Delta I_x (x, y) = I(x+1, y) - I(x,y) $$ また, これは前進差分と呼ばれ、以下であらわされる微分が後進差分となります。 $$ \Delta I_x (x, y) = I(x, y) - I(x-1,y) $$ さらに平均をとることもできます。 $$ \Delta I_x (x, y) = \frac{I(x+1, y) - I(x-1,y)}{2} $$ $y$方向についても同様であり、これにより、勾配が大きい点(=色の変化が大きい点=何らかの境界となる点)が得られます。

さらに、一階微分だけでなく二回微分を用いたラプラシアンオペレータが存在します。 ここでは、ラプラシアンオペレータを図1に適用した結果を示します。

img_laplacian = cv2.Laplacian(img, cv2.CV_64F) #x方向, y方向の2回微分の和

f:id:hiyoko9t:20170925202638p:plain
図8. しぶりんにラプラシアンオペレータを適用した結果

また同作者が描かれた初音ミクについてもラプラシアンオペレータを適用してみます。

f:id:hiyoko9t:20170925005656p:plain
図9. 初音ミクラプラシアンオペレータを適用した結果

このように、人物と背景、人物と衣類の境界などをきちんと分離できていることがわかります。

局所記述

サンプリングで選んだ代表点だけでは、ノイズの混じった点を選んでしまった時に、誤った特徴となりやすいので、その周囲(図7における青)のパッチ領域からも情報を取得します。これを局所記述と言います。

局所記述子として有名なものにSIFT記述子が挙げられます。SIFT記述子は異なるスケールや回転に対してロバスト(頑健)になることが知られています。 SIFT記述子に関しては、こちらのスライド( MIRU2013チュートリアル:SIFTとそれ以降のアプローチ) に詳しく載っていますが、簡単に触れると平滑化された画像を$L$として、その$(x, y)$において勾配強度mと勾配方向$\theta$を求めます。 $$ m(x,y)=\sqrt{L_x^2(x,y) + L_y^2(x,y)}, \theta (x,y) = tan^{-1}\frac{L_y(x,y)}{L_x(x,y)} $$ これを、あらかじめ分割した空間に割り当てて、特徴量を求めます。例えば、勾配方向を8分割(8ビン)すれば、横軸に$0°, 45°, 90°, …$といった$8$次元のヒストグラムが得られます。

統計的特徴抽出

サンプリングと局所記述で得られた情報には、ノイズなどが含まれている可能性があります。 そこで、これらのノイズを削減する方法として、統計的特徴抽出が用いられます。

具体的には、主成分分析などがよく用いられます。これは、前記事で行列の特異値分解と絡めて紹介しているのでそちらをご覧ください。 hiyoko9t.hatenadiary.jp

コーディング

サンプリングと局所記述で得られた特徴を分類が容易になるような高次元空間へ写像する手法をコーディングと言います。

有名な手法の一つとして、自然言語処理の分野で用いられているBag of Words(BoW)の考え方から生まれたBag of Visual Wordsという手法があります。 BoWの例を以下に示します。

import numpy as np
from sklearn.feature_extraction.text import CountVectorizer

count = CountVectorizer()

strings = np.array(["This is a pen", "This is a pen and that is a pencil"])
bag = count.fit_transform(strings)

print(count.vocabulary_)
print(bag.toarray())

ここで、"This is a pen"と"This is a pen and that is a pencil"という二つの文章が存在する時、BoWはそれぞれの文書中における単語の出現回数をあらわします。 以下が、出力結果であり、1行目でそれぞれの単語とインデックスが結びついており、2, 3行目にそれぞれの単語の出現回数が表示されています。3行目の2つ目の要素が2となっているのは、文中に"is"が2回あらわれたことを意味しています。

{'pen': 2, 'is': 1, 'and': 0, 'this': 5, 'pencil': 3, 'that': 4}
[[0 1 1 0 0 1]
 [1 2 1 1 1 1]]

BoWでは出現した単語の数をカウントしますが、BoVWでは特徴を代表するベクトルの数を求めます。(その後、全体の数で割って生起確率を求めます。) もちろん特徴を代表するベクトルを適当に選んだとき、すべての特徴ベクトルがそれに一致することはないので、最近傍の代表ベクトルに特徴ベクトルを割り振ります。

プーリング

プーリングは、コーディングで得られた特徴ベクトルを一本にまとめる作業を行います。

よく使用されるプーリングとして平均値プーリングが用いられます。領域$R$内の$N$本のコーディング後の特徴ベクトルを$c_n$とすると、平均値プーリングは以下であらわされます。 $$ \frac{1}{N} \sum_{n=1}^{N} c_n $$ これにより、コーディング後の局所特徴の数が異なっている場合でも、プーリング後の次元は領域ごとに一致することがわかります。

分類

分類手法については、一般的な機械学習の手法を用います。例としては、SVMやロジスティック回帰、決定木などが挙げられます。 これらの分類手法については、すでに多くの優れた参考書や論文がありますので、ここでは割愛させていただきます。

畳み込みニューラルネットワーク(CNN)

ニューラルネットワークは、これまで紹介してきたサンプリング、局所記述、統計的特徴抽出、コーディング、プーリングをネットワークとして表現することができます。

しかし、画像はその情報量の多さ(次元の大きさ)から学習するべきパラメータが膨大になりがちです。そこで画像の特徴を活かして、学習コストを抑えたものが畳み込みニューラルネットワークとなります。

まず、一般のユニット(ここでは一つ一つの画素をイメージしてください)が全結合している例を挙げます。
ここで、$l$層から$l+1$層の重みを$w_{ij}$とします。今、$l$層が$5$つのユニットから構成され、$l+1$層が$3$つのユニットから構成されるとき、学習すべきパラメータ$w_{ij}$は$5\times 3 = 15$個となります(図10)。

f:id:hiyoko9t:20170924234614j:plain
図10. 全結合層の例

ここで、画像は注目している画素から近い画素の影響が大きく、遠い画素の影響は小さいという特徴を持つので、ニューラルネットワークにおいてもユニットを全結合させるのではなく、近傍のユニットだけを結合させることができます。 また画像は、局所的に近い構造を持つという特徴があると考えると、それぞれのパラメータが共通であると考えられます。
この考え方を適用したものが、畳み込み層となります。

f:id:hiyoko9t:20170924235019j:plain
図11. 畳み込み層の例

図11からわかるように、レイヤー間で全結合せず、また重みを共有しているため学習すべきパラメータは$3$個と大幅に削減されていることがわかります。

このようにネットワーク間の計算コストを下げたものをCNNと呼びます。

計算コストが下がることにより、様々なフィルターを適用した特徴を画像から得ることができ、そうして得た特徴に対して畳み込み層とプーリング層を適用することでより良い特徴が得られます。

アニメ絵から顔の位置を検出

物体検出は、目標となる物体が存在する領域を推定する手法です。対象の物体が存在する領域を四角の枠で囲むことが多いですが、きっちりと境界を求める手法も存在します。
物体検出については後日、別記事で詳しく触れたいと思うので、ここでは概要だけ紹介します(詳しくは[10]など)。

大まかな手順は以下の通りです。

  1. 入力画像から物体が存在しそうな領域を抽出する
  2. 1.で得た領域に対して、分類問題を解く
  3. 2.で得た結果が重複している領域などを除去して整える

今回、物体検出について調べている時にアニメの顔検出を既にこちら( GitHub - nagadomi/lbpcascade_animeface: The face detector for anime/manga using OpenCV.) で実装されていたので、試しに以下の画像に対して、実行してみました。物体検出のイメージだけでも伝われば幸いです。

f:id:hiyoko9t:20170924191903p:plain
図12. ベンチに座るしぶりん

f:id:hiyoko9t:20170924191919p:plain
図13. ステージ上のアイドル

なぜかりーなだけ認識されていません。こういうこともあります。

敵対的生成ネットワーク(Generative Adversarial Network; GAN)

ここまでは、画像を認識することを考えてきました。

近年、GANと呼ばれる画像を生成する技術が盛んに研究されています。GANのはじまりは、Goodfellowらの論文[3]となります。GANに関してははじめてのGANがよくまとまっており、また Chainerで顔イラストの自動生成 - Qiitaでは、クオリティの高い顔イラストの生成が実現されています。機械が絵を描く日も近いかもしれないですね。

ここでは、簡単にGANの概念だけを紹介します。 GANでは、生成器(generator)と識別器(discriminator)という二つの要素が重要となります。 生成器が、訓練データを見て最終的に得たい画像を生成し、識別器は入力された画像が本物か偽物かを判定します。生成器は、識別器に本物だと思わせるように学習を行い、画像を生成します。識別器は、本物の画像なのか生成器が作った画像なのかを見破るように学習します。こうして、お互い学習し合うことで、よりリアルな画像を生成することができます(図14)。

f:id:hiyoko9t:20170925201456j:plain
図14. GANの仕組み

また、以下がもとの論文から引用したアルゴリズムとなっています。アルゴリズム中の$G$が生成器、$D$が識別器をあらわしており、これらが交互に更新されていくことがわかります。(詳細な解き方は、別記事でやるかもしれません)

f:id:hiyoko9t:20170925122147p:plain
図15. GANのアルゴリズム([3]より引用)

さらに、近年注目されている技術として、Deep Convolutional GAN(DCGAN)が挙げられます。DCGANは、GANにCNNを適用したものであり、より鮮明な画像を得ることができます。その生成器の概要が以下の図16となります。

f:id:hiyoko9t:20170925115625p:plain
図16. DCGAN([1]より引用)

ここでは、入力として100次元のベクトルが与えられ、出力として$64\times 64$の画像が生成されます。これは、画像認識の逆のプロセスを辿っていることがわかります。(画像認識では、入力が画像、出力がクラス認識であれば所属するクラスのラベルでした。)中間の層の詳細な説明はここでは行いませんが、このネットワークを用いて、画像を生成します。

DCGANはこちら(GitHub - carpedm20/DCGAN-tensorflow: A tensorflow implementation of "Deep Convolutional Generative Adversarial Networks")で実装されているので、今回はそれを利用しました。また使用するデータとしては、手書き文字認識で有名なMNISTを用いました。(本当は、イラストやアニメ画像の生成を行いたかったのですが、マシンパワーやデータの取得などの観点から今回は見送りました。後日、リベンジしたいと思います。)

以下に、DCGANを用いて生成された数字の画像を示します。

f:id:hiyoko9t:20170925171607p:plain
図17. エポック0で生成された画像

f:id:hiyoko9t:20170925171607p:plain
図18. エポック1で生成された画像

f:id:hiyoko9t:20170925171740p:plain
図19. 最終的に生成された画像

図17、18は訓練の初期で生成された画像でまだ数字らしくない画像が生成されています。しかし、学習を続けて得られた図19では、かなり鮮明に数字として読み取れる画像が生成されていることがわかります。これが、画像生成の一例です。

正直GANに関しては、まだほとんど追えていないのですが、画像を生成することができる、というのが非常に興味深く響いたのでここでは取り上げてみました。これから論文を追っていこうと思うので、気が向けばまた記事を書くと思います。

まとめ

本記事では、画像認識で知っておくべき基礎的な内容を紹介してきました。直感的な理解を優先したため、複雑な数式などはほとんど使いませんでしたが、実際に数式を追えば、より理解が進むと思います。さらに詳しいことが知りたい方は、画像認識 (機械学習プロフェッショナルシリーズ), 原田達也 を一読されることをオススメします。一週間ほど前、画像認識に関する知識がほぼゼロの状態から、本書を三日ほどかけて一読させて頂きましたが、数式なども追いやすく、また構成も理解しやすいものだったのでぜひ興味のある方はぜひどうぞ。

最後に、今回イラストをお借りする許可を頂いた花かんざらしさん(@kore099)に改めて感謝致します。

参考文献

[1] Alec Radford, Luke Metz, Soumith Chintala, “Unsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks”, ICLR 2016, 2015.
[2] Carpedm20, “DCGAN-tensorflow”, https://github.com/carpedm20/DCGAN-tensorflow, 2017.
[3] Goodfellow, Ian J., Pouget-Abadie, Jean, Mirza, Mehdi, Xu, Bing, Warde-Farley, David, Ozair, Sherjil, Courville, Aaron C., and Bengio, Yoshua., “Generative adversarial nets”, NIPS, 2014.
[4] 花かんざらし, Available at https://twitter.com/kore099, 2017.
[5] 原田達也, “画像認識 (機械学習プロフェッショナルシリーズ)”, 講談社, 2017.
[6] 藤吉弘亘, “画像局所特徴量SIFTとそれ以降のアプローチ”, Available at https://www.slideshare.net/hironobufujiyoshi/miru2013sift, 2013.
[7] Mattya, “Chainerで顔イラストの自動生成”, Available at http://qiita.com/mattya/items/e5bfe5e04b9d2f0bbd47, 2015.
[8] 美濃導彦, “画像処理論 - Web情報理解のための基礎知識 -” , 昭晃堂, 2010.
[9] Nagadomi, “lbpcascade_animeface”, Available at https://github.com/nagadomi/lbpcascade_animeface, 2011.
[10] Ross Girshick, Jeff Donahue, Trevor Darrell, Jitendra Malik, “Rich feature hierarchies for accurate object detection and semantic segmentation”, UC Berkeley and ICSI, 2014.
[11] Satya Mallick, “Image Recognition and Object Detection : Part 1”, Available at http://www.learnopencv.com/image-recognition-and-object-detection-part1/, 2017.
[12] Shinya Yuki, “はじめてのGAN”, Available at https://elix-tech.github.io/ja/2017/02/06/gan.html, 2017.