前回のおさらい
前回の投稿では、豪雪地帯における除雪作業中の視界不良による事故リスクの軽減を目的として、暗所でも人物検出が可能な機械学習モデルの構築に取り組みました。
当初は、被写体の服装や色の影響を受けにくい赤外線カメラの利用を前提としていましたが、容易に入手することができる、一般的なWEBカメラでの代用を検討しています。
しかしながら、一般的なWEBカメラは光量が不十分な環境(特に夜間の降雪時)では撮影対象がほとんど映らないという問題が生じる可能性があります。
この課題に対して、今回はWEBカメラでキャプチャしたウィンドウに対してRGBチャンネルごとのフィルタリング処理を行うことで、視認性を改善できるかを試みました。
まだご覧になられていない方はぜひ読んでいただけると嬉しいです。
今回やること
前回の記事で、カメラレンズに付着した雨粒を機械学習によって除去するという選考事例を紹介しましたが、この技術がとても面白く僕も同様のことをやってみたいと思いました。
そこで今回は、レンズに付着した「雪粒」による視界不良を対象に、異なるアプローチで除去を試みます。
具体的には、GAN(Generative Adversarial Network)の一種であるCycleGANを用いて、降雪によって視界が妨げられた画像を、元のクリアな画像に変換するモデルの構築を目指します。
通常のGANは「教師あり学習(ペア画像あり)」が前提ですが、CycleGANではペアデータがなくても画像変換が可能であるため、今回のような「雪あり/雪なしの対応が取れていないデータセット」にも適用可能です。

GANとは
GAN(Generative Adversarial Network:敵対的生成ネットワーク)は、新たなデータを生成することを目的とした深層学習モデルです。
このモデルは「生成器(Generator)」と「識別器(Discriminator)」という2つのニューラルネットワークで構成されています。
この2つのモデルが敵対的にあることで、生成器はより精度の高い画像を出力するようになります。この学習過程を通じて、GANは高品質な偽画像を生成できるようになります。
CycleGAN
CycleGANはGANの応用モデルのひとつで、特に画像から画像への変換を目的としています。
一般的なGANでは「入力画像と正解画像のペア」が必要ですが、CycleGANは対応関係のない画像(非ペア画像)を使って学習できるのが大きな特徴です。
例えば、馬からシマウマへの変換の他にも緑葉から紅葉へ変換することができるようになります。

このような変換が可能になるのは、CycleGANがドメインAとドメインBの間で「双方向の変換」を学習し、その整合性を保つように設計されているためです。
class Generator(nn.Module):
def __init__(self, img_channel, res_block):
super().__init__()
self.encode_block = nn.Sequential(
nn.ReflectionPad2d(3),
nn.Conv2d(img_channel,64,kernel_size=7,stride=1),
nn.InstanceNorm2d(64),
nn.ReLU(inplace=True),
nn.Conv2d(64,128,kernel_size=3,stride=2, padding=1, bias=True),
nn.InstanceNorm2d(128),
nn.ReLU(inplace=True),
nn.Conv2d(128,256,kernel_size=3,stride=2, padding=1, bias=True),
nn.InstanceNorm2d(256),
nn.ReLU(inplace=True),
nn.Conv2d(256,512,kernel_size=3,stride=2, padding=1, bias=True),
nn.InstanceNorm2d(512),
nn.ReLU(inplace=True),
)
res_blocks = [ResidualBlock() for _ in range(res_block)]
self.res_block = nn.Sequential(
*res_blocks
)
self.decode_block = nn.Sequential(
nn.ConvTranspose2d(512,256,kernel_size=3,stride=2, padding=1, output_padding=1, bias=True),
nn.InstanceNorm2d(256),
nn.ReLU(inplace=True),
nn.ConvTranspose2d(256,128,kernel_size=3,stride=2, padding=1, output_padding=1, bias=True),
nn.InstanceNorm2d(128),
nn.ReLU(inplace=True),
nn.ConvTranspose2d(128,64,kernel_size=3,stride=2, padding=1, output_padding=1, bias=True),
nn.InstanceNorm2d(64),
nn.ReLU(inplace=True),
nn.ReflectionPad2d(3),
nn.Conv2d(64,img_channel,kernel_size=7,stride=1),
nn.Tanh()
)
def forward(self, x):
x = self.encode_block(x)
x = self.res_block(x)
x = self.decode_block(x)
return x
class Discriminator(nn.Module):
def __init__(self,img_channel):
super().__init__()
self.block = nn.Sequential(
nn.Conv2d(img_channel,64,kernel_size=4,stride=2,padding=1),
nn.LeakyReLU(0.2,inplace=True),
nn.Conv2d(64,128,kernel_size=4,stride=2,padding=1,bias=True),
nn.InstanceNorm2d(128),
nn.LeakyReLU(0.2,inplace=True),
nn.Conv2d(128,256,kernel_size=4,stride=2,padding=1,bias=True),
nn.InstanceNorm2d(256),
nn.LeakyReLU(0.2,inplace=True),
nn.Conv2d(256,512,kernel_size=4,stride=1,padding=1,bias=True),
nn.InstanceNorm2d(512),
nn.LeakyReLU(0.2,inplace=True),
nn.Conv2d(512,1,kernel_size=4,stride=1,padding=1)
)
def forward(self, x):
x = self.block(x)
return x
ちなみに、僕自身は大学の卒業研究で、CycleGANを用いて塗りが拙いイラストを厚塗り風に変換するという研究に取り組んだ経験があります。もし興味ある方がいましたら是非お声がけいただけると嬉しいです!

