OpenCVで画像検出

### 概要
Pythonで2つの画像の一致率とその場所を示すコードを実装した
### はじめに
動画の再生時やウィンドウに特定のアイコンや画像が出現した時にスクリーンショットやアイコンにマウス近づけてクリックなどの何かしら処理をする。そのような作業が仕事で多々ある。
ずーっとモニターしてないといけないし「面倒だなぁぁ」と感じていた。
この作業を効率化するためには画像検出ができれば解決できるかも！と思い調べてみた。
### 画像の用意
画像検出のため2つの画像を用意。
1つ目は絵本の写真を撮影して検索画像とした。
2つ目はその画像の1部分をトリミングして見本画像とした。(ライオンさんの鼻部分)
|　検索画像 | 見本画像 |
|:--:|:--:|
| ![sample_image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3940191/d143056f-a810-4a3b-8390-240afaa07502.png)  | ![sample_tamplate.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3940191/dfd486a7-03e5-4c4c-8f96-bbc0c009a7eb.png)  |

この2つの画像の一致率とその場所が分かるようにしたい。

### マッチテンプレート
Pythonの画像検出について、ChatGPTに聞いたりネットで調べていくとOpenCVのmatchTemplate関数がやりたいこととマッチしてる気がする
#### cv2.matchTemplate()
OpenCVのテンプレートマッチング（画像の中から特定のパターンを検索する手法）を行う関数

```sample.py
import cv2
# テンプレートマッチングの実行
result = cv2.matchTemplate(img, template, cv2.TM_CCOEFF_NORMED)
# 一番類似度が高い場所を取得
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
```

#### cv2.matchTemplate() の引数
```sample.py
cv2.matchTemplate(image, template, method)
```
image : 検索対象の画像（グレースケールが推奨）
template : 検索したいテンプレート画像（グレースケールが推奨）
method : マッチングの計算方法（以下の6種類）
#### マッチング手法（method の値）
| 方法| 説明 |
|:-|:-|
| cv2.TM_SQDIFF  | 平方差の小さい部分をマッチとする（値が小さいほど良い）  |
| cv2.TM_SQDIFF_NORMED  | 正規化された平方差（値が小さいほど良い）  |
| cv2.TM_CCORR  | 乗算を使った相関（値が大きいほど良い）  |
| cv2.TM_CCORR_NORMED  | 正規化された相関（値が大きいほど良い）  |
| cv2.TM_CCOEFF  | 平均を引いた後の相関（値が大きいほど良い）  |
| cv2.TM_CCOEFF_NORMED  | 正規化された相関係数（値が大きいほど良い）  |

#### 注意点
1\. スケールの違いに対応できない
    ・matchTemplate() は、サイズの異なるテンプレートには対応していない
    ・スケールが異なる場合は、画像をリサイズするか、ORB や SIFT などの特徴点ベースの手法を使う
2\. 回転に弱い
    ・テンプレートが回転していると検出しづらい
    ・回転に対応するには、cv2.warpAffine() などでテンプレートを回転させながら複数回試す必要がある
3\. 閾値を決める必要がある
    ・cv2.matchTemplate() の出力結果は類似度マップなので、しきい値を決めて候補を選ぶ必要がある

上記をもとに用意した画像で実装してみた

### 実装
```
pip install opencv-python
```
```matchTemplate.py
import cv2


def image_show(image):
    """画像を表示"""
    cv2.imshow(f"{image}", image)
    cv2.waitKey(1)

def read_image(path):
    """画像を読み込む関数"""
    image = cv2.imread(path, cv2.IMREAD_COLOR)

    return image

def image_edit(image):
    """画像を加工する関数"""
    image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)                     # グレー
    # image = cv2.GaussianBlur(image, (5,5), 0)                         # ガウシアンフィルタでノイズ除去
    # image = cv2.Canny(image, 50, 150)                                 # Cannyエッジ検出
    # image = cv2.equalizeHist(image)                                   # コントラスト強調（ヒストグラム平坦化）Cannyを入れるなら不要
    # _, image = cv2.threshold(image, 128, 255, cv2.THRESH_BINARY)      # 2値化

    return image

def matchTemplate(image, template):
    """テンプレとの一致率と場所を計算"""
    result = cv2.matchTemplate(image, template, cv2.TM_CCOEFF_NORMED)
    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
    top_left = max_loc  # TM_CCOEFF_NORMED は最大値が最も一致
    # テンプレートのサイズを取得
    w, h = template.shape[::-1]
    print(f"一致率 {round((max_val * 100),2)}%")

    return top_left, w, h

# templateパス
template_path = r"sample_tamplate.png"
image_path = r"sample_image.png"

# 画像を読み込む
template = read_image(template_path)
image = read_image(image_path)
# 画像を加工する
edit_template = image_edit(template)
edit_image = image_edit(image)

# matchTemplateを計算
top_left , w, h = matchTemplate(edit_image, edit_template)

# 一致部分を矩形で囲む
cv2.rectangle(image, top_left, (top_left[0] + w, top_left[1] + h), (0,255,0), 5)
# 結果を表示
image_show(image)

```
### 結果

一致率 100.0%
![matchTemplate.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3940191/c066f720-7b46-4216-a954-4feea37c58b3.png)

一致率は100.0%で出力ができ、場所も表示することができた！
今回行った画像処理はグレースケールのみだが、画像によってはいろいろ処理が必要そう
ケースバイケースで画像処理を加えていけば良いでしょう

### 終わり
マッチテンプレートが使えるようになった！これをベースにして画像検出ができそう！
例えば、スクリーンショット画像を取得してテンプレートに対して一致率が閾値を超えたら画像を保存、超えなかったら再スクリーンショットの繰り返しをしたり、カメラの映像を監視してテンプレートが出現したらその瞬間の画像として保存。などなどいろいろ使えそうだ！