周南ロボコン参戦

昨年まで運営を務めていた周南ロボコンですが、今年は参加者側に挑戦しました。 今年の競技についてはパワーアップした周南ロボコンのWebサイトを見てみて下さい! http://www.tokuyama.ac.jp/robocon/2019/index.html

ロボット概要

1台目のロボットは塩ビパイプを登るロボットです。 糸を巻いていくロボットが主流でしたが、糸の先におもりをつけてパイプの中に入れておくことで、 タイヤで昇っていくロボットの重さを軽くするという斬新なアイデアで高速昇降しているチームもありました。 私はArduino互換のESP32(IoT Expressで検索)を用いてスマホから昇降用のモーターとピン球の保持・排出用のサーボを制御しました。

www.mgo-tec.com

開発途中にフラッシュ削除が必要になったりしましたが、SNSのおかげで何とかなりました。

ESP8266/ESP32環境向上委員会公開グループ | Facebook

ESP32 が動作しない(Rebootを繰り返す)時の対処 | くまぱんだ日記

2台目のロボットはピン球を箱に向けて落としていくロボットです。展開制限がないのでガイドを垂らしていくロボットが多かったです。 私はとにかく「ロボット視点の映像を見ながら遠隔操縦する」ことに捕らわれていたので、Zerobotの情報をもとにRaspberry Pi Zero Wを使って足回りのみのロボットを作りました!ただ、ボールの受け取り場所も排出機構もない上に、操縦も難しかったので、2試合目はピン球落とせる箱を付けて有線で出ましたw

hackaday.io

反省点

