2021年8月27日金曜日

iPhoneからAndroidに変更

 iPhoneのAmenbo、もう、相当出来上がった。顔認識も音声認識も精緻に組み上げたが、ここにきて、Dji Mobile Sdkで作ったアプリをApp Storeで公開するのが困難な状況になっていることがわかり、やむを得ず、Androidに変更する。

Android Studioをアップグレードして、DJI Mobile SDKをインストールしようとしたが、その手順の紹介で、解説が古い。DJIサイト

やりながら、この記事を書いていくことにする。

(1) build.gradleの追加で

Could not find method compile() for arguments [com.dji:dji-sdk:4.12] on object of type org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler

というエラーがでる。

    compile ('com.dji:dji-sdk:4.12')

    provided ('com.dji:dji-sdk-provided:4.12')

で、compileなどのコマンドは、新しいStudioでは廃止されているようだ。
implementation 'com.dji:dji-sdk:4.15'
implementation 'com.dji:dji-sdk-provided:4.15'
変更すれば、"Sync Project with Gradle Files" は成功する。
結果以下のように確認した。
確認する場所は、少し違っている。


2021年8月7日土曜日

MAVIC MINI コントロールスクリプト

この時点で、ドローンをコントロールしているスクリプトは以下のようなものです。もっと、細かい解説が必要ですが、その時間が取れないので、感じだけを味わってください。

------------------------ 以下 ----------------

############
# ユーザースクリプト
# 2021年8月8日 ver.26
# 2021年8月5日 ver.22 単位を全てメートルに変更
# 2021年7月18日 ver.0
############

@grammar
# 音声認識文法
# AmenboScriptと矛盾しない形で入れておく
0: Object + Object + Verb
0: Object + Object + Unit
0: Object + Object + Question
0: Subject + Verb
0: Verb + Negation
0: Subject + Adjective
1: Object + Verb
1: Verb + End
1: Object + End
1: Adjective + End

@concept
# 音声に含まれる概念
Subject : [僕,私]
Object : 顔,口,テスト,角度,周り,距離
Adjective : 嫌い,好き,終わり
Verb : [始め,一,はじめ],キス,見つけ,着陸,見なさ,回転,回り
Unit : メートル
Question : どれ,教え
End : [よ,ます,ましょう,なさい],か,です
Negation : 止め,やめ

@command
# 音声コマンド
始めるよ : ^takeoff() ^sleep(2000) はい、アメンボです。よろしくお願いします ^sleep(1000)
終わります : おわります ^landing()
着陸しなさい : 着陸します ^landing()
僕が好きですか : ^sleep(1000) はい、好きです ^yes()
僕が嫌いですか : ^sleep(1000) いいえ、好きです ^no()
顔を見つけなさい : ^sleep(1000) はい、顔を探します &searchface()
キスをしましょう : ^sleep(1000) はい、キスします &kiss()
テストをします : テストですね ^getaltitude()
口を見なさい : 口を見ます &shapeLip()
角度を見なさい : 角度を見ます &faceAngle()
回転しなさい : 回転します ^hloop(0.5,1.0)
回転止めなさい : 回転やめます ^stophloop()

# 関数やマクロ内の変数は全てグローバル変数
# $distには、&approachefaceで近づいた距離が入っているはず

顔の周りを回りなさい : 顔の周りを回ります &noseincircle($dist)

# $0,$1....の変数は同時にグローバル変数になる
# 関数の引数にはグローバル変数しか使えないので

顔までの距離を $0 メートルにしなさい : 顔まで $0 メートルにします &approacheface($0)
顔までの距離はどれくらいですか : 顔までの距離は $dist メートルです

