UnitV AI Camera(OV7740版)を使ってみる

はじめに

今回はM5stackマイコンのUnitV AI Cameraのサンプルプログラムを触ってみたいと思います。

UnitV AI Cameraのサンプル動画を見て、画像検査に利用できるのでは?と思い立ったのがきっかけです。

サンプル動画ではM5stick c PlusとUnitV AI Camera、ラジコンの車体がつながっており、緑色の円柱をカメラで認識すると追尾するというものでした。

今回はM5stick c PlusとUnitV AI Cameraのみを使用し、認識する色を変更してみたいと思います。

概要

使用するもの

  • M5stick C Plus

UnitV AI Camera(OV7740版)

 

プログラム環境

  • M5 stick c Plus
    • Arduino IDE  (C++)
  • UnitV AI Camera
    • MaixPy IDE  (Micro python)

プログラム

SWITCH SCIENCEのUnitV AI Cameraのページからサンプルプログラムのリンク先をクリックするとgithubにアクセスします。

今回はM5 stick c PlusとUnitV AI Cameraそれぞれでプログラムが必要であり、githubにも2つ分のプログラムがあります。

プログラムをダウンロードしてマイコンにそれぞれ書き込みます。

M5 stick c Plus側のプログラム

M5 stick c Plus側のプログラムは以下の通りです。

このプログラムでは色を検知するとM5 stick c Plusの液晶パネルの色が変わり、M5 stick c Plusに接続したモーターが検知した色を追尾するように動くプログラムです。

今回はモーターには接続せず、色を検知すると液晶パネルが変化する、という動きができればいいため、プログラムはいじらずこのまま使用します。

#include <M5StickCPlus.h>


HardwareSerial VSerial(1);


typedef struct
{
    int16_t dx;
    uint32_t area;
}v_response_t;



uint8_t I2CWrite1Byte(uint8_t Addr, uint8_t Data)
{
    Wire.beginTransmission(0x38);
    Wire.write(Addr);
    Wire.write(Data);
    return Wire.endTransmission();
}


uint8_t I2CWritebuff(uint8_t Addr, uint8_t *Data, uint16_t Length)
{
    Wire.beginTransmission(0x38);
    Wire.write(Addr);
    for (int i = 0; i < Length; i++)
    {
        Wire.write(Data[i]);
    }
    return Wire.endTransmission();
}


uint8_t Setspeed(int16_t Vtx, int16_t Vty, int16_t Wt)
{
    int16_t speed_buff[4] = {0};
    int8_t speed_sendbuff[4] = {0};


    Wt = (Wt > 100) ? 100 : Wt;
    Wt = (Wt < -100) ? -100 : Wt;


    Vtx = (Vtx > 100) ? 100 : Vtx;
    Vtx = (Vtx < -100) ? -100 : Vtx;
    Vty = (Vty > 100) ? 100 : Vty;
    Vty = (Vty < -100) ? -100 : Vty;


    Vtx = (Wt != 0) ? Vtx * (100 - abs(Wt)) / 100 : Vtx;
    Vty = (Wt != 0) ? Vty * (100 - abs(Wt)) / 100 : Vty;


    speed_buff[0] = Vty - Vtx - Wt;
    speed_buff[1] = Vty + Vtx + Wt;
    speed_buff[3] = Vty - Vtx + Wt;
    speed_buff[2] = Vty + Vtx - Wt;


    for (int i = 0; i < 4; i++)
    {
        speed_buff[i] = (speed_buff[i] > 100) ? 100 : speed_buff[i];
        speed_buff[i] = (speed_buff[i] < -100) ? -100 : speed_buff[i];
        speed_sendbuff[i] = speed_buff[i];
    }
    return I2CWritebuff(0x00, (uint8_t *)speed_sendbuff, 4);
}


void setup()
{
    M5.begin();
    M5.Lcd.setRotation(3);
    M5.Lcd.fillScreen(RED);


    VSerial.begin(115200, SERIAL_8N1, 33, 32);
    Wire.begin(0, 26);
}


enum
{
    kNoTarget = 0,
    kLeft,
    kRight,
    kStraight,
    kTooClose
};


const uint16_t kThreshold = 20; // If target is in range ±kThreshold, the car will go straight
v_response_t v_data;    // Data read back from V
uint8_t state = 0;  // Car's movement status


void loop()
{
    VSerial.write(0xAF);


    if(VSerial.available())
    {
        uint8_t buffer[5];
        VSerial.readBytes(buffer, 5);
        v_data.dx = (buffer[0] << 8) | buffer[1];
        v_data.area = (buffer[2] << 16) | (buffer[3] << 8) | buffer[4];


        if(v_data.dx > -120 && v_data.dx < 120)
        {
            if(v_data.area > 10000)
            {
                state = kTooClose;  // Stop
            }
            else if(v_data.dx > -kThreshold && v_data.dx < kThreshold)
            {
                state = kStraight;  // Go straight
            }
            else if(v_data.dx <= -kThreshold)
            {
                state = kLeft;  // Go left
            }
            else if(v_data.dx >= kThreshold)
            {
                state = kRight; // Go right
            }
            else
            {
                state = kNoTarget;  // Rotate
            }
            M5.Lcd.fillScreen(GREEN);
        }
        else
        {
            state = kNoTarget;  // Rotate
            M5.Lcd.fillScreen(RED);
        }


        Serial.printf("%d, %d, %d\n", v_data.dx, v_data.area, state);
    }


    //The speed and time here may need to be modified according to the actual situation
    switch(state)
    {
        case kNoTarget:
            Setspeed(0, 0, 20); //Duty ratio ±100
            delay(20);
            Setspeed(0, 0, 0);
        break;


        case kLeft:
            Setspeed(-20, 0, 0);
            delay(20);
            Setspeed(0, 0, 0);
        break;


        case kRight:
            Setspeed(20, 0, 0);
            delay(20);
            Setspeed(0, 0, 0);
        break;


        case kStraight:
            Setspeed(0, 20, 0);
            delay(20);
            Setspeed(0, 0, 0);
        break;


        case kTooClose:
            Setspeed(0, 0, 0);
        break;


    }
}

