■ホビーロボット部品の製造・販売 
  モータコントローラ、センサ、音声、画像、無線モジュールなど、
■ホビーロボット制作記事のページ (各種マイコン、PCとの接続事例)
■特殊メイク、特殊材料の販売 ※特殊メイクのコーナーはこちらに移りました。
Top(お知らせ) 製品紹介 使い方とサンプルプログラム 通信販売 リンク ロボット掲示板 会社案内
 Easy Robotics for all enthusiastic people!!!  ---HOBBY ROBOT PARTS SHOP ASAKUSAGIKEN---   Since 2003...
H8/3687FとCMOS-EYEで色の記憶と抽出、位置判定
はじめに

 ここでは H8/Tinyの中の、H8/3687Fを搭載したマイコンボード BTC065(ベストテクノロジー製) と浅草ギ研製CMOSイメージセンサーボードCMOS-EYEを使って、撮影した画像の特定範囲の平均色を記憶し、その後撮影した画像が画面のどの位置にあるかを検出してみます。

 尚、このページは「H8...バス接続及びシリアル接続」のページので作成した環境を使用しています。ハード接続、画像へのアクセス方法は事前にこちらを参照願います。

 ※このページで紹介する内容はあくまでも一例です。個別の作成のご相談ご質問はお答えできませんのでご了承下さい。このページと同じ内容についてのご質問についてはロボット掲示板にてお願いいたします。


用語について

 このページで説明する用語について解説します。

 CMOS-EYEはマイコンと接続することが前提となっております。以下の説明で「マイコン」と書いたところは、CMOS-EYEに接続するユーザー側のマイコンを示します。マイコン側の端子名は極力、そのマイコンのデータシートに書かれている名前を記載しています。
 マイコンチップが載ったCPUボードを「マイコンボード」とします。マイコンボードの端子名は極力、そのマイコンボードの取扱説明に書かれている名称を記載しています。

 CMOS-EYE上にも制御用のマイコンが搭載されていますが、これは「CMOS-EYE上のマイコン」とします。(バス接続時には特にこの「CMOS-EYE上のマイコン」を意識する必要はありません。)

 CMOS-EYEからはいろいろな端子が出ています。アドレスを指定する端子の集まりを「アドレスバス」とします。又、データを指定したり受けたりする端子の集まりを「データバス」とします。



ソースプログラムと全体の説明

 CMOS-EYE_ColorDetect.c (右クリックで対象を保存)

 ピンアサイン、名称定義、アドレスセット関数、IO初期化関数、ウェイト関数、SRAMアクセス時の前後処理については、前回の「H8...バス接続及びシリアル接続」のページを参照願います。

 今回は、何度も使う処理を関数化しました。下がその部分です。
 
 撮影関数ですが、COMOS-EYEは撮影中は他の命令を受け付けないので、ST-HIGH後にBSY端子がHIGH、つまり撮影動作終了まで待つ必要があります(行244)。ファームを作った私もこの仕様を忘れていて、すぐに他の命令を出そうとしてハマりましたので注意して下さい。撮影は70〜100mSかかりますので、この間他の作業を行うなどして、BSY端子をハードウェア割り込みで監視するなどした方がCPUの使用効率は良いでしょう。

 その他は、前回と同じ内容なので、コメントを参照願います。

 各処理で共通に使用するものに、R,G,Bの値などがありますので、これらはグローバル変数宣言をしています。この場合、各関数の再利用性が悪いのですが、読みやすさ重視でこのような形式にしてます。ポインタ渡しなどに変更すれば再利用性は上がると思います。
 

 snd_data[]:シリアル通信時のデータを格納する配列。今回、シリアル命令は1つしか使ってないのですが。
 R,G,B:平均濃度値を格納する変数
 diff:平均濃度からどのぐらい誤差があっても良いか、の変数
 x,y:画面の幅と縦サイズ
 length:画面サイズ
 pix9[]:周囲9ピクセル(自分を含む)のデータを格納する変数


 おおまかな流れは、「色記憶」−>「記憶した色でニ値化画像作成」−>「収縮処理」となっています。また、

 


 以下、各処理の詳細を説明します。また、TEST端子のHIGH/LOWを使って、各処理時間をオシロで計測してますのでその結果も報告します。

 注意)SCIxx関数がコメントつぶしになっていますが、これは、GDLでプログラム作成中に、デバック時に変数値などをシンプルタームで表示させた名残です。GDLのバージョンが変わったりするとコンパイルできないことがあるかもしれませんので注意して下さい。その他はなるべく環境に依存しないような記述を心がけています。(但し、ヘッダファイルはGDL添付のヘッダファイルでないとNGだと思います。)


