OpenCVの顔の識別機能とカレンダー読み上げの組み合わせ

OpenCVの顔の識別機能とカレンダー読み上げの組み合わせ

まず顔の識別で自分の顔だった時に動作するように
カレンダー読み上げ機能をモジュールにする

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
from calendar_utils import authenticate, get_upcoming_events, synthesize_speech, format_date_with_weekday
from playsound import playsound
 
def main():
    creds = authenticate()
    audio_files = []  # 音声ファイルのリスト
    if creds:
        events = get_upcoming_events(creds)
        if not events:
            print('今週の残りの予定はありません。')
            # 音声ファイルは再生しない
        else:
            print('今週の残りの予定:')
            for event in events:
                start = event['start'].get('dateTime', event['start'].get('date'))
                summary = event.get('summary', '(タイトルなし)')
                formatted_date = format_date_with_weekday(start)
                event_text = f"{formatted_date} - {summary}"
                print(event_text)
                filename = synthesize_speech(event_text)
                if filename:
                    audio_files.append(filename)  # 生成されたファイル名をリストに追加
 
        # 音声ファイルが存在する場合のみ notice.wav と各予定の音声を再生
        if audio_files:
            # notice.wavを最初に再生
            print("再生中: notice.wav")
            playsound("notice.wav")
             
            # 各予定の音声ファイルを再生
            for audio_file in audio_files:
                print(f"再生中: {audio_file}")
                playsound(audio_file)
 
if __name__ == '__main__':
    main()

をモジュールにして他のプログラムから呼び出せるようにしたい

1
touch calendar_audio_utils.py

でファイルを作成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
from calendar_utils import authenticate, get_upcoming_events, synthesize_speech, format_date_with_weekday
from playsound import playsound
 
def get_weekly_schedule_with_audio(play_audio=False):
    """
    今週の残りの予定を取得し、音声ファイルを生成する関数。
     
    :param play_audio: 予定を音声で再生するかどうか(デフォルトは再生しない)
    :return: 今週の予定をテキスト形式で返すリスト
    """
    creds = authenticate()
    audio_files = []  # 音声ファイルのリスト
    event_texts = []  # 予定のテキストリスト
 
    if creds:
        events = get_upcoming_events(creds)
        if not events:
            print('今週の残りの予定はありません。')
        else:
            print('今週の残りの予定:')
            for event in events:
                start = event['start'].get('dateTime', event['start'].get('date'))
                summary = event.get('summary', '(タイトルなし)')
                formatted_date = format_date_with_weekday(start)
                event_text = f"{formatted_date} - {summary}"
                event_texts.append(event_text)  # テキストをリストに追加
                print(event_text)
 
                # 音声ファイルを生成
                filename = synthesize_speech(event_text)
                if filename:
                    audio_files.append(filename)  # 生成されたファイル名をリストに追加
 
        # 音声を再生するオプションがTrueの場合にのみ、音声ファイルを再生
        if play_audio and audio_files:
            # notice.wavを最初に再生
            print("再生中: notice.wav")
            playsound("notice.wav")
             
            # 各予定の音声ファイルを再生
            for audio_file in audio_files:
                print(f"再生中: {audio_file}")
                playsound(audio_file)
 
    return event_texts

として保存

念のため動作するかチェック

1
vim testvoice.py

でファイル作成

1
2
3
4
5
6
7
8
from calendar_audio_utils import get_weekly_schedule_with_audio
 
# 音声再生なしで予定を取得
schedule = get_weekly_schedule_with_audio(play_audio=False)
print(schedule)
 
# 音声再生ありで予定を取得
schedule = get_weekly_schedule_with_audio(play_audio=True)

保存したら

1
python testvoice.py

で実行

これで動作するのが確認できたので
次に顔の識別

以前使ったものを再利用する

Pixcel8で撮影したスマホの写真で顔データを作る場合には
元画像の1/4にする必要があるため
変換のため
resize_save.py
を作成したのでこれを使う

コードは

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import cv2
import os
import argparse
 
def main():
    # コマンドライン引数を解析するパーサーを作成
    parser = argparse.ArgumentParser(description="Resize and save an image")
    parser.add_argument("image_path", help="Path to the image file")
    args = parser.parse_args()
 
    # 画像を読み込む
    image = cv2.imread(args.image_path)
    if image is None:
        print("画像が読み込めませんでした。")
        return
 
    # 画像の元の高さ、幅を取得
    height, width = image.shape[:2]
 
    # 新しい寸法を計算(元のサイズの1/4)
    new_width = width // 4
    new_height = height // 4
 
    # 画像をリサイズ
    resized_image = cv2.resize(image, (new_width, new_height))
 
    # 新しいファイル名を設定
    new_file_path = os.path.splitext(args.image_path)[0] + "_quarter.jpg"
 
    # リサイズした画像を保存
    cv2.imwrite(new_file_path, resized_image)
    print(f"リサイズされた画像が保存されました: {new_file_path}")
 
