はじめに
カメラ画像をWi-FiでPCに送信。
PCでカメラ画像からボールの位置を確認、XBeeを使ってArduinoにモータを動かす指示を送信。
上記を繰り返すことによってボールを追いかけることができた。
ボールの認識はMXNetの物体検出モデルを使用した。(こちらを参照)
購入したもの
Arduino Uno R3(The Arduino Starter Kitとして購入) Hブリッジモータードライバ [L293DNE] (The Arduino Starter Kitに付属) サンハヤト SAD-101 ニューブレッドボード XBee ZB(S2C)ワイヤアンテナ型 ×2 XBee エクスプローラ USB (Sparkfun Electronics) Arduino用XBee スタック可能シールド V2 (SeeedStudio) APEMAN A79 アクションカメラ Hitecコネクタ付き、6Vニッケル水素2000mAhバッテリーパック(モーター用に購入) cheero Canvas 3200mAh IoT機器対応 モバイルバッテリー(Arduino用に購入) 変換名人 USB A(オス) -USB B(オス)変換ケーブル [ 約20cm ] タミヤ ロングユニバーサルアームセット (オレンジ) タミヤ ユニバーサルプレート用スライドアダプター タミヤ スポーツタイヤセット (56mm径) タミヤ ボールキャスター (2セット入) タミヤ ユニバーサルプレート (2枚セット) タミヤ ダブルギヤボックス(左右独立4速タイプ) タミヤ 1.5A 平行コード (5m) ダイソー すべりどめシート 適当なボール
- Arduinoはユニバーサルプレート用スライドアダプターを使ってユニバーサルプレートに固定した。適当なスペーサーがあればスライドアダプターはいらないと思う。
- ブレッドボードは両面テープでユニバーサルプレートに張り付けた。
- バッテリー類はすべりどめシートを下に敷いて置いただけ。平地を走行する分にはこれで十分であった。
- APEMAN A79のカバーをユニバーサルプレートにボンドで固定した。
- タミヤ 1.5A 平行コード (5m)の商品説明には「芯線が30本と多く、ハンダを使わずに手でひねった結線でも接触不良になりにくいのもポイントです。」と書かれている。それを信じてはんだ付けはしていないが今のところ問題ない。そもそもはんだごてを持っていない(笑)。
モータードライバの使用法
動作環境
Python 3.7.8
certifi==2020.6.20 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.3.0 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.3.0.38 Pillow==7.2.0 portalocker==2.0.0 pyparsing==2.4.7 pyserial==3.4 python-dateutil==2.8.1 pywin32==228 requests==2.18.4 scipy==1.5.2 six==1.15.0 tqdm==4.48.2 urllib3==1.22
Pythonコード
import mxnet as mx import gluoncv import serial, time import cv2, queue, threading delay = 50 class VideoCapture: def __init__(self, name): self.cap = cv2.VideoCapture(name) self.q = queue.Queue() t = threading.Thread(target=self._reader) t.daemon = True t.start() # read frames as soon as they are available, keeping only most recent one def _reader(self): while True: ret, frame = self.cap.read() if not ret: break if not self.q.empty(): try: self.q.get_nowait() # discard previous (unprocessed) frame except queue.Empty: pass self.q.put(frame) def read(self): return self.q.get() def motion(r_PWM, l_PWM, delay_time): a = r_PWM.to_bytes(1, 'big') b = l_PWM.to_bytes(1, 'big') c = delay_time.to_bytes(1, 'big') ser.write(a) ser.write(b) ser.write(c) ser =serial.Serial("COM4", 9600) time.sleep(2) ctx = mx.gpu() # Load the model classes = ['ball'] net = gluoncv.model_zoo.get_model('ssd_512_mobilenet1.0_custom', classes=classes, pretrained=False, root='./model') net.load_parameters('ssd_512_mobilenet1.0_ball.params') net.collect_params().reset_ctx(ctx) # Compile the model for faster speed net.hybridize() cap = VideoCapture('rtsp://192.72.1.1:554/liveRTSP/av4/track0') time.sleep(1) #最初にストップした状態を送る motion(0,0,100) while True: frame = cap.read() frame = mx.nd.array(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)).astype('uint8') rgb_nd, frame = gluoncv.data.transforms.presets.ssd.transform_test(frame, short=320) # Run frame through network class_IDs, scores, bounding_boxes = net(rgb_nd.as_in_context(ctx)) if ser.in_waiting != 0: ser.reset_input_buffer() if scores[0][0] > 0.55: 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] width = x_max - x_min height = y_max - y_min if max(width, height) < 135: #右旋回 if x_min > 360: l_PWM = 180 if x_min > 480: r_PWM = 110 else: r_PWM = 130 motion(r_PWM, l_PWM, delay) #左旋回 elif x_max < 280: r_PWM = 180 if x_max <160: l_PWM = 110 else: l_PWM = 130 motion(r_PWM, l_PWM, delay) #前進 else: r_PWM = 180 l_PWM = 180 motion(r_PWM, l_PWM, delay) else: motion(0, 0, 25) #ボールが近いのでストップ else: motion(0, 0, 25) #ボールを見失ったのでストップ # 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.55) gluoncv.utils.viz.cv_plot_image(img) #time.sleep(0.2) # escを押したら終了 if cv2.waitKey(1) == 27: break ser.close() cv2.destroyAllWindows()
Arduinoスケッチ
const int PIN_1A = 4; const int PIN_2A = 5; const int PIN_3A = 6; const int PIN_4A = 7; const int motor_right = 9; const int motor_left = 10; int delay_time; int right_pwm; int left_pwm; void setup() { pinMode(PIN_1A, OUTPUT); pinMode(PIN_2A, OUTPUT); pinMode(PIN_3A, OUTPUT); pinMode(PIN_4A, OUTPUT); pinMode(motor_right, OUTPUT); pinMode(motor_left, OUTPUT); Serial.begin(9600); //right_wheel前進 digitalWrite(PIN_1A, LOW); digitalWrite(PIN_2A, HIGH); //left_wheel前進 digitalWrite(PIN_3A, HIGH); digitalWrite(PIN_4A, LOW); } void loop() { if(Serial.available()>2){ right_pwm = Serial.read(); left_pwm = Serial.read(); delay_time = Serial.read()*4; analogWrite(motor_right, right_pwm); analogWrite(motor_left, left_pwm); delay(delay_time); Serial.write(255); } }