反省点としては大きく2つ。
1つは「有線で勝負できるロボットを作ってから無線・自動に挑もう」ということ。
無線操作をRaspberry PiArduinoそれぞれで実現するという当初の目的は叶いましたが、ハードウェアの設計としては競技に全く適しておらず、悲しい結果に終わりました(´;ω;`) 案外有線⇔無線の切り替えは楽なので、先に機能的なロボットの設計を頑張りましょう!!
2つ目は「オープンソースに頼りすぎない」ということ。 両ロボットともネットから拾ってきた情報をもとに製作しました。 難しめの制御を短期間で導入するという意味ではよい選択だったと思いますが、実際の競技に適した形で改良しようとしたときに、結局サンプルの理解・修正に時間を取られてしまいます。また、オープンソースに頼るという事は大げさに言えば「既に世の中あるものしか作れない」という事でもあるので、研究等においては致命的だなとか考えたりしました。

まとめ

初めて参加者として周南ロボコンに出れたことは新鮮でしたが、体育館で高専祭を迎えるのはそろそろやめたいですねw というわけで来年は今年の学びを生かしてロボットを別の場所から操縦できるようにしたいと思います(反省してない)。 これができるようになれば全国の高専生に参加してもらえるようになり、活動の幅も広がると思うので応援よろしくお願いします!!

Maker Faire 京都

Maker Faire に初めて参加しました!

ものづくりのあらゆる分野が集まっていて楽しかったです。

今回はその一部を紹介したいと思います。

 

1)技科大の木製時計

これまでの金属時計の重さが気になり製作したそうです。

着脱部分を磁石+フックで実現していました。

 

2)ベゼリー

Raspberry Piを使ったコミュニケーションロボット。

現在販売中ですが、活用事例を模索中みたい。

 

3)不思議な楽器

機械の近くで手を動かしてみると...

何故か手の動きに合わせて音楽が流れます...不思議

 

4)組み紐自動作成機

3DプリンターArduinoを使って組み紐が自動で作られていました。

 

5)OriHime拡張ツール

出展者のお母さんがOriHimeで参加してました。

M5stackを使って今誰がOriHimeを使ってるか確認できるようです。

 

こういう場所にくると製作意欲が湧くので定期的に行きたいですね!

地方でも取り組みが広がるよう努力します。

3Dプリンター入門

学校で3Dプリンター自体は何度も使っていましたが

遂にMy 3Dプリンターを手に入れました。

ja.aliexpress.com

私が購入したとき(2019/7)は21,640円でした。

組み立ては大変でしたが、動画もあったので何とかなります。


TEVO Tarantula PRO - Detailed Assembly & Calibrations

スライサーには「KISSlicerr64」を使いました。

私が使っている設定を共有しておくので参考にしてみてください。

kisslicer_win64.zip - Google ドライブ

 

今のところPLA(白)とABS(黒)を使いましたが問題なく造形出来ました。

ABSの時は保温のために、ゴミ袋で覆ってから造形しています。

f:id:massiro-myaon:20190922235408j:plain

1家に1台の時代もそう遠くないかも知れませんね笑 

 

2021/02/26 追記

新規に買ったABSの設定温度がベッド(80~100)ノズル(190~230)だったので

ベッド(80→90)ノズル(235→226)に設定変更。

また、造形終了時に Printer G-code の Postfix の設定で「G1 X0 Y235 F3000」が行われていた(テーブルを前に出してくれる)ものの、そのせいで断熱用ビニール袋を突き破ってしまうことが多々あったので「G1 X0 Y200 F3000」に変更した。

指紋認証入門

私のいる高専ではICカードを使った認証をよく使っていて私もPasoriで遊んでみたりしていました。
blackuma.hatenablog.com

ただしこれにはデメリットもあり、例えば出席管理でICカードを使う場合は他の人に託してしまえるのでよく「監視しておく人」というのが設けられます。

せっかく自動化・効率化の技術を使うのに、一時的に生産性が著しく低い人間が生まれてしまうというのは私のプライドが許しませんでした。

他の認証を考えた時、生体認証しか思いつかなかった私は、Ali Express で指紋認証モジュールを約2000円で購入しました。(生体認証のデメリットは調べてない...)
ja.aliexpress.com

海外の文献は充実していたので基本的な認証動作はすぐにできました。
How to work with Fingerprint scanner using Arduino nano — Steemit

これだけでも十分使えますが、出席管理を想定して誰がいないかを表示できるようにステップ3.2を編集します。

#include <Adafruit_Fingerprint.h>

#include <SoftwareSerial.h>
SoftwareSerial mySerial(2, 3);

Adafruit_Fingerprint finger = Adafruit_Fingerprint(&mySerial);

// 127個の1の配列
int List[127];

void setup()  
{
  int i;
  for (i = 0; i < 127; i++ ) {
    List[i] = 1;
  }
  
  Serial.begin(9600);
  while (!Serial);  // For Yun/Leo/Micro/Zero/...
  delay(100);
  Serial.println("\n\nAdafruit finger detect test");

  // set the data rate for the sensor serial port
  finger.begin(57600);
  
  if (finger.verifyPassword()) {
    Serial.println("Found fingerprint sensor!");
  } else {
    Serial.println("Did not find fingerprint sensor :(");
    while (1) { delay(1); }
  }

  finger.getTemplateCount();
  Serial.print("Sensor contains "); Serial.print(finger.templateCount); Serial.println(" templates");
  Serial.println("Waiting for valid finger...");
}

void loop()                  
{
  if(Serial.available()>0)
  {
    switch(Serial.read())
    {
      case 'a': //データが'a'であれば次を実行する
        int i;
        for (i = 0; i < 127; i = i + 1) {
          if (List[i]==1){
            Serial.print(i+1);Serial.print(" ");
          }
        }
        break; 
      default:
        break;
    }
  }
  getFingerprintIDez();
  delay(50);            
}

uint8_t getFingerprintID() {
  uint8_t p = finger.getImage();
  switch (p) {
    case FINGERPRINT_OK:
      Serial.println("Image taken");
      break;
    case FINGERPRINT_NOFINGER:
      Serial.println("No finger detected");
      return p;
    case FINGERPRINT_PACKETRECIEVEERR:
      Serial.println("Communication error");
      return p;
    case FINGERPRINT_IMAGEFAIL:
      Serial.println("Imaging error");
      return p;
    default:
      Serial.println("Unknown error");
      return p;
  }

  // OK success!

  p = finger.image2Tz();
  switch (p) {
    case FINGERPRINT_OK:
      Serial.println("Image converted");
      break;
    case FINGERPRINT_IMAGEMESS:
      Serial.println("Image too messy");
      return p;
    case FINGERPRINT_PACKETRECIEVEERR:
      Serial.println("Communication error");
      return p;
    case FINGERPRINT_FEATUREFAIL:
      Serial.println("Could not find fingerprint features");
      return p;
    case FINGERPRINT_INVALIDIMAGE:
      Serial.println("Could not find fingerprint features");
      return p;
    default:
      Serial.println("Unknown error");
      return p;
  }
  
  // OK converted!
  p = finger.fingerFastSearch();
  if (p == FINGERPRINT_OK) {
    Serial.println("Found a print match!");
  } else if (p == FINGERPRINT_PACKETRECIEVEERR) {
    Serial.println("Communication error");
    return p;
  } else if (p == FINGERPRINT_NOTFOUND) {
    Serial.println("Did not find a match");
    return p;
  } else {
    Serial.println("Unknown error");
    return p;
  }   
  
  // found a match!
  Serial.print("Found ID #"); Serial.print(finger.fingerID); 
  Serial.print(" with confidence of "); Serial.println(finger.confidence); 

  return finger.fingerID;
}

// returns -1 if failed, otherwise returns ID #
int getFingerprintIDez() {
  uint8_t p = finger.getImage();
  if (p != FINGERPRINT_OK)  return -1;

  p = finger.image2Tz();
  if (p != FINGERPRINT_OK)  return -1;

  p = finger.fingerFastSearch();
  if (p != FINGERPRINT_OK)  return -1;
  
  // found a match!
  Serial.print("Found ID #"); Serial.print(finger.fingerID); 
  Serial.print(" with confidence of "); Serial.println(finger.confidence);
  List[finger.fingerID-1] = 0;
  return finger.fingerID; 
}

ステップ3.1に「from 1 to 127」とあったから127個の配列にしていますが128でも登録&認証できたのでたぶん無限に増やせます。

シリアル通信で"a"を送信してやると配列が表示されるようになっていて、認証が済んだ番号の配列は次から表示されなくなります。

番号と人との対応表を作ったうえで、127の所をすべて登録人数に置き換えてやると出席管理ツールの出来上がりです。

Codamaでお地蔵さん

ユカイ工学でインターンをさせて頂きました!

「Codama」という音声コミュニケーションキットを使ってお地蔵さんのような形をした司会者支援ツールを作ることにした私は、3日目かけてハードとソフトの両方に取り組みました。

まずハードの面では2時間ほどで大まかに設計した後、業務用3Dプリンターで出力させてもらいました。造形時間の関係で修正がいくつかありましたが、出来上がりはすごく綺麗でした。

f:id:massiro-myaon:20190402020507j:plain

ソフトの面では、Google Assistantをベースにするため公式サイトの手順に沿って準備しました。私はGoogle Assistant Serviceの方を選びましたが、手順通りとはいえすごく時間がかかりました。

https://developers.google.com/assistant/sdk/guides/service/python/

pushtotalk まで使えるようになったら、pushtotalk.pyの460行目付近を編集して、ウェイクアップワードで起動するようにします。(pushtotalk.pyの階層はgooglesamples-assistant-pushtotalkをctl+cで停止したときのエラー文に表示される)

while True:
if GPIO.input(27) == GPIO.HIGH:
        #if wait_for_user_trigger:
        #    click.pause(info='Press Enter to send a new request...')
        continue_conversation = assistant.assist()
        # wait for user trigger if there is no follow-up turn in
        # the conversation.
        wait_for_user_trigger = not continue_conversation

次にpushtotalk.py とひたすら睨めっこして、Assistant が認識した音声によって分岐を作ることに取り組みました。

AIY キットを使ったものは試したことがありましたが、1から image を書き換えるわけにもいかないので苦労しました。 

どうやらrespというjsonファイルに認識結果を格納しているようですが、150行目付近の%sがそのまま使えそうなので、今回はよく理解しないままここを使いました。

if resp.speech_results:
    logging.info('Transcript of user request: "%s".',
        ' '.join(r.transcript
            for r in resp.speech_results))
    word = (' '.join(r.transcript
                         for r in resp.speech_results))
if word == u"今日は":
    ~

LEDやサーボの機能もcodama ボードで実装出来ているので、気が向いたらGoogle Assistantの分岐先で組み合わせて行こうかと思います!

画像処理入門

周南ロボコンの際ねじやナットが大量に混ざり合い何時間もかけて

分別・収納する期間があります。

メカトロ部の1年生も毎年オフシーズンに苦労するそうです。

 

というわけで近いうちにねじの分別装置作ろうと思います!

今回はその前段階というわけで先輩の記事を参考にマッチング処理をしてみます。


m12watanabe1a.hatenablog.com


こちらの後半にあるプログラム、基本どんな画像でもマッチングしてくれます。

今回はマッチするかどうかでON or OFFをさせたいのでその部分を紹介致します。


マッチしているかを判断する方法はいくつかあると思いますが、今回でいうと描画する線の数が気になるところです。

それをどこで判断しているかというと# マッチング精度が高いもののみ抽出のところ。

ratioがマッチング判断の範囲を表しているわけですが、goodという配列を用意して
画素の特徴量を1つずつ検証して高いものだけを追加しているようです。

つまり
good内の要素の数=マッチした画素の数(描画する線の数)
となっていることが分かる。
配列の要素の数はlen関数で求まるので以下のようになる。

# マッチング精度が高いもののみ抽出
MIN_MATCH_COUNT = 8
ratio = 0.8
good = []
for m, n in matches:
    if m.distance < ratio * n.distance:
        good.append([m])
if len(good) < MIN_MATCH_COUNT:
    print("OFF")
if len(good) > MIN_MATCH_COUNT:
    print("ON")

すこし論証みたいになってしまいましたが、あとは基準を決めて線が何個以上ならON(今回は8個)以下ならOFFにするか決めて必要な処理を書いてやるだけです。

ついでにラズパイでのOpenCVのセットアップ方法とラズパイカメラを使ったマッチング処理プログラムを載せておくので参考にしてみてください!

*install of opencv の方だけでopencvは基本動作しますが、Akaze自体の導入は比較的最近なので今回の場合は additional の方も必要になります。

# install of opencv
$ sudo apt-get update
$ sudo apt-get install libopencv-dev python-opencv

# additional install for Akaze
$ sudo pip3 install opencv-contrib-python
$ sudo apt-get install libatlas-base-dev
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import RPi.GPIO as GPIO
import time
import picamera
import subprocess

import cv2
import numpy as np

from datetime import datetime

MIN_MATCH_COUNT = 8

camera = picamera.PiCamera()
camera.resolution = (640,480)
filename = datetime.now().strftime('assets/img/sample/test.jpg')


# 画像読み込み先のパス,結果保存用のパスの設定
template_path = "assets/img/template/"
template_filename = "maku.jpg"

sample_path = "assets/img/sample/"
sample_filename = "test.jpg"

result_path = "assets/img/result_AKAZE/"
result_name = "perfect.jpg"

akaze = cv2.AKAZE_create() 

camera.capture(filename)

# 文字画像を読み込んで特徴量計算
expand_template=2
whitespace = 20
template_temp = cv2.imread(template_path + template_filename, 0)
height, width = template_temp.shape[:2]
template_img=np.ones((height+whitespace*2, width+whitespace*2),np.uint8)*255
template_img[whitespace:whitespace + height, whitespace:whitespace+width] = template_temp
template_img = cv2.resize(template_img, None, fx = expand_template, fy = expand_template)
kp_temp, des_temp = akaze.detectAndCompute(template_img, None)

# 間取り図を読み込んで特徴量計算
expand_sample = 2
sample_img = cv2.imread(sample_path + sample_filename, 0)
sample_img = cv2.resize(sample_img, None, fx = expand_sample, fy = expand_sample)
kp_samp, des_samp = akaze.detectAndCompute(sample_img, None)

# 特徴量マッチング実行
bf = cv2.BFMatcher()
matches = bf.knnMatch(des_temp, des_samp, k=2)


# マッチング精度が高いもののみ抽出
ratio = 0.8
good = []
for m, n in matches:
    if m.distance < ratio * n.distance:
        good.append([m])
if len(good) < MIN_MATCH_COUNT:
    print("OFF")
if len(good) > MIN_MATCH_COUNT:
    print("ON")

dualshock3でメカナム制御

メカナムホイールは4つのタイヤにそれぞれ45度傾いたタルを付けることで

全方向駆動を可能にするタイヤである。

tosadenshi.co.jp

今回はそれをPS3用コントローラ「dualshock3」で制御する。

 

先に参考文献を紹介。

Arduino + USBホストシールドの実験。 - robo8080のブログ

ArduinoでRC可動戦車(5) - Ganponブログ

 

通信周りの構成としては Arduino + USBホストシールド + Bluetoothトングル の組み

合わせが無難であるため真似して作った。モータードライバーにはTA7291Pを使用。

 

プログラム

GitHub - Myaon/PS3_Mecanum

 

今後もdualshock3を使って多方面で遊んでいきます!