/NCS2Guide

How to use NCS2 in Ubuntu

Primary LanguagePython

Neural Compute Stick 2 환경 구성 및 동작

OS

  • Ubuntu LTS 16.04
  • Rasbian Stretch

Raspberry PI 3 환경 설정

라즈베리파이에 라즈비안 스트레치가 설치되어있다고 가정하고 아래 내용을 진행한다. 이 곳을 클릭하여 Raspberry PI 용 OpenVINO를 다운로드 받는다. 이 설치 패키지에는 OpenCV 4.0버전과 추론 엔진 및 샘플 애플리케이션이 포함되어 있다.

기본 설치 경로는 ~/Downloads이다.

설치 후 압축 파일의 압축을 푼다.

tar -xf <l_openvino_toolkit_ie_p_<version>.tgz

설치된 폴더의 절대경로로 setupvars.sh 를 바꾸어 스크립트를 수정한다.

이 과정을 완료하면 OpenVINO를 사용할 준비가 완료 되었고, 환경 변수 설정을 해야한다.

임시로 환경 변수를 설정하기 위해서

source /Downloads/inference_engine_vpu_arm/bin/setupvars.sh

를 입력하지만 재실행 후에도 유지를 하기 위해서 .bashrc파일을 변경해줘야한다. 최상위 디렉토리로 이동 후 vi .bashrc를 이용해 편집한다.

bashrc 파일 최 하단에 환경 변수 설정 코드를 넣어주면 재실행 후에도 유지가 된다.

[setupvars.sh] OpenVINO environment initialized

메시지가 터미널을 실행 시 나타나면 성공적으로 설정된 것이다.

sudo usermod -a -G users "$(whoami)"

현재 리눅스 사용자를 users 그룹에 추가한다, 로그아웃 후 로그인 시 적용된다. Inference를 하기 위해서 Neural Compute Stick 2를 설치(?)한다.

sh inference_engine_vpu_arm/install_dependencies/install_NCS_udev_rules.sh

이 과정을 마치면 Neural Compute Stick 2를 라즈베리파이에서 구동할 준비가 완료된다.

데스크탑(리눅스) 환경 구성

설치 환경은 Ubuntu 16.04 LTS 이다. 이 곳을 클릭하여 OpenVINO 인텔 배포판을 다운로드한다.

기본 설치 경로는 cd ~/Downloads에 설치된다.

tar -zxf l_openvino_toolkit_p_<version>.tgz

압축 파일을 푼다.

cd l_openvino_toolkit_p_<version>
sudo -E ./install_cv_sdk_dependencies.sh

실행파일을 실행하면 종속적인 외부 소프트웨어를 자동으로 다운로드하고 설치한다. OpenVINO 핵심 구성 요소를 설치하기 위해 실행 파일을 실행한다.

sudo ./install_GUI.sh
sudo ./install.sh

이 두가지 명령의 차이점은 설치를 GUI 환경으로 하느냐, 텍스트 환경에서 하느냐의 차이가 유일하다.

이 과정을 완료하면 cd /opt/intel/openvino 경로에 openvino파일이 생긴다고 공식 문서에는 설명이 되어 있지만, /opt/intel/computer_vision_sdk라고 설치가 되었다.

OpenVINO 애플리케이션을 컴파일하고 실행하려면 환경 변수들을 업데이트 해야한다.

source /opt/intel/computer_vision_sdk/bin/setupvars.sh

를 통해 설정할 수 있고, 이 소스 역시 bashrc파일에 추가하면 재실행 시에도 환경이 유지된다.

Model Optimizer

데스크탑에서 설계한 모델을 라즈베리파이에서 구동하기 위해서 일련의 변환 작업이 필요하다. 여기서는 모델의 체크포인트를 저장한 .ckpt파일이 있다고 가정한다. 먼저 이 .ckpt 파일을 .pb 파일로 변환해 주는 작업이 필요하다. 이 변환하는 방법은 여러가지가 있지만, tensorflow에서 제공하는 함수를 이용하기로 한다.

from tensorflow.python.framework import graph_io
...
...
frozen = tf.graph_util.convert_variables_to_constants(sess, sess.graph_def, ["hypothesis"])
graph_io.write_graph(frozen, './saved/model/', 'inference_graph_char.pb', as_text=False)

