Raspberryのカメラモジュールで静止画から動体検知

Raspberry PI と言えば、色々な電子工作、という事で、今回カメラモジュールを使ってみたいと思います。
Raspberry OS が、Buster の頃は、カメラを接続して「motion」をinstallすれば終わり…だったのですが、Bullseye からカメラ周りの仕様変更にmotionが対応できないとの事で、試行錯誤で使っておりました。
現在では、Bookworm になり、OpenCV を勉強してみよう…　の、今回です。

**Raspberryの接続**
カメラモジュールを取り付けます。ケーブルを取り付ける時、眼鏡を外す自分に歳を痛感します。（後何年続けられるかな？）力を入れ過ぎると、ポロっと壊れてしまうので、注意が必要です。

**Step１．カメラで静止画（JPG）を保存します**
Raspberry OS Lite で始めます。
sudo apt-get update
sudo apt-get upgrade
sudo apt-get install libcap-dev
sudo apt-get install libcamera-dev
sudo apt-get install python3-picamera2
sudo mkdir /test
sudo mkdir /test/camera
sudo chmod 777 -R /test
cd /test/camera

準備が出来たらプログラムを作りましょう。
vi createjpg.py

```diff_python:createjpg.py
import picamera2
import time

# カメラの設定
picam2 = picamera2.Picamera2()
picam2.start_preview()
config = picam2.create_preview_configuration(main={"size": (1920, 1080)})
picam2.configure(config)
picam2.start()

# 写真を撮影して保存
time.sleep(2)  # プレビュー開始後、2秒待つ
picam2.capture_file("image.jpg")  # 画像を image.jpg として保存

# カメラを停止
picam2.stop()
```
プログラムが出来たら実行してみましょう。
python createjpg.py

カメラモジュールを利用する時のメッセージがいくつか表示されます。
撮影したJPGファイルを確認しましょう。
ls -l　（image.jpg の存在を確認しましょう）
カメラ撮影により、JPGファイルが作成されたのが解ります。

後続作業で画像を比較する為、先程の画像をコピーして２つにしておきます。
cp image.jpg image1.jpg


**Step２．２つのJPGファイルから相違点を検知します**
「motion」の代わりを「OpenCV」で求めるので、動体（２つの画像の差）を検知する…は必要な機能です。色々試してみました。下段に失敗作もあり、次に挙げる手法が、確実なのかも自信が無い状況ですが、データとして処理するには、扱い易いと思いました。

必要なパッケージをインストールします
sudo apt-get install libopencv-dev python3-opencv

プログラムを準備しましょう
vi chkjpg.py

```diff_python:chkjpg.py
import cv2
import numpy as np

# 画像を読み込む
image1 = cv2.imread('image1.jpg')
image2 = cv2.imread('image.jpg')

# グレースケールに変換
gray1 = cv2.cvtColor(image1, cv2.COLOR_BGR2GRAY)
gray2 = cv2.cvtColor(image2, cv2.COLOR_BGR2GRAY)

# 画像を平滑化（ぼかし）
blur1 = cv2.GaussianBlur(gray1, (5, 5), 0)
blur2 = cv2.GaussianBlur(gray2, (5, 5), 0)

# 画像の差分を計算
diff = cv2.absdiff(blur1, blur2)

# 差分画像を二値化
_, thresh = cv2.threshold(diff, 50, 255, cv2.THRESH_BINARY)

# 輪郭を検出
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

# 小さな輪郭をフィルタリング
min_contour_area = 100  # 最小の輪郭面積
filtered_contours = [contour for contour in contours if cv2.contourArea(contour) > min_contour_area]

# 相違点の個数を表示
print(f'相違点の個数: {len(filtered_contours)}')
```

プログラムを実行しましょう。
python chkjpg.py

同じ画像を比較して相違点を確認しているので、「相違点の個数: 0」が表示されるはずです。
python createjpg.py
で撮影を実行し、image.pyとimage1.pyに、微妙な差を作ってから、再実行してみましょう。
python chkjpg.py
相違点の個数が、少し発見されましたかね。

:::note warn
【廃案１】ヒストグラムの比較で画像の差を検知する
2つの画像のヒストグラムを比較し、その相関係数を計算することで、画像間の類似度を評価してみます。
:::
必要なパッケージをインストールします
sudo apt-get install libopencv-dev python3-opencv

プログラムを準備しましょう
vi chkHST.py

```diff_python:chkHST.py
import cv2
import numpy as np

def compare_images(img1_path, img2_path):
    # 画像を読み込む
    img1 = cv2.imread(img1_path, cv2.IMREAD_GRAYSCALE)
    img2 = cv2.imread(img2_path, cv2.IMREAD_GRAYSCALE)

    # 画像のサイズを揃える
    img1 = cv2.resize(img1, (img2.shape[1], img2.shape[0]))

    # 画像のヒストグラムを計算
    hist1 = cv2.calcHist([img1], [0], None, [256], [0, 256])
    hist2 = cv2.calcHist([img2], [0], None, [256], [0, 256])

    # ヒストグラムの正規化
    hist1 = cv2.normalize(hist1, hist1).flatten()
    hist2 = cv2.normalize(hist2, hist2).flatten()

    # ヒストグラムの相関係数を計算
    correlation = cv2.compareHist(hist1, hist2, cv2.HISTCMP_CORREL)

    return correlation

if __name__ == "__main__":
    img1_path = "/test/camera/image1.jpg"
    img2_path = "/test/camera/image.jpg"

    similarity = compare_images(img1_path, img2_path)
    print(f"1.0は一致、差があると小さくなる: {similarity}")
```

プログラムを実行しましょう。
python chkHST.py

同じ画像で比較すると「1.0」が表示されるようですが、少しでも違うと、「1.0」にはならないようです。
ただ、少し物が入った程度では、ほぼ「1.0」なので、判断が難しい感じです。

:::note warn
【廃案２】構造的類似性指数（SSIM）で画像の差を検知する
SSIMは、人間の視覚系が画像をどのように知覚するかをモデル化し、輝度、コントラスト、構造の3つの要素に基づいて類似度を評価します。
:::

必要なパッケージをインストールします
sudo apt-get install libopencv-dev python3-opencv
sudo apt-get install python3-skimage

プログラムを準備しましょう
vi chkSSIM.py

```diff_python:chkSSIM.py
import cv2
from skimage.metrics import structural_similarity as ssim
import numpy as np

# 画像を読み込む
image1 = cv2.imread('image1.jpg')
image2 = cv2.imread('image.jpg')

# グレースケールに変換
gray1 = cv2.cvtColor(image1, cv2.COLOR_BGR2GRAY)
gray2 = cv2.cvtColor(image2, cv2.COLOR_BGR2GRAY)

# SSIMを計算し結果を表示
score, diff = ssim(gray1, gray2, full=True)
diff = (diff * 255).astype("uint8")
print("SSIMスコア: {:.4f}".format(score))
```
プログラムを実行しましょう。
python chkSSIM.py

ヒストグラムと同じように、同じ画像で比較すると「1.0」が表示されるようですが、少しでも違うと、「1.0」にはならないようです。
ヒストグラムと違い、少しの違いで、「1.0」から非常に小さな数値になるので、これも判断が難しい感じです。

以上、OpenCVの知見や画像系AIの知識不足のまま、色々と試してみました。結果が正しいのかも、判断が出来てないのが実情ですが、お疲れさまでした～