Raspberry Pi で世界最高水準の物体認識を行う。　〜Google Vision API による物体認識〜

![result.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1486355/696d5385-baa6-2a56-7a77-44f6acbd170a.jpeg)
　Deep Learning で物体認識を行うには一般に、高性能なコンピューターが必要です。高性能でも、単なる一般処理向けの CPU では、Deep Learning の処理には非力です。ここ数年、Deep Learning が注目されるようになった切っ掛けも、GPU を Deep Learning 用の演算装置(GPGPU: General Purpose Graphics Processing Unit 汎用GPU)  として利用できるようになったからです。なのに Raspberry Pi で Deep Learning? と思ってしまいます。Deep Learning の処理に使える GPU を搭載した PC は、持ち歩くことなんてできません。お試しではなく、まともに Deep Learning の計算をさせようと思ったら、そんな PC が一台では足りませんし、Note PC であっても重量級です。それじゃあ Raspberry Pi では、尚の事だとお思いでしょう。　　−　　その通りです。Raspberry Pi で、Deep Learning の処理を行おうなんて、言ったこともなければ、心に昇ったこともありません。コンピューター・エンジニアリング、或いは、コンピューター・サイエンスの世界では、「クライアント・サーバー・アーキテクチャ」は当然の常識です。重たい処理は、バックのサーバーや汎用・大型コンピューターで行う仕組みです。見た目は子供向けの学習用シングルボードコンピュータ−、頭脳は世界最高水準の並列処理コンピューター、それが今回ご紹介するシステムです。ロマンチックですね。

![result.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1486355/faccaf8b-aedf-fb8f-0685-b97d56c88fb6.jpeg)<br>

:::note info
スマートフォン向けには、Flutter と Firebase を使った「**わんぱく Flutter! 第四回 いきなりっ！画像認識をしてみよう。**」でも、画像認識について解説しています。
:::

https://a-software-resources.hatenablog.com/entry/2021/04/13/223017

<br>


#### 1.準備

　以前、職場の技術者が、私が作業に使っている 最軽量 Windows Tablet PC を見て、「それがスーパーコンピューターなの？」と言ってきたことがありました。私はその PC を端末にしてオフィスから、研究所のスーパーコンピューターに接続してプログラムを開発していたのですが、彼はそれを知らなかったらしく、こんな質問をしてしまったようです。ご存知の通り、PC と言うのは、Personal Computer つまり、個人用コンピューターと言う意味ですが、汎用機や大型コンピューターには、それを操作する為の端末、Work Station (WS) と言うコンピューターがあります。汎用機や大型コンピューターには、直接、キーボードやディスプレイは付いていないのです。まあ、当然の常識なので、私たち技術者や研究者の間では説明の必要はないのですが、じゃあ、彼はなぜ知らなかったのでしょうか。深入りはしてませんけどね。

まず、Raspberry Pi を用意することは言うまでもないことなのですが、今、これを読んでいる ちびっこ達は、もう持っていることでしょう。ですから、Google Cloud Vision API を使うために Google Cloud Platform に登録するところから初めましょう。下記の URL からアクセスして下さい。

https://cloud.google.com/

![GCP_Title.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1486355/c6240249-7d63-e75d-0acb-5db0746ae618.jpeg)**図1**


もちろん、Google のアカウントは持っていますよね。まだの方は、Google のアカウトも作成しておきましょう。そうしたら、「無料で使ってみる」のボタンを押して、画面の指示に従って登録して下さい。

Google Cloud Platform への登録が終わったら、下記の URL から Cloud Vision API を有効にしましょう。

https://cloud.google.com/vision/docs/before-you-begin?hl=ja

![VisionAPI1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1486355/531638cf-3380-7256-5ff5-014083ca3195.jpeg)**図2**


説明されている通り、プロジェクトを選択するのではなく、何か新しいプロジェクトを作成しておきましょう。そして、Cloud Vision API は課金の掛かるサービスなので、項目 3 でも説明されているように、課金の設定をして API を有効にして下さい。月に 1,000 回までは課金されません。API を有効にしたら、説明されている通りにサービスアカウントを作成し、「___サービスアカウントキー___」の json を取得します。これが強調している通り、重要です。このキーを使って Vision API は、利用者の認証を行っています。これも、説明の通りに環境変数 ___GOOGLE_APPLICATION_CREDENTIALS___ へ設定しておきます。これで準備完了です。
<br>
<br>
<br>
#### 2.物体認識について

　Deep Learning で行う物体認識は、ほぼ CNN(Convolutional Neural Network : 畳み込みニューラルネットワーク)であると言っていいでしょう。最近、AI と持てはやされている技術は、この CNN を Modify つまり、手を加えたものです。

