Raspberry Piで動かす、撮る、しゃべらせる

I2C通信の機器を使う

前回はLEDを点滅させ、気温のデータをI2C通信で読み取ってみました。

I2C通信を使用するのは、なにもセンサーだけではありません。

前回はモーターを制御してよりIoTらしくしてみます。

モータードライバーを繋いで、サーボモーターを動かしてみましょう。

モーターを動かす制御装置、モータードライバーを接続してみます。

今回は、このサーボモーター駆動キットを使用しました。

created by Rinker
¥950 2021/08/30
I2C接続16チャンネル サーボ&PWM駆動キット

キットの名前の通り、足の部分など、ハンダ付けが必要です。

キットを組み立てたら、次のように配線します。

この図ではオレンジのラインが繋がっているGPIO端子は、前回は気温センサーのプラスとして使用しましたが、電圧が3.3Vなので、今回はブランクしています。

代わりに、5Vプラスを出しているGPIOをプラスとして結線してあります。

今回はテストでもあり、またサーボを2つしか使用しないため、この方法としましたが、サーボの動きが頻繁だったり、サーボ数が増えると、過剰給電でRaspberry Piが壊れる可能性はありますので、モータードライバーへ5Vの給電を別に用意する必要があります。

モーターは、サーボモーターを使用しました。

created by Rinker
マイクロサーボ9g SG-90
¥440 2021/08/30
TowerProのサーボです。

モーターをモータードライバーに接続し、モーターを動かすためのモジュールをインストールします。

pip3 install adafruit-circuitpython-servokit

これで、モーターを動かすための準備が完了です。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

#Libraries
import time    #https://docs.python.org/fr/3/library/time.html
from adafruit_servokit import ServoKit    #https://circuitpython.readthedocs.io/projects/servokit/en/latest/

#Constants
nbPCAServo=2

#Parameters
MIN_IMP  =[500, 500]
MAX_IMP  =[2500, 2500]
MIN_ANG  =[0, 0]
MAX_ANG  =[180, 180]

#Objects
pca = ServoKit(channels=16)

# function init 
def init():

    for i in range(nbPCAServo):
        pca.servo[i].set_pulse_width_range(MIN_IMP[i] , MAX_IMP[i])


# function main 
def main():

    pcaScenario();


# function pcaScenario 
def pcaScenario():
    """Scenario to test servo"""
    for i in range(nbPCAServo):
        for j in range(MIN_ANG[i],MAX_ANG[i],1):
            print("Send angle {} to Servo {}".format(j,i))
            pca.servo[i].angle = j
            time.sleep(0.01)
        for j in range(MAX_ANG[i],MIN_ANG[i],-1):
            print("Send angle {} to Servo {}".format(j,i))
            pca.servo[i].angle = j
            time.sleep(0.01)
        pca.servo[i].angle=None #disable channel
        time.sleep(0.1)




if __name__ == '__main__':
    init()
    main()

このプログラムを実行した結果がこちらです。

サーボモーターはレゴブロックの中に組み込み、エポキシパテで固定。

稼働部分にも円形のレコブロックを固定してあります。

レゴブロックは、歯車やクランクなどの動的部品がけっこうありますので、試作品をつくるのに優れています。

カメラを使う。

Raspberry Piには、専用のカメラモジュールがあります。

カメラモジュール 感光チップOV5647センサー 5M画素 Raspberry Pi
created by Rinker
カメラモジュール 感光チップOV5647センサー 5M画素 Raspberry Pi 1 2 3 Model B B A+対応

テストの機能としては、このカメラで充分です。

カメラを取り付ける

Raspberry Pi には、カメラモジュールを差し込む専用のコネクタがありますので、そこにカメラをセットします。

有線LANコネクタのある方に、青いリボンがくるようにします。

逆にするとカメラは認識しません。

カメラを有効にする。

カメラを取り付けたら、カメラが使えるよう設定します。

Raspberry Piのメインメニューから、設定>Raspberry Piの設定を選びます。

設定画面で、インターフェイスを選び、カメラを有効に変更します。

設定が終わったら、一度Raspberry Piを再起動します。

これで、Raspberry Piのカメラは有効になりました。

カメラの設定がない場合

2021年10月以降の新しいRaspberry Pi OSの場合、カメラ設定が無い場合があります。

カメラ周りが新しくなったためですが、いままでのカメラ関係のコマンドなどが使用できないのはかなり困りますので、旧カメラシステムを有効化します。

コマンドラインから、sudo raspi-config と入力し、設定画面を開きます。

