目次
前
次
画像処理プログラム
今回利用する画像センサーは、GameBoyCameraです。
GBCからの画像データを、32kバイトのSRAMに保存し
シリアルでPersonalComputerに転送して、どの様な
画像になっているかを確認します。
VC#や他のWindowsアプリケーションで、画像表示すると
Windows専用になります。マルチプラットホームで動く
ようPythonを利用します。
シリアル転送で画像データ情報をテキストファイルにすると
Python、Tcl/Tk等のスクリプト言語を利用しImageMagickでの
変換や表示が可能となります。
Pythonはリストを使った処理が簡単にできるので
画像処理システムを作成するのに、1日程度時間
を使うだけで済みました。
Pythonは、Unixに標準でバンドルされていた処理系を
そのまま利用。Windowsでも、使える処理系があるので
ダウンロードすれば、すぐに使えます。
MCR_VCマシンを動かすためには、次のような
手順で処理するため、Pythonで同じプロセス
をなぞります。
- GBCから画像データ取得
- 指定矩形領域のみデータを限定
- 最大値、最小値を求める
- 最大値、最小値から閾値計算
- 閾値を利用して2値化
- 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の画像を変換すると、ライン情報として
最適な位置と幅がどこにあるのか判断できます。
閾値は、どの程度がよいのかも推定できます。
今回は、次のような画像処理を適用します。
- センターに白色があるテンプレートを用意
- 0から63ラインの32から95ピクセルの画像データ取得
- 矩形領域の最大値、最小値を閾値計算
- 矩形領域情報を2値化
- テンプレートからのズレを使い、モータ制御値を算出
画像処理に必要となるメモリ容量を計算します。
画像処理専用マイコンに、内蔵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 ;
}
ここまでで必要な関数を定義したので
変化点を見つけるアルゴリズムとして
まとめます。
- 64ピクセルがすべて0か1を判定
- バイト境界の0→1、1→0を判定
- バイト中の1の個数から、変化点を求める
- 1から3の内容をまとめる
欲しい情報を、数値としてまとめていきます。
- 0から63 0→1、1→0の変化位置
- 65 すべて1
- 66 すべて0
- 67 判定した
- 68 判定できない
センターラインの幅、重心位置を求めるには
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ライン分のセンターライン情報を
利用できれば、クランク、レーンチェンジの判断は
より上位のファームウエアに任せます。
目次
前
次