TJ3Bにm5stck UnitV AI Cameraをつなぐ(暫定版)

 


とりあえず、UnitV AI Cameraを買ってみました。

なぜか。カメラは欲しいけど、Pixyの(たぶん)値段がネックになって?なかなかカメラが普及しないからです。

このUnitVは旧型が2千円ちょっと(現品限りの製造停止品)、新型でも3千円ちょっとで買えます。
※上記は記事作成当時、今(2023.3.29)は5千円超えてます

今回試してるのは旧型です。これと、M5Atom Matrixもいっしょに買いました。こちらも面白いデバイスでいろいろ遊べます。

私はOpenMVとか何にも知らなかったんですが、このCameraを使って色追従しているデモ動画を見つけて、手の出る値段だったのでロボカップジュニアで使えるんではないかと期待して購入してみました。

これと確実につながるM5stack製品でロボット作るのも面白そうだと思いました。

結果、このカメラ本体にI2C slaveのプログラムを記載して、TJ3Bから色の位置データを受信することに成功しました。

まだまだ課題がありますが、とりあえず動いたので、暫定版記事として公開します。

UnitVは、最初どう使うかまったくわからなくて、Firmwareの書き込みソフトとかいろいろ説明すっとばして、いきなり本命のMaixpyというソフトをダウンロードしてUnitVをPCに繋げたら動きました。これについてはいろいろ記事があるんで参考に進めてください。

参考ページ: 
UnitV Docs(M5STACKのUnitVのページ)

MaixPyダウンロード(0.2.5)

Firmwareは新しいもののほうが安定するらしいです。結局後からFirmwareも書き込みしましたので古いままで問題あるのかどうかはわかりません。テストぐらいならそのままできました。(2022/05/10 購入後そのままでもMaixPyに接続して動作はしました。)

この時点で、サンプルプログラムを動かして、色設定を変えたら、けっこうちゃんとオレンジボールを認識することを確認。


簡単にここまで行けてしまって、結構すごい。ただ、色の設定はちょっと特殊だけど、オレンジはやってしまえばほぼ同じ設定でいける。
このあと、m5AtomをUIFlowで動かしてV-Function使ってUnitVをつないだりいろいろやってて、何か間に挟めばI2Cでデータを送受信することは余裕なことはイメージできました。

でも、ものが増えれば増えるほど、コストも手間もメンテもかかってめんどうなので、直接このUnitVをTJ3Bにつなげたらいいな、と思いました。

まずはMaixpyでの、I2Cのslaveのサンプルを発見したので、これを参考に。


とりあえず、slaveにはなれました。あとはカメラ処理と並列して動かして値が送信できればOKです。

はじめはPixyの通信をまねて、C-STYLEのGUIで出そうかと思ったんですが、パケット解析が面倒だったので、Pixyの真似はやめて、この記事を参考にしました。

UnitVはI/O[1]になってもらい、TJ3Bからは、slaveのTJ3B I/O[1]がいるように見せ、代わりにUnitVに応答させることにします。


TJ3Bには手を入れず、他のTJ3Bにさしても動くようにしたいので、C-STYLE上で編集さえすれば動くように、こんなイメージでつなぎます。





TJ3B側のプログラム

002: I/O[1](カメラ)の変数に値が代入されると、カメラが撮影し、I/O[1](カメラ)側のCN1に検出X軸座標とCN2に検出範囲の大きさの値を格納する
003: 100ms待つ(カメラ処理のため)
004: TJ3BからCN1のデータを取得する
005: TJ3BからCN2のデータを取得する
006: CN1:検出X軸座標とCN2:検出範囲の大きさをセンサモニタに表示する

MaixpyでカメラをモニタしながらTJ3Bを実行して正しく動いているかを確認できます。
これを実行すると確かにカメラがオレンジボールを認識し、それをTJ3Bに送り受信できていることが分かります。

I/O[1]に成り代わって応答するUnitVのプログラムです。

試行錯誤でソースがぐちゃぐちゃですが、整理しようと思おうと時間がかかりそうなのでとりあえず動いたということで暫定版として記載しておきます。
コードのきれいさはともかく、このまま入れれば誰でも動かせるとは思います。

UnitVのプログラム(とりあえず動いてる版) MaixPy IDE0.2.4で作成、テスト
# i2c slave test - By: tomoa - 火 1 25 2022
import time
from modules import ws2812
from fpioa_manager import *
fm.register(8)
class_ws2812 = ws2812(8,100)
  
a = class_ws2812.set_led(0,(5,0,0))
a = class_ws2812.display()

debug = 0

from machine import I2C
I2C_SLAVE_ADDR = 0x18
count=0

