詳解 確率ロボティクス 2.3 確率モデル

前回の投稿に引き続き,詳解 確率ロボティクスの2.3 確率モデルを通して学んだことやコードを実行してみた結果のメモです.この投稿はGithubでもご覧になれます(こちら).

ガウス分布*は(例えば)センサ値zがa以上b未満( [a, b) )に入る確率を

 P (a \leq z < b) = \int_{a}^{b} p(z) dz

ここで,

p(z) = \cfrac{1}{\sqrt{2\pi\sigma^{2}}}\exp \left\{ - \cfrac{(z - \mu)^{2}}{2 \sigma^{2}} \right\}

を表します. また,\sigma^{2}は分散,\muは平均値を表します.

 *ガウス分布正規分布は同義です.

センサ値の平均値\mu = 209.7[mm],分散\sigma^{2} = 23.4を代入してP, pを描画するには以下のようにします.

import math

def p(z, mu = 209.7, dev = 23.4):
    return math.exp(-(z -mu)**2 / (2*dev))/math.sqrt(2*math.pi*dev)
zs = range(190, 230)
ys = [p(z) for z in zs]
 
import matplotlib.pyplot as plt
%matplotlib inline
plt.plot(zs, ys)
plt.show()

zsは横軸の数値のリスト,ysは縦軸の関数pの値のリストです.

さらに,$p$を積分して,センサ値が整数に限定される場合の確率分布を作ってみます.
センサ値$x$に対して,区間$[ x - 0.5, x + 0.5)$の範囲で積分することにします(ここでは台形公式で近似).

def prob(z, width = 0.5):
    return width*(p(z - width) + p(z + width))
 
zs = range(190, 230)
ys = [prob(z) for z in zs]
 
import pandas as pd
data = pd.read_csv("sensor_data_200.txt", delimiter = " ",
                   header = None, names = ("data", "time", "ir", "lidar"))
freqs = pd.DataFrame(data["lidar"].value_counts())
freqs["probs"] = freqs["lidar"] / len(data["lidar"])
 
plt.bar(zs, ys, color = "red", alpha = 0.3) # Make graphs transparent with alpha
f = freqs["probs"].sort_index()
plt.bar(f.index, f.values, color = "blue", alpha = 0.3) 
plt.show()
ガウス分布\mu, \sigma^{2}を決めると形状が決まるので,式を記述する必要がなければ
\mathcal{N}(z|\mu, \sigma^{2}) \ あるいは $\mathcal{N}(\mu, \sigma^{2})
などと略記します.
  • モデル化:ある現象を説明するために適切な確率分布の数式を用いてパラメータを決めること
  • 確率モデル:モデル化で分布に当てはめられる数式
ガウス分布確率密度関数(probability density function, pdf)
p(z) = \cfrac{1}{\sqrt{2\pi\sigma^{2}}}\exp \left\{ - \cfrac{(z - \mu)^{2}}{2 \sigma^{2}} \right\}
ガウス分布などの確率密度関数を扱うにはSciPyが便利です.
このモジュール下にあるサブモジュールstatsにはガウス分布確率密度関数norm.pdfが実装されています.
mean1 = sum(data["lidar"].values)/len(data["lidar"].values)
 
zs = data["lidar"].values
mean = sum(zs) / len (zs)
diff_square = [(z -mean)**2 for z in zs]
sampling_var = sum(diff_square)/(len(zs))
stddev1 = math.sqrt(sampling_var)
 
from scipy.stats import norm
 
zs = range(190, 230)
ys = [norm.pdf(z, mean1, stddev1) for z in zs]
 
plt.plot(zs, ys)
plt.show()
変数$z$が実数のとき,確率密度関数p積分したものは累積分布関数(cumulated distribution function, cdf)と呼ばれます.
p(z < a) = \int_{-\infty}^{a} p(z) dz
zs = range(190, 230)
ys = [norm.cdf(z, mean1, stddev1) for z in zs]
 
