前回の記事に引き続きOpenCV4.0の新機能を紹介します。今回は待望のONNXフォーマット対応を試してみます。
また、OpenCVのDeep Learning関連機能については昔の記事に書いてあります。基本的な使い方はこの頃からあんまり変わってないので参考までに。
ONNXとは
ONNX is a open format to represent deep learning models. With ONNX, AI developers can more easily move models between state-of-the-art tools and choose the combination that is best for them. ONNX is developed and supported by a community of partners.
ONNX(Open Neural Network Exchange)はニューラルネットワークのモデルを定義するためのオープンフォーマットの一つで、様々なDNNフレームワークで作成したモデルをONNXフォーマットで出力することで相互利用することができます。OpenCVでは既にCaffeやTensorFlow等のモデルを読み込みできましたが、OpenCV4.0ではONNXフォーマットもサポートされました。
環境
* CentOS 7.5 (x86_64/Xeon 8core CPU/16GB RAM)
* Python 3.6.5 (Anaconda custom)
使い方
これまでのOpenCV関連記事では基本的にC++から使っていましたけど、たまにはPythonだけでさくっと試してみます。まずは適当なONNXモデルをModel Zooから探します。
ここでは動作確認しやすい画像分類用モデル(ImageNet ILSVRC2012)であるResNet-50のCNNモデルで試してみます。必要なファイルは上記のリポジトリからダウンロードできます。
* 入力画像 (beer.jpg みなとみらいのオクトーバーフェストの時の写真)
* ONNXファイル
## ResNet-50のONNXファイルは98MB $ ls -lh resnet50/model.onnx -rw-r--r-- 1 nobody nobody 98M 9月 20 03:56 resnet50/model.onnx
* 実装 (read_onnx.py)
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import numpy as np
import cv2
def preprocess(img_data):
''' 画像データのスケーリング/正規化 '''
mean_vec = np.array([0.485, 0.456, 0.406])[::-1]
stddev_vec = np.array([0.229, 0.224, 0.225])[::-1]
norm_img_data = np.zeros(img_data.shape).astype('float32')
for i in range(img_data.shape[2]):
norm_img_data[:,:,i] = (img_data[:,:,i]/255 - mean_vec[i]) / stddev_vec[i]
return norm_img_data
if __name__ == '__main__':
try:
## 画像ファイルの読み込み
file_name = 'beer.jpg'
input_image: int = cv2.imread(file_name)
if input_image is not None:
## 画像のリサイズ
resized = cv2.resize(input_image, (224, 224))
## 画像のスケーリング/正規化
preprocessed = preprocess(resized)
## Blob形式に変換(行列形状の変換)
blob = cv2.dnn.blobFromImage(preprocessed)
print(blob.shape)
## ONNXモデルファイルの読み込み
model_file = 'resnet50/model.onnx'
net = cv2.dnn.readNetFromONNX(model_file)
## 入力画像データの指定
net.setInput(blob)
## フォワードパス(順伝播)の計算 & 不要な次元の削除
pred = np.squeeze(net.forward())
print(pred.shape)
## ImageNet(ILSVRC2012)のカテゴリ定義ファイルの読み込み
rows = open("synset.txt").read().strip().split("\n")
classes = [r[r.find(" ") + 1:].split(",")[0] for r in rows]
## 推論結果から信頼度の高い順にソートして上位5件のカテゴリ出力
indexes = np.argsort(pred)[::-1][:5]
for i in indexes:
text = "{}: {:.5f}%".format(classes[i], pred[i] * 100)
print(text)
else:
print('can\'t read image')
except cv2.error as e:
print(e)
* 出力結果
(1, 3, 224, 224) ## (batch size x channels x height x width) (1000,) beer glass: 99.98% goblet: 0.02% eggnog: 0.00% beer bottle: 0.00% saltshaker: 0.00%
出力結果を見る限り特に問題は無さそうです。前処理としての入力画像の正規化や、カテゴリ定義ファイルの読み込み処理等を省いて見てみると、ONNXモデルの入力と推論処理は簡潔に書けることがわかります。ONNXファイルの読み込みはcv2.dnn.readNetFromONNX
メソッドを使うだけです。以前のようにImporterオブジェクトを作る必要も無いので楽ですね。また、前処理部分のコードはModel Zooで紹介されているものをOpenCVのデータ形式と合うように微修正したものを利用しています。一応、cv2.dnn.blobFromImage
メソッドでもスケーリング処理等の前処理用オプションが提供されていますが、ここでは手動でスケーリング/正規化処理をしています。
ついでにResNet-50だけではなくShuffleNetも試してみます。こちらもModel Zooからダウンロードできます。
ShuffleNetはエッジデバイスでの効率的な推論処理を実現できる軽量なモデルアーキテクチャの一つです。ONNXファイル比でResNet-50の約18分の1のサイズで収まっています。
## ShuffleNetのONNXファイルは5.5MB $ ls -lh shufflenet/model.onnx -rw-r--r-- 1 nobody nobody 5.5M 9月 20 03:56 shufflenet/model.onnx
ソースコードの変更は読み込むONNXファイル名を変えるだけです。
< model_file = 'resnet50/model.onnx' --- > model_file = 'shufflenet/model.onnx'
* 出力結果
(1, 3, 224, 224) (1000,) 1.0000018063240619 beer glass: 99.37% goblet: 0.44% eggnog: 0.09% vase: 0.03% pitcher: 0.01%
分類精度はResNet-50と比べても遜色ないレベルですね。軽量で推論処理も高速なので、久しぶりにRaspberry Pi等のデバイスで動かしてみようかなという気になってきます。
おわりに
近年のOpenCVは機械学習関連の機能を充実させていますが、ONNXフォーマットに対応したことで様々なDNNフレームワークとの併用がより推進されそうです。以前の記事でも書きましたが、個人的にOpenCV3.xを使う機会に恵まれなかった分、OpenCV4.0はどんどん使い倒したいと思います。
前述のサンプルコードはGitHubにも上げてあります。今回は珍しくPython版のみでしたけどC++版も後ほど作って上げておこうと思います。
あと、あけましておめでとうございます。今年もほどほどにがんばろう。