人がいる方向をむく(iPhone)

はじめに

カメラにうつりこんだ人の顔を検出し、その顔の方向にカメラが向くようにしてみた。

  1. まずはサーボの上にiPhoneを乗せた。(多少の工作が必要)
  2. iPhoneのカメラ画像をWi-Fi経由でPCに送信。
  3. PC内で画像内の顔を検出し、XBee経由でArduinoにサーボを動かす命令を送信。
  4. Arduinoはサーボを動かすだけ。

iPhone→PCは「EpocCam」(無料版)を使用。
Wi-FiXBeeの両方を使って無線化している。
Wi-Fiは2.4GHz, 5GHzどちらを使っても今のところXBeeとの干渉問題は起きていない。

環境

Windows10 Pro
NVIDIA GeForce GTX1080
Python 3.7.7
Arduino IDE 1.8.12
Arduino Uno R3

バージョン確認(pip freeze)

インストールが必要なのは「mxnet-cu101]と「gluoncv」と「opencv-python」と「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 opencv-python
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
opencv-python==4.2.0.34
Pillow==7.1.2
portalocker==1.7.0
pyparsing==2.4.7
pyserial==3.4
python-dateutil==2.8.1
pywin32==227
requests==2.18.4
scipy==1.4.1
six==1.14.0
tqdm==4.46.0
urllib3==1.22

Pythonコード

import mxnet as mx
import gluoncv

import serial, time

import cv2

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()

# Load the webcam handler
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
# letting the camera autofocus
time.sleep(1)

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

#初期設定
servo_angle = 90

while(True):
    # Load frame from the camera
    ret, frame = cap.read()

    if ret==False:
        break

    # Image pre-processing
    frame = mx.nd.array(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)).astype('uint8')
    rgb_nd, frame = gluoncv.data.transforms.presets.ssd.transform_test(frame, short=480)
    # 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 > 320:
            servo_angle += 5
        if x_max < 320:
            servo_angle -= 5

        servo_angle = 170 if servo_angle > 170 else servo_angle
        servo_angle = 10 if servo_angle < 10 else servo_angle

        #bufferがゼロになるまで待つ
        finished = False
        while not finished:
            finished = (ser.out_waiting == 0)
        
        send_data = servo_angle.to_bytes(1, 'big')

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

    # Display the result
    img = gluoncv.utils.viz.cv_plot_bbox(frame, bounding_boxes[0], scores[0], class_IDs[0], class_names=net.classes, thresh=0.6)
    gluoncv.utils.viz.cv_plot_image(img)

    time.sleep(0.2)

    # escを押したら終了
    if cv2.waitKey(1) == 27:
        break
    
ser.close()
cap.release()
cv2.destroyAllWindows()

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);
    dummy = Serial.read();
    delay(100);
  }
}

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