plt.plot(zs, ys, color = "red")
plt.show()
先に台形公式で近似計算を行なった下式の計算は,確率の計算に置き換えられます.
P(a \leq z < b) = \int_{a}^{b} p(z) dz = \int_{-\infty}^{b} p(z) dz - \int_{-\infty}^{a} p(z) dz = P(z < b) -P(z < a)
上式を使って確率分布を描くには以下のようにします.
zs = range(190, 230)
ys = [norm.cdf(z + 0.5, mean1, stddev1) - norm.cdf(z - 0.5, mean1, stddev1) for z in zs]
 
plt.bar(zs, ys)
plt.show()
確率密度関数は,確率質量関数が大文字$P$で表記されるのに対して,多くの場合,区別のために小文字pと表記されます.
  • 期待値:確率分布$P$について$z \sim P(z)$を無限に繰り返した場合に,$z$の平均値がどれくらいになるかを表す値(確率分布$P$から$z$を発生させる).
zが離散的な場合,期待値は
\sum_{z = \infty}^{\infty} z P(z)
で計算できます.

zが連続的な場合は
\int_{- \infty}^{\infty} zP(z) dz
で計算できます.
期待値は具体的な値をドローしなくても分布が決まっていれば,下式のいずれか(離散的な場合は上の式,連続的な場合は下の式)
\sum_{z = \infty}^{\infty} z P(z)
 
\int_{- \infty}^{\infty} zP(z) dz
で計算できます(定義通りに何回もドローして値をサンプリングし,平均を取ることでも期待値を近似的に求めることが可能).
z \sim p(z)や,z \sim P(z)のとき,$z$の期待値は
E_{p(z)} [z, \ E_{P(z)} [z] \ あるいは \ _{p(z)}, \ _{P(z)}]
と表記されます.

期待値をさらに一般化して,z \sim p(z)から計算される関数の値$f(z)$の期待値を考えると
[tex:<f(z)>_{p(z)} = \int_{- \infty}^{\infty} f(z)p(z) dz]
と定義できます.この式は,ある確率も出るから別の確率モデルのパラメータを求める時に頻出します.

詳解 確率ロボティクス 2.2 度数分布と確率分布

UASの自律飛行にあたって,ロボティクスに興味を持ちました.上田隆一先生の書籍 詳解 確率ロボティクスが出版され,非常に分かり易そうだったので,詳解 確率ロボティクスの2.1センサデータの収集とJupyter Notebook場での準備,2.2 度数分布と確率分布を通して学んだことやコードを実行してみた結果のメモです.この投稿はGithubでもご覧になれます(こちら).

  • 頻度ヒストグラムの縦軸の値のこと.
  • 雑音(ノイズ):計測値(センサ値)の変動(e.g. LiDARの計測値).例えば,LiDARの場合は,外乱光や電気回路中の電圧や電流を乱す"何か(原因は複雑にあって,互いに影響し合っていることが多い)"が影響して雑音が発生する.
  • 誤差:何らかの測りたいものの"真の値"とセンサ値の差のこと.
  • 偶然誤差(accidental error, random error):雑音によって発生する誤差のこと.
  • 偏り(バイアス):計測機器の取り付け位置がずれていたり,取り付けた本体が傾いていたりするときに生じるずれのこと.
  • 系統誤差(Systematic error):バイアスによって生じる定常的な誤差のこと.系統誤差の量はセンサ値から推定することができないので,別のセンサや計測方法で突き止める必要がある.しかし,別の計測方法やセンサにも雑音やバイアスが存在する.
  • 系統誤差は,アルゴリズムの出力にも悪影響を及ぼす.対策は取りにくいが,バイアスや系統誤差の存在を頭の隅に置いて,ロボットのアルゴリズムを考えていく必要がある.
書籍 詳解 確率ロボティクスの中では,Python 3系とJupyter Notebookを用いてコードの実行結果を確認する方法が取られています.コードは著者の上田隆一先生GitHubで公開されています.書籍中のコードの閲覧はこちらから可能です.完成したコードのみを見たい場合はこちらから閲覧が可能です.
 
以下は,詳解 確率ロボティクス 2.2.2 頻度,雑音,バイアス にあるコードを実行してみた際のメモです.
 
まずは,Pythonのバージョン等のチェック(Python 2系だとコードが動かないため,3系であることを確認)
import sys
sys.version    # Check Python version
 
