画像処理の実践

前回、画像処理の入門ということで、OpenCVを使って画像の読み書き、グレースケール化や２値化、簡単な図形の描画を紹介しました。
前回の記事はこちらになります。

https://qiita.com/fsd-shuhashi/items/0bd0efa3411eee0682f7

今回は、もう少し実践的な内容を紹介したいと思います。
前回同様、今回もレナの画像を使用します。
[レナの画像](http://www.ess.ic.kanagawa-it.ac.jp/std_img/colorimage/lena_std.bmp)

# 画像の切り出し
画像を読み込んで、その中の一部だけを抽出する処理になります。
処理は非常にシンプルです。
``` 
入力画像[top:bottom, left:right]
```
外の四角が画像全体となり、top,bottom,left,rightで指定した値が以下のように囲まれ、切り出しされます。
![スクリーンショット 2023-10-21 142840.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3314839/99ba0aec-ff7b-2505-95f8-eb48819b2ea0.png)

ソースコードはこちら。
``` python
import cv2
import numpy as np

# 画像読み込み
img = cv2.imread("lena_std.bmp")

# 切り出し
output = img[0:256, 0:256]

# 画像書き込み
cv2.imwrite("output.bmp", output)
```

実行結果はこちらになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3314839/1c32d62b-d7f7-a4f5-9fed-5bbc97b7fc2a.png)

# 画像の平滑化
平滑化とはぼかす処理のことです。
OpenCVにはいくつかの平滑化手法がありますが、今回は一般的な平滑化処理(cv2.blur)で記述します。
``` python
import cv2
import numpy as np

# 画像読み込み
img = cv2.imread(filename="lena_std.bmp")

# 平滑化
k = 25
output = cv2.blur(src=img, ksize=(k, k))

# 画像書き込み
cv2.imwrite(filename="output.bmp", img=output)
```
ksizeは、カーネルサイズを意味します。
詳細は、下記のサイトを参考にしてください。
簡単に言えば、数値を大きくすればするほど画像がぼけます。

https://www.frontier.maxell.co.jp/blog/posts/12.html#:~:text=%E3%82%AB%E3%83%BC%E3%83%8D%E3%83%AB%E3%81%A8%E3%81%AF,7x7%E7%94%BB%E7%B4%A0%E3%81%AA%E3%81%A9%E3%81%8C%E3%81%82%E3%82%8A%E3%81%BE%E3%81%99%E3%80%82

実際の出力例をいくつか見てみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3314839/594d0159-9952-07f0-294e-47f4fdefc0cb.png)


その他の平滑化の手法は以下のサイトを参考にしてください。

https://shikaku-mafia.com/opencv-smoothing/

# エッジ検出
エッジ検出は、画像の色の境目が急激に変わる場所のことをエッジと言い、それを検出することです。
エッジ検出にもいくつかの手法がありますが、今回はCanny法という手法を使用します。
``` python
import cv2
import numpy as np

# 画像読み込み
img = cv2.imread(filename="lena_std.bmp")

# エッジ検出
output = cv2.Canny(image=img, threshold1=0, threshold2=0)

# 画像書き込み
cv2.imwrite(filename="output.bmp", img=output)
```
２つの閾値をもとにエッジ検出を行います。
値が小さいと、色の変化が小さい箇所でも検出され、値が大きいと色の変化が大きな箇所でも検出されないことがあります。
実際に、いくつかパラメータを調整して試してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3314839/bfc6001e-cefc-df9a-3f66-d02a549ef7aa.png)

このように、値を調整してエッジ検出を行っていきます。
その他のエッジ検出は以下のサイトを参考にしてください。

https://di-acc2.com/programming/python/19060/

# 顔検出
顔検出とは名前の通り、画像にある人の顔を検出することです。
``` python
import cv2
import numpy as np

# 画像読み込み
img = cv2.imread(filename="lena_std.bmp")

# 顔検出
cascade = cv2.CascadeClassifier("haarcascade_frontalface_default.xmlのパス")
list = cascade.detectMultiScale(img)

# 検出箇所を四角で描画
if len(list):
    for (x, y, w, h) in list:
        cv2.rectangle(img=img, pt1=(x, y), pt2=(x+w, y+h),
                      color=(0, 0, 255), thickness=2)

# 画像書き込み
cv2.imwrite(filename="output.bmp", img=img)
```
Haar Cascadesを使った顔検出を使用します。
詳細は下記のサイトを参考にしてください。