@macro
# マクロの引数は、$macroarg_0, $macroarg_1,・・・の変数に入っている
# ノーズインサークルの自動実行
# 人の顔が真ん中にあることを前提にする
# 事前に、回転する半径を引数に入れておく
&noseincircle
    let $radius = $macroarg_0
    ^print("マクロ: noseincircle の開始")
    ^speek("顔を中心に旋回をします")
    ^print("顔を中心に旋回をします。半径は",$radius,"メートルです")
    ^hloop($radius,1.0)
    ^print("マクロ: noseincircle を終了")
    ^speek("旋回を終えました")
&&

# 顔を画面の中心にしながら指定した距離まで顔に近づく
# 事前に、変数 $distface に近づく距離(メートル単位)を指定しておく
&approacheface
    let $distface = $macroarg_0
    ^print("マクロ: approacheface の開始")
    # 顔を真ん中にするのに成功していることが前提
    # 顔を真ん中にしないと人との距離が図れない
    ^speek("顔との距離を調整します")
    ^print("顔との距離を",$distface,"メートルに調整します")
    let $maxtry = 10
    for $i=0 ; $i < $maxtry ;$i += 1 {
        let $dist = ^checkface()
        # 目標距離との差が0.1メートル以下になるまで 最大10回
        if ^abs($dist - $distface) < 0.1 && $dist > 0.0 {
            # $dist が -1 は、エラー
            break
        }
        # 接近を刻む距離
        let $unitdist = 0.1
        if $dist > 1.0 {
            ## 1メートル以上離れていたら25センチ移動する
            $unitdist = 0.25
        }
        let $diff = $dist - $distface
        if $diff > 0.0 {
            ^speek($unitdist,"メートル近づきます")
            ^print("No.",$i," 距離差:",$diff," => ",$unitdist,"メートル接近")
            ^forward($unitdist)
            ## 距離の改訂 moveForFaceCenter の引数
            let $reviseddist =  $dist - $unitdist
        }else{
            ^speek($unitdist,"メートル離れます")
            ^print("No.",$i," 距離差:",$diff," => ",$unitdist,"メートル離脱")
            ^backward($unitdist)
            let $reviseddist =  $dist + $unitdist
        }
        # 次のコマンドまで少し時間を空ける
        ^sleep(1000)
        # ここで、再度、顔が正面になるように調整する   
        # $unitdist m近づいたので、その分差し引く
        # 3回に1回は、顔の中心に、調整する
        if $i % 3 == 1 {
            ^speek("顔が中心になるよう再設定します")
            ^print("顔が中心になるよう再設定します")
            ^moveForFaceCenter($reviseddist)
        }
    }
    if ^abs($dist - $distface) < 0.1 && $dist > 0.0 {
        ^speek("距離の調整に成功しました")
        ^print("距離の調整に成功しました")
    }else{
        ^speek("距離の調整に失敗しました")
        ^print("距離の調整に失敗しました")
    }
&&

