Raspberry Pi와 딥 러닝으로 이미지 인식을 시도하면 무거웠기 때문에 서버가 계산했습니다.

소개



Raspberry Pi에서 ImageNet의 학습 모델을 사용하여 카메라에서 입력 한 이미지를 인식하려고 시도했지만 모델이 너무 커서 메모리를 타지 않았습니다.
그래서 다음과 같이 라즈파이에서 서버로 이미지를 보내 인식 결과를 반환하는 구조를 구현했습니다.


방법



소켓 통신으로 서버와 통신합니다.
코드는 GitHub 리포지토리에도 업로드되었습니다.

서버측



이미지 인식 부분은 Chainer에서 실시하고 있습니다.
모델은 VGG를 사용했습니다.

vgg_server.py
# 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의 설치는 여기 를 참고로 했습니다.

vgg_client.py
# 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으로 바꿨다가 라즈파이에서 움직일 수는 있었지만 이미지 인식에 몇 초가 걸렸습니다.
네트워크나 컴퓨터의 환경에 따라 다르지만, 통신에 걸리는 시간을 빼도 서버를 사용하는 것은 유효하다고 생각합니다.

참고


  • 라즈파이 3에 OpenCV3를 쉽게 도입
  • leetenki/googlenet_chainer
  • TCP로 카메라에서 영상 전송(Python)
  • 파이썬, 멀티스레드 사용 간이 채팅
  • 좋은 웹페이지 즐겨찾기