register = [1,0,2,0,3,0,4,0,5,0,1,0,2,0,3,0,4,0,5,0,0,0,1,1]
cmdreg = [0,0,0,0,0,0,0,0]
def i2c_on_receive (data):
    global count
    cmdreg[count]=data
    count = count + 1
    #if debug : print  ("on_receive:", data)

def i2c_on_transmit ():
    global count
    if cmdreg[1] == 129:
        a= cmdreg[2]
        if a==10:
            a = 0
        else:
            pass
        ret = register[a*2+count]
        #if debug : print  ("on_transmit, send:", ret,"cmd",a)
        count = count + 1
    else:
        ret=1
    return ret

def i2c_on_event (event):
    global count
    count = 0
    #if debug : print("on_event:", event)

#if debug : print  ("starting i2c")
i2c = I2C(I2C.I2C1, mode = I2C.MODE_SLAVE, scl=34, sda=35,
            freq = 100000, addr = I2C_SLAVE_ADDR,
            addr_size = 7,
            on_receive = i2c_on_receive,
            on_transmit = i2c_on_transmit,
            on_event = i2c_on_event)



import sensor
import image
import lcd
import utime
from Maix import GPIO

sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.set_windowing((320,120))             #FPSを上げるため上下をカット
sensor.set_vflip(0)                         #カメラ上下反転
sensor.set_hmirror(0)                       #カメラ左右反転
sensor.set_auto_gain(0)                     #検出安定化のためオートゲインオフ
sensor.set_auto_whitebal(0)                 #検出安定化のためオートホワイトバランスオフ
sensor.run(1)

color = 1
#ターゲットに合わせてRGB(565)LABカラーベースで閾値調整が必要
target_lab_threshold1 =(0, 100, 13, 79, 16, 70) #オレンジ
target_lab_threshold2 =(0, 100, 13, 79, 16, 70) #青
target_lab_threshold3 =(0, 100, 13, 79, 16, 70) #黄
target_lab_threshold = target_lab_threshold1

def color_trace (color):
    img=sensor.snapshot()
    blobs = img.find_blobs([target_lab_threshold],
            x_stride = 2, y_stride = 2, pixels_threshold = 100,
            merge = True, margin = 20)
    if blobs:
        max_area = 0
        target = blobs[0]
        for b in blobs:
            if b.area() > max_area:
                max_area = b.area()
                target = b
        s1 = (target[5]-160)*3+511   #UnitVとM5stackVではカメラ取付向きが違うためxセンター位置を320pix/2,x軸パラメータをtarget[6]→[5]へ変更
        s2 = round(target.area()**0.5)*4
        tmp=img.draw_rectangle(target[0:4])
        tmp=img.draw_cross(target[5], target[6])
        c=img.get_pixel(target[5], target[6])
    else:
        s1 = 0
        s2 = 0
    #if debug : print(s1, s2)
    x = s1.to_bytes(2,'little')
    register[0]=x[0]
    register[1]=x[1]
    x = s2.to_bytes(2,'little')
    register[2]=x[0]
    register[3]=x[1]
    #utime.sleep_ms(100)


a = class_ws2812.set_led(0,(5,5,5))
a=class_ws2812.display()

while True:
    if cmdreg[1] == 22:
        utime.sleep_ms(1)
        cmdreg[1] = 0
        color_trace(1)
    utime.sleep_ms(5)



※注意1 このプログラムはTJ3BなしでMaixPyだけで動かしてもソフト上のカメラのモニタは動きません。TJ3BのI2Cの通信をトリガにカメラを動作させるからです。
※注意2 閾値設定がオレンジはそのままで動きますが、青と黄は、オレンジと同じになっています。こちらはご自分で環境に合わせて設定する必要があります。

I2CとかOpenMVとかとても勉強になりました。
ただ、今の環境だと、センサーのデータ読み出しが遅くて10fpsが限界といったところです。
TJ3Bのwait_ms(100)を減らすと、I2Cに応答できない可能性があり、通信が失敗して止まってしまいます。TJ3Bからするとカメラの映像が0.1秒遅れる、ということなので、プログラムでは先を予測してロボットに命令を出す必要がありそうです。衛星みたいですね。

UnitVのFirmwareをカスタマイズすることで、20fpsぐらいは行けるらしいのでソースをきれいにするのと合わせてぜひ試してみたいです。

とりあえずこれがあれば、TJ3Bに気軽にカメラが搭載できるようになると思うので、ぜひカメラは高いから、と言わずに試してみてほしいです。

とりあえず適当に書いたボールを追いかけるプログラムのTJ3B
ちゃんとPixy同様とは言いませんが、追いかけられることが確認できました。










コメント

このブログの人気の投稿

センサー自作のための参考情報

TJ3BでHMC5883L(QMC5883L)を使う方法