平均色記憶のプログラム

 色を記憶するのに、画面全体を指定するのではなく、ある範囲を選択して抽出することにします。位置の指定は左上(x1,y1)、右下(X1,Y2)の四角の中とします。この四角の中のR、G、Bそれぞれの値を加算して、四角内のピクセル数で割れば平均色濃度が算出できます。

 この部分を colorMem( ) という関数を作りました。

 <引数の説明>

 x1,y1,x2,y1:記憶エリアを示す。これは開始アドレスからの相対アドレスになります。
 myX:画面幅(今回は80)
 myY:画面縦(今回は60)
 mySize:画面サイズ(今回は80x60で4800ピクセル)
 stAddr:検出開始アドレス

 

 RGBは連続した領域に撮影されますので、例えば今、撮影開始アドレスが0番地で80x60カラーで撮影されていた画像を処理する場合は、Rの先頭は0番地、Gの先頭は4800番地、Bの先頭は9600番地となります。

 以下が関数の内容です。

 

 特に難しいところは無いと思いますが、データ型がポイントになるでしょうか。H8Tinyは char が1バイト、 int が2バイト、 long が4バイトになります。ということで、アドレス指定を19ビット分したい場合は、long型になります。 また、画面のxy指定をする場合、320x240になると1バイトで表される255を超える可能性があるので、intにした方が無難です。

 計算した平均値はグローバル変数のR,G,Bに格納しますが、数値がわからないのでとりあえず同じデータを30000番地以降に記録してます。これはCMOS−EYE Viewerを使って読み取ることができますので、後で確認してみます。
(CMOS−EYE Viewerについては前ページを参照下さい)

 プログラム作成中は、SCI3_PRINTF( ) 関数を使って値をデバックしてましたが、環境が変わると動かなくなる可能性があるのでこの関数の使用はあまりお勧めできません。

 その他細かい流れはコメントを参照願います。


 では実際にこの部分だけを動かしてみましょう

 とりあえず、赤いものを撮影してみました。
 

 Viewerで画像を見たところです。近すぎて赤しか写ってませんね。GとBはほぼ真っ黒に見えます。(実際には場所によって1とか2の非常に低い数値が入ってました)
 

 同じく、Viewerで30000番地から3つの値を読んでみました。
 

 R=249
 G=0
 B=0

 ということで意図したとおりの値になりました。

 GとBが動いているかわからなかったので、別な色も試して見ます。手元にあった緑色のライターで実験した結果が下になります。
 

 R=46
 G=231
 B=77

 ということで問題なさそうです。


 ついでに、この関数の前後に TEST=HIGH; TEST=LOW と入れて、TEST端子をオシロで測定した結果が下の写真になります。
 
 1メモリが10mSなので約30mSということになります。記憶範囲を広げると、当然この時間は長くなることが予測されます。