# 顔の角度で、機体を水平、垂直に動かす
&faceAngle
    ^print("マクロ: faceAngle の開始")
    # 基本的移動距離を定める
    let $movedist = 0.2
    ^print("教授の顔の傾きを観察します")
    ^startFaceStatus()
    let $maxtry = 15 # この回数だけ反応します
    for $i=0 ; $i < $maxtry ; $i += 1 {
        if $i % 4 == 3 {
            let $dist = ^checkface()
            if $dist < 0 {
                # 顔を見失っています
                ^print("顔を見失いました")
                ^sleep(1000) 
            }
            # 4回に1回は顔中心の補正
            #^speek("顔が中心になるよう再設定します")
            ^print("顔が中心になるよう再設定します")
            ^moveForFaceCenter($dist)
            ^sleep(500)
        }else{
            # 中心補正しない場合
            # 1秒待機。これを怠ると、瞬間的に終わる可能性がある
            ^sleep(1000) 
        }
        let $hdist = ^getHorizontalFaceAngle()
        if $hdist > 9000 { # 距離は指数で見ている、メートルでもセンチでもない
            ^print("顔の水平傾斜が取れません ",$i,"回")
            #^speek("顔の水平傾斜が取れません")
            #continue # これがあると縦を見ない
        }
        if $hdist < 9000 && $hdist > 0.55 {
            #^speek("右に動きます")
            ^print("右に",$movedist,"センチ動きます 回数 ",$i)
            ^right($movedist)
            #continue # これがあると縦を見ない
        }
        if $hdist < 0.45 {
            #^speek("左に動きます")
            ^print("左に",$movedist,"センチ動きます 回数 ",$i)
            ^left($movedist)
            #continue # これがあると縦を見ない
        }
        let $vdist = ^getVerticalFaceAngle()
        if $vdist > 9000 {
            ^print("顔の垂直傾斜が取れません ",$i,"回")
            #^speek("顔の垂直傾斜が取れません")
            continue
        }
        if $vdist > 0.6 {
            #^speek("上に動きます")
            ^print("上に",$movedist,"センチ動きます 回数 ",$i)
            ^upward($movedist)
            continue
        }
        if $vdist < 0.45 {
            #^speek("下に動きます")
            ^print("下に",$movedist,"センチ動きます 回数 ",$i)
            ^downward($movedist)
            continue
        }
        #^speek("顔の角度は通常です")
        ^print("顔の角度は通常です 回数 ",$i)
    }
     ^speek("顔の角度の観察を停止します")
     ^print("顔の角度の観察を停止します")
     ^stopFaceStatus()
&&


# 口の形で前後に動かすマクロ
&shapeLip
    ^print("マクロ: shapeLip の開始")
    # 基本的移動距離を定める
    let $movedist = 0.2
    ^print("教授の口を観察します")
    ^startFaceStatus()
    let $maxtry = 15 # この回数だけ口に反応します
    for $i=0 ; $i < $maxtry ; $i += 1 {
        if $i % 4 == 3 {
            let $dist = ^checkface()
            if $dist < 0 {
                # 顔を見失っています
                ^print("顔を見失いました")
                ^sleep(1000) 
            }
            # 4回に1回は顔中心の補正
            #^speek("顔が中心になるよう再設定します")
            ^print("顔が中心になるよう再設定します")
            ^moveForFaceCenter($dist)
            ^sleep(500)
        }else{
            # 中心補正しない場合
            # 1秒待機。これを怠ると、瞬間的に終わる可能性がある
            ^sleep(1000) 
        }
        let $ldist = ^getLipDistance()
        if $ldist < 0 {
            ^print("口の形が取れません ",$i,"回")
            #^speek("口の形が取れません")
            continue
        }
        if $ldist < 0.1 { # 距離は指数で見ている、メートルでもセンチでもない
            #^speek("近づきます")
            ^print("キス型なので",$movedist,"センチ近づきます 回数 ",$i)
            # $movedist m近づく
            ^forward($movedist)
            # 次のコマンドまで少し時間を空ける
            continue
        }
        if $ldist > 0.15 {
            #^speek("離れます")
            ^print("口を開けているので",$movedist,"センチ離れます 回数 ",$i)
            # $movedist cm離れる
            ^backward($movedist)
            # 次のコマンドまで少し時間を空ける
            continue
        }
        #^speek("口の形は通常です")
        ^print("口の形は通常です 回数 ",$i)
    }
     ^speek("口の観察を停止します")
     ^print("口の観察を停止します")
    ^stopFaceStatus()
&&

