この記事はOpenCV Advent Calendar 2022の2日目の記事です。
概要
今年は「OpenCVではじめよう ディープラーニングによる画像認識」(技術評論社)という書籍を出版しました。OpenCVのdnnモジュールを利用してディープラーニングによる画像認識を手軽に試してみようという内容になっています。(宣伝)
この記事では書籍には間に合わなかったOpenCVのdnnモジュールのちょっとした問題の修正や問題を回避する方法を紹介します。あわせて、書籍では省略したPyTorchからONNXへエクスポートする方法などを軽く紹介します。
サンプルプログラム
サンプルプログラムのノートブックは以下で公開しています。
Google Colaboratoryですぐに動かして試すことができます。
PyTorch(torchvision)のクラス分類の学習済みモデルをONNXにエクスポートする
torchvisionの学習済みモデルをONNXにエクスポートします。
まずはtorch
、torchvision
をインポートします。
# インポート
import torch
import torchvision
from torchvision.models import mobilenet_v3_large, MobileNet_V3_Large_Weights
torchvision.models
からモデルを重み付きで取得します。
torchvision v0.12.0まではpretrained=Trueという引数を渡していましたが、v0.13.0以降は仕様が変更されたようです。
ここではMobileNet V3というクラス分類のモデルを利用しています。
# 学習済みモデルの取得
weights = torchvision.models.MobileNet_V3_Large_Weights.DEFAULT
model = torchvision.models.mobilenet_v3_large(weights=weights)
# model = torchvision.models.mobilenet_v3_large(pretrained=True) # 非推奨
torch.onnx.export()
にダミーの入力テンソルなどを指定してONNXにエクスポートします。
入力テンソルの形状は各モデルのページに記載があります。
# 学習済みモデルをONNXにエクスポート
dummy_tensor = torch.randn((1, 3, 224, 224))
torch.onnx.export(model, dummy_tensor, 'mobilenet_v3_large.onnx', verbose=True, opset_version=13, input_names=['input'], output_names=['output'])
ラベルのリストはtorchvision.models.weights.meta['categories']
に含まれています。
テキストファイルに書き出しておきましょう。
# ラベルリストを保存
with open('imagenet.names','w') as f:
f.writelines([f"{label}\n" for label in weights.meta['categories']])
モデルの構造を確認する
出力されたONNXをNetronで可視化します。
以下の画像はネットワークの最終層付近の構造です。
Softmaxが含まれていないことが今回のポイントです。
OpenCVのdnnモジュールで推論する
エクスポートしたONNXをOpenCVのdnnモジュールで推論してみます。(一部省略)
詳しくは書籍「OpenCVではじめよう ディープラーニングによる画像認識」を参照してください。
# インポート
import cv2
# 画像を読み込む
image = cv2.imread('yorkie.jpg')
# モデルを読み込む
model = cv2.dnn_ClassificationModel('mobilenet_v3_large.onnx')
# モデルの入力パラメーターを設定する
scale = 1.0 / 255.0
size = (224, 224)
mean = tuple(map(lambda x: x * 255.0, (0.406 , 0.456, 0.485)))
swap = True
crop = True
model.setInputParams(scale, size, mean, swap, crop)
# クラス分類して信頼度が最も高いクラスを取得する
class_id, confidence = model.classify(image)
# 結果を表示する
top_1 = f"{class_id} {classes[class_id]} ({confidence:.3f})"
print(top_1)
とても可愛いヨークシャーテリアの画像なので「187 Yorkshire terrier」と正しく推論できています。
信頼度の出力範囲を[0.0-1.0]にする
ここからがこの記事の本題です。
OpenCVのdnnモジュールのクラス分類の出力結果を見るとTop1のラベルは正しそうです。
しかし、信頼度が[0.0-1.0]の範囲の値になっておらず、結果が信頼できるのか判断できません。
これは後処理にSoftmaxが組み込まれていないことが原因です。
187 Yorkshire terrier (8.077)
OpenCV 4.5.5のcv2.dnn_ClassificationModel()ではモデルにSoftmaxレイヤーが組み込まれていることを前提としていました。しかし、PyTorch(torchvision)のクラス分類のモデルにはSoftmaxが組み込まれていません。
これを解決する方法として2つのアプローチがあります。
解決方法1. cv2.dnn_ClassificationModel.setEnableSoftmaxPostProcessing()
OpenCV 4.6.0ではcv2.dnn_ClassificationModel.setEnableSoftmaxPostProcessing()
が追加されました。True
に設定することで、Softmaxレイヤーが組み込まれていないモデルでもSoftmaxが後処理された結果を得ることができます。
本来であればこの方法を書籍で紹介したかったのですが、この機能を追加するPull Request #21692のMergeが間に合いませんでした。ちなみにこれは私が実装しました。書籍の執筆を通してこうした不足している機能の追加やバグの修正を行えたのはよかったです。
1-1. OpenCVのdnnモジュールでSoftmaxの後処理を有効に設定して推論する
次の一行を追加して実行してみましょう。
model.setEnableSoftmaxPostProcessing(True)
とすることで後処理としてSoftmaxされた結果が出力されるようになります。
# 後処理のSoftmaxを有効にする
model.setEnableSoftmaxPostProcessing(True)
結果を確認すると信頼度の値が[0.0-1.0]の範囲になっていることがわかります。
これでどれくらい結果が信頼できるのか判断ができるようになりました。
解決方法2. ONNXにSoftmaxレイヤーを組み込む
さて、お気づきのようにモデルの最終層にSoftmaxレイヤーが含まれていれさえすればこの問題は解決しますね。(脳筋発想
ONNXにSoftmaxレイヤーを挿入する編集をしましょう。
最近ではPINTOさんがsimple-onnx-processing-toolsというツールを作成されています。
この記事ではこのツールを使ってONNXを編集してSoftmaxレイヤーを挿入します。
(昔はJSONに変換して編集したりしてた記憶があるけど、ツールが充実してきてずいぶん簡単になりました。)
simple-onnx-processing-toolsは様々なツールが含まれています。
この中からオペレーターを生成するsog4onnxとモデルを結合するsnc4onnxを使います。
sog4onnxでSoftmaxのみのONNXを生成、snc4onnxで対象のモデルの最終層にSoftmaxのみのONNXを結合します。
2-1. SoftmaxのみのONNXを生成(sog4onnx)
まずは、sog4onnxでSoftmaxのみのONNXを生成します。
オペレーターの一覧と対象のモデルの情報を確認しながら以下のオプションを指定します。
オペレーターの名前や入出力の名前は対象のモデルと被らないようにしておきましょう。
- --op_type:オペレーターの種類
- --opset:オペレーターのバージョン
- --op_name:オペレーターの名前
- --input_variables:オペレーターの入力(名前、型、形状)
- --output_variables:オペレーターの出力(名前、型、形状)
- --output_onnx_file_path:出力ファイルのパス
- --non_verbose:エラーログのみの表示
# SoftmaxのみのONNXを生成
sog4onnx --op_type Softmax --opset 13 --op_name Softmax --input_variables input_softmax float32 [1,1000] --output_variables output_softmax float32 [1,1000] --output_onnx_file_path Softmax.onnx --non_verbose
このようなSoftmaxのみのONNXが生成されます。
2-2. モデルの最終層にSoftmaxのみのONNXを結合(snc4onnx)
つぎに、snc4onnxで対象のモデルの最終層にsna4onnxで生成したSoftmaxのみのONNXを結合します。
モデルの情報を確認しながら以下のオプションを指定します。
- --input_onnx_file_path:入力ファイルのパス
- --srcop_destop:結合元と結合先の入出力の名前(input1 output1 input2 output2 ...の並び順で指定)
- --output_onnx_file_path:出力ファイルのパス
# モデルを結合する
snc4onnx --input_onnx_file_path mobilenet_v3_large.onnx Softmax.onnx --srcop_destop output input_softmax --output_onnx_file_path mobilenet_v3_large_with_softmax.onnx
2-3. Softmaxレイヤーを組み込んだモデルの構造を確認する
さて、Softmaxレイヤーを組み込んだモデルの構造をNetronで確認してみましょう。
正しくレイヤーが挿入されていることが確認できます。
2-4. OpenCVのdnnモジュールでSoftmaxの後処理を無効に設定して推論する
それでは、Softmaxレイヤーを組み込んだモデルを使ってOpenCVのdnnモジュールで推論してみます。
# モデルを読み込む
# model = cv2.dnn_ClassificationModel('mobilenet_v3_large.onnx') # Softmaxなし
model = cv2.dnn_ClassificationModel('mobilenet_v3_large_with_softmax.onnx') # Softmaxあり
このとき、model.setEnableSoftmaxPostProcessing(False)
でcv2.dnn_ClassificationModel
の後処理を無効にしておきます。(デフォルトでOFFです)
# 後処理のSoftmaxを無効にする
model.setEnableSoftmaxPostProcessing(False)
信頼度が[0.0-1.0]の範囲の値になっていることが確認できます。
まとめ
この記事では書籍「OpenCVではじめよう ディープラーニングによる画像認識」に掲載するには間に合わなかった、OpenCVのdnnモジュールのクラス分類を利用したときの信頼度の範囲についての問題と解決する方法を紹介しました。手軽に使えるOpenCVのdnnモジュールを是非使ってみてください。
明日のOpenCV Advent Calendar 2022はKazuma_Kikuyaさんの「【画像認識】AIひろゆきがスプラトゥーン3のキルデス報告してくれるアプリを作った話」です。