【特徴部位可視化】Grad-CAMとGuided Grad-CAMをmovie化！

Grad-camとGuided-Crad-camはDLのAdversarialAttackの研究やどうしてDLがそのように選択したか説明できる可能性をもつ技術である。

ということで、今回はそれを動画化して、入力画像や動画入力に対してリアルタイムに解析することを試みる。

###やったこと
・動画化するために
・入力画像に対するカテゴリ５を動画で示す
・カメラ入力のGrad-cam、Guidel-Crad-camの動画化
・入力画像に対するカテゴリ５を動画化と保存
###・動画化するために
####Gard-camのコード
まず、簡単にカテゴリ５を表示するコードは以下のとおりです。

```py
preprocessed_input = load_image(sys.argv[1])
model = VGG16(weights='imagenet')
#model.summary()
target_layer = 'block5_conv3' #'activation_49' add_16 "block5_conv3"

predictions = model.predict(preprocessed_input)
print('Predicted:', decode_predictions(predictions, top=5)[0])
register_gradient()
guided_model = modify_backprop(model, 'GuidedBackProp')
guided_model.summary()
for i in range(5):
    top_1 = decode_predictions(predictions)[0][i]
    print(predictions.argsort()[0][::-1][i])
    print('Predicted class:')
    print('%s (%s) with probability %.2f' % (top_1[1], top_1[0], top_1[2]))
    predicted_class = predictions.argsort()[0][::-1][i] #np.argmax(predictions)
    cam, heatmap = grad_cam(model, preprocessed_input, predicted_class, target_layer)
    cv2.imwrite("gradcam"+str(top_1[1])+".jpg", cam)
    saliency_fn = compile_saliency_function(guided_model)
    saliency = saliency_fn([preprocessed_input, 0])
    gradcam = saliency[0] * heatmap[..., np.newaxis]
    cv2.imwrite("guided_gradcam"+str(top_1[1])+".jpg", deprocess_image(gradcam))
```
このコードをさらに簡単に整理すると以下のようなコードになります。

```py
model = VGG16(weights='imagenet')
#model.summary()
def preprocessed(guided_model,preprocessed_input,s):
    predictions = model.predict(preprocessed_input)
    top_1 = decode_predictions(predictions)[0][s]
    predicted_class = predictions.argsort()[0][::-1][s]
    cam, heatmap = grad_cam(model, preprocessed_input, predicted_class, "block5_conv3")
    saliency_fn = compile_saliency_function(guided_model)
    saliency = saliency_fn([preprocessed_input, 0])
    gradcam = saliency[0] * heatmap[..., np.newaxis]
    return predictions, top_1, cam, gradcam

s1=0
size=(224,224)
#video_input = cv2.VideoCapture(0)
register_gradient()
guided_model = modify_backprop(model, 'GuidedBackProp')
#guided_model.summary()
fig = plt.figure(figsize=(10, 10))
ax1 = fig.add_subplot(2,2,1)
ax2 = fig.add_subplot(2,2,2)
ax3 = fig.add_subplot(2,2,4)
frame = cv2.imread("dog_cat_noframe.png")
for i in range(5):
    s=i
    input= cv2.resize(frame, (480,480))
    input = cv2.cvtColor(input, cv2.COLOR_BGR2RGB)
    ax1.imshow(input)
    ax1.set_title("original_"+str(s))
    preprocessed_input= cv2.resize(frame, size)
    preprocessed_input= np.expand_dims(preprocessed_input, axis=0)
    predictions, top_1, cam, gradcam = preprocessed(guided_model,preprocessed_input,s)
    input= cv2.resize(cam, (480,480))
    ax2.imshow(input)
    ax2.set_title("gradcam_"+str(top_1[1])+"_"+ str(int(top_1[2]*1000)/10)+" %")
    input= cv2.resize(deprocess_image(gradcam), (480,480))
    ax3.imshow(input)
    ax3.set_title("guided_gradcam_"+str(top_1[1])+"_"+ str(int(top_1[2]*1000)/10)+" %")
    plt.pause(1)
    cv2.imwrite("gradcam"+str(i)+".jpg", cam)
    cv2.imwrite("guided_gradcam"+str(i)+".jpg", deprocess_image(gradcam))
```