tf.graph_util.convert_variables_to_constants 함수에서 변수들을 동결시킨다. 마지막 인자로 들어간 ["hypothesis"]는 출력 값의 name_scope를 지정해 준 것이다. 자세한 사항은 동봉된 TypeTest.py파일의 41번째 줄 및 191번째 줄을 참고하길 바란다. graph_io.write_graph 함수의 인자로 동결된 값, 저장 장소, 저장 파일명, as_text 가 들어가는데 as_textFalse로 주었다. (용도는 잘 모르겠다.)

이 과정에서 오류가 발생한다면 아마 name 지정 문제가 가장 크리라 생각된다.

.pb 파일을 다시한번 변환을 해주어야 라즈베리파이에서 사용이 가능하다. .xml파일과 .bin파일로 다시 변환을 해줘야하는데, .xml파일은 그래프의 구조를 저장한 것이고, .bin파일은 그래프에서 사용하는 weight를 저장한 파일이다.

이 변환 과정을 Intermediate Representation (IR)라고 하고, 직역하면 중간 표현으로 변환한다는 뜻이다. 다운로드 받은 OpenVINO Toolkit의 model_optimizer를 여기서 사용한다.

먼저 model_optimizer의 경로로 찾아 들어간다.

cd /opt/intel/computer_vision_sdk/deployment_tools/model_optimizer
extensions             mo_onnx.py              requirements_onnx.txt
install_prerequisites  mo.py                   requirements_tf.txt
mo                     mo_tf.py                requirements.txt
mo_caffe.py            requirements_caffe.txt  tf_call_ie_layer
mo_kaldi.py            requirements_kaldi.txt  version.txt
mo_mxnet.py            requirements_mxnet.txt

model_optimizer파일의 내부이다. mo.py를 이용한 변환 전에 설치해야 할 것이 있다. cd install_prerequisites로 들어가서 ./install_prerequisites.sh로 구성 요소들을 설치한다. tensorflow만 이용 시 ./install_prerequisites_tf.sh만 이용해도 무방할 것으로 생각된다.

권장되는 사항은 /mo/utils/summarize_graph.py를 이용해 그래프의 구조를 재확인 하는 것이다.

sudo python3 summarize_graph.py --input_model /home/leehanbeen/PycharmProjects/TypeClassifier/inference_graph_type.pb
1 input(s) detected:
Name: input, type: float32, shape: (-1,64,128,3)
1 output(s) detected:
hypothesis

위에서 나오는 대로 IR로 변환 시 입력 매개변수에 넣어줘야한다. 만약 여기서 dropout같이 의도하지 않은 입력이 출력되면 모델을 수정을 해야한다. dropout 같은 것에 name을 지정해주면 이를 입력으로 인식하는 듯하다.

다시 model_optimizer 폴더로 들어와서 변환을 수행해준다.

sudo python3 mo_tf.py --input_model /home/leehanbeen/PycharmProjects/TypeClassifier/inference_graph_type.pb --input_shape "[1, 64, 128, 3]" --input "input"

--input_model 매개변수는 .pb파일의 경로, --input_shape 매개변수는 input의 shape을 넣어줘야하는데, 여기서 모델 훈련 시 placeholderNone을 넣었지만, 변환 시에는 1로 넣어줘야 한다. --input 매개변수는 입력 placeholder의 name을 넣어주면 된다. 기타 매개변수 사용은 이 곳을 참조하면 된다.

또 주의할 것이 흑백 이미지를 입력으로 받을 시 placeholder를 [None, H, W]와 같이 Channel을 지정하지 않는 경우가 있는데, 이럴 경우 변환이 안되는 것 같다. 따라서 모델 훈련 시 [None, H, W, C] 형태로 훈련하는 것이 나으리라 생각된다.

[ SUCCESS ] Generated IR model.
[ SUCCESS ] XML file: /opt/intel/computer_vision_sdk_2018.5.455/deployment_tools/model_optimizer/./inference_graph_type.xml
[ SUCCESS ] BIN file: /opt/intel/computer_vision_sdk_2018.5.455/deployment_tools/model_optimizer/./inference_graph_type.bin
[ SUCCESS ] Total execution time: 2.41 seconds.