if __name__ == '__main__':
    main()

使用する時にはターミナルでコマンドで実行する

1
python resize_save.py PXL_20240612_091410912.jpg

というようにファイルを指定すれば
実行後1/4サイズにした画像が作成される

ファイルサイズを調べるスクリプトも作成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import cv2
import os
import argparse
 
def main():
    # コマンドライン引数を解析するパーサーを作成
    parser = argparse.ArgumentParser(description="Display image properties")
    parser.add_argument("image_path", help="Path to the image file")
    args = parser.parse_args()
 
    # 画像を読み込む
    image = cv2.imread(args.image_path)
    if image is None:
        print("画像が読み込めませんでした。")
        return
 
    # 画像の高さ、幅、チャンネル数を取得
    height, width, channels = image.shape
    print(f"画像の幅: {width} ピクセル")
    print(f"画像の高さ: {height} ピクセル")
    print(f"色チャネル数: {channels}")
 
    # ファイルサイズを取得
    file_size = os.path.getsize(args.image_path)
    print(f"ファイルサイズ: {file_size} バイト")
 
if __name__ == '__main__':
    main()

これを

1
python file_info.py PXL_20240612_091410912_resized_resized.jpg

というように実行すればサイズが表示される

1
2
3
4
画像の幅: 684 ピクセル
画像の高さ: 912 ピクセル
色チャネル数: 3
ファイルサイズ: 228769 バイト

この2つは自分以外の写真から登録画像を作るのに使うので

1
2
cp ../face_recog/file_info.py .
cp ../face_recog/resize_save.py .

でコピーしておく

次に
入力した写真から人の顔の部分を切り出して保存するプログラム

1
generate_aligned_faces.py

に写真のファイルを引数にして実行すれば個人ごとの顔写真ができる

これは
入力した写真から人の顔の部分を切り出して保存するプログラム

複数の人物が写っている場合は全員を切り出して face001.jpg , face002.jpg ・・・ と名前を付けて保存する
出力されたファイル名を 人の名前に変更しておくと後々便利です。 
face001.jpg → taro.jpg

1
python generate_aligned_faces.py image.jpg

とすれば
写真に写っている人の分だけファイルができる
そのファイル名を人の名前に変更する

つまり全て
face001.jpg
という感じで
Face00x.jpg
となっているので
写真ごとに名前を変える

これもコピーしておく

1
cp ../face_recog/generate_aligned_faces.py .

次に

1
generate_feature_dictionary.py


切り出した顔のjpgファイルを読み込んで、顔の特徴量に変換する

例えば 顔写真 taro.jpg を入力すると 顔の特徴量 taro.npy が出力される
このnumpyファイルに各個人の顔の特徴量が128次元ベクトルに変換されて入っている

1
2
python generate_feature_dictionary.py face001.jpg
python generate_feature_dictionary.py face002.jpg

つまり
写真の人の分だけ実行すればOK

これもコピーしておく

1
cp ../face_recog/generate_feature_dictionary.py .

次に顔の得微量が近い人を検出するにはモデルが必要なのでコピー

1
2
cp ../face_recog/face_recognizer_fast.onnx .
cp ../face_recog/face_detection_yunet_2023mar.onnx .

そして作成した自分の顔の得微量ファイルもコピーしておく

1
cp ../face_recog/satoru.* .

Webカメラから映った時に顔の識別をするので

1
cp ../face_recog/webcam_face_recognizer.py .

でコピー

念の為動作確認

1
python webcam_face_recognizer.py

で自分の顔を識別しているのがわかる

次にこの中で
顔認識した時に

1
2
from calendar_module import get_weekly_schedule_with_audio # 音声再生なしで予定を取得
schedule = get_weekly_schedule_with_audio(play_audio=False) print(schedule) # 音声再生ありで予定を取得 schedule = get_weekly_schedule_with_audio(play_audio=True)

を実行

また
毎回読み上げでは負荷がかかるため、次の呼び出しは12時間後になるように設定

顔を識別できたときに特定の関数を呼び出し、
呼び出しが12時間に1回のみになるように制限するためには、識別が成功した時間を記録し、
次に呼び出すタイミングを管理する

1. call_function_when_recognized: この関数が顔認識時に呼び出され、最後の呼び出し時間を記録します。
次に呼び出すまでの間隔が12時間経過していない場合は、新たに処理を実行しないようにしています。
2. THROTTLE_TIME: 12時間を秒単位(12 * 60 * 60)で設定しています。
3. last_called_time: この変数は最後に呼び出された時間を記録し、
次の呼び出しが12時間以内であれば、新たな処理をスキップします。

