GANでハンコジェネレーターを作ってみた


## はじめに
河野さんが行政改革で着手した「ハンコ廃止」  
それに対して「本人確認の最も有効な手段」と、異を唱えるハンコ議連  
（参考[「はんこ議連「押印は本人確認に有効」廃止に対応要請」](https://www.nikkansports.com/general/news/202010070001057.html)）

よろしい･･･では敢えて問おう！  
「**ハンコって本当に本人確認能力あるのか？**」  
この逆説を検証するために、GAN（Generative Adversarial Network）で　“ハンコジェネレーター”を作ってみた。

※結論だけ見たいせっかちさんはコチラ　⇒　[結論](#結論)

## 準備
### サンプルデータ作成
- とりあえず、力業でハンコのサンプルを作ってみた。（全国の石原さん。あしからず・・・）
  - A4用紙を2cm×2cmの格子状に区切って、**縦14×横10の計140個のハンコを押印**
  - ひたすら押印！押印！押印！押印！・・・・・・・・・・・・（なんか、とっても偉い人の苦労が分かった気がする）
  - スキャン→画像処理ソフトで格子線を消して余白をトリミング
  - できた！（下図）
  
  ![hanko.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/528836/91ecb827-8a7f-0089-1cb5-d4d55a0f6f84.jpeg)


- なんか、こう見ると欠けていたり、傾いていたり、薄かったり、同じ三文判を同じ人が押しても、結構いろんなバリエーションが出来る。

- 次に、ハンコ１つ１つを分割する
  - 画像処理ソフトで手作業は大変だし、MNISTの64*64pixelにデータ仕様に合わせたいので、簡単なプログラムを作った。（[参考](https://note.nkmk.me/python-skimage-util-view-as-blocks/)）
  
    ```python
    import os
    import glob
    import numpy as np
    import skimage.util
    import skimage.io
    import skimage.transform
    out_size = 64 # 最終出力サイズ（横、縦）
    img = skimage.io.imread('data/low/hanko.jpg')
    print(img.shape)
    # (3307, 2340, 3)
    #ここで割り切れない中途半端なサイズだとエラーが出るので、リサイズ
    img_ = skimage.transform.resize(img, (64*14, 64*10), anti_aliasing=True)
    img_gray = skimage.color.rgb2gray(img_) #グレースケール変換
    print(img_gray.shape)
    #(3307, 2340)
    blocks = skimage.util.view_as_blocks(img_gray, (img_gray.shape[0]//14, img_gray.shape[1]//10)).squeeze()
    print(blocks.shape)
    # (14, 10, 64, 64)
    path_to_out = 'data/low/hanko/' #出力先
    os.makedirs(path_to_out, exist_ok=True) #無ければ作成
    file_list = glob.glob(path_to_out + "hanko*jpg") #存在チェック＆削除
    for f in file_list: os.remove(f)
    idx = 0
    for i in range(14):
        for j in range(10):
            skimage.io.imsave(path_to_out + 'hanko{:0=3}.jpg'.format(idx), blocks[i,j])
            idx += 1
    ```

### GANの実装
- プログラムは下記書籍で紹介されているものを基本そのまま流用。（中身の詳しい原理などは、ここでは割愛）
  - 「つくりながら学ぶ！PyTorchによる発展ディープラーニング」
  - 第5章 GANによる画像生成（DCGAN, Self-Attention GAN）
  - 書籍：https://www.amazon.co.jp/dp/4839970254
  - サンプルプログラム：https://github.com/YutaroOgawa/pytorch_advanced/tree/master/3_semantic_segmentation

- 一部、下記のように変更
  - フォルダパスを適当に書き換えて、データサンプル数も調整。
  - もともとのMNIST（手書き数字データセット）の場合200枚だったけど、今回は60枚も少ない140枚。大丈夫かな･･･
  
  ```python
  def make_datapath_list():
    train_img_list = list()
    img_num = 140 #サンプル数
    for img_idx in range(img_num):
        img_path = "../data/low/hanko/hanko{:0=3}.jpg".format(img_idx)
        train_img_list.append(img_path)
    return train_img_list
  ```

  - 試行錯誤も想定して、epoch数をコマンド引数から取れるように、学習実行部分をちょこっとだけ改造

  ```python
  import sys
  args = sys.argv
  num_epochs = int(args[1]) if len(args)>1 else 300 
  G_update, D_update = train_model(
      G, D, dataloader=train_dataloader, num_epochs=num_epochs)
  ```    

## 学習＆実行
- さあ、準備は整った！やってみよう。
- 今回は、GANの典型である「**DCGAN**」と、その改良版である「**SAGAN**」の2種類のプログラムを試した。
- （参考）実行環境
  - CPU : Intel(R) Xeon(R) CPU E3-1225 v6 @ 3.30GHz
  - OS : Windows 10 Pro for Workstations
  - GPU : NVIDIA Quadro P4000


### DCGAN（Deep Convolutional GAN） 実装
- ソースコード
    - https://github.com/ishihara-jp/HankoGenerator/tree/main/DCGAN
- 実行結果

  ![DCGAN_hanko_epoch300.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/528836/48cb920b-f862-c4fa-7a49-39819a1c0676.png)


  - 上段が本物（学習サンプル）、下段がフェイク（生成サンプル）
  - なんかいまいちだね・・・・やっぱりバリエーション多すぎ＆サンプル少なすぎが災いしたかな。
  - 続いて、局所・大域性を考慮したSAGANを試してみよう


### SAGAN（Self-Attention GAN）実装
- ソースコード
  - https://github.com/ishihara-jp/HankoGenerator/tree/main/SAGAN
- 実行結果
  - お、ちょっとだけ見えてきた！（学習時間はGPUで約10分くらい）
  
   ![SAGAN_hanko_epoch300.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/528836/28258aeb-4240-9ae7-670a-ce8ed42aad1f.png)
 
  - この時のSelf-Attention Map（色が濃いところほど画像生成時に着目している部分）も見てみる
  
   ![SAGAN_hanko_epoch300_map.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/528836/d20e743b-d0f8-0359-9c2a-fbbfcea9b232.png)

  - なんとなく、ハンコのあたりに発火しているようだ
  - epoch（学習の試行回数）を300 → 500に増やしてみる  
  
   ![SAGAN_hanko_epoch500.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/528836/1b4d9d8c-8412-dc9c-d49c-2a750ccbe89b.png)
   ![SAGAN_hanko_epoch500_map.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/528836/b272fa81-8ff2-7291-2454-2b243d020c5a.png)

  
  - 見える！見えるぞ！・・・・じゃあ、倍の1000epochだ！
  
   ![SAGAN_hanko_epoch1000.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/528836/76c96604-45b9-a1dc-8b68-113f06cc65e4.png)

  - Self-Attention Map のコントラストがはっきりしてきた。
  
   ![SAGAN_hanko_epoch1000_map.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/528836/db7baf3d-b843-b3e7-cd62-759c0d7d546d.png)


  - どうも、ハンコの右上の濃い部分が効いているようだ。もしかすると、ハンコ押すときに右上から着地するクセを見ているのかも（勝手な解釈）

## 結論
さあ、問題です。
AとBのハンコ。どちらが本物でしょうか？  

![logo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/528836/9705a7f7-8c4a-f695-9da6-67ba129ea712.png)
   

見破れますか？


ちょっと色の濃さで分かってしまうかもしれませんが、ぱっと見では見分けがつかないかも。

もしかすると「こんなの画像処理で複製すれば一発じゃん」と言われそうですが、
今回作ったハンコジェネレーターは乱数を入力するだけで、
“**無限通りのフェイクのハンコ**”を作れるところがミソです。

単なるコピーではなく、私の押し方のバリエーションと同じように、GANも様々なバリエーションのハンコを作り出してくれるわけです。

今回は手前味噌で用意した少数サンプルと基本的なプログラムでこのレベルです。
最新GANである[Deepfake](https://lionbridge.ai/ja/articles/deepfake/)は、最も人間の識別能力が高いと呼ばれる「顔」を対象にした場合でも、本物と偽物の顔の見分けがもはや不可能に近いレベルまで来ています。

ということで、私の中では「**AI時代においては、ハンコは既に本人証明能力が疑わしい**」という結論に達しました。

（インクか朱肉かで判別できるじゃん！という野暮なツッコミはしないでね）

ちなみに正解はBが本物。AはGANで生成した偽物でした。

## 次回予告？
人の目は騙せても、もしかすると、コンピュータの計算上は見破れるかもしれない。
そこで、指紋判定で使われる「特徴点マッチング」を使って、本物と偽物のハンコ画像間の類似度を計算してみたい。
ニーズがあれば記事出します。