이런 메시지가 뜨면 성공적으로 변환된 것이고, 저장되는 기본 디렉토리는 /opt/intel/computer_vision_sdk/deployment_tools/model_optimizer이다.

만약 변환 시 오류가 발생한다면 이 곳을 참고하면 된다.

이제 데스크탑에서의 작업은 모두 완료되었다. 여기서 생성한 .xml, .bin 파일을 USB등을 통해 Raspberry PI에 옮겨서 소스를 작성할 것이다.

Raspberry PI를 이용한 Inference

Inference를 하는 방법은 Inference Engine을 OpenCV에서 백엔드를 사용할 수도 있고, IE를 직접 사용할수도 있다. IE를 직접 사용하는 방법은 이 곳 을 참고하면 된다. IE를 OpenCV에서 사용할 때와 직접 사용할때의 장단점이 모두 존재한다. 직접 사용할 경우 성능 상의 이득을 볼 수 있지만, 사용 방법이 OpenCV를 이용할 때보다 약간 복잡하고 (사용해보니 복잡하지 않았다.) 오직 model_optimizer(.xml+.bin)로 변환된 파일만 사용이 가능하다. IE를 OpenCV에서 백엔드로 사용할 때, 사용 방법은 상당히 간단하고, 다양한 딥러닝 프레임워크에서 작성한 모델 포맷을 로드할 수 있다.

결국 편리성과 성능의 Trade off 라고 볼 수 있다.

cv2의 dnn 클래스는 프롬프트에서 pip 등을 이용한 설치로는 사용할 수 없다. 위 과정을 제대로 수행하였으면 dnn 모듈의 사용은 문제가 없을 것이다. 출처

  1. IE를 OpenCV의 백엔드로서 사용

위 과정이 모두 성공적으로 종료되면 나머지는 간단하다.

import tensorflow as tf
import cv2
import numpy as np
BIN_PATH = '/home/pi/Downloads/inference_graph_type.bin'
XML_PATH = '/home/pi/Downloads/inference_graph_type.xml'
IMAGE_PATH = '/home/pi/Downloads/plate(110).jpg_2.jpg' #naming miss.. :(

여기서 OpenCV의 버전은 4.0.0 이상이여야 한다.

net = cv2.dnn.readNet(XML_PATH, BIN_PATH)
net.setPreferableTarget(cv2.Dnn.DNN_TARGET_MYRIAD)

readNet의 매개변수로 .xml.bin파일의 경로를 잡아준다. setPreferableTarget으로 기기를 인식하는 것으로 보인다.

frame = cv2.imread(IMAGE_PATH)
frame = cv2.resize(frame, (128, 64))
blob = cv2.dnn.blobFromImage(frame, size=(128, 64), ddepth=cv2.CV_8U)

입력에 맞게 이미지를 전처리를 수행해준다. 네트워크에 들어가기 위해서 이미지를 blob이라는 형태로 변경해주는 것으로 보인다. blob의 shape은 (1, 3, 64, 128)로 (size, C, H, W) 형태로 들어가는 듯 하다. OpenCV는 다양한 데이터 타입을 지원하지만 blobFromImage는 CV_8U 혹은 CV_32F 두가지 형태만 가능하다. 하지만 추론에는 영향을 미치지 않는 것으로 보인다.

net.setInput(blob)
out = net.forward()

입력을 넣고 출력을 얻는다. net.forward() 메소드로 간단하게 결과를 얻을 수 있다.

전체 소스코드는 아래와 같다.

import tensorflow as tf
import cv2
import numpy as np
BIN_PATH = '/home/pi/Downloads/inference_graph_type.bin'
XML_PATH = '/home/pi/Downloads/inference_graph_type.xml'
IMAGE_PATH = '/home/pi/Downloads/plate(110).jpg_2.jpg' #naming miss.. :(
net = cv2.dnn.readNet(XML_PATH, BIN_PATH)
net.setPreferableTarget(cv2.Dnn.DNN_TARGET_MYRIAD)
frame = cv2.imread(IMAGE_PATH)
frame = cv2.resize(frame, (128, 64))
blob = cv2.dnn.blobFromImage(frame, size=(128, 64), ddepth=cv2.CV_8U)
net.setInput(blob)
out = net.forward()
out = out.reshape(-1)
print(out)
print(np.max(out))
print(np.argmax(out))