.wavファイルを毎回生成していると容量を圧迫するため

1
2
3
4
5
6
7
8
from calendar_audio_utils import get_weekly_schedule_with_audio
 
# 音声再生なしで予定を取得
schedule = get_weekly_schedule_with_audio(play_audio=False)
print(schedule)
 
# 音声再生ありで予定を取得
schedule = get_weekly_schedule_with_audio(play_audio=True)

の処理の後に notice.wavファイル以外の .wavファイルをすべて削除する

1
touch webcam_face_calendar.py

でファイルを作成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import os
import glob
import numpy as np
import cv2
import time
from calendar_module import get_weekly_schedule_with_audio  # カレンダーから予定を取得するためのインポート
 
COSINE_THRESHOLD = 0.363
NORML2_THRESHOLD = 1.128
 
# 12時間(秒単位)
THROTTLE_TIME = 12 * 60 * 60
last_called_time = 0  # 最後に呼び出した時間を初期化
 
def match(recognizer, feature1, dictionary):
    for element in dictionary:
        user_id, feature2 = element
        score = recognizer.match(feature1, feature2, cv2.FaceRecognizerSF_FR_COSINE)
        if score > COSINE_THRESHOLD:
            return True, (user_id, score)
    return False, ("", 0.0)
 
def call_function_when_recognized(user_id):
    global last_called_time
    current_time = time.time()
     
    # 最後に呼び出してから12時間経過しているかを確認
    if current_time - last_called_time >= THROTTLE_TIME:
        print(f"認識されました: {user_id}")
         
        # 予定を音声再生なしで取得
        schedule = get_weekly_schedule_with_audio(play_audio=False)
        print("予定:", schedule)
         
        # 予定を音声再生ありで取得
        schedule = get_weekly_schedule_with_audio(play_audio=True)
        print("音声で再生される予定:", schedule)
         
        # notice.wavファイル以外の.wavファイルを削除
        cleanup_audio_files(exclude_file="notice.wav")
         
        # 最後に呼び出した時間を更新
        last_called_time = current_time
    else:
        print("まだ12時間経過していないため、次の呼び出しは行われません。")
 
def cleanup_audio_files(exclude_file):
    """指定された.wavファイル以外の.wavファイルを削除する関数"""
    directory = os.getcwd()  # 現在のディレクトリを取得
    wav_files = glob.glob(os.path.join(directory, "*.wav"))  # すべての.wavファイルを取得
 
    for wav_file in wav_files:
        if os.path.basename(wav_file) != exclude_file:
            try:
                os.remove(wav_file)  # 指定されたファイル以外を削除
                print(f"削除しました: {wav_file}")
            except OSError as e:
                print(f"ファイル削除エラー: {wav_file}, {e}")
 