色抽出−>ニ値化のプログラム

 RGBで色を表す場合、白っぽいものは全て数値が大きく、黒っぽいものは全て数値が低くなります(CMOS−EYEの紹介のページのシリアル命令のところでも説明しています。)。よって、たとえば、赤を抽出したい場合はRの値だけを使うと白系の部分も抽出しかねないことになります。よって、「Rは高いがGとBは低い」というようにRGB3つのデータを使う必要があります。

 また、経験によると、取得したRGBの値は、対象物が少し動くと、環境光の関係などで値はズレます。よって、取得した平均値に幅をもたせて色検出しないと、対象物がまったく検知できないことが多いです。ここでは、取得したRGB値に幅をもたせて対象物を検知することにしました。myDiffがその引数で、この値プラスマイナスが許容値となります。例えば、R=120を検出したい場合、myDiff=10だとすると、R=110〜130の値を検出することになります。
 値は1バイトで表されますので、0以下は0、255以上は255にまるめる必要があります。この部分が行298〜327のところになります。

 行297のtoAddrは、ニ値化画像を格納するアドレスになります。カラー画像はRGB3色で画面サイズx3の大きさがありますので、その隣の領域を指定しています。

 

 行328から実際の検出動作に入ります。

 行331〜333でRGB各色の値を取得しています。行333では、BのアドレスはRより2画面分離れた位置ですのでアドレス指定は
 stAddr + cnt + (mySize*2)
 となりますが、ビットシフトした方が速いかと思って1ビット左シフトしてます(2倍と同じ)。

 行335で、各色の上限値と下限値全て範囲内に入っているかを確認しています。範囲内なら黒(0)描画、範囲外なら白(255)描画をしています。


 では実際に動かしてみます。

 計算対象の画像はこのような感じ。
 

 ニ値化プログラム部分を動かしたのが下になります。このとき、RGBの平均値データは、上でトマトを近づけて撮影した値の R=249、G=0、B=0で計算しています。
 
 ロボット競技などでは、あまりありえない背景なので雑音がかなり入っていますが、対象のトマト部分は検出されています。上の画像と比べてみると、光の加減で赤に見えてしまっている背景が検出されています(後で背景に近づいて見ると実際に赤い物体もありました...)。

 競技などではよいのですが、実際に室内などで使うことも想定されます。よって雑音を除去する必要があるでしょう。これは次の節でやります。

 時間を測定してみました。
 
 こんどは1メモリ100mSですので、約480mSかかっているのがわかります。やってみてわかったのですが少し遅いですね。ちなみに、CMOS−EYE内蔵プログラムでも同じ処理をするものがありますが、80x60だと120mSです。CMOS−EYEに搭載されているマイコンは約14MIPSなので、H8tinyはクロック=MIPSでは無いということでしょうか。(プログラムの書き方やマイコンの構造にもよりますので一概にはいえませんが。)

 素直にシリアル命令を使えば良いのでは?という感もありますが、とりあえずここでは、画像処理がこのようにできる、ということを学んで頂いて、独自の処理をするときの参考に、ということでお願いします。



縮小のプログラム(雑音除去を兼ねる)

 経験上から言うと、この画面サイズ(80x60)で、意図しない背景の色を検出してしまった場合は、上のように大体1ピクセルぐらいの雑音になります。この場合は、「1ピクセル縮小」という処理をすれば雑音が消えることになります。

 このような色検出の場合は大体これで解決できます。また、背景色の種類が少ないなどの、画像処理には非常に良い環境の場合にはこの処理も不要な場合が多いので、使用する環境によってこの処理を入れるかどうか検討してください。

 計算する対象画像は、すでにニ値化されている画像とします。つまり、判定はそのピクセル濃度が黒(0)か、白(255)かのニ値判定になります。

 1ピクセル縮小処理の場合には、対象ピクセルの周囲8ピクセルと、自分を入れた9ピクセルで判定します。ロジックとしては「自分を含めて周りに白(255)があったらその位置を白、それ以外は黒で描画」すると、元画像が1ピクセル縮小された画像になります。

 周囲8ピクセル、つまり前後左右の値で判定するのですが、これだと外周は判定できませんので、外周は問答無用で白(255)としています。

 周囲8ピクセルを取得、という作業は他の画面フィルターでも多用する作業なので、ここでは getPix9( ) という関数にしました。関数間で値を渡すので、結果値はグローバル変数( pix9[ ] )に入れています。行351〜362の部分です。
 配列の0〜7に対応する周囲のピクセルの位置ですが、自位置右から反時計周りに対応しています。これは、他の処理などで、右から反時計回りに処理する、というパターンが多いので、このような順番になってます。(ループでまわしやすい)これはCMOS−EYEの紹介のページの画像処理の勉強について、の節で紹介している書籍などにも書いてあります。

 縮小プログラム本体部分は Contract( ) という関数にしてます(行367〜) 引数は次の通り。

 stAddr:元アドレス
 toAddr:縮小したニ値化画像格納アドレス
 myX:画面幅(今回は80)
 myY:画面縦(今回は60)


 
 ここもあまり複雑ではないので、詳細説明は省きます。コメントを参照願います。


 では上で行ったニ値化画像を1ライン縮小してみましょう。

 元の二値化画像です。
 

 結果はこうなりました。
 

 これで、記憶した物体の色だけを検出可能になりました。

 但し、これは万能というわけではなく、実際には環境によってはこれでもまだ問題が起きる可能性があります。これについては次の節で説明します。


 また時間を計測してみます。
 
 今回は1メモリ200mSなので1200mS,つまり1.2秒もかかってしまいました。ちなみにCMOS−EYEの内蔵プログラムの場合は80x60のときに310mSでした。やはり4倍ほど時間がかかっていますね。