実行すると,以下のように返ってくるはずです(バージョンなどは環境によって異なります).
'3.7.5 (default, Nov 1 2019, 02:16:32) \n[Clang 11.0.0 (clang-1100.0.33.8)]'
続いて,Pandasモジュールの"read_csv"関数を使って data という変数に既存の計測データ(ファイル名:sensor_data_200.txt, 計測した日付,時間,光センサの計測値,LiDARの計測値)*を読み込んでいます.
import pandas as pd
data = pd.read_csv("sensor_data_200.txt", delimiter = " "
                  header = None, names = ("date", "time", "ir", "lidar"))
data
実行すると,以下のように返ってくるはずです(長いので途中を省略しています).

date time ir lidar
0 20180122 95819 305 214
1 20180122 95822 299 211
2 20180122 95826 292 199
3 20180122 95829 321 208
4 20180122 95832 298 212
5 20180122 95835 327 212
         
... ... ... ... ...
         
         
58983 20180124 120023 313 208
58984 20180124 120026 297 200
58985 20180124 120030 323 204
58986 20180124 120033 326 207
58987 20180124 120036 321 208
58988 rows × 4 columns

続いて,sensor_data_200.txt における,LiDARのセンサ値値の規則性の有無を調べるために,センサ値のヒストグラムの描画を行います.
%matplotlib inline    #necessary if the graph is not displayed.
import matplotlib.pyplot as plt
data["lidar"].hist(bins = max(data["lidar"]) - min(data["lidar"]), align = 'left')
plt.show()


実行すると,以下のようなヒストグラムが描画されます.



* 計測データの中身は以下のようになっています.
20180122 095819 305 214
20180122 095822 299 211
20180122 095826 292 199
20180122 095829 321 208
20180122 095832 298 212
...途中省略...
20180124 120023 313 208
20180124 120026 297 200
20180124 120030 323 204
20180124 120033 326 207
20180124 120036 321 208

雑音の原因を突き止めたり除去するのは困難な場合が多いので,原因はわからないままにして,雑音の傾向を把握する(分からないことは放っておく).
傾向を把握するためには以下の値を求めます.

  1. センサ値の平均値を求める.
  2. センサ値の分散を求める.
  3. センサ値の標準偏差を求める.

なお,分散と標準偏差は各センサ値のばらつきを表します.

{\bf{Z}}_{LiDAR} = \{ z_{i} | i = 0, 1, 2, \cdots, N-1\}

ここで,[tex:{\bf{Z}}_{LiDAR}]はリスト(ベクトル)を表し,[tex:z_{i}]のiは(0からの)要素番号を表し,要素数Nとなります.

  • 分散:集合の要素の値を全て足して,要素数で割ったもの
\mu = \cfrac{1}{N} \sum_{i = 0}^{N-1} z_{i}
  • 分散には標本分散と不偏分散の二つがある.
  • 標本分散:各値と平均値(\mu)の差を二乗したものの平均値(各値と平均値(\mu)が離れているほど大きい)
\sigma^{2} = \cfrac{1}{N} \sum_{i = 0}^{N-1} (z_{i} - \mu)^{2} \ \ \ (N &gt; 0)
  • 不偏分散標本分散の割り算の分母が N ではなく N-1 としたもの.
(s^{2} = )\sigma^{2} = \cfrac{1}{N-1} \sum_{i = 0}^{N-1} (z_{i} - \mu)^{2} \ \ \ (N &gt; 1)
  • 標本分散と不偏分散は,Nの値が小さい時に違いが現れる.

上記の分散を実際に計算するコードは以下のようになります.

# Calculate from definition
zs = data["lidar"].values
mean = sum(zs) / len(zs)
diff_square = [(z - mean)**2 for z in zs]
 
sampling_var = sum(diff_square) / (len(zs))     # sample variance
unbiased_var = sum(diff_square) / (len(zs) - 1) # unbiased sample variance
 
print(sampling_var)
print(unbiased_var)
 
# Use Pandas
pandas_sampling_var = data["lidar"].var(ddof = False) # sample variance
pandas_default_var = data["lidar"].var()              # default(unbiased sample variance)
 