def main():
    directory = os.path.dirname(__file__)
    capture = cv2.VideoCapture(0)  # Use the default camera
 
    if not capture.isOpened():
        print("Error: The webcam could not be opened.")
        return
 
    dictionary = []
    files = glob.glob(os.path.join(directory, "*.npy"))
    for file in files:
        feature = np.load(file)
        user_id = os.path.splitext(os.path.basename(file))[0]
        dictionary.append((user_id, feature))
 
    weights = os.path.join(directory, "face_detection_yunet_2023mar.onnx")
    face_detector = cv2.FaceDetectorYN_create(weights, "", (0, 0))
    weights = os.path.join(directory, "face_recognizer_fast.onnx")
    face_recognizer = cv2.FaceRecognizerSF_create(weights, "")
 
    while True:
        result, image = capture.read()
        if not result:
            print("Error: No image from webcam.")
            break
 
        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)  # Ensure image is in RGB
 
        height, width, _ = image.shape
        face_detector.setInputSize((width, height))
 
        result, faces = face_detector.detect(image)
        faces = faces if faces is not None else []
 
        for face in faces:
            aligned_face = face_recognizer.alignCrop(image, face)
            feature = face_recognizer.feature(aligned_face)
 
            result, user = match(face_recognizer, feature, dictionary)
 
            box = list(map(int, face[:4]))
            color = (0, 255, 0) if result else (0, 0, 255)
            thickness = 2
            cv2.rectangle(image, box, color, thickness, cv2.LINE_AA)
 
            id, score = user if result else ("unknown", 0.0)
            text = "{} ({:.2f})".format(id, score)
            position = (box[0], box[1] - 10)
            font = cv2.FONT_HERSHEY_SIMPLEX
            scale = 0.6
            cv2.putText(image, text, position, font, scale, color, thickness, cv2.LINE_AA)
 
            if result and id != "unknown":
                call_function_when_recognized(id# 顔が認識された時にカレンダーの予定取得を実行
 
        # 画像を表示する前にRGBからBGRに再変換
        cv2.imshow("face recognition", cv2.cvtColor(image, cv2.COLOR_RGB2BGR))
 
        key = cv2.waitKey(1)
        if key == ord('q'):
            break
 
    capture.release()
    cv2.destroyAllWindows()
 
if __name__ == '__main__':
    main()

で実行

1
2
3
4
Traceback (most recent call last):
  File "/Users/snowpool/aw10s/week_calendar_voice/webcam_face_calendar.py", line 6, in <module>
    from calendar_module import get_weekly_schedule_with_audio  # カレンダーから予定を取得するためのインポート
ImportError: cannot import name 'get_weekly_schedule_with_audio' from 'calendar_module' (/Users/snowpool/aw10s/week_calendar_voice/calendar_module.py)

となる

これはChatGPTで作成した時のモジュールのエラー
結構あることでライブラリのインポートを間違えたり削除下入りしている

1
from calendar_module import get_weekly_schedule_with_audio  # カレンダーから予定を取得するためのインポート

に変更すれば解決

起動はしたけど、このままだとOpenCVで画面描画するので
これは不要なので非表示にする
これをしないとリモート環境などで動作しない

v2.imshow()やキーボードの操作に関する部分を削除し、
無限ループで顔認識を行うコードに修正

cv2.VideoCapture の映像確認が不要な場合は、その部分を省略しても動作する

ということで
画面表示とキー入力待機を削除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import os
import glob
import numpy as np
import cv2
import time
from calendar_audio_utils import get_weekly_schedule_with_audio  # カレンダーから予定を取得するためのインポート
 
COSINE_THRESHOLD = 0.363
NORML2_THRESHOLD = 1.128
 
# 12時間(秒単位)
THROTTLE_TIME = 12 * 60 * 60
last_called_time = 0  # 最後に呼び出した時間を初期化
 
def match(recognizer, feature1, dictionary):
    for element in dictionary:
        user_id, feature2 = element
        score = recognizer.match(feature1, feature2, cv2.FaceRecognizerSF_FR_COSINE)
        if score > COSINE_THRESHOLD:
            return True, (user_id, score)
    return False, ("", 0.0)
 
def call_function_when_recognized(user_id):
    global last_called_time
    current_time = time.time()
     
    # 最後に呼び出してから12時間経過しているかを確認
    if current_time - last_called_time >= THROTTLE_TIME:
        print(f"認識されました: {user_id}")
         
        # 予定を音声再生なしで取得
        schedule = get_weekly_schedule_with_audio(play_audio=False)
        print("予定:", schedule)
         
        # 予定を音声再生ありで取得
        schedule = get_weekly_schedule_with_audio(play_audio=True)
        print("音声で再生される予定:", schedule)
         
        # notice.wavファイル以外の.wavファイルを削除
        cleanup_audio_files(exclude_file="notice.wav")
         
        # 最後に呼び出した時間を更新
        last_called_time = current_time
    else:
        print("まだ12時間経過していないため、次の呼び出しは行われません。")
 
def cleanup_audio_files(exclude_file):
    """指定された.wavファイル以外の.wavファイルを削除する関数"""
    directory = os.getcwd()  # 現在のディレクトリを取得
    wav_files = glob.glob(os.path.join(directory, "*.wav"))  # すべての.wavファイルを取得
 
    for wav_file in wav_files:
        if os.path.basename(wav_file) != exclude_file:
            try:
                os.remove(wav_file)  # 指定されたファイル以外を削除
                print(f"削除しました: {wav_file}")
            except OSError as e:
                print(f"ファイル削除エラー: {wav_file}, {e}")
 
def main():
    directory = os.path.dirname(__file__)
    capture = cv2.VideoCapture(0)  # Use the default camera
 
    if not capture.isOpened():
        print("Error: The webcam could not be opened.")
        return
 
    dictionary = []
    files = glob.glob(os.path.join(directory, "*.npy"))
    for file in files:
        feature = np.load(file)
        user_id = os.path.splitext(os.path.basename(file))[0]
        dictionary.append((user_id, feature))
 
    weights = os.path.join(directory, "face_detection_yunet_2023mar.onnx")
    face_detector = cv2.FaceDetectorYN_create(weights, "", (0, 0))
    weights = os.path.join(directory, "face_recognizer_fast.onnx")
    face_recognizer = cv2.FaceRecognizerSF_create(weights, "")
 
    while True:
        result, image = capture.read()
        if not result:
            print("Error: No image from webcam.")
            break
 
        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)  # Ensure image is in RGB
 
        height, width, _ = image.shape
        face_detector.setInputSize((width, height))
 
        result, faces = face_detector.detect(image)
        faces = faces if faces is not None else []
 
        for face in faces:
            aligned_face = face_recognizer.alignCrop(image, face)
            feature = face_recognizer.feature(aligned_face)
 
            result, user = match(face_recognizer, feature, dictionary)
 
            if result and user[0] != "unknown":
                call_function_when_recognized(user[0])  # 顔が認識された時にカレンダーの予定取得を実行
 
        # 適当な待機時間を設けてリソースの使用を抑える
        time.sleep(1)
 
    capture.release()
 
