人がいる方向をむく(アクションカメラ APEMAN A79)

はじめに

touch-sp.hatenablog.com
前回はiPhoneを使った。アクションカメラの方が軽量なのでそちらでもチャレンジ。
アクションカメラは安さ優先で「APEMAN A79」を選択。

1. まずはサーボの上にカメラを乗せた。(多少の工作が必要)
f:id:touch-sp:20200602004522j:plain:w320
Arduino用、サーボ用、カメラ用(カメラのバッテリー)の3つの電源を使っている(笑)。PCの電源も含めると4つ。

2. カメラ画像をWi-Fi経由でPCに送信。

3. PC内で画像内の顔を検出し、XBee経由でArduinoにサーボを動かす命令を送信。

4. Arduinoはサーボを動かすだけ。

  • Wi-FiXBeeの両方を使って無線化している。
  • カメラのWi-Fiは2.4GHzでXBeeと周波数が同じであるが今のところ干渉問題は起きていない。
  • カメラ画像の転送で若干のタイムラグがある。(iPhoneの時は気にならなかった)

環境

OpenCVではどうしてもうまくいかなかった。
代わりにVLC media playerを使用したので、あらかじめインストールが必要。

Windows10 Pro
NVIDIA GeForce GTX1080
Python 3.7.7
VLC media player 3.0.10
Arduino IDE 1.8.12
Arduino Uno R3


  • 2020年6月5日追記

その後「OpenCV」を使ってもできた。
こちらを参照。

バージョン確認(pip freeze)

Pythonパッケージでインストールが必要なのは「mxnet-cu101]と「gluoncv」と「python-vlc」と「pyserial」のみ。

pip install https://repo.mxnet.io/dist/python/cu101/mxnet_cu101-1.6.0-py2.py3-none-win_amd64.whl
pip install gluoncv
pip install python-vlc
pip install pyserial

その他は勝手についてくる。

certifi==2020.4.5.1
chardet==3.0.4
cycler==0.10.0
gluoncv==0.7.0
graphviz==0.8.4
idna==2.6
kiwisolver==1.2.0
matplotlib==3.2.1
mxnet-cu101 @ https://repo.mxnet.io/dist/python/cu101/mxnet_cu101-1.6.0-py2.py3-none-win_amd64.whl    
numpy==1.16.6
Pillow==7.1.2
portalocker==1.7.0
pyparsing==2.4.7
pyserial==3.4
python-dateutil==2.8.1
python-vlc==3.0.9113
pywin32==227
requests==2.18.4
scipy==1.4.1
six==1.15.0
tqdm==4.46.0
urllib3==1.22

Pythonコード

import mxnet as mx
import gluoncv

import serial, time

import vlc

ctx = mx.gpu()
# Load the model
classes = ['face']
net = gluoncv.model_zoo.get_model('ssd_512_mobilenet1.0_custom', classes=classes, pretrained=False, root='./model')
net.load_parameters('ssd_512_mobilenet1.0_face.params')
net.collect_params().reset_ctx(ctx)

# Compile the model for faster speed
net.hybridize()

pl = vlc.MediaPlayer('rtsp://192.72.1.1:554/liveRTSP/av4/track0')
pl.play()
time.sleep(1)

ser =serial.Serial("COM4", 9600)
time.sleep(1.5)

#初期設定
servo_angle = 90

while(True):

    try:

        #bufferがゼロになるまで待つ
        finished = False
        while not finished:
            finished = (ser.out_waiting == 0)
        
        pl.video_take_snapshot(0,'.snapshot.tmp.png', 0, 0)

        # Image pre-processing
        rgb_nd, frame = gluoncv.data.transforms.presets.ssd.load_test('.snapshot.tmp.png', short=360)
        # Run frame through network
        class_IDs, scores, bounding_boxes = net(rgb_nd.as_in_context(ctx))

        if scores[0][0] > 0.6:
            x_min = bounding_boxes[0][0][0]
            y_min = bounding_boxes[0][0][1]
            x_max = bounding_boxes[0][0][2]
            y_max = bounding_boxes[0][0][3]
    
            if x_min > 360:
                if x_min > 480:
                    servo_angle += 15
                else:
                    servo_angle += 3
            if x_max < 280:
                if x_max <160:
                    servo_angle -= 15
                else:
                    servo_angle -= 3

            servo_angle = 160 if servo_angle > 160 else servo_angle
            servo_angle = 20 if servo_angle < 20 else servo_angle
        
            send_data = servo_angle.to_bytes(1, 'big')

            ser.write(send_data)
            ser.write((255).to_bytes(1, 'big'))
 
        time.sleep(0.8)

    except KeyboardInterrupt:
        print("プログラムを終了します")
        break

pl.stop()    
ser.close()

Arduinoスケッチ

#include <Servo.h>

Servo myServo;
int var = 90;
int dummy;

void setup() {
  myServo.attach(9);
  myServo.write(var);
  Serial.begin(9600);
}

void loop() {
  if(Serial.available()>1){
    var = Serial.read();
    myServo.write(var);
    delay(100);
    dummy = Serial.read();
  }
}

dummyデータを読み込むことで命令完了をPC側に知らせている。