はじめに
Raspberry Pi上でImageNetの学習済みモデルを使い,カメラから入力した画像を認識しようとしましたが,モデルが大きすぎてメモリに乗りませんでした。
そこで,以下のようにラズパイからサーバーに画像を送信して認識結果を返すような仕組みを実装しました。
方法
ソケット通信でサーバーとやり取りします。
コードはGitHubのリポジトリにもアップロードしてあります。
サーバー側
画像認識の部分はChainerで行っています。
モデルはVGGを利用しました。
# coding: utf-8
import socket, threading
import chainer
import chainer.links as L
import numpy as np
class ImageNetPredictor:
def __init__(self):
self.model = L.VGG16Layers() # 初回実行時はモデルをダウンロードするので時間がかかる
self.categories = np.loadtxt("synset_words.txt", str, delimiter="\n").tolist()
def __call__(self, x):
x = x[:,:,::-1] # BGR -> RGB
h = self.model.extract([x], layers=["fc8"])["fc8"]
h = h.array.argmax() # 出力は1000次元で各カテゴリのスコアを表すので最大値のインデックスを求める
return self.categories[h]
class Handler:
def __init__(self, model):
self.model = model
def __call__(self, clientsock, client_address):
data = b""
while True:
r = clientsock.recv(2048) # 分割して受信
data += r
if len(data) >= 224*224*3:
# 画像サイズ224*224*3のバイト数だけ受信したらループを抜ける
break
data = np.fromstring(data, dtype=np.uint8)
data = data.reshape((224,224,3)) # データが1次元配列になってしまっているので整形する
category = self.model(data) # 画像認識
print(" ", client_address, category)
clientsock.sendall(category.encode("utf-8")) # 認識結果(カテゴリ名の文字列)をクライアントに返す
clientsock.close() # ソケットを閉じる
def main():
HOST, PORT = "", 55555
serversock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serversock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
serversock.bind((HOST, PORT))
serversock.listen(20)
model = ImageNetPredictor() # モデル
print("OK")
while True:
clientsock, client_address = serversock.accept() #接続されればデータを格納
print("conected to "+client_address[0])
# 接続されたら新規にスレッドを立てて処理する
handle_thread = threading.Thread(target=Handler(model), args=(clientsock, client_address), daemon=True)
handle_thread.start()
if __name__ == "__main__":
main()
Chainerとnumpyはsudo pip install numpy chainer pillow
で入ります。
PillowもChainer内部で使われるので一緒に入れておきましょう。
また,synset_words.txt
はカテゴリ番号からカテゴリ名を参照するために必要なのでこちらからダウンロードして同じフォルダに配置してください。
上記のvgg_server.py
を起動したらモデルのロードが終わるまで少し待ちます。
初回実行時はモデルをダウンロードするので時間がかかります。
「OK」と表示されたら準備完了です。
クライアント(ラズパイ)側
画像を読み込んで$224\times 224$に縮小した後,サーバー側に送信します。
OpenCVのインストールはこちらを参考にさせていただきました。
# coding: utf-8
import socket, time
import numpy as np
import cv2
# サーバーのIPアドレス(適宜変える),ポート番号
HOST, PORT = "192.168.31.150", 55555
def image_recog(img):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as clientsock:
clientsock.connect((HOST, PORT))
clientsock.send(img.tostring())
data = clientsock.recv(2048)
return data.decode("utf-8")
img = cv2.imread("pizza.jpg")
img = cv2.resize(img, (224,224))
print("send image")
start_time = time.time() # 認識結果が出るまでの時間を計測
category = image_recog(img)
elapsed_time = time.time() - start_time
print(category, elapsed_time)
上記の例ではカメラは使わず,以下の画像をpizza.jpg
として読み込んでいます。
結果
サーバー側でvgg_server.py
を起動した状態で,ラズパイ側でvgg_cilent.py
を実行します。
$ python vgg_client.py
send image
n07873807 pizza, pizza pie 0.6916546821594238
正しく認識できました!
画像を送信してからおよそ0.7秒で結果が返ってきています。時間的にもまあまあです。
おわりに
ラズパイでディープなモデルを扱おうとするとリソース不足なことがままあります。
今回紹介したようにサーバーに投げてしまえば,画風変換などいろいろなことができそうです。
実はモデルをGoogLeNetに変えたところ,ラズパイ上で動かすことはできたのですが画像認識に数秒かかりました。
ネットワークや計算機の環境にもよりますが,通信にかかる時間を差し引いてもサーバーを使うのは有効だと思います。