Avance.Lab

技術紹介

第6回 顔認識デバイス Raspberry Pi 4B編 〜操作周辺のソフトウェア編 〜

公開日:2021.12.10 更新日:2021.12.10

tag: IoTRaspberry Pi

こんにちは。Kです。

Raspberry Pi 4Bでのハードウェア編に続きまして、ソフトウェア編ということで、1回では内容が纏められず、解説は2回に渡ります。

顔認識デバイスでは主に、3つの主機能に分かれます。

  1. 首振り機能(Pan-Tilt Hatライブラリ)
  2. 通信機能(Bluetooth通信)
  3. カメラ+画像解析機能

今回は周辺機器に関する首振り機能と通信機能について解説します。

セットアップ方法について

セットアップについては簡単に紹介していきます。

OSはRaspberry Pi OSを使用します。

各パッケージのインストール方法は下記よりご参照ください。

Arducam IMX298

URL:https://github.com/ArduCAM/MIPI_Camera

PanTiltHat

URL:https://github.com/pimoroni/pantilt-hat

首振り機能(Pan-Tilt Hat)

ソースコード解説に入る前に首振りの大まかな仕様を説明します。

このパーツでは横方向(Pan)と縦方向(Tilt)に対して二つのサーボモータを使って制御しています。

横方向に対して左右180°と少しだけ遊びがある程度まで動かす事ができます。

カメラの中心位置を0°とした場合、横振りの場合 −90°は左向き、90°は右向きが最大稼働範囲となります。

上記を踏まえてカメラの中心位置を手動でカメラの可動域まで動かしながら確認します。

パーツに使用しているサーボモーターは小型で非常に壊れやすいため、慎重に動かして調整します。

中心位置をずらすにはサーボ下部のマイナスネジ4箇所の固定ネジを外すと底部が外れます。フラットケーブルを外してから底部ネジの中心にあるネジを外せばサーボが外れます。この位置をずらして嵌め込む事で中心位置を変更出来ます。

縦方向に対して下方向がフラットケーブルが干渉する関係上、上下おおよそ150°程度まで動かす事ができます。

同じ要領で縦振りに対しても調整を行ってください。

性能上サーボモーターは稼働範囲外まで設定する事が出来ますが、故障の原因になりますので、稼働範囲外へ設定することは避けてください。

下記にソースコードを抜粋しました。

import pantilthat
:
# 首振りの角度
panvalue = 0
tiltvalue = -50
:
pantiltalive = True
:
def pantilt():
  sleep_time = 0.05
  change = 5

  while pantiltalive:
    global panvalue
    global tiltvalue

    time.sleep(sleep_time)

    # from spp
    if spp.mode != sppconnection.Mode.AUTO:
      panvalue = (180 - spp.vx) - 90
      tiltvalue = (180 - spp.vy) - 90

    if panvalue > 90:
      panvalue = 90
    elif panvalue < -90:
      panvalue = -90

    if tiltvalue > 60:
      tiltvalue = 60
    elif tiltvalue < -90:
      tiltvalue = -90

    diffPan = panvalue - pantilthat.get_pan()
    if diffPan > 0:
      if diffPan > change:
        pantilthat.pan(pantilthat.get_pan() + change)
      else:
        pantilthat.pan(panvalue)
    if diffPan < 0:
      if diffPan < - change:
        pantilthat.pan(pantilthat.get_pan() - change)
      else:
        pantilthat.pan(panvalue)

    diffTilt = tiltvalue - pantilthat.get_tilt()
    if diffTilt > 0:
      if diffTilt > change:
        pantilthat.tilt(pantilthat.get_tilt() + change)
      else:
        pantilthat.tilt(tiltvalue)
    if diffTilt < 0:
      if diffTilt < - change:
        pantilthat.tilt(pantilthat.get_tilt() - change)
      else:
        pantilthat.tilt(tiltvalue)

pantilthatのライブラリを使うことで、角度の取得と設定を簡単に行えます。

変更直後、設定した角度へ動きます。この時1ループ0.05秒の周期ですので、0°から90°に設定した場合、急激に首が動くため、逆方向へ反動するような動作があったり、最悪の場合サーボ内部のギアが壊れやすくなります。