設定画面が開きました。

下向きカーソルで移動できるので、3個下がり、Interface Option に移動し、Enterを入力します。

画面が切り替わります。一番上がLegacy Camera の設定なので、Enterで設定します

Would you Like to enable legacy camera support? (レガシーカメラのサポートを有効にしたいですか?) 初期は「いいえ」です。

左向きカーソルで「はい」に移動し、Enterで確定します。

非推奨機能である警告がでますが、了解ですすみます。

元の設定画面に戻りました。TABキーを3回で、Finishに移動します。

再起動しないと設定が反映されません。他に作業していなければ「はい」で即再起動。他になにかしている場合は「いいえ」で抜けます。

途中の警告画面にもあったように、

Legacy camera supoort is enabled.
Please note that this functionalty is deprecated and will not be supported for future development.

(レガシーカメラのサポートが有効になっています。
この機能は非推奨であり、今後の開発ではサポートされませんのでご注意ください。)

この機能は非推奨で、いきなりプログラムなどからCameraが使えなくなると困るための暫定です。

いずれ、新Cameraシステムが整備されたら、順次そちらに切り換えていく必要があります。

この状態になれば、カメラが使用できますので、コマンドラインから次のようにすると、写真や動画が撮影できます。

raspistill -o test.jpg 

静止画を撮影します。test.jpgという名前で撮った写真が保存されます。

raspivid -o test.h264 

動画を撮影します。撮影時間は指定しないと5秒になります。 test.h264 という名前で動画が保存されます。

そして、先ほどモーターが稼働できるようになりましたので、このモーターにカメラをセットすると、いわゆる「雲台」となります。

スピーカーを繋ぐ

Raspberry Piには、3.5mmのオーディオジャックがついています。

これにスピーカーを繋いでみましょう。

ダイソーで販売している300円のスピーカーを使用しました。

ただ、繋いでも音は出ません。

スピーカージャックから音を出すには、Raspberry Piの端末画面を出し、

sudo raspi-config

と入力して、設定画面を表示させます。

表示した raspi-config の画面で、「1 System Options」→「S2 Audio」へ進みます。

「1 System Options」 はそのままリターン。 「S2 Audio」 へは下矢印キーで移動しリターンを入れます。

 

規定値として、「0 HDMI 1」になっていますので、これを「1 Headphones」に下矢印キーで移動し、リターンします。

希望の設定に赤帯が移動したら、TABキーを押す事で、了解に移動できますのでリターンを入れます。

取り消したいときは、右矢印で取消に移動してリターンすれば、取消が可能です。

この設定で 「1 Headphones」 に設定すれば、スピーカーから音が出るようになります。

Raspberry Piに日本語を喋らせる

音が出たので、合成音声を喋らせてみます。

Open JTalkというモジュールを使用すれば、テキストを読み上げさせる事ができます。

モジュールをRaspberry Piにインストールします。

sudo apt-get install -y open-jtalk open-jtalk-mecab-naist-jdic hts-voice-nitech-jp-atr503-m001
sudo apt-get install python-opencv
sudo apt-get install libopencv-dev


これで、pythonから Open JTalk を使用する事ができます。

#!/usr/bin/env python3
import sys
import subprocess

args = sys.argv

DICT_PATH='/var/lib/mecab/dic/open-jtalk/naist-jdic/'
VOICE_PATH='/usr/share/hts-voice/nitech-jp-atr503-m001/nitech_jp_atr503_m001.htsvoice'

VOICE_TEXT=args[1]
SPEED = args[2]

with open('speech.txt', 'w') as f:
    f.write(VOICE_TEXT)
subprocess.run(['open_jtalk', '-x', DICT_PATH, '-m', VOICE_PATH, '-ow', 'speech.wav','-r',str(SPEED), 'speech.txt'])
subprocess.run(['aplay', 'speech.wav'])

のプログラムを、chatty.py という名前で保存します。

コマンドライン引数の1番目の内容を、2のスピードで喋ります。

python3 chatty.py こんにちわ田中さん 1.5

と起動すると、「こんにちわ 田中さん」を速度1.5で喋ります。

日本語入力が出来ない時

sudo apt install ibus-mozc

を実行し、インストールしたあと再起動します。

女性の声に切り換える

男性の声でも悪くはないのですが、女性の声に切り換えてみましょう。

女性の声サンプルはこちらにあります。

上記の部分のリンクから、MMDAgent_Example-1.8.zip をダウンロードします。

ZIPファイルを解凍したら、 Open JTalk の声サンプルの場所にフォルダをすべてコピーします。