|Category|Grad-CAM | Guided-Grad-CAM|
|:-:|:-:|:-:|
|　　1　　　|![gradcam0.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233744/3c205669-b44a-6701-9365-fa3c40a77bfd.jpeg)|![guided_gradcam0.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233744/ac9718de-83a0-c79b-2826-103b10ad79c4.jpeg)|
|　　2　　|![gradcam1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233744/14f816d5-47ea-feaf-6f54-5069480c466c.jpeg)|![guided_gradcam1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233744/0f1fca54-5b69-422e-1bee-15c955ae7392.jpeg)|
|　　3　　|![gradcam2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233744/ed410166-572b-4e0c-cfab-bcab3ee82551.jpeg)|![guided_gradcam2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233744/cb5dd96f-b840-e4c3-41f0-563ff8df6fb6.jpeg)|
|　　4|![gradcam3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233744/6796b145-fa7d-a9a9-2571-41677b4acd9f.jpeg)|![guided_gradcam3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233744/9d71d773-bd6c-0210-54ba-25b23801987b.jpeg)|
|　　5|![gradcam4.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233744/1780cf77-0cd0-740c-21d5-b29b12debce7.jpeg)|![guided_gradcam4.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233744/4a788109-f216-7470-89bd-b90afcaa5394.jpeg)|
####動画化について
基本は、以下の参考のとおりです。
【参考】
・①[【ゲーム】cv2.waitKeyの学習用アプリを作って遊んでみた♪](https://qiita.com/MuAuan/items/d7d0202cd29c92f76bf4)
・②[Opencv: meanshift＆CamShiftで遊んでみた♬その２；VGG16で物体識別](https://qiita.com/MuAuan/items/0e4a305a5989bb972420)
参考①はcv2.imshow()とcv2.waitKey()の使い方を解説しています。また、参考②では動画の保存の仕方をしめしています。
###・入力画像に対するカテゴリ５を動画で示す
これらの技術を使うと、以下のようなコードが書けます。
変更点は以下のとおりです。ここで、継続的に繰り返すためにwhile(1):を使っています。
そして、cv2.waitKey(0)&0xffを利用して中断できるようにしました。
更新は何かをキーを押すと更新します。

```py
。。。
s1 = 0
while (1):
    s =s1%5
    print(s)
。。。
    cv2.imshow("guided_gradcam_"+str(top_1[1])+"_"+ str(int(top_1[2]*1000)/10)+" %",input)
    s1 += 1
    k = cv2.waitKey(0)&0xff
    if k == ord('q'):
        cv2.destroyAllWindows()
        break
    else:
        cv2.destroyAllWindows()
```
さらに以下のようにすると、キー入力に対して、描画するGrad-CAMやGuided-Grad-CAMがカテゴリに応じて変化します。
k = cv2.waitKey(-1)&0xffなので、何かのキーを押すと更新されます。

```py
frame = cv2.imread("cat_dog.png")
s = 0
while (1):
    print(s)
    #ret, frame = video_input.read()
    input= cv2.resize(frame, (480,480))
    input = cv2.cvtColor(input, cv2.COLOR_BGR2RGB)
    cv2.imshow("original_"+str(s),input)
    preprocessed_input= cv2.resize(frame, size)
    preprocessed_input= np.expand_dims(preprocessed_input, axis=0)
    predictions, top_1, cam, gradcam = preprocessed(guided_model,preprocessed_input,s)
    input= cv2.resize(cam, (480,480))
    cv2.imshow("gradcam_"+str(top_1[1])+"_"+ str(int(top_1[2]*1000)/10)+" %",input)
    input= cv2.resize(deprocess_image(gradcam), (480,480))
    cv2.imshow("guided_gradcam_"+str(top_1[1])+"_"+ str(int(top_1[2]*1000)/10)+" %",input)
    k = cv2.waitKey(-1)&0xff
    if k == ord('0'):
        cv2.destroyAllWindows()
        s=0
    if k == ord('u'):
        cv2.destroyAllWindows()
        s=1
    elif k == ord('h'):
        cv2.destroyAllWindows()
        s=2
    elif k == ord('j'):
        cv2.destroyAllWindows()
        s=3
    elif k == ord('n'):
        cv2.destroyAllWindows()
        s=4
    elif k == ord('q'):
        cv2.destroyAllWindows()
        break  
```

###・カメラ入力のGrad-cam、Guided-Crad-camの動画化と保存
こうして#ret, frame = video_input.read()のコメントアウトを外すと、カメラ入力についての動画化ができます。
そして、保存は以下のとおりで実施できます。
cv2.imshow()だと保存は難しいので、以下ではpltで実施しています。
これはムービーなので自動的に更新されていきます。
※以下ではout.write(img_dst)で動画"video.mp4"も出力されていますが、Gifアニメーションのみ示します
※Gifアニメーションの生成は以下のコードを利用しています
・[cheating_DL/out_gif.py](https://github.com/MuAuan/cheating_DL/blob/master/out_gif.py)

```py
def cv_fourcc(c1, c2, c3, c4):
        return (ord(c1) & 255) + ((ord(c2) & 255) << 8) + \
            ((ord(c3) & 255) << 16) + ((ord(c4) & 255) << 24)

def main():
    OUT_FILE_NAME = "video.mp4"
    FRAME_RATE=1
    w=224 #1280
    h=224 #960
    out = cv2.VideoWriter(OUT_FILE_NAME, \
              cv_fourcc('M', 'P', '4', 'V'), \
              FRAME_RATE, \
              (480, 480), \
              True)
    #is_video = 'True'

    s1=0
    size=(224,224)
    video_input = cv2.VideoCapture(0)
    register_gradient()
    guided_model = modify_backprop(model, 'GuidedBackProp')
    #guided_model.summary()
    fig = plt.figure(figsize=(8, 8))
    ax1 = fig.add_subplot(2,2,1)
    ax2 = fig.add_subplot(2,2,2)
    ax3 = fig.add_subplot(2,2,4)
    timer0=time.time()
    while True:
        s1 += 1
        s = 0 #s1%5
        print(s)
        timer = cv2.getTickCount()
        ret, frame = video_input.read()
        input= cv2.resize(frame, (480,480))
        input = cv2.cvtColor(input, cv2.COLOR_BGR2RGB)
        ax1.imshow(input)
        fps = cv2.getTickFrequency() / (cv2.getTickCount() - timer);
        ax1.set_title("original_fps;"+str(int(fps)))
        preprocessed_input= cv2.resize(frame, size)
        preprocessed_input= np.expand_dims(preprocessed_input, axis=0)
        predictions, top_1, cam, gradcam = preprocessed(guided_model,preprocessed_input,s)
        input= cv2.resize(cam, (480,480))
        ax2.imshow(input)
        ax2.set_title("gradcam_"+str(top_1[1])+"_"+ str(int(top_1[2]*1000)/10)+" %")
        input= cv2.resize(deprocess_image(gradcam), (480,480))
        ax3.imshow(input)
        ax3.set_title("guided_gradcam_"+str(top_1[1])+"_"+ str(int(top_1[2]*1000)/10)+" %")
        plt.pause(0.1)
        fig1=plt.pause(0.001)
        #Gifアニメーションのために画像をためます
        plt.savefig("output/image"+str(s1)+".jpg")
        dst = cv2.imread('output/image'+str(s1)+'.jpg')
        img_dst = cv2.resize(dst, (int(480), 480))
        out.write(img_dst)
        print(time.time()-timer0)
        if time.time()-timer0>=20:
            out.release()
            break
if __name__ == '__main__':
    main()
```
![desk2-36.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233744/487b824e-fabf-737b-4c7c-82ab608555d9.gif)
###・入力画像に対するカテゴリ５を動画化と保存
上のコードとあまり変わりませんが、実はこれがコントロールという意味で一番難しかったので、最後に持ってきました。
上記のカテゴリの表示を動画化して残すにはいくつか方法がありそうですが、以下のようなコードにしました。
このコードでも、一枚一枚更新することとして、k = cv2.waitKey(0)&0xffとしています。
※何かのキーを押すと次のカテゴリに進みます

```py
def main():
    model = VGG16(weights='imagenet')
    #model.summary()
    OUT_FILE_NAME = "kenbikyo_video.mp4"
    FRAME_RATE=1
    w=224 #1280
    h=224 #960
    out = cv2.VideoWriter(OUT_FILE_NAME, \
              cv_fourcc('M', 'P', '4', 'V'), \
              FRAME_RATE, \
              (480, 480), \
              True)
    s1=0
    size=(224,224)
    #video_input = cv2.VideoCapture(0)
    register_gradient()
    guided_model = modify_backprop(model, 'GuidedBackProp')
    #guided_model.summary()
    fig = plt.figure(figsize=(10, 10))
    ax1 = fig.add_subplot(2,2,1)
    ax2 = fig.add_subplot(2,2,2)
    ax3 = fig.add_subplot(2,2,4)
    frame = cv2.imread("dog_cat.png") #""dog_cat.png")
    s1 = 0
    timer0=time.time()
    while (1):
        s =s1%5
        print(s)
        #ret, frame = video_input.read()
        input= cv2.resize(frame, (480,480))
        input = cv2.cvtColor(input, cv2.COLOR_BGR2RGB)
        ax1.imshow(input)
        ax1.set_title("original_"+str(s))

        preprocessed_input= cv2.resize(frame, size)
        preprocessed_input= np.expand_dims(preprocessed_input, axis=0)
        predictions, top_1, cam, gradcam = preprocessed(model,guided_model,preprocessed_input,s)

        input= cv2.resize(cam, (480,480))
        ax2.imshow(input)
        ax2.set_title("gradcam_"+str(top_1[1])+"_"+ str(int(top_1[2]*1000)/10)+" %")

        input= cv2.resize(deprocess_image(gradcam), (480,480))
        ax3.imshow(input)
        ax3.set_title("guided_gradcam_"+str(top_1[1])+"_"+ str(int(top_1[2]*1000)/10)+" %")
        plt.pause(1)
        cv2.imshow("guided_gradcam_"+str(top_1[1])+"_"+ str(int(top_1[2]*1000)/10)+" %",input)
        s1 += 1
        plt.savefig("output/image"+str(s1)+".jpg")
        dst = cv2.imread('output/image'+str(s1)+'.jpg')
        img_dst = cv2.resize(dst, (int(480), 480))
        out.write(img_dst)
        k = cv2.waitKey(0)&0xff
        if k == ord('q'):
            cv2.destroyAllWindows()
            break
        else:
            cv2.destroyAllWindows()
        print(time.time()-timer0)
        if time.time()-timer0>=120:
            out.release()
            break
            
if __name__ == '__main__':
    main()
```
![category5-30.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233744/84bce62e-0876-a5b0-db1e-5a57db81b3da.gif)
※DLの不安定性のため、猫の画像が得られませんでした（＞＜）
###コードは以下に置きました
[cheating_DL/plot_grad-cam_categoryC.py](https://github.com/MuAuan/cheating_DL/blob/master/plot_grad-cam_categoryC.py)
[cheating_DL/plot_grad-cam_categoryC_cv2.py](https://github.com/MuAuan/cheating_DL/blob/master/plot_grad-cam_categoryC_cv2.py)
[cheating_DL/plot_grad-cam_movie.py](https://github.com/MuAuan/cheating_DL/blob/master/plot_grad-cam_movie.py)
###まとめ
・Grad-CAM movieを作ってみた

・AdversarialAttackの画像の違いやそもそも誤回答な画像の分析に応用する
・物体検出とのコラボを考えたい