H8で画像認識&追尾

H8-3069で赤いものを追いかけるテスト.

使ったもの

シリアルカメラモジュールは80×64〜640×480までの解像度でJPEG/RGB撮影が可能なUARTインターフェースのモジュール.今回は画像認識をやりたいので,デコードにCPUを食われるJPEGフォーマットではなく,12BitRGBにした.またH8-3069はRAM領域がたったの16kBなので,解像度は必然的に80×64に.そのサイズでも画像1枚でRAMの約半分を消費するし,シリアル転送は最短で0.5秒以上が必要.RAMだけなら外部にDRAMなり増設すればOKだけど,転送速度はどうしようも無い.物体追尾をするために一定以上のペースで画像が撮りたいので,このサイズで頑張る.

コード

C328動かす部分のコードを抜粋.シリアル送受信には秋月ボード付属CDから拾ってきたsci.hを使っている.SCIのリングバッファとしてjpeg[7260]を指定してやると,受信したデータが勝手に入ってくれる.また,合間合間に黒魔術的なwaitが入っているが,これが無いと動いてくれない.
あとはjpegを読み取って,赤色っぽい*1ピクセルの座標hを取得.tan-1(h/h0)を取って角度に変換*2,サーボへの指令値としている.
そうやってできたのが冒頭の動画の子.赤色を視界に捉えた場合は一番赤いポイントが中央に来るように首を振り,見つからなかった場合は暴れます.

#define CAMERA_INIT sci2_init
#define CAMERA_CLEAR sci2_rx_purge
#define CAMERA_IN_STRING c328_getcommand
#define CAMERA_OUT_STRING sci2_puts
#define CAMERA_IN_DATA_CHECK sci2_rx_buff
#define CAMERA_IN_DATA sci2_getc
#define CAMERA_OUT_DATA sci2_putc
#define CAMERA_OUT_CLEAR sci2_tx_purge

char txbc[256],jpeg[7260];
char ack[6];

/**
 * 画像取得関数.
 */
unsigned char c328_take()
{
    unsigned int i,count;

    for(i = 0; i < 60 ; i ++){       
	CAMERA_CLEAR();
	CAMERA_OUT_CLEAR();		
        c328_send(0xAA,0x0D,0x00,0x00,0x00,0x00);//sync
        wait_msec(20);
        if(CAMERA_IN_DATA_CHECK() >= 12){
		CAMERA_IN_STRING(ack);
		if(ack[1] = 0x0E){	
			c328_send(0xAA,0x0E,0x0D,0x00,0x00,0x00);//ack
			break;
		}
        }
	if(i == 59) return 1;//Failed to Sync
    }

    wait_msec(50);
    CAMERA_CLEAR();

    c328_send(0xAA,0x01,0x00,0x05,0x01,0x01);
    /* RGB : 12bits */
    /* RAW data / JPEG Preview : resolution : 80 x 64 */
    /* JPEG : resolution : 80 x 64 */

    while(CAMERA_IN_DATA_CHECK() < 6);
    CAMERA_IN_STRING(ack);
	
    if(ack[1] != 0x0E || ack[2] != 0x01) return 1;//Invalid Response

    CAMERA_CLEAR();
    c328_send(0xAA,0x05,0x01,0x00,0x00,0x00);
    while(CAMERA_IN_DATA_CHECK() < 6);
    CAMERA_IN_STRING(ack);

    if(ack[1] != 0x0E || ack[2] != 0x05) return 1;//Invalid Response

    CAMERA_OUT_CLEAR();
    wait_msec(15);
    c328_send(0xAA,0x04,0x01,0x00,0x00,0x00);

    count =  0;
    while(CAMERA_IN_DATA_CHECK() < 7260){
        count = CAMERA_IN_DATA_CHECK();
        wait_msec(400);
        if(count == CAMERA_IN_DATA_CHECK()){
            return 1;//Time Out
        }
    }
    return 0;
}

/**
 * 変数とシリアル通信の初期化を行う関数.
 */
void c328_init()
{
    unsigned int i;
    CAMERA_INIT(br115200,txbc,sizeof(txbc),jpeg,sizeof(jpeg));
    for(i = 0; i < sizeof(jpeg); i++){
        jpeg[i] = 0;
    }
    for(i = 0; i < sizeof(txbc);i++){
	txbc[i] = 0;
    }
}

/**
 * カメラに指定したコマンドを送信
 */
void c328_send(char c0,char c1, char c2, char c3, char c4, char c5)
{
    CAMERA_OUT_DATA( c0 ) ;
    CAMERA_OUT_DATA( c1 ) ;
    CAMERA_OUT_DATA( c2 ) ;
    CAMERA_OUT_DATA( c3 ) ;
    CAMERA_OUT_DATA( c4 ) ;
    CAMERA_OUT_DATA( c5 ) ;
}

/**
 * 文字列受信関数.
 * sci_getscのように改行コードを待たず,単純に6バイトを読み込んでsに格納する.
 */
void c328_getcommand(char* s){
    unsigned int i;
    for(i=0;i<6;i++){
        s[i] = (char)CAMERA_IN_DATA();
    }
}

回路

カメラはピンが4本(3.3V-Tx-Rx-GND),サーボは3本(5V-PWM-GND)なので繋ぐのはとても簡単.カメラは正しいコマンドを送っていてもたまに機嫌が悪くなるので,そのときには電源リセットし,モジュール側の電荷を抜いてから再起動すると良い.モジュールには(見たところ)電源に対して30μFのタンタルコンデンサが付いているので,このコンデンサを短時間で放電するためにモジュールと並列に電荷放出用の抵抗Rを付けておく.これで最悪でも時定数RC程度のリセット時間で電荷が抜けることが期待できる.

また,ただ単に3.3V電源を切っただけだと,無送信時に5Vに固定されているH8のシリアルTxを経由して電圧漏れが起こり,うまく放電できない.対応としては電源を落とすときにシリアルコントロールレジスタもいったん0に戻しておけば良い.

void c328_reset(){
	SCI2.SCR.BYTE = 0;//シリアルコントロールレジスタを初期値にセット.この時点でただのGPIOに戻り,Txピンが5→0Vに
	camera(0);//電源オフ
	wait_msec(300);//コンデンサの電荷が抜けるまで待つ
	camera(1);//電源オン
	wait_msec(200);				
	c328_init();//シリアル初期化
}

ちなみに

遊んでるわけじゃなくて,https://sites.google.com/site/2010cansat/:Title=ARLISSコンペティションに参加させる機体(Formation-Flying+画像認識による相対制御CanSat)のためのプロトタイプなのです.

*1:とりあえず今回はRGB→YCbCr系Crへの変換式Cr = 0.439R - 0.368G - 0.071B + 128にだいたい合い,かつビット演算のみで算出可能な値Cr' = R - G - B/4で赤っぽさを評価

*2:H8内部での計算は重いので,あらかじめテーブルを作っている