Unit V AI Camera側のプログラム

UnitV AI Camera側のプログラムは以下の通りです。

import sensor
import image
import lcd
import time
import utime
from machine import UART
from Maix import GPIO
from fpioa_manager import *


fm.register(34,fm.fpioa.UART1_TX)
fm.register(35,fm.fpioa.UART1_RX)
uart_out = UART(UART.UART1, 115200, 8, None, 1, timeout=1000, read_buf_len=4096)


sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.run(1)


while False:
    uart_out.write('TEST\n')
    utime.sleep_ms(100)


target_lab_threshold = (28,   82,  8,   -8,  20,  127)
while True:
    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
        if uart_out.read(4096):
            area = target.area()
            dx = 120 - target[6]
            hexlist = [(dx >> 8) & 0xFF, dx & 0xFF, (area >> 16) & 0xFF, (area >> 8) & 0xFF, area & 0xFF]
            uart_out.write(bytes(hexlist))
        else:
            pass
        print(target.area())
        tmp=img.draw_rectangle(target[0:4])
        tmp=img.draw_cross(target[5], target[6])
        c=img.get_pixel(target[5], target[6])


    else:
        if uart_out.read(4096):
            hexlist = [0x80, 0x00, 0x00, 0x00, 0x00]
            uart_out.write(bytes(hexlist))
        else:
            pass

検知したい色を変更するには上記プログラム上の”target_lab_threshold = (28, 82, 8, -8, 20, 127)“の数字を変更します。変更方法を説明します。

UnitV AI CameraをつなげてMaixPy IDEの左下にあるチェーンマーク(接続する)をクリックします。

次にチェーンマークの下の「スクリプトを実行」をクリックします。

すると右側にカメラで取得した映像と色のヒストグラムがリアルタイムで表示されます。

今回はUnitV AI Cameraが入っていた容器にUnitV AI Cameraの絵が描かれているので、その青色を検知するように変更します。

「ツール」→「マシンビジョン」→「しきい値エディタ」を選択します。

するとソースイメージの場所をフレームバッファか画像ファイルのどちらから取得するかを聞かれるので、今回はフレームバッファ(現在PCにつなげているUnitV AI Cameraが取得している映像)を選択します。

しきい値エディタが立ち上がります。

ここでUnitV AI Cameraの青色が検知できるように、L分、Lマックス、A分、A最大、B分、B最大のバーを調節します。L, A, Bはそれぞれ画像の赤領域、緑領域、青領域の検知幅を設定でき、分が下限値、マックスまたは最大が上限値になります。

(ところどころ日本語がおかしいように感じますが、もしかしてminimumの短縮形minを時間の分と誤翻訳しているかもしれません。)

左側のバイナリ画像に調整した結果の画像が表示されるので、今回検知したいUnitV AI Cameraの青色部分だけがくっきりと表示されるように調整します。

調整出来たら「LABしきい値」欄に書かれた数字をコピーして、先ほどのMaix Py IDEのプログラム”target_lab_threshold = (28, 82, 8, -8, 20, 127)“のかっこの数字を書き換えます。

変更が完了したら、UnitV AI Cameraへプログラムを書き込みます。

「ツール」→「Save open script to board(boot.py)」を押し、プログラムを書き込みます。

先ほどのフレームバッファの映像上に検知した色を囲う白い枠が表示されれば、無事検知したい色の変更が完了です。

プログラムの実行結果

ではM5 stick c Plusにも先ほどのArduino IDEのプログラムを書き込んだうえで、M5 stick c PlusとUnitV AI CameraをGrove互換ケーブルで接続して動作を確認します。

  • 検知なし

  • 色検知

無事検知したい色の変更ができました。

製品に異物が付着していないか、もしくは製品に特定の部品がとりついているかの検査に応用できそうです。

考察

しきい値の調整にはコツが必要ですが慣れれば5分もかからず設定できます。大手メーカー製のセンサーでも検知したい色をタッチパネルで選択するだけで自動で調整してくれる製品もありますが価格は高めです。『カメラが取得した画像を見ながら』、『検知する色の変更をする』、この2つの機能を求めるなら安くても10万円以上はすると思います。今回購入したUnitV AI CameraとM5 stick c Plusのセットであればほぼ1/10以下の金額で実現可能です。ただししきい値の設定がかなりシビアであり、かつそれを自分でやらないといけないこと、またプログラムに関する知識が必須となるデメリットも存在します。費用と効果の関係からUnitV AI Cameraとメーカー製センサどちらを選ぶかを考えたほうがいいかもしれません。

また今回説明できなかったのですが、しきい値エディタのソースファイルの場所を画像ファイルからに選択すれば、UnitV AI Cameraで撮影したものでなくともしきい値の設定が可能です。

例えばあらかじめスマホで検知したい画像を撮影しておき、その画像をしきい値エディタで開いてしきい値の設定を行い、プログラムに書き込むことも可能です。わざわざしきい値のためにノートPCとそれにつながったUnitV AI Cameraを現場へ持ち運ぶ必要がないのは便利ですね。

 

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA



reCaptcha の認証期間が終了しました。ページを再読み込みしてください。