if __name__ == '__main__':
    main()

というコードに変更

これでwebカメラの画面描画はなくなり
停止手段は ctrl + c で停止となる

実際に動かしたけど
M1macbookAir 16GB で
顔認識してからGoogle カレンダーを読み込み
Voicevox で音声ファイルを生成し、予定を読み上げるまで一分かかる

Docker ではなくインストールタイプにしたり
マシンスペックを上げれば短縮できるかもしれない

Ubuntu 22.04 OpenCVをVNCで表示

Ubuntu 22.04 OpenCVをVNCで表示

1
ssh snowpool@192.168.1.69

でログイン

Opencvの処理をSSHで表示するには
ポートフォワーディングが必要

1
sudo vim /etc/ssh/sshd_config

1
X11Forwarding yes

となっているのを確認

一度ログアウト

1
ssh -X snowpool@192.168.1.69

とオプションに -X をつければOK

OpenCV については
既にソースからインストール済み

1
wget https://github.com/opencv/opencv/blob/master/data/haarcascades/haarcascade_frontalface_default.xml


Haar Cascadeファイルの取得

次にpixcel8で自分の写真を撮影し
GoogleDrive へアップロード

これをubuntuに転送する

1
scp PXL_20231208_210913098.jpg-20231208T211055Z-001.zip snowpool@192.168.1.69:/home/snowpool/aw10s/

ファイル名が長いので変更

圧縮されているので

1
unzip PXL_20231208_210913098.jpg-20231208T211055Z-001.zip

で解凍後

1
mv PXL_20231208_210913098.jpg image.jpg

でファイル名変更

あとは

1
vim face_recognition.py

でファイルを作成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import cv2
 
# 分類器の読み込み
face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
 
# 画像の読み込み
img = cv2.imread('image.jpg')
 
# グレースケール変換
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
 
# 顔の検出
faces = face_cascade.detectMultiScale(gray, 1.1, 4)
 
# 顔の周囲に枠を描画
for (x, y, w, h) in faces:
    cv2.rectangle(img, (x, y), (x+w, y+h), (255, 0, 0), 2)
 
# 結果の表示
cv2.imshow('img', img)
cv2.waitKey()
cv2.destroyAllWindows()

を保存し
実行したら

1
2
    face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
SystemError: <class 'cv2.CascadeClassifier'> returned a result with an exception set

となる

原因は
https://qiita.com/hatorijobs/items/df2c8793509430f8d543
にあるように
githubのhaarcascade_frontalface_default.xmlをダウンロードして、読み込むとエラーになる。
そのため、公式サイトのhaarcascade_frontalface_default.xmlファイルをダウンロードして、
読み込んだら、成功
とのこと

結構chatgptだけだとヒントにはなるけどエラーが多い

普通に
ubuntu22.04 OpenCV 顔認識で検索し

https://techlog.mydns.jp/?p=417
を参考に
https://github.com/opencv/opencv/tree/master/data/haarcascades
から
haarcascade_eye.xml
haarcascade_frontalface_default.xml
をダウンロード

これをubuntu に転送する

1
scp haarcascade_*  snowpool@192.168.1.69:/home/snowpool/aw10s/

そして

1
pip install opencv-python

でライブラリインストール

1
vim kao.py

でファイルを作成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import cv2
import time
 
# Haar Cascade分類器の読み込み
face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
eye_cascade = cv2.CascadeClassifier('haarcascade_eye.xml')
 
# Webカメラの設定
cap = cv2.VideoCapture(0)  # 0番目のカメラを使用する場合
 
# 最後の顔検出時刻
lastTime = None
 
