工業製造
産業用モノのインターネット | 工業材料 | 機器のメンテナンスと修理 | 産業プログラミング |
home  MfgRobots >> 工業製造 >  >> Manufacturing Technology >> 製造プロセス

全方向移動ロボットで人物を安全追跡:オムニホイール搭載「ハンターフラッタラーボット」

必要な部品と材料

部品数量備考
L298N デュアルステッピングモータードライバー23モーター駆動用(2A対応)
LM2596 DC-DCステップダウンレギュレーター112V→5V変換用
リポバッテリーアラーム(低電圧検知器)1バッテリー保護用
24RPM DC 12V ギアードモーター(4mmシャフト)3オムニホイール用
Arduino Nano R31モーター制御用
Microsoft LifeCam HD-3000 Webカメラ1顔検出用(安価な代替も可)
ジャンパーワイヤー10本以上電源・信号配線用
Creator Ci201メイン処理(OpenCV実行用)
11.1V 3セル リポバッテリー1メイン電源

必要な工具

使用アプリ・オンラインサービス

プロジェクト概要

「Hackster Terminate the competition」コンテストへの応募作品です。ターミネーターのような人間を追跡するロボットですが、攻撃する代わりに「褒め言葉」をかけてくれるフレンドリーなロボット「Hunter Flatterer Robot」を製作しました。

設計コンセプト

システム構成

全方向移動ロボットで人物を安全追跡:オムニホイール搭載「ハンターフラッタラーボット」

システムは4つの主要ブロックで構成:

  1. 電源系:リポバッテリー→LM2596で5V生成、アラームで低電圧検知
  2. Ci20(頭脳):OpenCVで顔検出、Arduinoへシリアル指令送信
  3. Arduino Nano:Ci20からの指令を受け、3つのモーターを制御
  4. メカニカル:3Dプリントシャーシ、オムニホイール、ギアードモーター

電源回路の配線

全方向移動ロボットで人物を安全追跡:オムニホイール搭載「ハンターフラッタラーボット」

ArduinoとL298Nの接続

詳細な接続チュートリアル:Instructables を参照。

3Dプリントケース

Ci20を目立たせるデザインで、トップ・ボトム・ホイールの3パーツ構成。M4ボルトでモーター固定。STLファイルは「カスタムパーツ」セクションで公開。

全方向移動ロボットで人物を安全追跡:オムニホイール搭載「ハンターフラッタラーボット」

Ci20でのOpenCV顔検出

Step 1: OpenCVインストール

標準的なインストール手順に従ってセットアップ。

Step 2: 顔追跡プログラムのビルド・実行

g++ -I/usr/local/include/opencv -I/usr/local/include/opencv2 \
    -L/usr/local/lib/ -g -o binary main.cpp \
    -lopencv_core -lopencv_imgproc -lopencv_highgui -lopencv_ml \
    -lopencv_video -lopencv_features2d -lopencv_calib3d \
    -lopencv_objdetect -lopencv_contrib -lopencv_legacy -lopencv_stitching
全方向移動ロボットで人物を安全追跡:オムニホイール搭載「ハンターフラッタラーボット」

Ci20とArduinoの連携

Arduinoにスケッチを書き込み、USBでCi20に接続。顔追跡プログラムを実行するとカメラ映像が表示され、顔が画面中央にあればロボットが前進します。

未実装のチャレンジ課題

時間の制約で実装できなかった機能。挑戦したい方へ:

ソースコード

Arduinoスケッチ - メイン

Ci20からシリアルで駆動指令を受信し、3モーターを適切に制御して全方向移動を実現。元々Raspberry Pi用に書いたコードですがCi20でも動作します。

// Motor 1
int dir1PinA = 3; int dir2PinA = 2; int speedPinA = 9;
// Motor 2
int dir1PinB = 4; int dir2PinB = 5; int speedPinB = 8;
// Motor 3
int dir1PinC = 6; int dir2PinC = 7; int speedPinC = 10;

