[python][openCV]類似画像探索で一致するはずが、しなかった話

# はじめに
車両メータに表示される特定の警告灯を見つけ出す処理を作成する。
その時に準備した画像が一致するはずなのにしなかった件について書く。

## 参考記事
この記事はスーパーマリオの「？ブロック」をプレイ画像の中から探し出す。
これを利用してメータ警告灯の中にエアバッグがあるか判定する。

https://pystyle.info/opencv-template-matching/

## 使用画像
### chr_meter.jpg[探索される画像]
![chr_meter.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632401/b3c15bae-9454-bd28-bddd-a23434c90f01.jpeg)

### airbag.jpg[探索する画像]
![airbag.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632401/85bf7e28-2300-5367-3536-7e6c06189244.jpeg)

:::note warn
大きいですね、この地点で何が起こるかわかっても、そっと見守ってください
:::

## 動作環境
- windows 11
- python 3.10.5
- opencv-python 4.6.0.66

# 処理内容
## 失敗パターン
### 画像を読み込みテンプレートマッチングを行う
```python
import cv2
from IPython.display import Image, display
from matplotlib import pyplot as plt

def imshow(img):
    ret, encoded = cv2.imencode(".jpg", img)
    display(Image(encoded))

# 入力画像、テンプレート画像を読み込む。
img = cv2.imread("chr_meter.jpg")  # 探索される画像
templ = cv2.imread("airbag.jpg")  # 探索する画像

# テンプレートマッチングを行う。
result = cv2.matchTemplate(img, templ, cv2.TM_CCOEFF_NORMED)
```
### 結果をカラーバーで表示する
```python
fig, ax = plt.subplots(figsize=(10, 5))
im = ax.imshow(result, cmap="jet")
fig.colorbar(im)

plt.show()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632401/00d8dd4d-6e7f-a648-33cc-ce8a3d5658e8.png)

全体的にピンボケしたような画像になる

### マッチした箇所を枠で囲む
```python
# 最も類似度が高い位置を取得する。
minVal, maxVal, minLoc, maxLoc = cv2.minMaxLoc(result)
print(f"max value: {maxVal}, position: {maxLoc}")
max value: 0.1377682238817215, position: (582, 71)

# 描画する。
tl = maxLoc[0], maxLoc[1]
br = maxLoc[0] + templ.shape[1], maxLoc[1] + templ.shape[0]

dst = img.copy()
cv2.rectangle(dst, tl, br, color=(0, 255, 0), thickness=2)
imshow(dst)
```
最大類似度は13%と低く、その場所を見つかった場所とするため
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632401/369bb333-46f6-defe-c6fe-37ed27c15143.png)

変なところを検出した

## 原因
メータ画像のエアバッグ警告灯は小さく、
準備したエアバッグ警告灯は大きいので
同じ画像として判定できない

## 成功パターン
### 画像を読み込みテンプレートマッチングを行う
```python
import cv2
from IPython.display import Image, display
from matplotlib import pyplot as plt

def imshow(img):
    ret, encoded = cv2.imencode(".jpg", img)
    display(Image(encoded))

# 入力画像、テンプレート画像を読み込む。
img = cv2.imread("chr_meter.jpg")  # 入力画像

templ = cv2.imread("airbag.jpg")  # テンプレート画像
# ☆追記：画像を0.1倍に縮小
height = templ.shape[0]
width = templ.shape[1]
templ = cv2.resize(templ , (int(width*0.1), int(height*0.1)))

# テンプレートマッチングを行う。
result = cv2.matchTemplate(img, templ, cv2.TM_CCOEFF_NORMED)
```
`templ = cv2.resize(templ , (int(width*0.1), int(height*0.1)))`の
`width*0.1`で幅、`height*0.1`で高さを10%にリサイズして小さくしている

```python
imshow(templ) #小さくした画像を表示する
```
縮小前後の画像を探索される画像と並べてみた
（qiitaはメータ画像の拡大率が変化するのでエクセルに張り付けて検証）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632401/3684d828-8475-246e-a847-191ee3faa9ba.png)
縮小したことで探索される画像の警告灯と同じぐらいのサイズとなった



### 結果をカラーバーで表示する
以降のコードは上記と同じ
```python
fig, ax = plt.subplots(figsize=(10, 5))
im = ax.imshow(result, cmap="jet")
fig.colorbar(im)

plt.show()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632401/ac47a5d9-e89a-b063-3f8e-4cb57d86c465.png)

先ほどのピンボケのような画像でなく、ピントが合ったような画像になり、
エアバッグ警告灯の部分が小さいが赤くなっている。

### マッチした箇所を枠で囲む
```python
# 最も類似度が高い位置を取得する。
minVal, maxVal, minLoc, maxLoc = cv2.minMaxLoc(result)
print(f"max value: {maxVal}, position: {maxLoc}")
max value: 0.7951300740242004, position: (462, 423)

# 描画する。
tl = maxLoc[0], maxLoc[1]
br = maxLoc[0] + templ.shape[1], maxLoc[1] + templ.shape[0]

dst = img.copy()
cv2.rectangle(dst, tl, br, color=(0, 255, 0), thickness=2)
imshow(dst)
```
類似度は79%になり
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632401/be60c137-8d42-29e5-1e66-cf1a6a431373.png)
エアバッグの警告灯をとらえることに成功！

## 考察(失敗パターンの探索位置)
エクセルで画像の透明度を上げてを重ねてみると
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/632401/a22efaa4-a33d-da39-4c4d-5823a765d336.png)
探索する画像の黒い部分、黒くない部分の割合が
この部分が一番マッチしている様な気がする。


# まとめ
「プログラムは書いた通りにしか動かない」と聞くことがあるが
コンピュータは書かれた通り「この大きい画像に一番近いところを示す」ので
まさしくそのとおりであると思う。

それに対して自分は「この画像が中にあるのになぜ見つけ出せないんだ？」
と思うのは自分が「大きさの変化を頭の中で置き換えて探している」からで
その処理を書いてやらないと成功しないなと感じた事例でした。