print(pandas_sampling_var)
print(pandas_default_var)
 
# Use Numpy
import numpy as np
 
numpy_default_var = np.var(data["lidar"])            # default(sample variance)
numpy_unbiased_var = np.var(data["lidar"], ddof = 1) # unbiased sample variance
 
print(numpy_default_var)
print(numpy_unbiased_var)


最初の# Calculate from definition以下は,定義から計算するもので,次の# Use Pandas以下はPandasを使って計算するもの,最後の# Use Numpy以下はNumpyを使って計算するものです.各計算結果を表示するように命令してあり,実行結果は以下のようになります.
この結果からPandasとNumpyを使った結果は一致していることがわかります.Pythonでは,上記のように定義から計算するよりもPandasやNumpyを使って計算することが推奨されており,計算速度もそちらの方が速いようです.
計算結果は以下のようになります.

23.407709770274106
23.40810659855441
23.4077097702742
23.408106598554504
23.4077097702742
23.408106598554504


続いて標準偏差を求めます(標準偏差は分散の正の平方根).

import math
 
# Calculate from definition
stddev1 = math.sqrt(sampling_var)
stddev2 = math.sqrt(unbiased_var)
 
# Use Pandas
pandas_stddev = data["lidar"].std() # Pandas calculates standard deviation from unbiased variance
 
print(stddev1)
print(stddev2)
print(pandas_stddev)
 

計算結果は以下のようになります.

4.838151482774605
4.83819249292072
4.838192492920729

計算結果から,Pandasでは不偏分散を用いて標準偏差を求めていることがわかります.

  • 確率:値の出やすさを数値化したもの

まず,各センサ値の頻度を集計してみます.以下では,value_countsでlidar列のセンサ値の頻度を数え上げて,pd.DataFrameでデータフレームにしています.

freqs = pd.DataFrame(data["lidar"].value_counts())
freqs.transpose() # Output horizontally


集計結果は以下のようになります.

1 rows × 35 columns


続いて,変数freqsに確率の列を追加してみます.1行目では,lidar列に入っているそれぞれの頻度を,dataの要素数で割っています.

freqs["probs"] = freqs["lidar"]/len(data["lidar"])
freqs.transpose()
2 rows × 35 columns

(表は途中-右側-を省略しています)

確率の合計が1になっていることを確認します.

sum(freqs["probs"])


合計の計算結果は以下のようになります.

1.0


出力結果を並べ替えて,横軸にセンサ値を,縦軸に確率を描いてみます.
ヒストグラムと似ていますが,縦軸は頻度ではなく確率であることに注意が必要です.

freqs["probs"].sort_index().plot.bar()
plt.show() # The vertical axis changes from frequency to establishment

描画結果は以下のようになります.
 
  • 確率質量関数:個別の確率$P(z)$を与える関数$P$
  • 確率分布:各変数に対して確率がどのように分布するのかを表す実体

最後に,確率分布を用いたシミュレーションを行ってみた結果です.
シミュレーションでは,先に求めた確率分布($P_{{\bf{Z}}LiDAR}$と記する)に従って$z$を選びます.Pandasでは,sampleメソッドを使用することで確率分布から値を選ぶことができます.
上記の処理を数式で表すと

z_{N} \sim P_{{\bf{Z}}LiDAR}

となります.
ここで,左辺のz_{N}は実際に選ばれた値を表します.

def drawing(): # Define as a function
    return freqs.sample(n = 1, weights = "probs").index[0]
 
 
drawing() # meaning of execution, not drawing graph
 
実行結果は以下のようになります.
211

sampleの引数nが選ぶ個数,weights = "probs"がデータフレームの"probs"の列に選ぶときの確率が入っていることを意味しています.sampleの後ろの.index[0]は,データフレームのレコードの名前(この場合はセンサ値)を取り出すためのもので,これによってセンサ値を得ることができます.
上記の処理を数式で表すと
z_{N} \sim P_{{\bf{Z}}LiDAR}

となります.
ここで,左辺の$z_{N}$は実際に選ばれた値を表します.

Pandasでsamplesメソッドを使って確率分布から値を選ぶことが可能です.N-1回目までのセンサ値で作った分布からz_{N}を発生させるには以下のようにします.