int x = 0, y = 0, dominantUltrasonic = 0;
bool moveMotor = false;
int startTime = 0;

void setup() {
  Serial.begin(9600);
  pinMode(dir1PinA, OUTPUT); pinMode(dir2PinA, OUTPUT); pinMode(speedPinA, OUTPUT);
  pinMode(dir1PinB, OUTPUT); pinMode(dir2PinB, OUTPUT); pinMode(speedPinB, OUTPUT);
  pinMode(dir1PinC, OUTPUT); pinMode(dir2PinC, OUTPUT); pinMode(speedPinC, OUTPUT);
  startTime = millis();
}

String rpiString;
void loop() {
  readDataFromRPi();
  delay(30);
}

void readDataFromRPi() {
  while (Serial.available()) {
    delay(3);
    if (Serial.available() > 0) {
      char c = Serial.read();
      rpiString += c;
      if (c == 'n') break;
    }
  }
  if (rpiString.length() > 0) {
    Serial.println(rpiString);
    String isRotate = getValue(rpiString, ' ', 0);
    String xval = getValue(rpiString, ' ', 1);
    String yval = getValue(rpiString, ' ', 2);
    x = xval.toInt(); y = yval.toInt();
    startTime = millis();
    if (isRotate == "r") rotate(x);
    else driveInDirection(x, y);
    rpiString = "";
  }
}

String getValue(String data, char separator, int index) {
  int found = 0;
  int strIndex[] = {0, -1};
  int maxIndex = data.length() - 1;
  for (int i = 0; i <= maxIndex && found <= index; i++) {
    if (data.charAt(i) == separator || i == maxIndex) {
      found++;
      strIndex[0] = strIndex[1] + 1;
      strIndex[1] = (i == maxIndex) ? i + 1 : i;
    }
  }
  return found > index ? data.substring(strIndex[0], strIndex[1]) : "";
}

Arduinoスケッチ - モーター制御

メインスケッチと同一プロジェクトにインクルードして使用。

void driveInDirection(float newX, float newY) {
  delay(20);
  float x = newX, y = newY;
  float theta = atan2(y, x);
  float mag = sqrt(x*x + y*y);
  float vx = mag * cos(theta);
  float vy = mag * sin(theta);
  
  // オムニホイール運動学(3輪120度配置)
  float w1 = -vx;
  float w2 = 0.5 * vx - sqrt(3)/2 * vy;
  float w3 = 0.5 * vx + sqrt(3)/2 * vy;
  
  // 最大値で正規化
  float wSet[] = {w1, w2, w3};
  float largestValue = 0.0;
  for (int i = 0; i < 3; i++) 
    if (abs(wSet[i]) > largestValue) largestValue = abs(wSet[i]);
  
  float speedCoef = 147.0 / largestValue;
  w1 *= speedCoef; w2 *= speedCoef; w3 *= speedCoef;
  
  if (x == 0 && y == 0) { w1 = 0; w2 = 0; w3 = 0; }
  
  w1 = constrain(w1, -150, 150);
  w2 = constrain(w2, -150, 150);
  w3 = constrain(w3, -150, 150);
  
  boolean w1_ccw = w1 < 0;
  boolean w2_ccw = w2 < 0;
  boolean w3_ccw = w3 < 0;
  
  byte w1_speed = map(abs(w1), 0, 150, 0, 255);
  byte w2_speed = map(abs(w2), 0, 150, 0, 255);
  byte w3_speed = map(abs(w3), 0, 150, 0, 255);
  
  analogWrite(speedPinA, w1_speed);
  analogWrite(speedPinB, w2_speed);
  analogWrite(speedPinC, w3_speed);
  
  digitalWrite(dir1PinA, !w1_ccw); digitalWrite(dir2PinA, w1_ccw);
  digitalWrite(dir1PinB, !w2_ccw); digitalWrite(dir2PinB, w2_ccw);
  digitalWrite(dir1PinC, w3_ccw);  digitalWrite(dir2PinC, !w3_ccw);
}

