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

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

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

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


用語について

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

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

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

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



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

 全体のソースプログラムは下を右クリックして対象を保存してください。

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


 メイン関数は下のようになりました。まず、各種設定を行い(行448〜463)、AWB機能をオフ(行466〜473)にしています。これで、自動機能による色の変化を防ぎます。AWBと色の変化については、CMOS-EYE紹介のページのシリアル命令の102を参照してください。

 

 その後、「メインループ」と書かれているところで、

 1)起動時に中心部分の平均色を記憶

 2)撮影

 3)色抽出

 4)収縮

 という作業を行って、はじめに記憶した部分の色がある場所を抽出しています。

 以下、それぞれの処理部分の詳細を説明します。(前のページで一度説明している関数は説明を省略しています。)


平均色記憶のプログラム

 色を記憶するのに、画面全体を指定するのではなく、ある範囲を選択して抽出することにします。位置の指定は左上(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番地となります。

 以下が関数の内容です。

 

 特に難しいところは無いと思いますが、データ型がポイントになるでしょうか。Atmega128は8ビットマイコンですが char が1バイト、 int が2バイト、 longが4バイトになります。ということで、アドレス指定を19ビット分したい場合は、long型(もしくはunsigned long)になります。

 計算した平均値はグローバル変数のR,G,Bに格納して記憶します。この値を後で読み出せるように、同じデータを30000番地以降に記録してます。これはCMOS−EYE Viewerを使って読み取ることができます。
(CMOS−EYE ViewerについてはCMOS-EYE紹介のページを参照下さい)

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


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

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

 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なので約28mSということになります。記憶範囲を広げると、当然この時間は長くなることが予測されます。
 ちなみに、前にH8tinyでまったく同じロジック(というかソースコードもこの部分はまったく同じ)で動かしましたが、30mSでしたのでおなじぐらいです。SH2だと2.3mSでした。



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

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

 色を記憶する部分を関数化した部分が下になります。

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

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

 

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

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

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


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

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

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

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

 時間を測定してみました。
 
 こんどは1メモリ100mSですので、約420mSかかっているのがわかります。ちなみにH8Tinyだと480mS、SH2だと38mSでした。



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

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

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

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

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

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

 周囲8ピクセルを取得、という作業は他の画面フィルターでも多用する作業なので、ここでは getPix9( ) という関数にしました。関数間で値を渡すので、結果値はグローバル変数( pix9[ ] )に入れています。

 

 配列の0〜7に対応する周囲のピクセルの位置ですが、自位置右から反時計周りに対応しています。これは、他の処理などで、右から反時計回りに処理する、というパターンが多いので、このような順番になってます。(ループでまわしやすい)これはCMOS−EYEの紹介のページの画像処理の勉強について、の節で紹介している書籍などにも書いてあります。


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

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


 
 外周1ピクセルは判定しないので、あらかじめ白(255)にしておきます。その部分が行410〜417になります。

 その後、外周部分は計算しなくても良いので、行418、行419のように1ピクセルずらした位置でXY方向にループ処理しています。

 行420は、以降で計算を行うピクセルの相対アドレスをセットしています。これに、引数のアドレスを足せば現在の画像の位置となります。

 「自分を含めて周りに白(255)があったらその位置を白、それ以外は黒で描画」の部分は行422〜426になります。黒描画の部分は、元画像が黒なので省略しています。

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


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

 元の二値化画像です。
 

 結果はこうなりました。
 

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

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


 また時間を計測してみます。
 
 今回は1メモリ200mSなので1040mSとなりました。H8Tinyの場合は1.2秒、SH2の場合は96mSでした。ちなみにCMOS−EYEの内蔵プログラムにも同じ処理をするプログラムが内蔵されていますが、80x60のときに処理時間は310mSです。


通して動かしてみる

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

 夜間に、白熱灯をつけた状態で撮影しており、少々条件は悪いです。

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


 色抽出画像(アドレス14400番地から格納)
 


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

 このように、対象物の位置を割り出すことができました。


 撮影を含む、色抽出−>縮小までの時間を測定してみました。
 
 約1.6秒でした。



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

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

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

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

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

 

 

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


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

 

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

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



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

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

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

 

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

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

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



おわりに

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

 次はニ値化画像についての説明をやってみようと思います。


2006年9月12日

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