
こんにちは,先日からハカルスにインターンで来ている,エッジエンジニアの岸本です.昨年末リリースされた,OpenVINO Toolkit R5から正式にNeural Compute Stick 2(NCS2)上でRaspberry Piがサポートされました.NCS2はUSBスティック型の推論アクセラレータで,スペック的に推論処理が困難な組み込み系のコンピュータなどでも高速な推論処理を実現できます.つまり,Raspberry Piが「Edge AI」デバイスのように使うことが出来ます.今回はRaspberry PiにNCS2を接続し,どのように使えるのか,実際推論処理がどれくらい高速化するか,エッジエンジニアの方なら気になる点をまとめてみました.
1. はじめに
ここ数年でEdge AIの中でもUSBスティックで組み込み機器などに接続するアクセラレータデバイスが流行ってきています.今回紹介するIntelのNCS2やGoogleのCoralといったものが有名ですね.アクセラレータのデバイスは従来のEdge用ボードやFPGAに比べると,以下の利点があります.
- 組み込み機器にUSB接続するだけで使用できるので,導入の敷居が低い
- 推論用API/SDKが公開されており,エンジニアの学習コストが低い
- 低スペックな組み込み機器でも推論処理が可能になる
そして,OpenVINO Toolkit R5でRaspberry Piに公式対応したということで,これは色々試してみるしかない…となった次第です.今回は上記の3つのポイントを調査してみました.
2. Neural Compute Stick 2(NCS2)
NCS2 とは Intelの傘下であるMovidius社が開発している,深層学習の推論処理を高速化する,外付けUSBアクセラレータです.
VPU(Vision Processing Unit) という,半精度浮動小数点数と8bit固定小数点数の行列演算ユニット「Myriad」を搭載しており,深層学習の推論処理をこのMyriadに委任させることで,低スペックの組み込み機器でも推論を高速に行うことが出来ます.
導入の敷居は低く,組み込み機器との接続はUSB接続のため,ハンダ付け等の作業は不要となります.
ユーザは自身の組み込み機器とNCS2をUSB接続し,後述のOpenVINOを導入すれば,NCS2による推論処理の高速化を試すことができます.
3. OpenVINO
OpenVINO は,Intelの推論用のフレームワークで,CPU,OpenCL,今回触れるNCS2に搭載されている前述のMyriad,FPGAなどのデバイスに対応しています.OpenVINOでは,推論用APIのラッパーがOpenCVで用意されています.開発者はこのラッパーを経由して,OpenVINOを意識することなく,OpenCVのライブラリを使用した推論処理の実装が可能です.
使用出来るモデルは既に用意されたOpenVINO用モデルやtensorflow,caffe,onnx形式などのモデルを読み込んで使うことが出来ます.
4. OpenVINOを使った顔識別プログラムの実装
以上の仕様を踏まえ,今回はraspberry pi 3 model b+(以後RasPi) にNCS2を接続し,OpenVINOで推論を高速化する顔識別プログラムを実装しました.また,このNCS2による高速化がどれくらいか確認するため,同様のプログラムをRasPiのCPUで動かした時の処理時間とWindowsのノートPCのCPUで動かした時の処理時間の比較を行いました.
4.1 使用環境 / モデルについて
使用環境
RasPi
- モデル : raspberry pi 3 model b+
- OS : Raspbian OS
- Neural Compute Stick 2(NCS2)
- OpenVINO
環境構築手順について
RasPiとNCS2のセットアップについては,以下を参照しました.
- https://software.intel.com/en-us/articles/OpenVINO-Install-RaspberryPI
顔認識モデル
今回は,NCSのデモに出てくる,以下の学習済み顔検出モデルを使用しました.
- https://download.01.org/openvinotoolkit/2018_R4/open_model_zoo/face-detection-adas-0001/FP16/face-detection-adas-0001.bin
- https://download.01.org/openvinotoolkit/2018_R4/open_model_zoo/face-detection-adas-0001/FP16/face-detection-adas-0001.xml
顔識別モデル
今回は,以下の学習済み顔識別モデルを用いました.
- https://github.com/ox-vgg/vgg_face2
4.2 顔識別の実施
使用画像データ
識別のためのプロトタイプ用画像として,今回はハカルスのホームページにある画像を使用しました.集めた画像をリネームしimageフォルダに入れ下のコードを実行すると,特徴ベクトルと元画像のファイル名のリストのpickleファイル(faceVec.pickle)が作成されます.
学習済みモデル
使用する学習済みモデル,以下の4つのファイルはmodelフォルダに置きます.
- face-detection-adas-0001.bin
- face-detection-adas-0001.xml
- resnet50_128.caffemodel
- resnet50_128.prototxt
実装コード
以下が今回実装した,顔識別プログラムとなります.OpenVINOが関わる点としては,以下の3点となります.
- cv2.dnn.readNetでモデルを読み込みsetPreferableTarget(cv2.dnn.DNN_TARGET_MYRIAD)で推論デバイスをNCS2に設定
- cv2.dnn.blobFromImageで画像を深層学習モデルに読み込ませるための形式にする
- setInput(blob)で先の画像をネットワークモデルの入力画像として設定し,forward()で推論を実行
import cv2
import numpy as np
import os
import pickle
class Recognize:
def __init__(self):
self.net = cv2.dnn.readNetFromCaffe("model/resnet50_128.prototxt","model/resnet50_128.caffemodel")
self.net.setPreferableTarget(cv2.dnn.DNN_TARGET_MYRIAD)
def recognize(self, image):
blob = cv2.dnn.blobFromImage(image, size=(224, 224), ddepth=cv2.CV_8U)
self.net.setInput(blob)
out = self.net.forward().flatten()
return out
def recognize_images(self, images):
res = [self.recognize(x) for x in images]
return np.array(res)
class Detect:
def __init__(self):
self.net = cv2.dnn.readNet('model/face-detection-adas-0001.xml', 'model/face-detection-adas-0001.bin')
self.net.setPreferableTarget(cv2.dnn.DNN_TARGET_MYRIAD)
image_dir = os.listdir("image")
self.images = [self.detect_once(cv2.imread("image/"+x)) for x in image_dir]
self.names = [x.split(".")[0] for x in image_dir]
self.recognizer = Recognize()
self.vec = self.recognizer.recognize_images(self.images).reshape(len(self.images), -1)
self.save()
def detect(self, image, threshold=0.99):
blob = cv2.dnn.blobFromImage(image, size=(672, 384), ddepth=cv2.CV_8U)
self.net.setInput(blob)
out = self.net.forward()
return out.reshape(-1, 7)[out[0,0,:,2]>threshold]
def detect_once(self, image):
a = self.detect(image, threshold=0)
a = a[a[:,2].argmax()]
xmin = int(a[3]*image.shape[1])
xmax = int(a[5]*image.shape[1])
ymin = int(a[4]*image.shape[0])
ymax = int(a[6]*image.shape[0])
return image[ymin:ymax, xmin:xmax]
def similarity(self, vec):
a = (self.vec*vec).sum(axis=1)/(np.sqrt(np.sum(self.vec ** 2, axis=1))*np.sqrt(np.sum(self.vec ** 2, axis=1)))
return a.max(), self.names[a.argmax()]
def save(self):
with open('faceVec.pickle', mode='wb') as f:
pickle.dump((self.vec, self.names), f)
if __name__ == '__main__':
detector = Detect()
認識/識別の実行
ハカルスのホームページにある画像で推論を行いました.認識した顔を矩形で囲み,識別モデルにかけて返ってきた特徴ベクトルに近いベクトルが登録されていれば,矩形を緑色にし名前と類似度を矩形の下に表示しました.
元画像
認識画像
認識,識別ともに正しく行われています.認識・識別には0.4秒ほどかかりました.
5. CPUとの比較
NCS2の高速化の効果を検証するため,RasPiのCPUのみ,及びcore i7 6500UのノートPCで実行した場合と比較してみました.OpenVINOでのCPU推論をするにはcv2.dnn.DNN_TARGET_MYRIADをcv2.dnn.DNN_TARGET_CPUに書き換える,と思って書き換えたところ動きませんでした.調べてみると,OpenVINOは現時点でARMは対応しておらず,RasPiのCPUを指定した推論はできないことを確認しました (参照).今回はCPUとの比較のため,caffeモデルを使った顔認識のプログラムをchainerで別途実装し比較しました.
5.1 比較プログラムの実装
入力値について
ここではNCS2とCPUを比較するだけなので,入力画像は500*500の真っ黒な画像を用いました.
OpenVINOでの実装
import cv2
import time
import os
import numpy as np
class Recognize:
def __init__(self):
self.net = cv2.dnn.readNetFromCaffe("model/resnet50_128_caffe/resnet50_128.prototxt", "model/resnet50_128_caffe/resnet50_128.caffemodel")
self.net.setPreferableTarget(cv2.dnn.DNN_TARGET_MYRIAD)
def recognize(self, image):
blob = cv2.dnn.blobFromImage(image, size=(224, 224), ddepth=cv2.CV_8U)
self.net.setInput(blob)
out = self.net.forward().flatten()
return out
def recognize_images(self, images):
res = [self.recognize(x) for x in images]
return np.array(res)
def similarity_matrix(self, images):
vec = self.recognize_images(images)
sm = [[((x-y)**2).sum() for y in vec] for x in vec]
return np.array(sm)
if __name__ == '__main__':
hoge = Recognize()
res = hoge.recognize(np.zeros((500,500,3), dtype=np.uint8))
t = time.time()
res = hoge.recognize(np.zeros((500,500,3), dtype=np.uint8))
print("elapsed time :", time.time()-t)
Chainerでの実装
import cv2
import time
import os
import numpy as np
import chainer.functions as F
import chainer.links as L
from chainer import Variable as V
import chainer
from chainer.links.caffe import CaffeFunction
class Recognize:
def __init__(self):
self.model = CaffeFunction('model/resnet50_128_caffe/resnet50_128.caffemodel')
def inference(self, x):
return self.model(inputs={'data': x}, outputs=['feat_extract'])
def recognize(self, image):
x = cv2.resize(image.astype(np.float32), (224, 224)).transpose((2,0,1))
out = self.inference(x.reshape((1,)+x.shape))[0].data.flatten()
return out
def recognize_images(self, images):
res = [self.recognize(x) for x in images]
return np.array(res)
def similarity_matrix(self, images):
vec = self.recognize_images(images)
sm = [[((x-y)**2).sum() for y in vec] for x in vec]
return np.array(sm)
if __name__ == '__main__':
hoge = Recognize()
res = hoge.recognize(np.zeros((500,500,3), dtype=np.uint8))
t = time.time()
res = hoge.recognize(np.zeros((500,500,3), dtype=np.uint8))
print("elapsed time :", time.time()-t)
5.2 比較結果
結果は以下のようになりました.NCS2を用いた推論はRasPiのCPU推論よりも750倍速くなりました.仮面を被った赤い人も驚きの性能差ですね♪ そもそもRasPi自体がメモリ不足が原因でまともに動かなかった結果,ここまで遅くなった感じです.そしてなんと,Core i7 6500Uを使ったCPU推論の処理時間より2倍ほど速いです.Core i7を積んだPCは安くても5,6万はするのでコスパも最高です.
推論 | 端末 | 実装 | 時間[s] |
---|---|---|---|
NCS2 | RasPi | OpenVINO | 0.084 |
CPU(ARM Cortex-A53) | RasPi | Chainer | 63 |
CPU(core i7 6500U) | ノートPC | OpenVINO | 0.17 |
CPU(core i7 6500U) | ノートPC | Chainer | 0.59 |
6. 結論
今回はRasPiにNCS2を接続してみて,顔認識のプログラムを動かしてみました.NCS2を用いたプログラムの処理時間と,RasPiでの推論がNCS2を用いるとノートPCのCPU推論よりも速くなることが分かりました.今回は顔認識をしましたが他にも様々な学習済みモデルが用意されており,エッジ端末でも手軽に推論をできました.また,pythonで書けるのでこれまでpythonで推論を実装していたエンジニアにとっては実装もしやすいと思われます.GoogleのCoral Edge TPUやNvidiaのJetson nanoなども出てきており,エッジでの推論が今後ますます盛んになりそうで興味深いですね.