실행 결과

[0.0128479 0.2097168 0.76416016 0.00606918 0.00246811 0.00198746 0.00129604 0.00117588]
0.76416016
2

의도한 출력이 생성되었다.

  1. Inference Engine을 직접 사용
from openvino.inference_engine import IENetwork, IEPlugin
import numpy as np
import cv2
import time

inrerence_engine의 IENetwork와 IEPlugin을 import 한다.

print(cv2.__version__)
s_time = time.time()
net = IENetwork(XML_PATH, BIN_PATH)
plugin = IEPlugin(device='MYRIAD')
exec_nt = plugin.load(net)

모델을 설정하고, NCS2 디바이스에 모델을 로드한다.

net_load_time = time.time()

frame = cv2.imread(IMAGE_PATH)
frame = cv2.resize(frame, (128, 64))
blob = cv2.dnn.blobFromImage(frame, size=(128, 64), ddepth=cv2.CV_32F)

preprocess_time = time.time()

위와 동일한 이미지 전처리를 해준다.

res = exec_nt.infer({'input': blob})

추론하는 부분이다. input이 Dictionary형태로 들어간다.

전체 소스코드

from openvino.inference_engine import IENetwork, IEPlugin
import numpy as np
import cv2
import time


BIN_PATH = '/home/pi/Downloads/inference_graph_type.bin'
XML_PATH = '/home/pi/Downloads/inference_graph_type.xml'

IMAGE_PATH = '/home/pi/Downloads/plate(114).jpg_1.jpg'

print(cv2.__version__)
s_time = time.time()

net = IENetwork(XML_PATH, BIN_PATH)
plugin = IEPlugin(device='MYRIAD')
exec_nt = plugin.load(net)

net_load_time = time.time()

frame = cv2.imread(IMAGE_PATH)
frame = cv2.resize(frame, (128, 64))
blob = cv2.dnn.blobFromImage(frame, size=(128, 64), ddepth=cv2.CV_32F)

preprocess_time = time.time()


res = exec_nt.infer({'input': blob})

inference_time = time.time()

print(res['hypothesis'])
print(np.argmax(res['hypothesis']))
print(np.max(res['hypothesis']))

print("---------------------")
print("Network Loading Time: %s" % (net_load_time - s_time))
print("Image Preprocessing Time: %s" % (preprocess_time - net_load_time))
print("Inference Time: %s" % (inference_time - preprocess_time))

결과 화면

4.0.1-openvino
[[0.15551758 0.70458984 0.1303711 0.00165462 0.001620217 0.00432587
0.00094889 0.00092173]]
1
0.70458984
---------------------
Network Loading Time: 0.045161008834839
Image Preprocessing Time: 0.05246901512145996
Inference Time: 0.01130819320678711

이 곳에 질문을 올렸었는데, 성능차이가 있다고는 했지만 생각보다 상당히 많이 성능차이가 발생했다.

  • 문제1: 추론은 성공적으로 진행되었으나, inference 시간이 너무 긴 문제. 약 4.7초 19.03.18
  • 해결: OpenCV에서 백엔드로 IE를 사용하지 않고, 직접 IE를 사용하였다. 19.03.20
  • 문제2: 컬러 이미지에 대한 추론은 굉장히 빠르게 진행되나 (40, 40, 1) 이미지 추론을 하지 못하고 있다. 19.03.20
    • 검색을 해보니 USB에 대해 extension cable을 사용할 경우 비슷한 문제가 발생한 경우를 볼 수 있었는데, 두 모델 중 하나는 정상적으로 작동하고 하나는 제대로 작동하지 않는 것이 신기하다.
      • inception 모델로 테스트 해볼 것. 19.03.20
  • 해결: Image Size가 너무 작거나 모델의 복잡도가 낮으면 추론이 되질 않았다. Input size를 성공 Case와 동일하게 맞추었더니 정상구동 확인