【Python】あれ？ dnnモジュールのONNX推論速くねえ？いや、やっぱ遅えかも。。。 ※2023/12/05時点

:::note info
この記事はOpenCV Advent Calendar 2023の6日目の記事です。
:::

2021年のAdvent Calendarで以下のような投稿をしました。
この投稿は、ある意味続編です🦔

https://qiita.com/Kazuhito/items/beaf01bf47893805cc03

# 結論
速、、、いや、やっぱ遅いかも（[ONNXランタイム](https://github.com/microsoft/onnxruntime)と比較して）
※1：CPU推論
※2：モデルによっては速い

# バージョン
以下のバージョンで確認しました。

```bash
pip show opencv-python

Name: opencv-python
Version: 4.8.1.78
(省略)
```

```bash
pip show onnxruntime

Name: onnxruntime
Version: 1.16.3
(省略)
```

# 計測環境
Google Colaboratory上でCPU推論を行い「%%time」や「%%timeit」を使用して推論時間を計測しています。
（本来はもっと厳密な方法と環境で計測すべきでしょうが、、、ご容赦を。。。🙇

<details><summary>参考：CPU情報</summary><div>

```bash
!cat /proc/cpuinfo

processor	: 0
vendor_id	: GenuineIntel
cpu family	: 6
model		: 79
model name	: Intel(R) Xeon(R) CPU @ 2.20GHz
stepping	: 0
microcode	: 0xffffffff
cpu MHz		: 2199.998
cache size	: 56320 KB
physical id	: 0
siblings	: 2
core id		: 0
cpu cores	: 1
apicid		: 0
initial apicid	: 0
fpu		: yes
fpu_exception	: yes
cpuid level	: 13
wp		: yes
flags		: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc cpuid tsc_known_freq pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch invpcid_single ssbd ibrs ibpb stibp fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm rdseed adx smap xsaveopt arat md_clear arch_capabilities
bugs		: cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs taa mmio_stale_data retbleed
bogomips	: 4399.99
clflush size	: 64
cache_alignment	: 64
address sizes	: 46 bits physical, 48 bits virtual
power management:

processor	: 1
vendor_id	: GenuineIntel
cpu family	: 6
model		: 79
model name	: Intel(R) Xeon(R) CPU @ 2.20GHz
stepping	: 0
microcode	: 0xffffffff
cpu MHz		: 2199.998
cache size	: 56320 KB
physical id	: 0
siblings	: 2
core id		: 0
cpu cores	: 1
apicid		: 1
initial apicid	: 1
fpu		: yes
fpu_exception	: yes
cpuid level	: 13
wp		: yes
flags		: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc cpuid tsc_known_freq pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch invpcid_single ssbd ibrs ibpb stibp fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm rdseed adx smap xsaveopt arat md_clear arch_capabilities
bugs		: cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs taa mmio_stale_data retbleed
bogomips	: 4399.99
clflush size	: 64
cache_alignment	: 64
address sizes	: 46 bits physical, 48 bits virtual
power management:
```
</div></details>

# ONNXランタイム VS OpenCV DNNモジュール VGG16推論時間比較
CPUで推論した場合、ぼちぼち推論時間がかかるVGG16で比較してみます。
VGG16のONNXモデルは、[ONNX Model Zoo](https://github.com/onnx/models) の [vgg16-7.onnx](https://github.com/onnx/models/raw/main/vision/classification/vgg/model/vgg16-7.onnx) を使用しました👀 

蜂(Bee)の画像で推論してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140207/8591e280-9d0d-c2dc-1e25-37d9901f2ff3.png)
※[ぱくたそ](https://www.pakutaso.com/)の画像を使用

以降のスクリプトは、[Qiita-AdventCalendar-OpenCV-20231206-01.ipynb](https://drive.google.com/file/d/1O__KuARMwgwxct-gmVtf0af2DDcQaQ7W/view?usp=sharing)をColaboratoryで。

#### ONNXランタイム
モデルをロードして

```python
import onnxruntime
onnx_session = onnxruntime.InferenceSession('vgg16-7.onnx', providers=['CPUExecutionProvider'])
```
画像の前処理をして

```python
import cv2 as cv
import numpy as np

# 前処理
input_image = cv.resize(image, dsize=(224, 224))
input_image = cv.cvtColor(input_image, cv.COLOR_BGR2RGB)

mean = [0.485, 0.456, 0.406]
std = [0.229, 0.224, 0.225]
input_image = (input_image / 255 - mean) / std

input_image = input_image.transpose(2, 0, 1).astype('float32')
input_image = input_image.reshape(-1, 3, 224, 224)

# 入力名・出力名取得
input_name = onnx_session.get_inputs()[0].name
output_name = onnx_session.get_outputs()[0].name
```
推論時間計測します。

```python
%%timeit -r 7 -n 10

# 時間計測
onnx_session.run([output_name], {input_name: input_image})
```
実験時は559msと出ました👀
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140207/bb67123c-08f0-53dd-a5dc-8b788e65eeba.png)

上位5クラスの推論結果を出してみます。

```python
result = np.array(result).squeeze()
print(np.argsort(result)[::-1][:5])
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140207/92d686ff-ca8f-3aaa-eb21-29396b58a380.png)
あっていそうですね👀
309：Bee(蜂)
308：fly(蝿)
94：hummingbird(ハチドリ)
95：jacamar(キツツキ目の鳥(キリハシ))
946：cardoon(菊科の植物(カルドン))

#### OpenCV dnnモジュール
モデルをロードして、バックエンドターゲットをCPUにして

```python
import cv2 as cv
net = cv.dnn.readNet('vgg16-7.onnx')
net.setPreferableTarget(cv.dnn.DNN_TARGET_CPU)
```
画像の前処理をして

```python
import numpy as np

# 前処理
input_image = cv.resize(image, dsize=(224, 224))
input_image = cv.cvtColor(input_image, cv.COLOR_BGR2RGB)

mean = [0.485, 0.456, 0.406]
std = [0.229, 0.224, 0.225]
input_image = (input_image / 255 - mean) / std
input_image = input_image.astype('float32')

input_blob = cv.dnn.blobFromImage(input_image)
out_layer_name = net.getUnconnectedOutLayersNames()

# 入力BLOB設定
net.setInput(input_blob, 'data')
```
推論時間計測します。

```python
%%timeit -r 7 -n 10

# 時間計測
net.forward(out_layer_name)
```
472msと出ました👀
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140207/3980ba39-cb84-993c-da0b-3a5fab6d511e.png)


上位5クラスの推論結果を出してみます。

```python
result = np.array(result).squeeze()
print(np.argsort(result)[::-1][:5])
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140207/df920aef-fcda-144d-1215-bcb4a5e20d00.png)
あっていそうですね👀
309：Bee(蜂)
308：fly(蝿)
94：hummingbird(ハチドリ)
95：jacamar(キツツキ目の鳥(キリハシ))
946：cardoon(菊科の植物(カルドン))

#### ONNXランタイム VS OpenCV dnnモジュール
dnnモジュールでの推論のほうが、速いですね👀
1～2割高速化できるって感じですかね。期待🦔

| モデル | ONNXランタイム | dnnモジュール |
|:-:|:-:|:-:|
| [VGG16](https://github.com/onnx/models/blob/master/vision/classification/vgg/model/vgg16-7.onnx?raw) | 559ms | 472ms(84.4%) |

#### Winogradアルゴリズムによる高速化
「OpenCVのDNNモジュールが速いのは、Winogradアルゴリズム（若干の精度低下と引き換えにCNNの乗算回数を減らすアルゴリズム）による高速化がデフォルトで有効化されているから」と小耳にはさみました👀
と言うわけで、Winogradを無効化して試してみます。

```python
net.enableWinograd(False)
```
```python
%%timeit -r 7 -n 10

# 時間計測
net.forward(out_layer_name)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140207/28ad7520-176c-6a6c-b06b-82fee00a3396.png)

```python
net.enableWinograd(True)
```
```python
%%timeit -r 7 -n 10

# 時間計測
net.forward(out_layer_name)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140207/1c763b83-99a9-07a8-7cb6-9f5e85a7575f.png)

うーん？
APIの使い方間違った🤔？
それとも、バグってる？
要調査。

2023/12/06追記：
　dandelionさんの調査を教えてもらいました🦔
 　根が深そうな雰囲気が出てきた。。。👀　
 　![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140207/a201022a-2ede-01d5-24df-de60b8795d3f.png)

2023/12/10追記：
　dandelionさんの調査を教えてもらいました🦔
　Ubuntuさんどーなってんの😇
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140207/c3454b8c-bc94-0832-9615-1d75287ca605.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140207/b7611677-ba00-b3e6-e5af-ebc4d68f534c.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140207/747a4c7f-5c60-a9ee-7c80-862be1053b19.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140207/ae7ca188-27b6-6b3f-1669-a479e0daf0c1.png)