# 顔を見つけることに限定したマクロ
&searchface
    ^print("マクロ: searchface の開始")
    ^print("身近な顔を探します")
    # ドローンの高度を160センチの、人の標準的身長に設定する
    ^print("ドローンの高度を160センチにします。")
    ^altitude(1.6)
    # FPVに顔がないかチェックする
    # 30度刻みで回転する
    let $flag = 0
    for $i=0 ; $i < 12 ;$i += 1 {
        ^print("角度を 30度 左回転させます")
        #^print("角度を " $i "度 右回転させます")
        # $distは、少数以下が丸められている倍精度数
        let $dist = ^checkface()
        if $dist > 0.1 {
            ^speek("顔を見つけました")
            ^print("顔を見つけました。距離は ",$dist,"cm です")
            $flag = 1
            break
        }
        ^turnleft(30)
        #^rightangle($i)
    }
    if $flag <= 0.1 {
        ^speek("顔が見つかりませんでした")
        ^print("顔が見つかりませんでした")
    }else{
        ^print("顔は見つかりました")
        let $maxtry = 15
        # 顔が見つかった場合の処理
        let $count = $maxtry # $maxtry 回だけ試みる
        ^print("顔が真ん中になるように姿勢を制御します")
        # 最大許容誤差の設定 pxel単位
        let $maxdiffpixel = 70
        # 修正ループの開始
        while ^checkFaceCenter($maxdiffpixel) < 0.1 {
            # まず、顔を真ん中にする
            ^print("顔を正面にします Try No. ",$maxtry-$count)
            ^moveForFaceCenter($dist)
            # 次のコマンドまで少し時間を空ける
            ^sleep(500)
            $count -= 1
            if $count < 0.1 {
                break
            }
        }
        ^print("顔を正面にする試みは終わりました 回数 ",$maxtry-$count)
        if $count > 0.1 {
            # 顔を真ん中にするのに成功した場合
            # 顔を真ん中にしないと人との距離が図れない
            ^speek("顔を真ん中にしました")
            ^print("顔を真ん中にすることに成功しました")
        }else{
            ^speek("顔を真ん中にできませんでした")
            ^print("顔を真ん中にできませんでした")
        }
    }
&&

# 一番近くの人とキスするマクロ
&kiss
    ^print("マクロ: kiss の開始")
    ^print("身近な顔を探します")
    # ドローンの高度を160センチの、人の標準的身長に設定する
    ^print("ドローンの高度を160センチにします。")
    ^altitude(1.6)
    # FPVに顔がないかチェックする
    # 30度刻みで回転する
    let $flag = 0
    for $i=0 ; $i < 12 ;$i += 1 {
        ^print("角度を 30度 左回転させます")
        #^print("角度を " $i "度 右回転させます")
        # $distは、少数以下が丸められている倍精度数
        let $dist = ^checkface()
        if $dist > 0.1 {
            ^speek("顔を見つけました")
            ^print("顔を見つけました。距離は ",$dist,"cm です")
            $flag = 1
            break
        }
        ^turnleft(30)
        #^rightangle($i)
    }
    if $flag <= 0.1 {
        ^speek("顔が見つかりませんでした")
        ^print("顔が見つかりませんでした")
    }else{
        ^print("顔は見つかりました")
        let $maxtry = 15
        # 顔が見つかった場合の処理
        let $count = $maxtry # $maxtry 回だけ試みる
        ^print("顔が真ん中になるように姿勢を制御します")
        # 45ピクセル以内にしている
        while ^checkFaceCenter(45.0) < 0.1 {
            # まず、顔を真ん中にする
            ^print("顔を正面にします Try No. ",$maxtry-$count)
            ^moveForFaceCenter($dist)
            # 次のコマンドまで少し時間を空ける
            ^sleep(500)
            $count -= 1
            if $count < 0.1 {
                break
            }
        }
        ^print("顔を正面にする試みは終わりました 回数 ",$maxtry-$count)
        if $count > 0.1 {
            # 顔を真ん中にするのに成功した場合
            # 顔を真ん中にしないと人との距離が図れない
            ^speek("顔を真ん中にしました もっと近づきます")
            ^print("顔を真ん中にすることに成功しました。顔に近づいていきます")
            for $i=0 ; $i < $maxtry ;$i += 1 {
                let $dist = ^checkface()
                # 25センチ以下になるまで近づく 最大10回
                if $dist < 0.25 && $dist > 0.0 {
                    # $dist が -1 は、エラー
                    # 0.0 < $dist < 35.0 を最接近とする
                    break
                }
                ^speek("10センチ近づきます")
                ^print("10センチ近づきます 回数 ",$maxtry-$count)
                # 10cmずつ近づく
                ^forward(0.1)
                # 次のコマンドまで少し時間を空ける
                ^sleep(1000)
                # ここで、再度、顔が正面になるように調整する   
                # 10cm近づいたので、その分差し引く
                # 3回に1回は、顔の中心に、調整する
                if $i % 3 == 1 {
                    ^speek("顔が中心になるよう再設定します")
                    ^print("顔が中心になるよう再設定します")
                    ^moveForFaceCenter($dist-0.25)
                }
            }
            if $dist < 0.25 && $dist > 0.0 {
                ^speek("もう、キスできます")
                ^print("もう、キスできます")
            }else{
                ^speek("近づくことができませんでした")
                ^print("近づくことができませんでした")
            }
        }else{
            ^speek("顔を真ん中にできませんでした")
            ^print("顔を真ん中にできませんでした")
        }
    }