samples = [drawing() for i in range(len(data))]
# samples = [drawing() for i in range(100)]
simulated = pd.DataFrame(samples, columns = ["lidar"])
p = simulated["lidar"]
p.hist(bins = max(p) - min(p), color = "orange", align = 'left')
plt.show()

描画結果は以下のようになります.
  • ドローイング(drawing):母集団から個々のものを抽出すること
  • サンプリング(sampling):母集団から集団の一部を抽出すること
 

Tello Programming 011 -Pythonで離陸と着陸を制御する-

Telloをラップトップから制御して,離陸と着陸をさせるコードのメモです.

ラップトップとTello間の情報の送受信に必要なポート情報については,Tello SDK 2.0 User Guideによると,送受信は以下のポートから行えると書いてあります.

  • ラップトップのIPアドレス:192.168.10.2(ラップトップとTelloWifiで接続すると自動的に割り振られる)
  • ラップトップからTelloへのUDPポート:8889
  • TelloIPアドレス:192.168.10.1
  • Telloからラップトップへの命令に対するUDPポート:8889
  • Telloからラップトップへの情報(状況)送信UDPポート:8890
  • TelloからラップトップへのビデオをストリームするUDPポート:1111
ラップトップとTelloの接続
 
Tello SDK 2.0 User Guide, p.2


また,Tello SDK 2.0 User GuideにはTelloの様々なコマンドが記載されていますが,今回は離陸した後に着陸するだけのプログラムなので,使用するのは,以下の二つです.

 

離陸後に10秒待機して着陸するコードにしてみます.

import logging
import socket
import sys
import time
 
 
logging.basicConfig(level=logging.INFO, stream=sys.stdout)
logger = logging.getLogger(__name__)
 
class DroneManager(object): 
    def __init__(self, host_ip='192.168.10.2', host_port=8889
                 drone_ip='192.168.10.1', drone_port=8889):
        self.host_ip = host_ip
        self.host_port = host_port
        self.drone_ip = drone_ip
        self.drone_port = drone_port
        self.drone_address = (drone_ip, drone_port)
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.socket.bind*1
        self.socket.sendto(b'command', self.drone_address)
        self.socket.sendto(b'streamon', self.drone_address)
    
    def __dell__(self):
        self.stop()
    
    def stop(self):
        self.socket.close()
    
    def send_command(self, command):
        logger.info({'action': 'send_command', 'command': command})
        self.socket.sendto(command.encode('utf-8'), self.drone_address)
    
    def takeoff(self):
        self.send_command('takeoff')
    
    def land(self):
        self.send_command('land')
 
if __name__ == '__main__'
    drone_manager = DroneManager()
    drone_manager.takeoff()
 
    time.sleep(10)
 

 

    drone_manager.land()
 

実行に当たっては,ラップトップとTelloをWifi接続します.

そして,ターミナルから以下のように(この例では上記のコードに drone-manager.pyという名前をつけています)実行するだけです.

$ python3 drone_manager.py


実行した際に,ターミナルでは以下のように命令の送信をしている(プログラムは動いている)にも関わらず,Telloが反応しないというトラブルが生じました....

$ python3 drone_manager.py
INFO:__main__:{'action': 'send_command', 'command': 'takeoff'}
INFO:__main__:{'action': 'send_command', 'command': 'land'}

IPアドレスの割り振りの問題かと思って,Telloとラップトップを接続した状態でのラップトップに割り振られているIPアドレスをSystem Preference(システム環境設定)から調べてみると以下のように,192.168.10.2 が割り振られています.
 
その後,Telloのバッテリーがなくなるまで試行錯誤を繰り返したのですが,状況は変わらず,別のバッテリーに差し替えて試してみたところ,無事にTelloは離陸してホバリングした後に着陸しました.

 

結果としては離着陸に成功したのですが,何故,最初の時点で反応しなかったのかは,謎のままです.

*1:self.host_ip, self.host_port

-OpenCV- 静止画の顔認識

OpenCVのライブラリを使用して顔認識を行ってみた際のメモです.なお,Python3とOpenCVがインストールされている前提です(Python3のインストールはこちら OpenCVのインストールはこちら).

