2012年8月31日金曜日

夏休みの工作 〜指の認識〜

もはや夏休みでも何でもない今日このごろ
実は先週の話だけどpythonとopencvでなんとか指の画像認識まで漕ぎつけたので概要をメモ

メモっとかないと絶対に忘れるわこれ・・・(;´д`)

〜あらすじ〜
凸包使って手は認識できるようになったけど指はちゃんと認識出来なかった。
しかも座標で一番上に来る指は全く認識出来なかった。
これまでの状態↓




〜今回のはなし〜
 凸包凹状欠損はやっぱりやめて参考文献(前回参照)を参考に輪郭(3点)の角度を計算して指先を検出してみた。それに伴う微調整項目の説明とメモ

〜「手」の輪郭を決定したところから

  rangewidth=int(〜〜) #3点の座標間の距離(2だと2個飛ばしで座標角度計算)
       
        ##指検出がらみの変数
        renzoku=0  #エッジ点が連続してるかどうかのフラグ
        egroup=[] #連続しているエッジのグループを入れるリスト。1グループ1指。

        for k in range(20): ##指の位置配列20もいらないけどエラー防止で
            egroup.append([]) #20個グループを作っておく

        fingernum=0 #指の番号
        robust=20  #多少不連続でも同じグループに入れる座標の個数
       
#処理画像に文字を入れる時に使う↓
        MyFont = cv.InitFont(cv.CV_FONT_HERSHEY_COMPLEX, 1, 1, 0, 2, 4)
        FontCOLOR = cv.CV_RGB(255, 0, 0) 

#輪郭の座標数がそもそも少なすぎるときは判定しない
        if len(contours)<rangewidth: 
            return frame  ←今の画像をメインルーチンに返す
           
#座標1個ずつ処理(座標には1個ずつ通し番号がふられている)
        for i in range(len(contours)):
           
            #角度を求める座標3点(前、中心、後ろ)を決定
            iprev=i-rangewidth
            inext=i+rangewidth
          
#前後座標が座標番号を超過していたらそこだけ2週目に入る処理
            if iprev<0:#前の座標が座標番号0番より前だったら・・・
               
                iprev=len(contours)+(i-rangewidth)
           
            if inext>=len(contours):#後ろの座標番号が最後の番号を超えていたら・・・
               
                inext=rangewidth-(len(contours)-i)
               
#角度を計算(別に角度を計算する関数作っておく)           
            deg=kakudo(contours[iprev],contours[i],contours[inext])
           
#角度に応じた処理。ロバストの回数だけ連続じゃなかったら次のグループへ行く         
            if  deg< 角度のしきい値 :##角度条件を満たした時(60度くらいがおすすめ、要調整)
               
                if renzoku==1:       ##1粒前の座標で条件を満足してたら・・
                    #print "renzoku"
     #今のグループにこの座標も加える
                    egroup[fingernum].append(contours[i])
               
                  
                renzoku=1 #連続フラグを1(ON)に
                robust=10 #連続だったらそのたびにロバストをリセット
                               
            else:##角度の条件を満たさなかった時
                if renzoku ==1: #1粒前は満たしてたけど今回は満たしてなかった時
                    robust -=1 #ロバスト変数を減らす
                    renzoku=0                   
                   
                elif renzoku ==0: #1粒前も今回も満たしてなかった時
                    robust -=1 #ロバスト変数を減らす
                    renzoku=0   
               ↑if とelifで処理一緒でワロタ。まとめてしまってOK

            if robust ==0: #ロバスト変数が無くなったらグループ終了。次のグループへ
                renzoku=0
                fingernum +=1 #グループを次へ
#指ごとの処理はここまで

#各グループ(指だといいな)ごとの処理。指の先端とかを設定したりなど
     egroup[0][:0]=egroup[fingernum]  #謎の処理。いらないのではないか
        fingcenter=[]  #グループ座標群の通し番号真ん中の座標
        fingeraverage=[] #グループ座標の平均。たぶん指の重心を表す。こっちがメイン
       
#グループ(指)の数だけ処理
        for i in range(fingernum):
            c=int(len(egroup[i])*0.5)#グループ通し番号真ん中の番号
           
            #fingcenterに加える。
            fingcenter.append(egroup[i][c])

            sum=[0,0] #座標平均出すための合計計算用

#グループの座標数が10より多ければ処理(少ないと指じゃないのまで拾うかも・・・要調整)
            if len(egroup[i])>10:          
                    for j in egroup[i]:#グループ内の座標1粒ごとに。x,y座標ごとに全部足す
                   
                      sum[0]=sum[0]+j[0]
                      sum[1]=sum[1]+j[1]
       
                sum[0]=sum[0]/len(egroup[i]) #足したら座標の数で割って平均出す
                sum[1]=sum[1]/len(egroup[i])

                fingeraverage.append((sum[0],sum[1]))#指の重心座標をリストに加える                 
 #重心に○でも書いておく
    cv.Circle (frame,fingeraverage[i] , 5,(255, 255, 255),cv.CV_FILLED, cv.CV_AA, 0)

おわり。ここまでの処理画像(この例だとframe)をメインルーチンに戻す。

↓出てきた画像(動画)。リアルタイム(7fpsだが)で指の重心と通し番号を書いてます。
 手のひらに余計な○が2つほどあるけど気にしない。


↓横にしても大丈夫!!(腕が写り込まなければね)



追伸:座標角度計算関数が地味に大事だったのを忘れてたので記述

def kakudo(a,b,c):#座標を3つ入力
   
    v1=(a[0]-b[0],a[1]-b[1])
    v2=(c[0]-b[0],c[1]-b[1])

#内積と外積を出しておく  
 dots=numpy.dot(v1,v2)
    cros=numpy.cross(v1,v2)

   
    v1=math.sqrt(numpy.dot(v1,v1))
    v2=math.sqrt(numpy.dot(v2,v2))
    coth=dots/(v1*v2)
   
    kakudo=numpy.arccos(coth) 
    kakudo=kakudo*180/math.pi #内積を使った角度の計算
   
#↓ここ大事。こんな実装の仕方でいいのかは不明
 この処理がないと指の股も検出されてしまう。逆に不等号変えると指の股だけ出せる。
    if cros<0:##エッジの向きが指先の方向でなければ強制的に角度を180度に。指の股を弾く
        kakudo =180

   
    return kakudo

フォントなどのテスト

######

#######

テスト12341234

あああ(´д`)ノ

import gtk

builder = gtk.Builder()
builder.add_from_file('test.glade')

win1=builder.get_object("window1")
win2=builder.get_object("window2")

def main():

dic = 123

MyFont = cv.InitFont(cv.CV_FONT_HERSHEY_COMPLEX, 1, 1, 0, 2, 4)
        Font = cv.CV_RGB(255, 0, 0)
        if len(contours)<rangewidth:
            return frameb
           
           
        for i in range(len(contours)):
           
           
            iprev=i-rangewidth
            inext=i+rangewidth
            

通勤中の筆者(これまで)

  }
   モニタ用
   Serial.print("\n pin=");
   Serial.print(pin,DEC);
    
   Serial.print("\n mode=");
   Serial.print(mode,DEC);
   Serial.print("\n value=");
   Serial.print(value,DEC);

    
     if(mode == 1){
    analogWrite(pin,value);
       }
      *   *
 *   + テストです
  n ∧_∧ n
+ (ヨ(*´∀`)E)
  Y   Y  *