通して動かしてみる

 今までは個別に関数を動かして見ましたが、通して動作させてみます。この結果は、冒頭のソースプログラムをそのまま動かしたものです。起動時にはトマトを近づけています。その後、5秒おきに処理された画像の一枚をViewerで見た結果が下のようになります。

 撮影画像(アドレス0番地から格納)
 

 色抽出−>縮小画像(アドレス19200番地から格納)
 

 時間は、
 
 約1.8秒でした。

 やはり、これぐらいになると(アプリによりますが)あまり実用的な速度とは言えません。コレと同じことをする場合には、シリアル命令だけを使って縮小画像まで行い、その画像をさらに中抜き(1らいんづつ飛ばすとか)して計算するなどの工夫が必要と思われます(ました。)。



問題がおきるケース画像処理

 とここまでやりましたが、実は環境によっては上手く判定されないケースもあります。それは光の方向です。

 実は、今までの画像は、「晴天時に、照明をつけずに室内でカーテンを閉め切った状態」で処理していました。この場合は、対象のトマトを近づけても遠ざけてもあまりかわらない色合いになります。

 しかし、たとえば、一方方向から光線が来ている場合に、記憶した場所(近く)と遠くではRGB値がかなり異なる場合があります。

 例として、カーテンの一部をあけてみます(画面手前が明るい)。この状態で、トマトを近づけると強い光があたっている状態になります。(写真撮り忘れ。すいません。)
 その後、トマトを遠ざけて、ニ値化までしたのが下のようになります。

 

 

 と、このように、トマト部分は逆にまったく判定されていません。


 また、例えば、丸い物体の場合は光の加減によっては色合いがまったく異なってきます。
 例えば、手近にあった黄色いラベルのキンカンのビンの、黄色い部分を記憶させてみます。その後処理した結果が下のようになります。記憶部分も色合いがきちんと黄色になっているかあやしい状態です。

 

 色検出画像です。太い縦帯状に結果が出ることを希望しましたが、黄色いラベル部分も雑音が多く、左の方は検出されていません。
 

 細線化すると、ほとんど消えてしまいました。
   



光線方向に左右されない色検出の検討

 色の表現はRGBだけではなく、色々な表現方式があり、その中に「色相」というものもあります。これは色を角度で表す方式です。

 下は、CMOS−EYE内蔵プログラムの色相表現の図です。(詳しくはCMOS−EYE紹介のページのシリアル命令の84を参照)

 

 色を表すのに、Y Cr Cb という方式もあります。Yが明暗情報、CrとCbで色を表します。色相はこのCrとCbだけを使って、色を角度で表します。よって、明暗に左右されずに色を検出することができます。RGBからYCrCbを計算することもでき、また逆も可能です。これらのロジックについては、前出の書籍や画像処理の本に書いてありますので研究してみてください。(ここで解説しようと思いましたが、説明が膨大になる(特に基礎的な数学の知識まで)のでやめました。すいません。)

 この方式の場合の欠点としては、色がグレー(白と黒を含む)に近くなったときに、何色になるかわからないというものです。しかし、これは彩度も計算することによって回避できます。彩度はグレーに近いかどうかを値にするもので、これもCrCbから計算できます。

 現在のところ、CMOS−EYEはRGBでしか画像を記録できませんが、将来のバージョンアップ時にこの色体系もサポートする予定です。(だいぶ先になりそうですが。)



おわりに

 ここまでで、色の扱いについてある程度は理解されたかと思います。色のついたボールを使う競技だけでなく、肌色を検出して簡易人検出センサーとしても使えるかと思います。プログラムは単純ですが、使い方はユーザー次第で面白いアプリケーションが作れるかもしれません。

 次は文字認識か形状認識をやってみようと思います。


2006年8月19日

 
(C)Copylight 2003. 有限会社浅草ギ研 | 通信販売の法規(訪問販売法第8条)に基づく通信販売業者の表示