コードは以下のようになります.

 
import cv2 as cv
 
face_cascade = cv.CascadeClassifier('haarcascade_frontalface_default.xml')
eye_cascade = cv.CascadeClassifier('haarcascade_eye.xml')
 
img = cv.imread('image.jpg')
 
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(gray, 1.3, 5) #default setting
print(len(faces))
 
for (x, y, w, h) in faces:
    cv.rectangle(img, (x, y), (x+w, y+h), (255, 0, 0), 2)
    eye_gray = gray[y:y+h, x:x+w]
    eye_color = img[y:y+h, x:x+w]
    eyes = eye_cascade.detectMultiScale(eye_gray)
    for (ex, ey, ew, eh) in eyes:
        cv.rectangle(eye_color, (ex, ey), (ex+ew, ey+eh), (0, 255, 0), 2)
 
cv.imshow('img', img)
cv.waitKey(0)

 

cv.destroyAllWindows()
 

以下は,コードの解説です.
1行目
import cv2 as cv
OpenCVのパッケージを読み込む.

3行目
face_cascade = cv.CascadeClassifier('haarcascade_frontalface_default.xml')
face_cascade にClasfierメソッドとしてxmlファイルを読み込む.

4行目
eye_cascade = cv.CascadeClassifier('haarcascade_eye.xml')
eye_cascade にClasfierメソッドとしてxmlファイルを読み込む.

6行目
img = cv.imread('image.jpg')
imgに画像をimreadメソッドとして読み込む.

8行目
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
gray変数にimgを引数としてOpenCVのCOLOR_BGR2GRAYメソッドを使ってグレースケールの画像に変換する.