ソースコードでは5°以上の移動量がある場合は細かく動作するように制御してあります。

Bluetooth通信

続いてBluetooth通信です。

既に前章でお話しした通りPythonではBluetooth通信が非常に簡単に実装できます。

まずはBluetooth通信のSPPプロファイルで実装したクラスを抜粋します。

class SppConnection(threading.Thread):
  def __init__(self):
    super(SppConnection, self).__init__()
    self.alive = True
    self.mode = Mode.AUTO
    self.facedetect = True
    self.lookface = True
    self.eyedetect = True
    self.agegenderdetect = True
    self.vx = 0
    self.vy = 0

  def run(self):
    last = time.time()
    # mode = Mode.AUTO
    # vx = 0
    # vy = 0
    recv = bytes(0)
    datasize = 13

    while self.alive:
      self.mode = Mode.AUTO
      self.facedetect = True
      self.lookface = True
      self.eyedetect = True
      self.agegenderdetect = True

      sock = bluetooth.BluetoothSocket(bluetooth.RFCOMM)
      sock.bind(("", bluetooth.PORT_ANY))
      sock.listen(1)
      sock.settimeout(3)

      # Set Discoverable
      subprocess.call(['sudo', 'hciconfig', 'hci0', 'piscan'])

      # uuid = 'ef7ce24a-a1eb-45d4-9208-f896b0ae8336'
      # uuid = "94f39d29-7d6d-437d-973b-fba39e49d4ee"
      uuid = '00001101-0000-1000-8000-00805F9B34FB'
      bluetooth.advertise_service(
        sock, "MyServer", service_id=uuid,
        service_classes=[uuid, bluetooth.SERIAL_PORT_CLASS],
        profiles=[bluetooth.SERIAL_PORT_PROFILE],
      )

      print('accept start')
      # client_socket, address = sock.accept()
      try:
        client_socket, address = sock.accept()
      except:
        print('Bluetooth accept exception')
        continue
      if client_socket is None:
        continue

      print('connected')

      while self.alive:
        try:
          result = client_socket.recv(13)

          recv = recv + result

          if len(recv) >= datasize:
            data = recv[0:datasize]
            # print(recv.hex())

            mode_old = self.mode
            self.mode = Mode(int.from_bytes(data[0:1], 'big'))

            facedetect_old = self.facedetect
            self.facedetect = int.from_bytes(data[1:2], 'big')

            lookface_old = self.lookface
            self.lookface = int.from_bytes(data[2:3], 'big')

            eyedetect_old = self.eyedetect
            self.eyedetect = int.from_bytes(data[3:4], 'big')

            agegenderdetect_old = self.agegenderdetect
            self.agegenderdetect = int.from_bytes(data[4:5], 'big')

            vx_old = self.vx
            self.vx = int.from_bytes(data[5:9], 'big')

            vy_old = self.vy
            self.vy = int.from_bytes(data[9:13], 'big')

            if self.mode != mode_old or self.facedetect != facedetect_old or self.lookface != lookface_old or self.eyedetect != eyedetect_old or self.agegenderdetect != agegenderdetect_old or self.vx != vx_old or self.vy != vy_old:
              print('mode={2}, face detect={5}, look face={6}, eye detect={3}, age/gender detect={4}, x={0}, y={1}'.format(self.vx, self.vy, self.mode, self.eyedetect, self.agegenderdetect, self.facedetect, self.lookface))

            if len(recv) > datasize:
              recv = recv[datasize:]
            else:
              recv = bytes(0)
        except Exception:
          break

通信内容はメンバーのパラメータ値を取得することで、現在の値などを調査する事が出来ます。

import sppconnection
:
spp = sppconnection.SppConnection(
:
def main():
  # 通信スレッド開始
  spp.start()

あとはインスタンスを生成して軌道開始時にstartのメソッドを呼び出したら以降は通信が可能です。

次回はいよいよカメラ周りの機能の解説です。

ではまた!

K

クラウド,IoT,小型PC,スマートフォンなど環境問わず担当しています。
最近はROS2に興味があります。

関連記事