http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_objdetect/py_face_detection/py_face_detection.html

OpenCVにはHaar Cascadesを用いた顔検出がデフォルトで使用できます。それがhaarcascade_frontalface_default.xmlというもので、私の場合は下記のパスにこのファイルがありました。
```
"C:\Users\**\AppData\Local\Programs\Python\Python311\Lib\site-packages\cv2\data"
```

実行結果は以下の通りになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3314839/c8f11222-3138-037f-be64-b98ce96839c0.png)

プログラムの流れは以下の通りになります。
1. 画像読み込み
1. 顔検出を適用し、検出結果を取得する。
1. １つでも検出できた場合は、赤い四角で検出箇所を描画する。
今回はlistに検出結果が入っており、listは検出した座標と幅と高さが返ってきます。
1. 画像書き込み

# 目の検出
目の検出は、顔検出ほぼ同じプログラムになります。
「haarcascade_frontalface_default.xml」を「haarcascade_eye.xml」に変えるだけです。
実行結果は以下の通りになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3314839/31919216-5ecd-84e9-1e4f-a417c5cf68fb.png)

# 顔にモザイクをかけてみよう
ここまでの内容を組み合わせて、実際に簡単なソフトを作ってみます。
今回は、顔にモザイクをかけるものを作ってみます。
コードは以下の通りになります。
``` python
import cv2
import numpy as np

# 画像読み込み
img = cv2.imread(filename="./lena_std.bmp")

# 顔検出
cascade = cv2.CascadeClassifier("haarcascade_frontalface_default.xmlのパス")
list = cascade.detectMultiScale(img)

if len(list):
    for (x, y, w, h) in list:
        # 顔検出した箇所を切り抜く
        cutout = img[y:y+h, x:x+w]

        # 平滑化でぼかす
        filter = cv2.blur(cutout, (25, 25))

        # ぼかした箇所を上書きする
        img[y:y+h, x:x+w] = filter


# 画像書き込み
cv2.imwrite(filename="output.bmp", img=img)
```
このコードを実行結果が下の画像になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3314839/0fb2b8ca-7372-30d2-f747-a530076438cd.png)

処理の流れは以下の通りになります。
1. 画像読み込み
1. 顔検出をかけて、顔の座標を取得する
1. 取得できた数だけ4～6を繰り返す
1. 顔検出した箇所を切り抜く
1. 切り抜いた画像を平滑化でぼかす
1. ぼかした箇所だけ入力画像を上書きする。
1. 画像を書き込む

図にすると、こんな感じになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3314839/c402d424-6925-70e6-13b8-04f81fbc26dd.png)

折角なので、目だけぼかしてみた結果も出してみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3314839/bb8d824b-2cb3-2fda-7a9b-60ab82db63fe.png)

綺麗にぼかすことが出来ました。

最後に、別の画像で顔のぼかしと目のぼかしをやってみたいと思います。
使用する画像は下の画像になります。
![person.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3314839/868e2ccc-4e61-1bab-d5bb-335842596970.jpeg)

まずは、顔をぼかします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3314839/f7fff720-a496-1749-c5b2-ddbd4f2b04e0.png)
1カ所、誤検出されていますが、4人ともぼかすことが出来ました。

目だけぼかしてみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3314839/d35f1e44-92c6-ee7a-6ee8-f0e7ad2c8cf2.png)
こちらは、残念ながら右の2人がぼかすことが出来ませんでした。

レナの画像に比べると、顔や目の大きさが小さいので、精度はその分落ちてしまいました。

# まとめ
今回は、画像処理の実践的なものとして以下のものを紹介しました・
1. 切り抜き
1. 平滑化
1. エッジ検出
1. 顔検出
1. 目の検出

紹介したものは一部の画像処理技術で、実際にはまだまだありますので、興味がある方は調べてみてください。
それでは、今回はここまでにしたいと思います。