# ONNXランタイム VS OpenCV DNNモジュール その他いくつかのモデルの推論時間比較
VGG16では期待感ありましたが、、、
他のモデルだと結果がふるわず、、、👀

| モデル | ONNXランタイム | dnnモジュール |
|:-:|:-:|:-:|
| [MobileNetV2](https://github.com/onnx/models/raw/main/vision/classification/mobilenet/model/mobilenetv2-7.onnx) | 15.3ms | 29.4ms(192.1%) |
| [ResNet50V1](https://github.com/onnx/models/raw/main/vision/classification/resnet/model/resnet50-v1-12.onnx) | 124ms | 160ms(129.0%) |
| [YOLOv4](https://github.com/onnx/models/raw/main/vision/object_detection_segmentation/yolov4/model/yolov4.onnx) | 1.38s | 1.73s(125.4%) |
| [YOLOX-S](https://github.com/opencv/opencv_zoo/raw/main/models/object_detection_yolox/object_detection_yolox_2022nov.onnx) | 521ms | 850ms(163.1%) |

上記の計測に使用したスクリプトは、[Qiita-AdventCalendar-OpenCV-20231206-02.ipynb](https://drive.google.com/file/d/1IOk8VZgNey6QyzUZOhGCyGsgD7VzCxEY/view?usp=sharing)で公開しています。

# 試したかったけど OpenCV 4.8.1.78 のdnnモジュールで読みこめなかったモデルたち
#### yolov3-10.onnx
以下のエラーが発生して読み込めず。

```python
error: OpenCV(4.8.1) /io/opencv/modules/dnn/src/onnx/onnx_graph_simplifier.cpp:1257: error: (-210:Unsupported format or combination of formats) Unsupported data type: BOOL in function 'getMatFromTensor'
```

#### MaskRCNN-10.onnx

以下のエラーが発生して読み込めず。

```python
error: OpenCV(4.8.1) /io/opencv/modules/dnn/src/onnx/onnx_importer.cpp:1083: error: (-2:Unspecified error) in function 'handleNode'
```
以上。