&&

### Script End ###

2021年8月4日水曜日

向中心の円形軌道(ノーズインサークル)の数学

 


以下、数式など綺麗なもので見たい方は、次のpdfをダウンロードしてご覧ください。

ドローンの向きを一つの方向に固定しながら、ドローンを円形軌道に飛行させようとする場合は、その速度のコントロールは次のようになる。
座標を水平方向に$x$をとって、垂直方向にyをとると、A点を基準にして、機体がB点にあるとし、円の半径を$r$、角速度を$w$、時間を$t$とすると、
$$x = r\sin(wt)$$
$$y = r(1-\cos(wt))$$
であり、これを時間で微分したものが必要な速度となるから、速度をそれぞれ$v_{x}, v_{y}$とすると、
$$v_{x} = rw\cos(wt)$$
$$v_{y} = rw\sin(wt)$$
となる。
ドローンの向きが一定方向に維持されている限り、このような制御でドローンの軌道は円形になる。
しかし、ドローンの向きが常に縁の中心に向くということは、期待そのものが回転しているということである。これは、月が常に地球に同じ面を向けている状況と同じである。 

この場合、上記の方法ではうまくいかない。機体の回転のたびに、座標そのものが回転するからである。ドローンには、機体の座標だけではなく、グラウンド座標というものがあり、その場合は、常に向きが北を基準にするので、違った状況になることが考えられるが、ここではあくまで機体の方向に座標が維持されているとしよう。

このとき、円形軌道となるためには、上記の速度が必要なのだが、機体の速度を上記の速度が実現できるようなものにすれば良い。

回転した状況における、機体を基準とした座標の速度を$u_{x}, u_{y}$としよう。

このとき、速度の分解方向から、

$$u_{x}\cos(wt) - u_{y}\sin(wt) = v_{x} (= rw\cos(wt)) $$

$$u_{x}sin(wt) - u_{y}sin(wt) = v_{y} (= rw\sin(wt))$$

という式が成立する。

これを解くと、次のような実に簡単な式になる。

$$u_{x} = rw$$

$$u_{y} = 0$$

つまり、機体を月の自転のように、一周当たり角速度wで回転させておけば、上記の式で機体の速度を維持することによって、常に中心向きにカメラを向けるドローンの制御ができるのだ。あまりにも簡単になるので、信じられなかったが、実際ドローンにこの制御を組み込むと、確かに意図する円形軌道を描いたのである。とても驚いた。

920MHz帯無線通信モジュールTY92SS-E2730を使う

 先にも書いたが、ドローン2号機上のコントローラーはラズパイ4で、それとのやりとりをもともとWIFI経由で予定していたが、機体がアルミパイプであるために通信が不安定で使い物にならなかった。そこで、プロポに変えた。プロポの信号取り出しもなんとか安定できるようになったが、そのシステム...