![Typical_cnn.png CC BY-SA4.0](https://upload.wikimedia.org/wikipedia/commons/6/63/Typical_cnn.png)**図3** (CC BY-SA 4.0)
<br>
CNN の基本は、この図の通り、Convolutions 層と、Subsampling で表されている Pooling 層の繰り返しです。Convolutions とは、「畳込み」と訳されていますが、畳込み積分、合成積の事で、従来の画像処理で言うところのフィルター、ぼかし処理に類似した処理です。

**式1 :　Convolution**
```math
u_{ij} = \sum_{p=0}^{with-1}{\sum_{q=0}^{height-1}{x_{(i+p),(j+q)}}} \cdot f_{pq}
```
<br>

```math
u : Convolution 値
```

```math
i,j : 入力画像の画素インデックス
```

```math
p,q : フィルターの画素インデックス
```

```math
x : 入力画像の画素値
```

```math
f : フィルターの画素値
```

```math
with : フィルターの横幅
```

```math
height : フィルターの縦幅
```
<br>

畳込み処理は、加重平均と言うと分かりやすいかも知れません。下記のアニメでは、kernel で表されているフィルターの画素値 f と、input の画素値 x の積をフィルターの (p,q) と入力画像の (i,j) をスライドさせながら足し込んでいるのがよく分かります。

![2D_Convolution_Animation.gif CC BY-SA 3.0](https://upload.wikimedia.org/wikipedia/commons/1/19/2D_Convolution_Animation.gif)**図4** (CC BY-SA 3.0)

これを ___プログラム___ で書くと、こういう意味となります。

**プログラム1**
```c
double convolution( int i, int j ){

    for(int p = 0; p < with; p++){
        for(int q = 0; q < height; q++){
            u[i][j] += x[i+p][j+q] * f[p][q];
        }
    }

    return( u[i][j] );
}
```
これで、説明の必要はなくなったかな？
<br>
アニメのフィルターは、中心が 5 の「点」の画像強調フィルターの例ですが、このフィルターを縦線・横線・斜め線など、色々なものを用意して、入力画像の ___特徴___ を捉えます。

**式2 :　フィルター**
```math
\begin{bmatrix} 0 & 3 & 0 \\ 0 & 4 & 0 \\ 0 & 3 & 0 \end{bmatrix}
\begin{bmatrix} 0 & 0 & 0 \\ 3 & 4 & 3 \\ 0 & 0 & 0 \end{bmatrix}
\begin{bmatrix} 3 & 0 & 0 \\ 0 & 4 & 0 \\ 0 & 0 & 3 \end{bmatrix}
\begin{bmatrix} 0 & 0 & 3 \\ 0 & 4 & 0 \\ 3 & 0 & 0 \end{bmatrix}  
```
<br>
そして Pooling 層では、Subsampling と表されている通り、Convolution の結果を単純に平均化したり、最大値を取って、Down Sampling 小さな画像に変換します。 

<br>
<br>

CNN は、この ___Convolution と Pooling をフィルター毎に行う処理を何層___ も重ねているものです。数学的には、意外に簡単ですね。

![VGG16.png CC BY-SA 4.0](https://upload.wikimedia.org/wikipedia/commons/2/24/VGG16.png)**図5** (CC BY-SA 4.0)
<br>
そうです、しかし、計算量が膨大です。例えば、2014年に ILSVRC(ImageNet Large Scale Visual Recognition Challenge) で優勝した VGG16 と言うモデルでは、16 層の計算を行っています。Deep Learning 深層学習の名前の所以ですね。しかし、まだまだ序の口です。

最後に Fully connected 全結合処理を行います。全結合は、三層のバックプロパゲーションや、サポートベクターマシンによるクラスタリング、分類処理です。Convolution と Pooling で得られた特徴から、分類するわけです。バックプロパゲーションについては、私も別のページで詳細に解説しておりますので、ご参照下さい。

https://a-kashiwagi.github.io/IntroductionToBackpropagationWithGPU/

![anintroductiontobackpropagation.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1486355/bdb64ef9-1e84-98f4-ec99-23463745d1ca.jpeg)**図6**

<br>
<br>
<br>
<br>

#### 3.Vision API

　Vision API には、物体認識(ImageAnnotator と呼ばれる)以外にも沢山の機能が用意されています。これらを、Raspberry Pi で処理しようとするのは、無知としか言いようがありません。　　ー　　そうです、不可能、あり得ないことなので、冒頭でも述べた通り、説明の必要のない当然のことなのです。しかし、これからコンピューターを学ぶラズパイちびっこ達には、説明が必要でしょう。Raspberry Pi を Google のコンピューターに接続して Raspberry Pi を機能拡張します。これが、コンピューター・エンジニアリング、コンピューター・サイエンスにおける「クライアント・サーバー」技術です。電気的には、Raspberry Pi の上に載せた Hat と同じなのです。

https://cloud.google.com/vision/docs/labels?hl=ja

```python
    response = client.label_detection(image=image)
    labels = response.label_annotations
```

上記 URL で説明されている通り、物体認識を行いたい画像を Vision API の引数 image に指定して渡すと、戻り値 response に json 形式で返ってくるものです。ターンアラウンドタイムは、悪くても数秒です。（ターンアラウンドタイムと、レスポンスタイムの違いは憶えてますね）　前述の VGG16 のような、より複雑な Deep Learning の処理がこのレスポンスで返ってくるなら充分でしょう。今回は、Python で行いますので、Google Cloud Vision のモジュールを pip でインストールします。

https://cloud.google.com/vision/docs/detect-labels-image-client-libraries?hl=ja#client-libraries-usage-python

```bash
pip install --upgrade google-cloud-vision
```

Python の環境設定で何か問題が起きたら、下記「Python 開発環境の設定」を参考に調整してみて下さい。

https://cloud.google.com/python/docs/setup?hl=ja#linux

それでは、Raspberry Pi 上のローカルにある JPEG 画像を何か処理してみましょう。物体認識してみたい画像を用意して下さい。

**プログラム2**
```python
import io
import sys
import cv2
import json
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image, ImageDraw
from google.cloud import vision

ALL_SIZE = 512
PATH = "recognize.jpg"
TMP = "tmp.jpg"
RESULT = "result.jpg"

client = vision.ImageAnnotatorClient()

print("read original: " + PATH) 
									# オリジナル画像の読み込み
img = cv2.imread(PATH)
h, w = img.shape[:2]

print("write resized: " + TMP)
									# リサイズ画像の書き込み
image_size = ALL_SIZE
height = round(h * (image_size /w))
img2 = cv2.resize(img,dsize=(image_size,height))
cv2.imwrite(TMP,img2)

print("re-read resized: " + TMP)
									# リサイズ画像の再読み込み
with open(TMP, 'rb') as image_file:
    content = image_file.read()
image = vision.Image(content=content)

print("object recognition")
									# 物体認識実行
objects = client.label_detection(
    image=image).label_annotations

print(objects)
img2 = img.copy()

print("font setting")
									# フォントの設定
fig = plt.figure(figsize=(11.46, 6.45), dpi=96)
color = (200, 200, 0)
font_color = (0, 180, 180)
font = cv2.FONT_HERSHEY_SIMPLEX
h, w = img2.shape[0:2]

count = len(objects)

for obj in objects:
									# フォント実サイズ取得
    obname = obj.description + " " + str(round(obj.score,2))
    (font_w,font_h),baseline = cv2.getTextSize(
        text=obname,
        fontFace=font,
        fontScale=1,
        thickness=1
    )
									# テキスト書き込み
    cv2.putText(
        img2,
        obname,
        (20, h - int((font_h + baseline) * 1.2) * count),
        font,
        1,
        font_color,
        thickness=3
    )

    count = count - 1

print("write result: " + RESULT)
									# 結果書き込み
cv2.imwrite(RESULT,img2)
cv2.imshow("recognized object",img2)
cv2.waitKey(0)
cv2.destroyAllWindows()

```

些か、Google のサンプルよりは複雑ですが、Raspberry Pi で処理することを考えて、画像を縮小したり、画像に結果を書き込むために、フォントの処理を追加しています。しかし、横幅 512 dot の数百[kB] 画像であれば、転送に時間は掛かりません。 ___Raspberry Pi におけるポイントは「画像縮小」___ です。冒頭の "ALL_SIZE" で縮小サイズを指定しています。また、読み込む元画像は、"PATH" に指定して下さい。"TMP" は縮小画像、"RESULT" は結果画像です。

![localized_detection.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1486355/e1bbd3be-c0e3-6ede-8006-14b5204c86fc.jpeg)**図7** 元画像

![tmp.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1486355/dc620537-cfa4-6494-4094-e12f5553a29e.jpeg)**図8** 縮小画像 67[KB]

![result.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1486355/4533e716-e545-87f7-83e5-f2dd83ca8280.jpeg)**図9** 結果画像


<br>
<br>

処理結果は元画像に書き込んでいますが、縮小画像に書き込むようにしてもいいかも知れません。

この鳥は何だか分かりますか？ 鳥に興味がない人は、「鳥」としか答えないでしょう。物体認識の結果もそんな感じです。ホオジロですけど、　　ー　　"Bird(鳥)","Branch(枝)","Twig(小枝)","Beak(くちばし)","Tree(木)","Songbird(鳴鳥)","Plant(植物)","Tail(尾)","Terrestrial plant(陸生植物)","Cedor Waxwing(ヒメレンジャク:スズメ目の小鳥)"　　ー　　まあいいのではないでしょうか。ヒメレンジャクと答えているのが興味深いですね。ホオジロも同じスズメ目の小鳥ですが、東アジアに分布してる鳥なので、US ではヒメレンジャクでいいのかも知れませんね、顔の模様も似ています。


**Reference:**

　1).「深層学習」-岡谷貴之　講談社

　2).「TensorFlow で学ぶディープラーニング入門」 -中井悦司　マイナビ出版

　3)."Convolutional neural network" - Wikipedia
　　　https://en.wikipedia.org/wiki/Convolutional_neural_network
　4)."Kernel (image processing)" - Wikipedia
　　　https://en.wikipedia.org/wiki/Kernel_(image_processing)