【pix2pix VS Excel】AIに強いExcelのアート効果はどれだ!  


<a id="#概要"></a>
#概要
　皆さんは、Excelのアート効果というものをご存じでしょうか？
　アート効果は図を絵具やクレヨンで書いたように加工してくれる機能なのですが、使い方によっては以下のように、個人情報を隠すことにも使えます。
　しかしAIの進化で[モザイク画像の元画像を予測する技術](https://ampmedia.jp/2020/06/29/ledge-ai-93/)が出てきており、これらのアート効果も簡単に取り除かれるのも時間の問題と考えられます。
　そこで、[pix2pix](https://affinelayer.com/pixsrv/)という技術を用いて、アート効果をあたえた文章の復元を行い、どのアート効果の復元が最も難しいのかを調査します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1033231/d7d3b7aa-5290-bfa8-74b7-a560193f6e51.png)

<a id="#目次"></a>
#目次
1. [使用するデータやアート効果](#使用するデータやアート効果)
1. [評価方法](#評価方法)
1. [使用したコード](#使用したコード)
1. [結果](#結果)
1. [まとめ](#まとめ)


<a id="#使用するデータやアート効果"></a>
#使用するデータやアート効果
　使用する文章については、驚くほど難解な漢字とカタカナやひらがながメチャクチャ大量に混じっている方がいいと考えて<b>「[クソデカ羅生門](https://anond.hatelabo.jp/20200611125508)」</b>を用いることにしました。
　この「クソデカ羅生門」の一部をExcelのテキストボックスに入れ、そのテキストボックスを画像に変換したものに、アート効果をあたえます。
　そして、使用したアート効果が以下の図です。
　自分で一部が読めないもののみ残し、そのまま読めてしまうアート効果は除外しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1033231/5fffb4e9-5119-5ce3-2b28-fd45a0b68ee8.png)

<a id="#評価方法"></a>
#評価方法
　評価方法は、IoUを用いて、元画像の文字と予想結果の文字を比べ、一番IoUの値が小さいアート効果が「最もAIに強い」とします。

```math
IoU = \frac{選択できた場所[赤]}{選択できなかった場所[青]+選択できた場所[赤]+間違った選択をした場所[緑]}
```

　![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1033231/e6a58911-863b-3caf-478b-87a614a297ba.png)

<a id="#使用したコード"></a>
#使用したコード

　Excelのテキストボックスに特定の文章を設定し、画像として保存するコードが以下のものです。
　動かす前に、Sheet1にテキストボックスと、"文章"と名前を付けたセルを用意し、"文章"と名前を付けたセルに好きな文章を入力して、main()を実行します。
　注意点としては、動作環境によっては、ウィンドウを画面に表示していないと、正常に画像が保存されませんし、まれに画像の保存が失敗します。
　ほんとうに、VBAの画像関連の処理がクソザコナメクジなところが大嫌いです。


```vb:Module1.bas
Option Explicit

#If VBA7 Then
Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal ms As LongPtr)
#Else
Private Declare Sub Sleep Lib "kernel32" (ByVal ms As Long)
#End If

Sub Sleep_(time As Long)
    Dim I As Long
    
    For I = 1 To Int(time / 25)
        Sleep 25
        DoEvents
    Next I

End Sub

Sub main()

    Call Makeimg(msoEffectPencilGrayscale, "PencilGrayscale")
    Call Makeimg(msoEffectPencilSketch, "PencilSketch")
    Call Makeimg(msoEffectLineDrawing, "LineDrawing")
    Call Makeimg(msoEffectChalkSketch, "ChalkSketch")
    Call Makeimg(msoEffectPaintStrokes, "PaintStrokes")
    Call Makeimg(msoEffectPaintBrush, "PaintBrush")
    Call Makeimg(msoEffectGlowDiffused, "GlowDiffused")
    Call Makeimg(msoEffectBlur, "Blur")
    Call Makeimg(msoEffectLightScreen, "LightScreen")
    Call Makeimg(msoEffectWatercolorSponge, "WatercolorSponge")
    Call Makeimg(msoEffectFilmGrain, "FilmGrain")
    Call Makeimg(msoEffectMosiaicBubbles, "MosiaicBubbles")
    Call Makeimg(msoEffectGlass, "Glass")
    Call Makeimg(msoEffectCement, "Drawing")

    Call Makeimg(msoEffectTexturizer, "Texturizer")
    Call Makeimg(msoEffectCrisscrossEtching, "CrisscrossEtching")
    Call Makeimg(msoEffectPastelsSmooth, "PastelsSmooth")
    Call Makeimg(msoEffectPlasticWrap, "PlasticWrap")
    Call Makeimg(msoEffectCutout, "Cutout")
    Call Makeimg(msoEffectPhotocopy, "Photocopy")
    Call Makeimg(msoEffectGlowEdges, "GlowEdges")
    Call Makeimg(0, "None")


End Sub

Sub Makeimg(msoEffect As Long, savefolder As String)

    Dim textobj As Object, imageobj As Object
    Dim text As String
    Dim count As Long
    
    Application.DisplayAlerts = False
    Application.ScreenUpdating = False
    '保存するフォルダを選択
    If Dir(ThisWorkbook.Path & "\" & savefolder, vbDirectory) = "" Then
        MkDir ThisWorkbook.Path & "\" & savefolder
    End If
    
    text = Sheet1.Range("文章")
    count = 1
    Do While Len(text) > 140
        'テキストをセット
        Set textobj = ActiveSheet.Shapes.Range(Array("TextBox 1"))
        textobj.TextFrame2.TextRange.Characters.text = text
        'テキストボックスを画像としてコピーする
        Do While Not (Copy_Text_Box(textobj))
            DoEvents
        Loop
        '先ほどコピーした画像を選択
        DoEvents
        Set imageobj = ActiveSheet.Shapes(2)
        DoEvents
        'アート効果をつける
        If msoEffect <> 0 Then
            Call imageobj.Fill.PictureEffects.Insert(msoEffect)
            Sleep_ (100)
        End If
        DoEvents
        '画像を保存する
        Do While Not (Save_Pic(imageobj, savefolder & "\" & count & ".png"))
            DoEvents
        Loop
        '新しいテキストを選択する
        count = count + 1
        text = Right(text, Len(text) - 10)
        imageobj.Delete
        DoEvents
    Loop
    Application.DisplayAlerts = True
    Application.ScreenUpdating = True
    
    
End Sub

Function Copy_Text_Box(textobj As Object) As Boolean
On Error GoTo copy_Error
    textobj.Select
    DoEvents
    Selection.copy
    Sheet1.Range("D1").Select
    DoEvents
    ActiveSheet.Pictures.Paste.Select

    Copy_Text_Box = True
    Exit Function
copy_Error:
    Copy_Text_Box = False
End Function

Function Save_Pic(imageobj As Object, savefile As String) As Boolean
On Error GoTo save_pic_Error
    Application.ScreenUpdating = True
    DoEvents
    imageobj.CopyPicture
    DoEvents
    ActiveSheet.ChartObjects.Add(0, 0, imageobj.Width, imageobj.Height).Name = "貼付用"
    DoEvents
    With ActiveSheet.ChartObjects("貼付用")
      .Chart.PlotArea.Fill.Visible = msoFalse
      DoEvents
      .Chart.ChartArea.Fill.Visible = msoFalse
      DoEvents
      .Chart.ChartArea.Border.LineStyle = 0
    End With

    Sleep_ (1000)
    ActiveSheet.ChartObjects("貼付用").Chart.Paste
    Sleep_ (1000)
    ActiveSheet.ChartObjects("貼付用").Chart.Export ThisWorkbook.Path & "\" & savefile
    ActiveSheet.ChartObjects("貼付用").Delete
    Application.ScreenUpdating = False
    Save_Pic = True
    Exit Function
save_pic_Error:
    Application.ScreenUpdating = False
    Save_Pic = False

End Function
```
　VBAは画像を処理するには不向きなので、以下のように、Excelから出力された画像を、pix2pixに読み込めるように変換する、pythonのコードを作成しました。

```python:main.py
from PIL import Image
import numpy  as np
import os
import glob

dirlist = os.listdir(path='./excelimg')
#フォルダのループ
for folder_ in dirlist:
    folder_fll = './excelimg/' + folder_ 
    print(folder_fll)
    list_ = glob.glob(folder_fll + "/*")
    for file_ in list_:
        filename =  file_.split("/")[-1].split("\\")[-1]
        print(filename)
        if os.path.isfile("./excelimg/None/" + filename):
            #画像の読み込みを行う
            img1 = np.asarray(Image.open(file_).convert('RGB').resize((256, 256)))
            img2 = np.asarray(Image.open("./excelimg/None/" + filename).convert('RGB').resize((256, 256)))
            outimg = np.zeros((img1.shape[0],img1.shape[1] * 2,3),dtype = "int")
            #pix2pix用の画像の作成
            outimg[:,:img1.shape[1],:] = img1
            outimg[:,img1.shape[1]:,:] = img2

            #フォルダがなければ作成する
            if not os.path.exists('./pix2pixdata/' + folder_):
                os.mkdir('./pix2pixdata/' + folder_)
            if not os.path.exists('./pix2pixdata/' + folder_ + "/train"):
                os.mkdir('./pix2pixdata/' + folder_+ "/train")
            if not os.path.exists('./pix2pixdata/' + folder_ + "/test"):
                os.mkdir('./pix2pixdata/' + folder_+ "/test")
            if not os.path.exists('./pix2pixdata/' + folder_ + "/val"):
                os.mkdir('./pix2pixdata/' + folder_+ "/val")
            #画像を保存する
            pilImg = Image.fromarray(np.uint8(outimg))
            if filename[0] == "1":
                pilImg.save('./pix2pixdata/' + folder_ +"/val/"+ filename)
            elif filename[0] == "2":
                pilImg.save('./pix2pixdata/' + folder_ +"/test/"+ filename)
            else:
                pilImg.save('./pix2pixdata/' + folder_ +"/train/"+ filename)
```

　次に以下のサイトからpix2pixのソースコードをクローンし、README.mdに書かれている通りにpix2pixの学習を実行していきます。


https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix

　IoUの計算を行うため、test.pyを以下のコードと入れ替えて実行してください。

```python:main.py
import os
from options.test_options import TestOptions
from data import create_dataset
from models import create_model
from util.visualizer import save_images
from util import html
from util import util
import cv2
import numpy as np

if __name__ == '__main__':
    opt = TestOptions().parse()  # get test options
    # hard-code some parameters for test
    opt.num_threads = 0   # test code only supports num_threads = 0
    opt.batch_size = 1    # test code only supports batch_size = 1
    opt.serial_batches = True  # disable data shuffling; comment this line if results on randomly chosen images are needed.
    opt.no_flip = True    # no flip; comment this line if results on flipped images are needed.
    opt.display_id = -1   # no visdom display; the test code saves the results to a HTML file.
    dataset = create_dataset(opt)  # create a dataset given opt.dataset_mode and other options
    model = create_model(opt)      # create a model given opt.model and other options
    model.setup(opt)               # regular setup: load and print networks; create schedulers
    # create a website
    web_dir = os.path.join(opt.results_dir, opt.name, '{}_{}'.format(opt.phase, opt.epoch))  # define the website directory
    if opt.load_iter > 0:  # load_iter is 0 by default
        web_dir = '{:s}_iter{:d}'.format(web_dir, opt.load_iter)
    print('creating web directory', web_dir)
    webpage = html.HTML(web_dir, 'Experiment = %s, Phase = %s, Epoch = %s' % (opt.name, opt.phase, opt.epoch))
    # test with eval mode. This only affects layers like batchnorm and dropout.
    # For [pix2pix]: we use batchnorm and dropout in the original pix2pix. You can experiment it with and without eval() mode.
    # For [CycleGAN]: It should not affect CycleGAN as CycleGAN uses instancenorm without dropout.
    area_of_ove = 0
    area_of_uni = 0
    
    if opt.eval:
        model.eval()
    for i, data in enumerate(dataset):
        if i >= opt.num_test:  # only apply our model to opt.num_test images.
            break
        model.set_input(data)  # unpack data from data loader
        model.test()           # run inference
        visuals = model.get_current_visuals()  # get image results
        img_path = model.get_image_paths()     # get image paths
        if i % 5 == 0:  # save images to an HTML file
            print('processing (%04d)-th image... %s' % (i, img_path))
        save_images(webpage, visuals, img_path, aspect_ratio=opt.aspect_ratio, width=opt.display_winsize)
        #Iou
        for label, im_data in visuals.items():
            im = util.tensor2im(im_data)
            if "fake_B" == label:
                fake_B = (cv2.cvtColor(im, cv2.COLOR_BGR2GRAY) < 128)
            elif "real_B" == label:
                real_B = (cv2.cvtColor(im, cv2.COLOR_BGR2GRAY) < 128)
        area_of_ove_flag = (fake_B & real_B)
        area_of_uni_flag = (fake_B | real_B)
        area_of_ove = area_of_ove + np.count_nonzero(area_of_ove_flag)
        area_of_uni = area_of_uni + np.count_nonzero(area_of_uni_flag)
    
    print("------------IoU----------------")
    print(area_of_ove/area_of_uni)
    print("-------------------------------")

    webpage.save()  # save the HTML
```

<a id="#結果"></a>
#結果
　以下の画像が、アート効果をあたえた画像を復元した結果です。
　ひらがなやカタカタは、うまく復元できているように見えますが、「パッチワーク」は日本語っぽい文字を張り付けているだけで、言語として読むことができません。
　漢字は全体的に復元がうまくいっていないようで、謎の漢字っぽい何かが出現することがあります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1033231/85588fa7-c527-f0a9-fb86-422f603228ae.png)

　そして、IoUの計算結果が、以下の表です。
　この表から、パッチワークとガラスが最も復元が難しいようです。
　一番驚いたのが、人間が見ても元の文章が分かりにくい「光彩：輪郭」が2番目に復元しやすかったということで、人間にはわからない文字の痕跡を、機械は簡単に見つけられるようです。

| アート効果 | IoU |
|:-----------|------------:|
| パッチワーク |0.353|
| ガラス      |0.369|
| スポンジ      |0.414|
| ぼかし      |0.428|
| 線画      |0.452|
| 鉛筆：モノクロ|0.471|
| チョークスケッチ |0.491|
| 光彩：輪郭 |0.527|
| カットアウト |0.536|

<a id="#まとめ"></a>
#まとめ
　pix2pixを用いてExcelのアート効果から元画像の復元を行いました。
　結果は、パッチワークが最も復元しにくく、カットアウトが最も復元しやすかったようです。
　ただし、今回行った方法はアート効果ごとに機械学習のモデルを作成するという方法であり、ちゃんと復元するには、どのようなアート効果を使用したかの詳細が必要です。
　例えば「ぼかし」を復元するpix2pixのモデルで「チョークスケッチ」を復元すると、以下のような画像になりIoUは0.201まで落ちます。
　実際に他人の修正の入った文章を復元しようとすると、使用したアート効果の詳細は分かりませんから、復元することは困難であると考えられます。
　当然、技術は日々様々な問題を解決していっているので、いずれこれらの問題も解決されるでしょうから、機密情報を隠したいときは、黒塗りを用いる方がいいでしょう。

![2_fake_B.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1033231/8f755033-99cb-d19e-9eb6-6d28ecf32371.png)