void rotate(float milliseconds) {
  // その場旋回用
  float w1 = 255, w2 = 255, w3 = 255;
  // ... 同様にモーター駆動 ...
  delay(milliseconds);
  analogWrite(speedPinA, 0);
  analogWrite(speedPinB, 0);
  analogWrite(speedPinC, 0);
}

Ci20用 顔追跡C++プログラム

OpenCVのHaarカスケード分類器で顔検出し、顔の中心座標に基づいてArduinoへ移動指令を送信。

#include "opencv2/objdetect/objdetect.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include 
#include 
#include 
#include 
#include 

using namespace std;
using namespace cv;

CascadeClassifier face_cascade;
String window_name = "Face Detection";

int sendSerial(char* message) {
  int fd = open("/dev/ttyUSB0", O_RDWR);
  if (fd == -1) { perror("dev/ttyUSB0"); return 1; }
  struct termios tios;
  tcgetattr(fd, &tios);
  tios.c_iflag = IGNBRK | IGNPAR;
  tios.c_oflag = 0; tios.c_lflag = 0;
  cfsetspeed(&tios, B9600);
  tcsetattr(fd, TCSAFLUSH, &tios);
  usleep(1000);
  write(fd, message, strlen(message));
  return 0;
}

void detectFaces(Mat frame) {
  vector faces;
  Mat frame_gray;
  cvtColor(frame, frame_gray, COLOR_BGR2GRAY);
  equalizeHist(frame_gray, frame_gray);
  face_cascade.detectMultiScale(frame_gray, faces, 1.1, 3, 0|CASCADE_SCALE_IMAGE, Size(30,30));
  
  for (size_t i = 0; i < faces.size(); i++) {
    Point center(faces[i].x + faces[i].width/2, faces[i].y + faces[i].height/2);
    ellipse(frame, center, Size(faces[i].width/2, faces[i].height/2), 0, 0, 360, Scalar(255,0,255), 4, 8, 0);
    
    // 顔が画面中央付近なら前進指令
    if (center.x > frame.cols/3 && center.x < frame.cols/3*2) {
      sendSerial("F 50 50");
    } else {
      sendSerial("0 0 ");
    }
  }
  imshow(window_name, frame);
}

int main() {
  sendSerial("0 0 ");
  VideoCapture cap(0);
  cap.set(CV_CAP_PROP_FRAME_WIDTH, 480);
  cap.set(CV_CAP_PROP_FRAME_HEIGHT, 240);
  Mat frame;
  
  if (!face_cascade.load("haarcascade_frontalface_default.xml")) {
    cout << "classifier load failed"; return -1;
  }
  
  while (cap.read(frame)) {
    detectFaces(frame);
    if (waitKey(30) >= 0) break;
  }
  return 0;
}

カスタムパーツ(3Dプリント)

回路図

全方向移動ロボットで人物を安全追跡:オムニホイール搭載「ハンターフラッタラーボット」

詳細な配線説明は本文の「電源回路の配線」を参照してください。

製造プロセス

  1. OpenCVでRaspberry Pi上級卓球球追跡
  2. Raspoulette:太陽光発電自律コープドアシステムのプロトタイプ
  3. DIY Wall-E 風 Raspberry Pi CD ボックスロボット
  4. ローリングアラームロボット:Raspberry Pi と Google カレンダーで動くスマートモバイル目覚ましシステム
  5. Windows 10 IoT Coreで音声制御コンピュータビジョンロボットを構築
  6. ROSロボット:Raspberry Piでターミナル操作ナビゲーション
  7. シンプルPiロボット - Raspberry Piで2WD自律ローバーを構築
  8. Arduino向けスマート屋内ナビゲーションロボットキット
  9. リニアモーター理解:電気エネルギーから精密直線運動へ
  10. 飲料ロボットの技術・自動化・業界役割:バブルティー・コーヒー自動化の最前線
  11. サーボモーターの仕組みと重要性