損失関数
CycleGANにおいては、学習の処理時に何枚の画像を同時に使いモデル推定の計算をするかというバッチサイズと、アイデンティ損失の重みを吟味することでモデルが変換をする対象についての調整ができます。これらについては標準的な値として設定しています。
データセット
CycleGANを用いた学習では、変換したい2つの画像ドメインに対応するデータセットが必要です。
例えば、図1で参照した馬をシマウマに変換する場合は、「馬の画像群(ドメインA)」と「シマウマの画像群(ドメインB)」という2種類の画像が必要になります。
今回の実験では、「レンズに雨粒が付着して視界が悪化した画像(ドメインA)」と、「クリアな視界の画像(ドメインB)」の2つを変換対象のドメインとして定義しました。これに対応する画像データセットを以下の公開リソースから取得しました。(なお、本来は雪粒が付着したデータセットが好ましいですが、見つけられなかったため同じ水分だろうということで今回はURL内で取得できるデータセットを使用します。)
収集した元データは、各ドメインにつき約900枚の画像で構成されていましたが、CycleGANを使用した学習は処理が重くなることが想定されること、実行環境ではGPUを積んでいないことを理由に各50枚の画像を使用して学習を行いました。
なお、収集した画像はリサイズすることなく、720×480の素の解像度のまま使用しています。

学習
先ほどにも述べた通り今回の学習はとても重く、1エポックあたりにかかる時間の目安は大体15分程度です。
生成モデルなどの学習では数百以上とする場合が多いですが、20エポック分の学習を試してみた結果、10エポックあたりでmacがありえないほど熱く、焼ける匂いがしてきたためこの辺りで中断しました笑
高性能なGPUが必要だということをひしひしと感じた次第です。
モデルの評価
学習済みのモデルを用いて、いくつかのテスト画像に対して推論を実行してみました。
結果として、出力画像はノイズのように破綻した画像が多く、視界のクリア化や雪粒の除去といった変換はほとんど達成できていない状況でした。
この原因として以下の2点が考えられました。
・学習データの枚数が少なすぎる
・学習エポック数が十分でない
CycleGANのような生成モデルは、特に非ペア画像での学習において、一定量以上のデータと十分なエポック数を必要とする傾向があります。
今後は、Google ColabのGPU環境を活用した再学習を検討しています。Colabであれば、より多くのデータを用いた長時間の学習が現実的になるため、変換性能の向上が期待できます。

おわりに
いかがでしたでしょうか。
学習結果はよくありませんでしたが、CycleGANがどのようなものなのかだけでも知っていただければ幸いです!
今回使用したソースコードは下記のURLから使用することができます。
参考URL