# メインループ
while True:
 
 
    # カメラからのフレームの取得
    ret, frame = cap.read()
     
    # フレームのグレースケール化
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
     
    # 顔の検出
    faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))
     
    # 検出された顔、目、鼻、口に矩形を描画
    for (x, y, w, h) in faces:
        # 検出自の処理(検出から1分たったら再度イベント動かす
        if lastTime is None or time.perf_counter() - lastTime > 60:
            # 検出時刻更新
            lastTime = time.perf_counter()
            print("人間発見、警戒せよw")
             
        cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2)
        roi_gray = gray[y:y+h, x:x+w]
        roi_color = frame[y:y+h, x:x+w]
        # 以下は目もマークする場合
        # eyes = eye_cascade.detectMultiScale(roi_gray)
        # for (ex, ey, ew, eh) in eyes:
        #     cv2.rectangle(roi_color, (ex, ey), (ex+ew, ey+eh), (255, 0, 0), 2)
 
     
    # 結果の表示
    cv2.imshow('Facial Feature Detection', frame)
     
    # 終了のキー入力
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break
 
# 後処理
cap.release()
cv2.destroyAllWindows()

これで実行したけど

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[ WARN:0@0.046] global cap_v4l.cpp:982 open VIDEOIO(V4L2:/dev/video0): can't open camera by index
[ WARN:0@0.046] global obsensor_stream_channel_v4l2.cpp:82 xioctl ioctl: fd=-1, req=-2140645888
[ WARN:0@0.046] global obsensor_stream_channel_v4l2.cpp:138 queryUvcDeviceInfoList ioctl error return: 9
[ WARN:0@0.047] global obsensor_stream_channel_v4l2.cpp:82 xioctl ioctl: fd=-1, req=-2140645888
[ WARN:0@0.047] global obsensor_stream_channel_v4l2.cpp:138 queryUvcDeviceInfoList ioctl error return: 9
[ WARN:0@0.047] global obsensor_stream_channel_v4l2.cpp:82 xioctl ioctl: fd=-1, req=-2140645888
[ WARN:0@0.047] global obsensor_stream_channel_v4l2.cpp:138 queryUvcDeviceInfoList ioctl error return: 9
[ WARN:0@0.047] global obsensor_stream_channel_v4l2.cpp:82 xioctl ioctl: fd=-1, req=-2140645888
[ WARN:0@0.047] global obsensor_stream_channel_v4l2.cpp:138 queryUvcDeviceInfoList ioctl error return: 9
[ERROR:0@0.047] global obsensor_uvc_stream_channel.cpp:156 getStreamChannelGroup Camera index out of range
Traceback (most recent call last):
  File "/home/snowpool/aw10s/kao.py", line 22, in <module>
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
cv2.error: OpenCV(4.8.0) /io/opencv/modules/imgproc/src/color.cpp:182: error: (-215:Assertion failed) !_src.empty() in function 'cvtColor'

となる

とりあえずリモートでopencvからやる