unzip MMDAgent_Example-1.8.zip
sudo cp -r MMDAgent_Example-1.8/Voice/mei /usr/share/hts-voice/

先ほど、喋らせたプログラムの中のVOICE_PATHをダウンロードしてきたmeiにあわせます。

いくつか種類がありますが、mei_bashful.htsvoice(恥ずかしがる?)で喋らせてみましょう。

#!/usr/bin/env python3
import sys
import subprocess

args = sys.argv

DICT_PATH='/var/lib/mecab/dic/open-jtalk/naist-jdic/'
VOICE_PATH='/usr/share/hts-voice/mei/mei_bashful.htsvoice'
VOICE_TEXT=args[1]
SPEED = args[2]

with open('speech.txt', 'w') as f:
    f.write(VOICE_TEXT)
subprocess.run(['open_jtalk', '-x', DICT_PATH, '-m', VOICE_PATH, '-ow', 'speech.wav','-r',str(SPEED), 'speech.txt'])
subprocess.run(['aplay', 'speech.wav'])

さきほどと同じく

python3 chatty_mei.py こんにちわ田中さん 1.5

で起動すると、このようになります。

動くものを検知してみる。

カメラモジュールに戻り、このカメラに動くものを探させる機能をつけたいと思います。

動体検知にはOpenCVを使用します。

OpenCVはコンピュータの画像向けライブラリで、画像を加工したり、顔や人物を検出したり、動くものを検知したりできます。

Raspberry Piの場合、前述のカメラモジュールでも使用できます。

まずはOpenCVをインストールします。

python3 -m pip install --upgrade pip
python3 -m pip install opencv-python
python3 -m pip install numpy --upgrade

次に下のコードを適当な場所にファイルとして設置します。

import cv2
cap = cv2.VideoCapture(0)
avg = None
while True:
    # 1フレームずつ取得する。
    ret, frame = cap.read()
    if not ret:
        break

    # グレースケールに変換
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    # 比較用のフレームを取得する
    if avg is None:
        avg = gray.copy().astype("float")
        continue

    # 現在のフレームと移動平均との差を計算
    cv2.accumulateWeighted(gray, avg, 0.95)
    frameDelta = cv2.absdiff(gray, cv2.convertScaleAbs(avg))

    # デルタ画像を閾値処理を行う
    thresh = cv2.threshold(frameDelta, 3, 255, cv2.THRESH_BINARY)[1]
    # 画像の閾値に輪郭線を入れる
    contours, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    frame = cv2.drawContours(frame, contours, -1, (0, 0, 255), 10)

    # 結果を出力
    cv2.imshow("Frame", frame)
    key = cv2.waitKey(30)
    if key == 27:
        break

cap.release()
cv2.destroyAllWindows()

こちらを参考に、色など若干修正したプログラムです。

python3 cv2test.py

実行すると、Raspberry Piのカメラモジュールがビデオ画像を撮影しはじめ、画面中でなにかが動くと、赤い縁取りがされます。

動作の様子を動画にすると、このようになります。

カメラを机に置き、上に手をかざしています。

手が動くところを捉え、赤いラインが表示されるのが判ると思います。なるべく手を動かさないように意識すると、殆ど赤いラインは見えません。

実行時に

ImportError: libcblas.so.3: cannot open shared object file: No such file or directory

というエラーが発生する場合は、

sudo apt-get install libatlas-base-dev

実現した機能で「だるまさんがころんだ」をしてみる。

ここまでの機能をつかって、「だるまさんがころんだ」をしてみましょう。

だるまさんが転んだは、「目をつぶる」→「だるまさんがころんだ を唱える」→「目を開けて動いたヒトがいるか確認する」の動作です。

目をつぶるは、カメラをサーボモーターで伏せさせる事とします。

「だるまさんがころんだ」の発声は JTalk にさせる事ができます。

発声後は、カメラを上げ、動体検知の処理を走らせます。

するとコードはこのようになります。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

#Libraries
import sys
import subprocess
import time
from adafruit_servokit import ServoKit
import datetime
import cv2 as cv
import concurrent.futures

#Objects
pca = ServoKit(channels=16)

DICT_PATH='/var/lib/mecab/dic/open-jtalk/naist-jdic/'
VOICE_PATH='/usr/share/hts-voice/mei/mei_bashful.htsvoice'
VOICE_TEXT='だるまさんがころんだ'
SPEED = 0.8


#画像を保存するディレクトリ
save_dir  = './image/'

