目次

画像処理プログラム

 今回利用する画像センサーは、GameBoyCameraです。



 GBCからの画像データを、32kバイトのSRAMに保存し
 シリアルでPersonalComputerに転送して、どの様な
 画像になっているかを確認します。



 VC#や他のWindowsアプリケーションで、画像表示すると
 Windows専用になります。マルチプラットホームで動く
 ようPythonを利用します。



 シリアル転送で画像データ情報をテキストファイルにすると
 Python、Tcl/Tk等のスクリプト言語を利用しImageMagickでの
 変換や表示が可能となります。

 Pythonはリストを使った処理が簡単にできるので
 画像処理システムを作成するのに、1日程度時間
 を使うだけで済みました。

 Pythonは、Unixに標準でバンドルされていた処理系を
 そのまま利用。Windowsでも、使える処理系があるので
 ダウンロードすれば、すぐに使えます。

 MCR_VCマシンを動かすためには、次のような
 手順で処理するため、Pythonで同じプロセス
 をなぞります。
  1. GBCから画像データ取得
  2. 指定矩形領域のみデータを限定
  3. 最大値、最小値を求める
  4. 最大値、最小値から閾値計算
  5. 閾値を利用して2値化
  6. 2値化した画像データをLCDに表示
 Pythonでは、対話とスクリプトの両モードが可能なので  最初は対話モードで、すべての処理をやってみます。  GBCから画像データ取得   テキストファイルに保存してあるので一気に読込み。 fin = open('mlog3.txt','r') line = fin.read() fin.close()  文字列から数値へ変換   テキストファイルから入力した情報は、文字列になって   ます。計算が必要なので、数字列を数値に変換します。 dout = line.split('\n') result = [] for x in range(len(dout)): # string -> list tmp = dout[x].strip() tmpx = tmp.split(' ') # string -> digit tmplst = [] for y in range(len(tmpx)): tmplst.append( int(tmpx[y]) ) # concatenate result += tmplst xlast = len(result)  指定矩形領域のみデータを限定   数値情報に変換後、スライス機能で矩形領域   の情報に変換します。 glist = [] for x in range(0,65): begin = (x << 7) last = begin + 128 glist.append( result[begin:last] )  最大値、最小値を求める   リスト中から、最大値、最小値を算出するには   数値処理関数を使います。 xmax = max(glist) ; xmin = min(glist) ;  最大値、最小値から閾値計算   最大値、最小値を求めてあるので、計算で求めます。   シフト演算で1/2にします。 xavr = (xmax+xmin) >> 1 ;   閾値には、照明の状態で変化することを考えて   最大値、最小値の差分の3/4にします。 xavr = (max(glist)+min(glist)) >> 1 xavr += (xavr >> 1)   1/2にすることは、右に1ビットシフトなので   論理演算が高速になるようにしておきます。   (Cでの処理では、結構使えます。)  閾値を利用して2値化   新しいリストを作成するだけで実現できます。   文字定数の'0'か'1'生成します。   テキストファイルへ出力すると考え、文字定数にしておきます。 blist = [] for x in range(xlast): if glist[x] < xavr : blist.append('0') else: blist.append('1')  2値化した画像データをLCDに表示   2値化した画像データをLCDに表示するのは面倒なので   文字定数にして、端末に表示します。 for x in range(60): # start begin = x * 80 # exit fine = begin + 80 # get range values tmp = blist[begin:fine] # generate strings tmpx = ''.join(tmp) print tmpx  Pythonの方は、対話モードでは変更が面倒なので  スクリプトモードで動くようにしました。 import sys ######################## # define main function ######################## def main(argv): #++++++++++++++++++++++ # get data from file #++++++++++++++++++++++ fin = open(argv,'r') line = fin.read() fin.close() #++++++++++++++++++++++ # string => digit #++++++++++++++++++++++ dout = line.split('\n') result = [] for x in range(len(dout)): # string -> list tmp = dout[x].strip() tmpx = tmp.split(' ') # string -> digit tmplst = [] for y in range(len(tmpx)): tmplst.append( int(tmpx[y]) ) # concatenate result += tmplst xlast = len(result) #++++++++++++++++++++++ # binary conversion #++++++++++++++++++++++ xavr = (max(result)+min(result)) >> 1 xavr += (xavr >> 1) blist = [] for x in range(xlast): if result[x] < xavr : blist.append('0') else: blist.append('1') #++++++++++++++++++++++ # show #++++++++++++++++++++++ for x in range(60): # start begin = x * 80 # exit fine = begin + 80 # get range values tmp = blist[begin:fine] # generate strings tmpx = ''.join(tmp) print tmpx if __name__ == '__main__': main(sys.argv[1])  使い方は、コマンドイランで次のように入力。 Python27>Python ftstxx.py mlogx.txt{enter}  スクリプトの引数で、ファイル名を与えられるように  しています。結果を残したいときには、I/Oリダイレクト  すれば、テキストファイルへ保存できます。  テキストファイルに保存した内容は、次のようになります。 00000000000000000000000000000000111111111111111111111000000000000000000000000000 00000000000000000000000000000000000000011111111111110000000000000000000000000000 00000000000000000000000000000000000000000000111111000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000100011111100000000 00000000000000000011111111111111000000000000000000000000000000110001111111111100 00000000000000000111111111111111111100000000000000000000000000000000000000000000 00000000000000001111111111111111111111100000000000000000000000000000000000000000 00000000000000011111111111111111111111111110000000000000000000000000000000000000 00000000011111111111111111111111111111111111111100000000000000000000000000000000 00000000001111111111111111111111111111111111111111100000000000000000000000000000 00000000001111111111111111111111111111111111111111111100000000000000000000000000 00000000000111111111111111111111111111111111111111111111000000000000000000000000 00000000000011111111111111111111111111111111111111111111111000000000000000000000 00000000000001111111111111111111111111111111111111111111111111000000000000000000 00000000000001111111111111111111111111111111111111111111111111100000000000000000 00000000000000111111111111111111111111111111111111111111111111100000000000000000 00000000000000111111111111111111111111111111111111111111111111100000000000000000 00000000000000011111111111111111111111111111111111111111111111100000000000000000 00000000000000011111111111111111111111111111111111111111111111100000000000000000 00000000000000001111111111111111111111111111111111111111111111000000000000000000 00000000000000000011111111111111111111111111111111111111111111000000000000000000 00000000000000000000111111111000000000000000000000000000010000000000000000000000 00000000000000000000001111000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000000000000000000000   PGM形式ファイルにすると、ImageMagickでJPEGやGIF形式に   変換できます。   ImageMagickも、Windows用があるのでダウンロード   して使うと同じように処理できます。   PGM形式のままでは、表示が面倒なので   ImageMagickでGIF形式に変換します。   Tcl/Tkを利用した画像表示スクリプトは、以下。 #!/bin/sh wm title . "Image Viewer" ####################### # clear file names ####################### set fname "" set input_image "" ####################### # add menu on TopLevel ####################### . configure -menu .mnuTop menu .mnuTop # add sub form .mnuTop add cascade -label File -underline 0 -menu .mnuTop.file menu .mnuTop.file -tearoff no # add sub menu "Open" .mnuTop.file add command -label "Open" -command {OpenImage} # add sub menu "Quit" .mnuTop.file add command -label "Quit" -command {exit} ####################### # declare image file ####################### image create photo input_image -file $fname ####################### # set window dimension ####################### set w 1000 set h 1000 ############################################## # define image canvas with both scroll bar ############################################## canvas .drawcanvas -background white -width 200 -height 200 \ -scrollregion "0 0 $w $h" -yscrollcommand ".yscrl set" \ -xscrollcommand ".xscrl set" scrollbar .yscrl -command ".drawcanvas yview" scrollbar .xscrl -command ".drawcanvas xview" -orient horizontal ############################################## # place objects ############################################## pack .yscrl -side right -fill y pack .xscrl -side bottom -fill x pack .drawcanvas -side left -fill both -expand yes .drawcanvas create image 0 0 -image input_image -anchor nw ####################### # Open Image procedure ####################### proc OpenImage { } { # set file types set ftype {{ "gif Files" .gif} { "All Files" * }} # get file name from built-in tool set fname [tk_getOpenFile -filetypes $ftype -parent . ] if { [regexp {ppm$|gif$|pgm$|pbm$} $fname] > 0 } { input_image read $fname } }  このスクリプトで、コース面をGBCで撮影した画像を  表示すると、次のようになります。  Pythonを利用しGBCの画像を変換すると、ライン情報として  最適な位置と幅がどこにあるのか判断できます。  閾値は、どの程度がよいのかも推定できます。  今回は、次のような画像処理を適用します。  画像処理に必要となるメモリ容量を計算します。  画像処理専用マイコンに、内蔵SRAMが8kバイト品を  採用すれば充分対応できると考えられます。  メカのもつ特徴から、ラインを32ラインまで減らしても  問題ないとすれば、内蔵SRAMが4kバイトのマイコンで  対応できるでしょう。  Z80のメモリ空間は32kバイトあり、そのうち4kバイトを  画像処理で使われても、問題はないと判断しました。  テンプレートからのズレをどう計算するかを考えます。  2値化情報から、センターの白線幅と重心位置は  計算で簡単に求められます。  白線幅は、0→1と1→0の変化(エッジ変化)が発生  するピクセル位置がわかれば、差の計算だけ。  重心は、ピクセル位置の相加平均で算出できます。  すべて黒、すべて白、半分白の3つ場合にどう  対応するのかを考えます。  すべて黒   64ピクセルがすべて黒なら、エッジ変化を求める前に   0の個数を数えて判断します。   幅が0、重心は255とします。  すべて白   64ピクセルがすべて白なら、エッジ変化を求める前に   1の個数を数えて判断します。   幅が64、重心は255とします。  半分白   64ピクセル中の白である個数を数えます。   テンプレートの白の幅は確定しているので   比較すると、半分白の可能性が判断できます。   重心位置は、テンプレートから左か右に偏る   ので、それを使った判断にかえます。  エッジ変化を求める前に、64ピクセル中の黒白の  数を数える処理が必要とわかります。  実際の画像情報を利用し、アルゴリズムにまとめます。  64ピクセル中の0、1の個数を数え上げる関数を定義。 UBYTE white_count_primitive(UBYTE x) { UBYTE tmp ; UBYTE loop ; UBYTE result ; tmp = x ; /* count */ result = 0 ; for ( loop = 0 ; loop < 8 ; loop++ ) { /* count */ if ( tmp & 1 ) { result++ ; } /* shift */ tmp >>= 1 ; } return result ; } UBYTE white_number(UBYTE *ptr) { UBYTE tmp ; UBYTE loop ; UBYTE result ; result = 0 ; for ( loop = 0 ; loop < 8 ; loop++ ) { /* get 8 pixel */ tmp = *(ptr+loop) ; /* count */ result += white_count_primitive(tmp); } return result ; }  64ピクセルを8ビットx8バイトと考えて  処理しています。これだけでは、バイト間  の0→1、1→0の変化を判断できません。  64ピクセルは、8バイト分になっているので  隣接するバイト数は7であることに注目して  7バイト分の境界条件を調べます。  2バイトを与え、1バイト目のLSB、2バイト目の  MSBのビットの組合せが、00、01、10、11のどれに  なっているのかを判断するだけになります。 #define NONE_EDGE 0 #define LOWHIGH_EDGE 1 #define HIGHLOW_EDGE 2 UBYTE judge_edge(UBYTE x0,UBYTE x1) { UBYTE result ; /* get bits */ result = ((x0 & 0x01) << 1) | ((x1 & 0x80) >> 7) ; /* judge */ if ( result == 3 ) { result = NONE_EDGE ; } return result ; }  64ピクセルは8バイト中にあるので、7回の  判定で結果を保存しておきます。 UBYTE between[7] ; for ( loop = 0 ; loop < 7 ; loop++ ) { /* default */ *(between+loop) = NONE_EDGE ; /* judge */ *(between+loop) = judge_edge(*(gbcline+loop),*(gbcline+loop+1)); }  バイト境界でのエッジがわかれば、1バイト内での  0→1、1→0のエッジを見つけます。  1バイト中の1の個数をすでに求めているので  1の個数が正の整数のときだけで判断します。  MSBから2進数11との論理積を求め01、10と合致  しているかを調べます。  01か10のどちらを判定するのかを指定します。 UBYTE judge_location(UBYTE which,UBYTE x) { UBYTE loop ; UBYTE tmp ; UBYTE result ; /* */ for ( loop = 0 ; loop < 7 ; loop++ ) { /* shift */ tmp = (x >> (6 - loop)) & 3 ; /* judge */ result = 65 ; /* default */ if ( tmp == which ) { result = loop ; } /* detect and exit */ if ( result < 8 ) break ; } return result ; }  ここまでで必要な関数を定義したので  変化点を見つけるアルゴリズムとして  まとめます。
  1. 64ピクセルがすべて0か1を判定
  2. バイト境界の0→1、1→0を判定
  3. バイト中の1の個数から、変化点を求める
  4. 1から3の内容をまとめる
 欲しい情報を、数値としてまとめていきます。  センターラインの幅、重心位置を求めるには  0→1、1→0の位置がわかればよいので  構造体変数の中にまとめます。 typedef struct { UBYTE cflag ; /* all 1 , all 0 , impossible */ UBYTE lh_location ; /* 0->1 location */ UBYTE hl_location ; /* 1->0 location */ UBYTE cwidth ; /* width of center line */ UBYTE gcenter ; /* gravity center */ } LINE_INFOP ;  構造体内部の変数を参照すると、走行に  必要な情報を得られるようにしています。  コーリングシーケンスは、以下。  構造体変数の中の変数をすべて0にして、関数judgexを呼び出す。  次のように記述します。 #define ALL_ONE 65 #define ALL_ZERO 66 #define XDONE 67 #define IMPOSSIBLE 68 LINE_INFOP *jx ; jx->lh_location = 0 ; jx->hl_location = 0 ; jx->cwidth = 0 ; judgex(jx);  コーリングシーケンスを決定すると  関数judgexを定義できます。 void judgex(LINE_INFOP *jxx) { UBYTE loop ; UBYTE xtmp ; UBYTE tmp[8]; UBYTE between[7] ; UBYTE bn ; UBYTE wn ; UBYTE bflag ; UBYTE wflag ; /* ? all one or all zero */ wn = white_number( gbcline ) ; bn = 64 - wn ; if ( wn == 64 ) { jxx->cflag = ALL_ONE ; jxx->lh_location = 0 ; jxx->hl_location = 0 ; jxx->cwidth = 64 ; jxx->gcenter = 32 ; } if ( bn == 64 ) { jxx->cflag = ALL_ZERO ; jxx->lh_location = 0 ; jxx->hl_location = 0 ; jxx->cwidth = 0 ; jxx->gcenter = 0 ; } /* judge */ if ( jxx->cflag == ALL_ONE ) return ; if ( jxx->cflag == ALL_ZERO ) return ; /* white number count */ for ( loop = 0 ; loop < 8 ; loop++ ) { *(tmp+loop) = white_count_primitive( *(gbcline+loop) ); } /* edge check */ for ( loop = 0 ; loop < 7 ; loop++ ) { /* default */ *(between+loop) = NONE_EDGE ; /* judge */ *(between+loop) = judge_edge(*(gbcline+loop),*(gbcline+loop+1)); } /* judge L-> H */ wflag = OFF ; if ( *(between+0) == LOWHIGH_EDGE ) { jxx->lh_location = 8 ; wflag = ON ; } if ( *(between+1) == LOWHIGH_EDGE ) { jxx->lh_location = 16 ; wflag = ON ; } if ( *(between+2) == LOWHIGH_EDGE ) { jxx->lh_location = 24 ; wflag = ON ; } if ( *(between+3) == LOWHIGH_EDGE ) { jxx->lh_location = 32 ; wflag = ON ; } if ( *(between+4) == LOWHIGH_EDGE ) { jxx->lh_location = 40 ; wflag = ON ; } if ( *(between+5) == LOWHIGH_EDGE ) { jxx->lh_location = 48 ; wflag = ON ; } if ( *(between+6) == LOWHIGH_EDGE ) { jxx->lh_location = 56 ; wflag = ON ; } /* judge H->L */ bflag = OFF ; if ( *(between+0) == HIGHLOW_EDGE ) { jxx->hl_location = 7 ; bflag = ON ; } if ( *(between+1) == HIGHLOW_EDGE ) { jxx->hl_location = 15 ; bflag = ON ; } if ( *(between+2) == HIGHLOW_EDGE ) { jxx->hl_location = 23 ; bflag = ON ; } if ( *(between+3) == HIGHLOW_EDGE ) { jxx->hl_location = 31 ; bflag = ON ; } if ( *(between+4) == HIGHLOW_EDGE ) { jxx->hl_location = 39 ; bflag = ON ; } if ( *(between+5) == HIGHLOW_EDGE ) { jxx->hl_location = 47 ; bflag = ON ; } if ( *(between+6) == HIGHLOW_EDGE ) { jxx->hl_location = 55 ; bflag = ON ; } /* byte check */ wn = 0 ; bn = 0 ; for ( loop = 0 ; loop < 8 ; loop++ ) { /* judge 00000000 */ if ( *(gbcline+loop) == 0 ) continue ; /* judge L->H */ if ( wflag == OFF ) { xtmp = judge_location(LOWHIGH_EDGE,*(gbcline+loop)) ; if ( xtmp != 65 ) { wn = (loop << 3)+xtmp ; jxx->lh_location = wn ; } } /* judge H->L */ if ( bflag == OFF ) { xtmp = judge_location(HIGHLOW_EDGE,*(gbcline+loop)) ; if ( xtmp != 65 ) { bn = (loop << 3)+xtmp ; jxx->hl_location = bn ; } } } /* calculate */ jxx->cwidth = abs(jxx->lh_location - jxx->hl_location)+1 ; jxx->cflag = XDONE ; jxx->gcenter = (jxx->lh_location + jxx->hl_location) >> 1 ; }  この関数を、3種のパターンに適用した結果は以下。  0000000000000000000000001111111111111111111110000000000000000000   cflag : 67   lh_location : 24   hl_location : 44   cwidth : 21   center : 34  0000000000000000000000000000000000000000000000000000000000000000   cflag : 66   lh_location : 0   hl_location : 0   cwidth : 0   center : 0  1111111111111111111111111111111111111111111111111111111111111111   cflag : 65   lh_location : 0   hl_location : 0   cwidth : 64   center : 32  GBCから得られた1ライン分のセンターライン情報を  利用できれば、クランク、レーンチェンジの判断は  より上位のファームウエアに任せます。
目次

inserted by FC2 system