1
2
3
4
5
import cv2
bgr = cv2.imread(‘image.jpg')
cv2.imshow("", bgr)
cv2.waitKey(0)
cv2.destroyAllWindows()

1
vim test_opencv.py

として保存

https://www.kkaneko.jp/tools/ubuntu/opencv.html
を参考に実行

しかし

1
2
3
bgr = cv2.imread('image,jpg')
[ WARN:0@168.990] global loadsave.cpp:248 findDecoder imread_('image,jpg'): can't open/read file: check file path/integrity
>>>

となる

pyenvのPythonと組み合わせるOpenCVのビルド: Ubuntu-22.04編
を参考に

1
2
3
4
5
6
7
8
#!/usr/bin/env python
 
import os
import sys
sys.path.insert(0, f"{os.environ['HOME']}/dev-root/opencv4/lib/python3.11/site-packages")
import cv2
 
print(cv2.__version__)

を実行したら

1
4.8.0

となるので
これは問題ないみたい

https://www.kkaneko.jp/tools/ubuntu/opencv.html
を参考に

1
2
3
import cv2
bgr = cv2.imread('fruits.jpg')
cv2.imshow("", bgr)

まで実行すると

1
cv2.error: OpenCV(4.8.0) /io/opencv/modules/highgui/src/window.cpp:1272: error: (-2:Unspecified error) The function is not implemented. Rebuild the library with Windows, GTK+ 2.x or Cocoa support. If you are on Ubuntu or Debian, install libgtk2.0-dev and pkg-config, then re-run cmake or configure script in function 'cvShowImage'

となる

これを検索すると
https://qiita.com/tik26/items/a75e03e523926cd2f059
にそれらしいものがあったので
一度OpenCV をアンインストール

1
2
3
4
sudo apt update
sudo apt install -y libgtk2.0-dev pkg-config
 
pip3 install opencv-python

今度は

1
2
3
4
5
6
7
qt.qpa.xcb: could not connect to display
qt.qpa.plugin: Could not load the Qt platform plugin "xcb" in "/home/snowpool/.local/lib/python3.10/site-packages/cv2/qt/plugins" even though it was found.
This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem.
 
Available platform plugins are: xcb.
 
中止 (コアダンプ)

となる

エラーメッセージを検索すると
QtとOpenCVの両方をインストールするとエラーが発生する[Python]
https://blog.nplpl.com/421
によれば
どうやら双方にGUI機能が含まれているから競合しているらしいので
opencv-python をアンインストール
GUI機能を含まない、OpenCVのヘッドレス版をインストール
でOKらしい

1
2
pip uninstall -y opencv-python
pip3 install opencv-python-headless

今度は

1
cv2.error: OpenCV(4.8.0) /io/opencv/modules/highgui/src/window.cpp:1272: error: (-2:Unspecified error) The function is not implemented. Rebuild the library with Windows, GTK+ 2.x or Cocoa support. If you are on Ubuntu or Debian, install libgtk2.0-dev and pkg-config, then re-run cmake or configure script in function 'cvShowImage'

となる

https://keep-loving-python.hatenablog.com/entry/2023/02/05/110149
によれば

1
python -m pip install opencv-python==4.6.0.66 --force-reinstall

というように

1
--force-reinstall

すればOKらしいが
その前に一度cmake をもう一度やってからにしてみる

とりあえずパスを調べる
ChatGPT によれば

1
2
import cv2
print(cv2.__file__)


Pythonでインストールされている場合のパスが出る
/usr/local/lib/python3.10/dist-packages/cv2/__init__.py

次にapt などの場合

1
sudo dpkg -L libopencv-dev

で表示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/.
/usr
/usr/bin
/usr/bin/opencv_annotation
/usr/bin/opencv_interactive-calibration
/usr/bin/opencv_model_diagnostics
/usr/bin/opencv_version
/usr/bin/opencv_visualisation
/usr/bin/opencv_waldboost_detector
/usr/lib
/usr/lib/x86_64-linux-gnu
/usr/lib/x86_64-linux-gnu/cmake
/usr/lib/x86_64-linux-gnu/cmake/opencv4
/usr/lib/x86_64-linux-gnu/cmake/opencv4/OpenCVConfig-version.cmake
/usr/lib/x86_64-linux-gnu/cmake/opencv4/OpenCVConfig.cmake
/usr/lib/x86_64-linux-gnu/cmake/opencv4/OpenCVModules-release.cmake
/usr/lib/x86_64-linux-gnu/cmake/opencv4/OpenCVModules.cmake
/usr/lib/x86_64-linux-gnu/pkgconfig
/usr/lib/x86_64-linux-gnu/pkgconfig/opencv4.pc
/usr/share
/usr/share/doc
/usr/share/doc/libopencv-dev
/usr/share/doc/libopencv-dev/copyright
/usr/share/licenses
/usr/share/licenses/opencv4
/usr/share/licenses/opencv4/SoftFloat-COPYING.txt
/usr/share/man
/usr/share/man/man1
/usr/share/man/man1/opencv_createsamples.1.gz
/usr/share/man/man1/opencv_haartraining.1.gz
/usr/share/man/man1/opencv_performance.1.gz
/usr/share/man/man1/opencv_traincascade.1.gz
/usr/share/doc/libopencv-dev/changelog.Debian.gz

確かソースビルドのはず


メモを見たが
https://www.kkaneko.jp/tools/ubuntu/ubuntu_opencv.html#S1
にあるようなログ
つまり
Make のオプションがhistoryコマンドで出ない
つまりpythonでは入っているが
Make して入っていないようだ

次にVNC接続し実験

ubuntuで

1
tigervncserver -xstartup /usr/bin/gnome-session -geometry 800x600 -localhost no :1

を実行後

Mac の場合
Finder から
移動 > サーバーに接続で
vnc://192.168.1.69:5901
で接続

すると

1
2
3
import cv2
bgr = cv2.imread('fruits.jpg')
cv2.imshow("", bgr)

まで実行すると

1
Failed to load module "canberra-gtk-module"

とエラーが変わる

これを検索

OpenCVを実行するとでてくるFailed to load module “canberra-gtk-module”というエラーの対処法
によれば

1
sudo apt-get install libcanberra-gtk*

でOK

これで
再度

1
2
3
4
import cv2
bgr = cv2.imread('fruits.jpg')
cv2.imshow("", bgr)
waitKey(0)

でフルーツ画像が表示される

消すには

1
cv2.destroyAllWindows()

を実行

とりあえずVNCではできるけど
Ssh Xポートフォワーディングでやるのは今は無理っぽいのがわかった

ラズパイゼロで ダイレクト TCP ストリーミングでの配信と opencv で MacBookAir で再生

ラズパイゼロで
ダイレクト TCP ストリーミングでの配信と
opencv で MacBookAir で再生

とりあえずラズパイゼロWをセット

1
2
sudo apt-get update
sudo apt-get upgrade

のあと

1
sudo raspi-config


最初のメニューの5番目の項目、Interfacing Optionsを開くと1番上にカメラの項目があるのでこれを有効化

再起動になるので
再度SSHで接続する

ラズパイゼロだと非力なので画像処理などは難しそうだけど
とりあえず
v4l2-ctl –list-devices
使用するカメラのデバイスファイルを調べる

結果は

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
bcm2835-codec-decode (platform:bcm2835-codec):
    /dev/video10
    /dev/video11
    /dev/video12
    /dev/video18
    /dev/video31
    /dev/media2
 
bcm2835-isp (platform:bcm2835-isp):
    /dev/video13
    /dev/video14
    /dev/video15
    /dev/video16
    /dev/video20
    /dev/video21
    /dev/video22
    /dev/video23
    /dev/media0
    /dev/media1
 
mmal service 16.1 (platform:bcm2835_v4l2-0):
    /dev/video0

次に

1
v4l2-ctl -d /dev/video0 --list-formats


サポートしている動画のフォーマットを表示

結果は

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ioctl: VIDIOC_ENUM_FMT
    Type: Video Capture
 
    [0]: 'YU12' (Planar YUV 4:two:0)
    [1]: 'YUYV' (YUYV 4:two:2)
    [2]: 'RGB3' (24-bit RGB 8-8-8)
    [3]: 'JPEG' (JFIF JPEG, compressed)
    [4]: 'H264' (H.264, compressed)
    [5]: 'MJPG' (Motion-JPEG, compressed)
    [6]: 'YVYU' (YVYU 4:two:2)
    [7]: 'VYUY' (VYUY 4:two:2)
    [8]: 'UYVY' (UYVY 4:two:2)
    [9]: 'NV12' (Y/UV 4:two:0)
    [10]: 'BGR3' (24-bit BGR 8-8-8)
    [11]: 'YV12' (Planar YVU 4:two:0)
    [12]: 'NV21' (Y/VU 4:two:0)
    [13]: 'RX24' (32-bit XBGR 8-8-8-8)

ラズパイで動画を撮影する方法として、FFmpegを使用してリアルタイムでハードウェアエンコードする方法がよく紹介されていますが、特にRaspberry Pi Zeroだとフレームレートがあまり出ないのでドラレコには不向き

しかしハードウェアエンコードなんてしなくても、H.264をサポートしているカメラモジュールを使用すれば、専用の動画撮影コマンドraspividを実行することで簡単にH.264動画を撮影することが可能

安価なUSB接続のWebカメラだとH.264をサポートしていなかったりしますが、私が確認した限りではラズパイ向けのカメラモジュールはどれもH.264をサポートしているので、基本的にraspividで録画することをおすすめ

raspividで動画を撮影するには
もっともシンプルに書くなら、-oで出力ファイル名のみを指定してこのように記述
なお指定した名前のファイルが既に存在する場合は実行に失敗するので注意

1
raspivid -o test.h264

このように何も指定せずに実行すると5秒の動画ファイルが生成される

とりあえず動画撮影はできたので
UDP配信をしてみる

これにはUDPストリーミング配信先のIPアドレスが必要
MacbookAir でまずは実験するので
LanScanでネットワークのIPアドレスを表示

192.168.1.137がアドレスなのが判明したので

RaspberryPiをWebカメラとして使用する
を参考に

1
raspivid -a 12 -t 0 -w 1920 -h 1080 -hf -ih -fps 30 -o udp://192.168.1.137:5000

を実行しても

1
2
mmal: Failed to write buffer data (2470 from 5350)- aborting
mmal: Failed to write buffer data (2739 from 8877)- aborting

となってしまう

1
raspivid -a 12 -t 0 -w 1920 -h 1080 -vf -ih -fps 30 -l -o tcp://0.0.0.0:5000


ダイレクト TCP ストリーミング
はできた

リモートの環境のPythonからOpenCVを使い、手元のWebカメラの映像を読みたい
を参考に

1
vim camera.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import cv2
cap = cv2.VideoCapture("tcp://192.168.1.215:5000") # 配信のURLを指定
 
while True:
  k = cv2.waitKey(1)
  if k == 27: # ESC key
    break
 
  ret, frame = cap.read()
  if not ret:
    continue
 
  cv2.imshow('Raw Frame', frame)
 
cap.release()
cv2.destroyAllWindows()

で保存

これで実行するとmac でラズパイゼロの動画が表示できる

後はここから mac側で画像認識とかできるかテストしていく