#ファイル名は日付時刻を含む文字列とする
#日付時刻のあとに付加するファイル名を指定する
fn_suffix = 'motion_detect.jpg'

# VideoCaptureのインスタンスを作成する。
cap = cv.VideoCapture(0) 

#縦と横の解像度指定
cap.set(cv.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv.CAP_PROP_FRAME_HEIGHT, 480)
cap.set(cv.CAP_PROP_FOURCC, cv.VideoWriter_fourcc('H', '2', '6', '4'))

#2値化したときのピクセルの値
DELTA_MAX = 255

#各ドットの変化を検知するしきい値
DOT_TH = 20

#モーションファクター(どれくらいの点に変化があったか)が
#どの程度以上なら記録するか。
MOTHON_FACTOR_TH = 0.20


def down():
    pca.servo[1].set_pulse_width_range(500, 2500)
    pca.servo[1].angle = 120

def up():
    pca.servo[1].set_pulse_width_range(500, 2500)
    pca.servo[1].angle = 20

def chat():
    with open('speech.txt', 'w') as f:
        f.write(VOICE_TEXT)
    subprocess.run(['open_jtalk', '-x', DICT_PATH, '-m', VOICE_PATH, '-ow', 'speech.wav','-r',str(SPEED), 'speech.txt'])
    subprocess.run(['aplay', 'speech.wav'])

def detection():

    #比較用のデータを格納
    avg = None

    while True:

        ret, frame = cap.read()     # 1フレーム読み込む
        motion_detected = False     # 動きが検出されたかどうかを示すフラグ

        dt_now = datetime.datetime.now() #データを取得した時刻

        #ファイル名と、画像中に埋め込む日付時刻
        dt_format_string = dt_now.strftime('%Y-%m-%d %H:%M:%S') 
        f_name = dt_now.strftime('%Y%m%d%H%M%S%f') + "_" + fn_suffix


        # モノクロにする
        gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)

        #比較用のフレームを取得する
        if avg is None:
            avg = gray.copy().astype("float")
            continue


        # 現在のフレームと移動平均との差を計算
        cv.accumulateWeighted(gray, avg, 0.95)
        frameDelta = cv.absdiff(gray, cv.convertScaleAbs(avg))

        # デルタ画像を閾値処理を行う
        thresh = cv.threshold(frameDelta, DOT_TH, DELTA_MAX, cv.THRESH_BINARY)[1]

        #モーションファクターを計算する。全体としてどれくらいの割合が変化したか。
        motion_factor = thresh.sum() * 1.0 / thresh.size / DELTA_MAX 
        motion_factor_str = '{:.08f}'.format(motion_factor)

        #画像に日付時刻を書き込み
    #    cv.putText(frame,dt_format_string,(25,50),cv.FONT_HERSHEY_SIMPLEX, 1.5,(0,0,255), 2)
        #画像にmotion_factor値を書き込む
    #    cv.putText(frame,motion_factor_str,(25,470),cv.FONT_HERSHEY_SIMPLEX, 1.5,(0,0,255), 2)

        #モーションファクターがしきい値を超えていれば動きを検知したことにする
        if motion_factor > MOTHON_FACTOR_TH:
            motion_detected = True

        # 動き検出していれば画像を保存する
        if motion_detected  == True:
            #save
            cv.imwrite(save_dir + f_name, frame)
            print("DETECTED:" + f_name)


        # ここからは画面表示する画像の処理
        # 画像の閾値に輪郭線を入れる
        image, contours, hierarchy = cv.findContours(thresh.copy(), cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
        frame = cv.drawContours(frame, contours, -1, (0, 255, 0), 3)
        #枠線を緑に


        # 結果の画像を表示する
        cv.imshow('camera', frame)


        # 何かキーが押されるまで待機する
        k = cv.waitKey(1000)  #引数は待ち時間(ms)
        if k == 27: #Esc入力時は終了
            break


    print("Bye!\n")
    # 表示したウィンドウを閉じる
    cap.release()
    cv.destroyAllWindows()

def main():
    executor = concurrent.futures.ThreadPoolExecutor(max_workers=3)
    down()
    chat()
    up()
    detection()


if __name__ == '__main__':
    main()
 to join this conversation on GitHub. Already 

このコードを実行した結果です。

これらの機能を発展させれば、動くものを検知し、音声で警告を出したり、カメラをそちらに向けるなどの動作をさせる事が可能になります。

このように、Raspberry Piは様々な機能を組み合わせ、様々な機能を開発する事が可能です。

コメントを残す

メールアドレスが公開されることはありません。