9行目
faces = face_cascade.detectMultiScale(gray, 1.35#default setting
face_cascadeのdetectMultiScaleメソッドを使って,グレースケールに変換した画像(gray)を渡す.セッテイングの引数としては,1.3, 5のデフォルトの値とする.
引数1.3を小さくして設定するとより細かく画像認識を行うようになる.
引数5は,顔が重複した時にどのように認識するかの値.
顔が認識されると,その数が変数facesに入る.

10行目
print(len(faces))
認識した顔の数を出力する.

12〜16行目

for (x, y, w, h) in faces:
    cv.rectangle(img, (x, y), (x+w, y+h), (25500), 2)
    eye_gray = gray[y:y+h, x:x+w]
    eye_color = img[y:y+h, x:x+w]
    eyes = eye_cascade.detectMultiScale(eye_gray)

認識した顔(faces)のx座標(x),y座標(y),横幅(w),高さ(h)の座標を取得する.
ループするので,結果として,認識したひとつひとつの顔に対して以下の処理を行うことになる.

cvのrectangleに引数としてimgを渡して四角で囲む.imgの中の顔として認識したエリア(座標(x, y)と(x+w, y+h))を指定して,その次に色((255, 0, 0) 青色)を指定する.さらにラインの太さ(2)を指定する.
なお,画像の座標は左上が (0, 0) となり,横軸(x座標)は左側が正で縦軸(y座標)は下側が正になる.よって,顔として認識するエリアの左上((x, y))から,右下((x+w, y+h))となる.

続いて,顔と認識した領域から目を検出する.
変数eye_grayに,グレースケールに変換した画像から顔と認識した領域を入れる.
同じようにして,元のカラー画像を変数eye_colorに入れる.
グレースケール画像に対して,eye_cascadeのdetectMultiScaleメソッドを使って目の検出を行う.

17〜18行目

    for (ex, ey, ew, eh) in eyes:
        cv.rectangle(eye_color, (ex, ey), (ex+ew, ey+eh), (02550), 2)

ネストのforループで,目が見つかった場合も,先と同様に検出領域を四角で囲む.
cvのrectangleに引数としてeye_colorを渡して四角で囲む.eye_colorの中の目として認識したエリア(座標(ex, ey)と(ex+ew, ey+eh))を指定して,その次に色((0, 255, 0) 緑色)を指定する.さらにラインの太さ(2)を指定する.

20行目
cv.imshow('img', img)
OpenCVのimgshowメソッドを使って,画像を表示させる.
表示する画像の名前をimgとして,四角の書いてあるimgを上書きする.

21行目
cv.waitKey(0)
OpenCVのwaitKeyメソッドを使って表示する.
0の単位はミリセカンド.

23行目
cv.destroyAllWindows()
OpenCVのdestroyAllWindowメソッドを使って,何らかの入力があった際にウィンドウを閉じることとする.

上記のコードを実行してみた結果は以下のようになりま.

Test1
使用する写真

実行結果

$ python3 image_face_detect.py
3
顔を3つと認識しています.
Daniel Craig(左側)の目は一つだけ,Eva Green(右側)の目は2つ認識しています.
 
Test2

写真を変えて実行してみます.

 

実行結果

$ python3 image_face_detect.py
2
顔を2つ認識していて,Daniel Craig(中央)とGemma Arterton(右側)の目は二つ認識していますが,Olga Kurylenko(左側)の顔は認識していません.
 
Test3

さらに,写真を変えて実行してみます.

 

実行結果

$ python3 image_face_detect.py
3
 

顔を3つ認識しています.
Daniel Craig(左)とJavier Bardem(右から2人目)の目は一つ認識しています.またNaomie Harris(右)の顔は認識していますが,目は認識していません.Bérénice Marlohe(左から2人目)の顔は認識していません.

また,写真を変えて実行してみます.

実行結果
$ python3 image_face_detect.py
6
顔を6つ認識しています.
上段では,Ben Whishaw(左)の顔は認識していません.Ralph Fiennes(中央)とAndrew Scott(右)の顔は認識していますが,目は認識していません.中段ではMonica Bellucci(左)とDaniel Craig(中央)の顔は認識していますが,Léa Seydoux(右)の顔は認識していません.下段ではErnst Blofeld(左)とDavid Bautista(右)の顔は認識していますが,Naomie Harris(中央)の顔は認識していません.
 

-macOS- OpenCVのインストール

macOSOpenCVをインストールした際のメモです.HomeBrewがインストールされている前提です.

以下のように,ターミナルからHomeBrew経由でOpenCVをインストールします(インストールの際のコマンドは "brew install opencv").
コマンドを実行すると,以下のようにインストールがスタートします.

$ brew install opencv
Updating Homebrew...
==> Auto-updated Homebrew!
Updated 2 taps (homebrew/core and homebrew/cask).
... 途中省略
==> tesseract
This formula contains only the "eng", "osd", and "snum" language data files.
If you need all the other supported languages, `brew install tesseract-lang`.

上記のような感じでインストールが終了します(多少時間がかかります).

続いて,PythonOpenCVを使うために,pip3を用いてインストールを行います(Anaconda環境などの場合はpip).

$ pip3 install opencv-python
Collecting opencv-python
     |████████████████████████████████| 45.2MB 2.5MB/s 
Requirement already satisfied: numpy>=1.14.5 in /usr/local/lib/python3.7/site-packages (from opencv-python) (1.16.4)
Installing collected packages: opencv-python
Successfully installed opencv-python-4.1.2.30

上記のように,成功したと返ってくればO.K..

最後にインストールがうまくいっているかを確認しておきます.
Python3のインタプリタを起動して,"import cv2"というコマンドを入力してエラーメッセージが返ってこない(特に何も応答がない)ようであればインストールは成功しています.

$ python3
Python 3.7.5 (default, Nov  1 2019, 02:16:32) 
[Clang 11.0.0 (clang-1100.0.33.8)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import cv2
>>> quit()
 

Tello Programming 006 -Telloの状態確認(LED)-

Telloには機体前方のカメラ横にLEDがついています.このLEDの点灯,点滅パターンはTelloの状態を表します.LEDの状態は通常,警告,充電の3つに分けることができます.トイドローンなので,LEDの点滅,点灯からしか状態を把握することができませんが,何か異常があると感じた場合は,LEDを確認すると現状が把握できます.
以下に各場合の機体の状態を示します.

通常時のLED点滅

LEDの色 パターン 機体の状態
交互点滅 セルフチェック(飛行準備)
2回づつ点滅 ビジョンポジショニングシステム稼動中
点滅(ゆっくり) ATTIモード


警告時のLED点滅

LEDの色 パターン 機体の状態
点滅(速い) 送信機と未接続
点滅(ゆっくり) バッテリー残量小(低電圧)
点滅(速い) バッテリー残量極小(超低電圧)
点灯 何らかの致命的エラーが発生


充電時のLED点灯,点滅

LEDの色 パターン 充電の状態
点灯 充電完了
点滅(ゆっくり) 充電中
点滅(速い) 何らかの充電エラー


充電はチャージャーがある場合はそちらの方が急速に充電されますが,ない場合は本体左のUSB端子を接続して充電します.

 

 

Tello Programming 005 -Scratchでプログラミング Test Flight 01-

これまでに数回に分けて,TelloScratchでプログラミングする準備やテストを行ってきました.これまでの投稿は以下のようになります.

前回は,一つのコマンドを送信すると,受信したTelloがコマンドにある動作を行う(離陸,前進,後退,着陸)プログラムを作ってテストをしてみました.

前回の投稿にあるように,ターミナルを立ち上げ,nodeを起動させます.

$ cd Tello.jsのあるディレクトリの場所 
$ node Tello.js

そして,Scratch2.0 Offline Editorを起動させ,Scratch2.0 Offline Editorの"File"メニューをShiftキーを押しながらクリックして,"Import Experimental HTTP Extension (実験的なHTTP拡張の読み込み)"をクリックして,Tello.s2eを読み込みます(手順の詳細は前回の投稿を参照して下さい).

ブロックパレットからブロックを移動させて,以下のように連続して動作を行うプログラムを作成します.
ブロックパレットに,以下のような動作をプログラミングしてみました.

動作は,
  1. 離陸
  2. 50cm前進
  3. 50cm上昇
  4. 100cm前進
  5. 反時計回りに90°旋回
  6. 100cm前進
  7. 反時計回りに90°旋回
  8. 150cm前進
  9. 反時計回りに180°旋回
  10. 右方向に100cm移動
  11. 50cm下降
  12. 着陸

の1〜12を途中に2秒間の静止時間を挟みながら連続的に行うという流れです.
一応,緊急時のために"space"キーを押したら緊急停止するコマンドも作成してあります(画面右上).
Telloがコマンドの通りに飛行すれば,元の場所に戻ってくるはずです.
上記のプログラムを実行してみた結果の動画は以下のようになります.
Y.P.S. が上手く動作していないと,1回目のフライトのように離陸場所と着陸場所がずれますが,2回目のフライトでは,離着陸の場所がほぼ同じなので,V.P.S. がそれなりに上手く動作したようです.
各命令が実行されると,nodeを立ち上げているターミナルには以下のように表示されます.
send: takeoff
Data received from server : ok
Received 2 bytes from xxx.xxx.xx.x:xxxx
 
send: forward 50
Data received from server : ok
Received 2 bytes from xxx.xxx.xx.x:xxxx
 
send: up 50
Data received from server : ok
Received 2 bytes from xxx.xxx.xx.x:xxxx
 
send: forward 100
Data received from server : ok
Received 2 bytes from xxx.xxx.xx.x:xxxx
 
send: ccw 90
Data received from server : ok
Received 2 bytes from xxx.xxx.xx.x:xxxx
 
send: forward 100
Data received from server : ok
Received 2 bytes from xxx.xxx.xx.x:xxxx
 
send: ccw 90
Data received from server : ok
Received 2 bytes from xxx.xxx.xx.x:xxxx
 
send: forward 150
Data received from server : ok
Received 2 bytes from xxx.xxx.xx.x:xxxx
 
send: ccw 180
Data received from server : ok
Received 2 bytes from xxx.xxx.xx.x:xxxx
 
send: right 100
Data received from server : ok
Received 2 bytes from xxx.xxx.xx.x:xxxx
 
send: down 50
Data received from server : ok
Received 2 bytes from xxx.xxx.xx.x:xxxx
 
send: land
Data received from server : ok
Received 2 bytes from xxx.xxx.x
上記の"x"には数字が入ります.