いよいよ面白くなりTelloを飛ばすことはできましたが、本来の機能であるカメラ画像の送信が生かされていない( ;∀;)
まだお持ちでない方、2台目希望の方 プログラミングドローン ”Tello” の購入はこちらドローンのプログラミングでどうやって画像をPCに送信したらいいだろう? YouTubeをたまたまみていたTechの部屋に出会いました。しかし私はプログラミング初心者でしかもTechの部屋のPCはMAC かなり勉強になりましたが、私はPCはWindowsでしたので、参考にして我流でしてみました。オープンソースはGitHub Tech の部屋「Pythonで小型ドローン Tello EDU を飛ばす!」サンプルプログラムからダウンロードできます。
まずOpenCvのダウンロードからです。


OpenCV(正式名称: Open Source Computer Vision Library)とは、オープンソースのコンピューター・ビジョン・ライブラリです。コンピューターで画像や動画を処理するのに必要な、さまざま機能が実装されており、BSDライセンスで配布されていることから学術用途だけでなく商用目的でも利用できます。加えて、マルチプラットフォーム対応されているため、幅広い場面で利用されていることが特徴です。
画像処理 (Image Processing)構造解析 (Structural Analysis)モーション解析と物体追跡 (Motion Analysis and Object Tracking)物体検出 (Object Detection)などが可能です。


リリースのウインドウズをクリックしダウンロードします。ダウンロードしたら展開してopencvのフォルダーをCドライブの直下に置きます。OPENCVのファイルの中のbild→x64→vc15→binを開きこのアドレスをコピーし、コントロールパネルから
システム&セキュリティのシステム環境変数に追加するため、Pathに先ほどコピーしたアドレスを追加します。
pythonでopencvを使いたいので opencv-pythonをpipを使用してインストールできます。
インストールする場合は以下のコマンドを使用します。
pip install opencv-python

コマンドプロンプトからですがアプリの右クリックで管理者として実行で起動させます。
オープンソースはGitHub Tech の部屋「Pythonで小型ドローン Tello EDU を飛ばす!」サンプルプログラムからダウンロードしたファイル cli.py(先のTello3.pyと同じプログラム) & vc.py(画像送信プログラム)
OpenCVが使われている箇所を強調してみました。
#vc.pyの内容は以下の通りです。
Tello Python3 Control Demo
http://www.ryzerobotics.com/
1/1/2018
Modified by MPS
#
import threading
import socket
import time
import cv2 ⇒ ここでOpenCVがインポートされている
host = ”
port = 9000
locaddr = (host, port)
Create a UDP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
tello_address = (‘192.168.10.1’, 8889)
sock.bind(locaddr)
def recv():
while True:
try:
data, server = sock.recvfrom(1518)
print(data.decode(encoding=”utf-8″))
except Exception:
print(‘\nExit . . . RECV\n’)
break
print(‘\r\n\r\nTello Python3 Demo.\r\n’)
print(‘Tello: command takeoff land flip forward back left right \r\n up down cw ccw speed speed?\r\n’)
print(‘end — quit demo.\r\n’)
recvThread create
recvThread = threading.Thread(target=recv)
recvThread.start()
sock.sendto(b’command’, tello_address)
print(‘command ok’)
time.sleep(0.5)
sock.sendto(b’streamon’, tello_address)
print(‘stream on’)
time.sleep(1)
sock.close()
cap = cv2.VideoCapture(“udp://%s:%s?overrun_nonfatal=1&fifo_size=50000000” % (‘192.168.10.1’, ‘11111’)) ⇒ #ここでビデをキャプチャーでTelloのカメラから画像受信
print(‘start cap’)
while True:
try:
ret, frame = cap.read()
if ret:
cv2.imshow(‘tello’, cv2.resize(frame, (360, 240))) #受信画像を画面表示
cv2.waitKey(1)
except KeyboardInterrupt:
cv2.destroyAllWindows()
cap.release()
print(‘\nExit . . .\n’)
break
をデスクトップでも貼り付けます。
あとは 各ファイルを実行できるようにコマンドプロンプトを2つ起動させます。
それでは、準備が整いましたので 2つのプログラムを同時に実行させてみます。動画をとってますので御覧ください。
次にOpenCvによる顔認識の仕方こちらへどうぞ!!





マッキーの趣味のドローン YouTube channel
-
ArUcoマーカーの検出を使った、Telloのプログラミングゲーム
OpenCVのArUcoマーカーの検出で構築。AR(拡張現実)(Augmented Reality)とは、現実…
OpenCVのArUcoマーカーの検出で構築。AR(拡張現実)(Augmented Reality)とは、現実世界にデジタル情報を重ねて表示し、現実を拡張する技術のことです。
マーカー型は、実在の場所に配置された図形(マーカー)をカメラなどで読み取り、マーカーに合わせてデジタル情報を配置します。実際にマーカーを用意する手間がかかるものの、技術的にはシンプルで場所を選ばず比較的自由に表示できることが特徴です。
ここでは、マーカー型を動画のように使ってやっていきます。
カメラで白黒マーカーを見たとき、コンピュータは次のステップで認識しています。
【ステップ1】白と黒のパターンを見つける
- 画像の中で「黒い四角に囲まれた白い領域」を探します。
- これは、マーカーの外枠(四角い黒いフチ)です。
【ステップ2】四隅の座標を見つける
- 見つけた四角の「4つの角」を検出して、カメラから見た「角度・向き」を計算します。
【ステップ3】中身のパターンを読む
- 中の白黒のグリッド(マス目)を読み取り、それがどのIDに当たるか、**辞書(dictionary)**と照らし合わせて確認します。
ARマーカーの構造
⬛⬛⬛⬛⬛⬛⬛⬛
⬛⬜⬜⬜⬜⬜⬜⬛
⬛⬜⬛⬜⬛⬛⬜⬛
⬛⬛⬜⬜⬛⬛⬜⬛
⬛⬜⬜⬜⬜⬜⬛⬛
⬛⬛⬜⬛⬛⬜⬜⬛
⬛⬜⬛⬜⬜⬜⬜⬛
⬛⬛⬛⬛⬛⬛⬛⬛外枠(黒い縁):検出しやすくするための「囲い」
内側のグリッド:白と黒のマス(5×5 など)が並び、これがID情報になります
なぜID番号が分かるの?
IDは「白黒のパターン」で表現されている!
- 内側の 5×5 マス(25ビット)の白黒パターンが、そのまま 2進数(ビット列)として使われます。
たとえば、次のようなデータとします:
黒=1、白=0
[1, 0, 0, 1, 0],
[0, 1, 1, 0, 1],
[1, 0, 1, 0, 0],
[0, 0, 1, 1, 0],
[1, 0, 1, 1, 1]]このデータを1列に並べると、10010 01101 10100 00110 10111 → 2進数 → 10進数に変換 → ID: 187543
OpenCVのarucoライブラリはこう動く:
- aruco.detectMarkers()でマーカーの画像を検出
- aruco.Dictionary_ get(aruco.DICT_4x4_50)などで、既知のID一覧と照合
- 一番近い一致を見つけて「これはID 23のマーカーだ」と判定します
ARマーカーの全体(8×8)
⬜ ⬛ ⬜ ⬛ ⬛
⬜ ⬜ ⬛ ⬛ ⬜
⬜ ⬜ ⬜ ⬜ ⬛
⬜ ⬛ ⬛ ⬜ ⬜
⬛ ⬜ ⬜ ⬜ ⬜黒=1、白=0に変換すると:
[0, 1, 0, 1, 1],
[0, 0, 1, 1, 0],
[0, 0, 0, 0, 1],
[0, 1, 1, 0, 0],
[1, 0, 0, 0, 0]行ごとに2進数 → 10進数で見てみると:
行 2進数 10進数 1 01011 11 2 00110 6 3 00001 1 4 01100 12 5 10000 16 最終的なIDは?
多くのARマーカー(例:ArUco)では、5×5の白黒ビットを1列に並べて1つの値に変換してIDにします。
コピーする編集するビット列を1行ずつ連結:
0101100110000010110010000→ これは 2進数:0101100110000010110010000
→ 10進数に変換すると:290576ID番号「1」のマーカー画像を作って、印刷したり表示するには!
- **マーカーの種類(辞書)**を選ぶ
例:DUCT_4x4_50(4×4ビット、50種類のIDがある) - 作りたいID番号
今回は ID = 1
Python + OpenCV で作る方法
以下のPythonコードを使えば、ID=1のマーカー画像を簡単に作れます。
import cv2 import cv2.aruco as aruco # マーカー辞書(4x4の50種類のうちの1つ) aruco_dict = aruco.getPredefinedDictionary(aruco.DICT_4X4_50) # ID=1 のマーカーを 200x200ピクセルで生成 marker_image = aruco.drawMarker(aruco_dict, id=1, sidePixels=200) # 画像として保存 cv2.imwrite("aruco_id1.png", marker_image) # 表示(任意) cv2.imshow("AR Marker - ID 1", marker_image) cv2.waitKey(0) cv2.destroyAllWindows()
使い方
- aruco_id1.png というファイルができます。
- 印刷したり、スマホやタブレットに表示したりできます。
- カメラで読み取ると、「これは ID=1 のマーカーだ!」と検出されます。
定義されている辞書を使って dictionary オブジェクトを作ります。どの「辞書」を使うべき?
辞書名 特徴 ID数 DICT_4X4_50 小さくて読みやすい 50種類のID DICT_5X5_100 中規模 100種類のID DICT_6X6_250 精度高い 250種類のID DICT_7X7_1000 非常に高精度 1000種類のID import cv2 import cv2.aruco as aruco import numpy as np import socket, threading, time import pygame
cv2
: OpenCVで映像処理をするためのライブラリ。aruco: ArUcoマーカーの検出に使います。
numpy: 数学的計算(座標計算など)に使います。
socket, threading, time:ドローンとの通信やスレッド処理に使います。
pygame: 効果音を鳴らすために使います。
aruco_dict = aruco.getPredefinedDictionary(aruco.DICT_4X4_50) parameters = aruco.DetectorParameters()
50種類の4×4のArUcoマーカーを使うと指定。
検出時の細かい動作設定を初期化。
host = '0.0.0.0' port = 8889 tello_addr = ('192.168.10.1', 8889) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind((host, port))
- PCがTelloからの命令を受け取るためのネットワーク設定です。
UDP
で送受信を行います。
def recv(): while True: try: data, _ = sock.recvfrom(1518) print(data.decode('utf-8')) except: break
- ドローンからの返答を常に受け取って表示するための関数です。
threading.Thread(target=recv, daemon=True).start()
recv
を別スレッドで実行。メインの映像処理に影響を与えないようにします。def send(cmd, delay=1): sock.sendto(cmd.encode(), tello_addr) print(f">>> {cmd}") time.sleep(delay)
ドローンにコマンド(例:takeoffやlandなど)を送信します。
待ち時間を入れることで安定動作を図っています。
send('command') send('streamon') cap = cv2.VideoCapture("udp://192.168.10.1:11111")
ドローンを「SDKモード」にして、映像ストリーミングを開始。
UDPでカメラ映像を受信できるように設定。
pygame.mixer.init() point_sound = pygame.mixer.Sound("point.wav")
Pygameで音を鳴らす準備。
“point.wav”という音ファイルをロード。
def play_point_sound(): point_sound.stop() point_sound.play()
効果音を毎回初めから再生する関数。
def draw_star(img, center, size, color, thickness):
星の形を描きます(5つの点で構成)。
score = 0 passed = set() MARKERS = { 0: 'star_floor', ... } DESIRED_HEIGHT_CM = 90
得点の初期値。
すでに通過したマーカーを記録するセット。
各マーカーIDに対応する図形名。
全ての図形を90cmの高さに表示する設定。
while True: ret, frame = cap.read() if not ret: continue
ドローンの映像を1フレームずつ取得。
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) corners, ids, _ = aruco.detectMarkers(gray, aruco_dict, parameters=parameters)
映像をグレースケールにして、マーカーを検出。
frame_center = (frame.shape[1] // 2, frame.shape[0] // 2)
画面の中心座標を計算。
ここからは、「マーカーの場所を見つけて、その上に指定した高さで、指定した図形を画面上に描くための座標計算」をしています。
if ids is not None: for idx, i in enumerate(ids.flatten()): c = corners[idx][0] cx, cy = int(c[:, 0].mean()), int(c[:, 1].mean()) size_px = np.linalg.norm(c[0] - c[2]) cm_per_px = size_px / 21.0 shape = MARKERS.get(i, 'unknown') size = int(25 * cm_per_px) offset_y = int(-DESIRED_HEIGHT_CM * cm_per_px) cy_offset = cy + offset_y cy_offset = max(0, min(frame.shape[0] - 1, cy_offset)) center_pos = (cx, cy_offset)
Telloのカメラに映ったArUcoマーカーを見つけたあとに、図形を描画する中心座標を計算する処理です。
マーカーが見つかっていれば、1つずつ処理。
if ids is not None:
aruco.detectMarkers()でマーカーが1つでも見つかったときに入る条件です。
ids
は見つかったマーカーのIDの配列です(例:array([[0], [3], [5]])など)for idx, i in enumerate(ids.flatten()):
- idsは2次元配列なのでflatten()で1次元にします。
- idxは見つかったマーカーの順番(0番目、1番目…)。
- iは実際のマーカーID(たとえば 0, 3, 5 など)。
c = corners[idx][0]
cornersはマーカーの角の座標リスト。
たとえば 4つの点 (x, y) の座標が入っており、
c
はそのマーカーの4点の座標です。
例:[(x1, y1), (x2, y2), (x3, y3), (x4, y4)]cx, cy = int(c[:, 0].mean()), int(c[:, 1].mean())
マーカーの中心座標を求めています。
c[:, 0]
はすべてのx座標、c[:, 1]
はすべてのy座標。それぞれの平均を取ることで、マーカーの中心が求まります。
size_px = np.linalg.norm(c[0] - c[2])
マーカーの大きさ(ピクセル)を測ります。
対角線の長さ(点0と点2の距離)を使っています。
np.linalg.norm は2点間のユークリッド距離(直線距離)を出す関数です。
cm_per_px = size_px / 21.0
ArUcoマーカーの実際のサイズは21cmと仮定。
1ピクセルが何cmかを換算しています。
(例:マーカーが画像上で105ピクセルなら 105 / 21 = 5 px/cm)shape = MARKERS.get(i, 'unknown')
マーカーID
i
に対して、どの図形を描くかをMARKERS
辞書から取得。見つからない場合は
'unknown'
とします。size = int(25 * cm_per_px)
描画する図形のサイズを決定。
ここでは実世界で「25cmサイズの図形」を想定し、ピクセルに変換。
offset_y = int(-DESIRED_HEIGHT_CM * cm_per_px)
図形を「マーカーの上空●cmの高さ」に表示するためのY方向の補正量。
DESIRED_HEIGHT_CM = 90 の場合、上に90cm分図形をずらすという意味です。
マイナスにしているのは「上方向に移動する」ため。
cy_offset = cy + offset_y
マーカーの中心
cy
にオフセットを加えて、図形のY座標を上方にずらします。cy_offset = max(0, min(frame.shape[0] - 1, cy_offset))
図形のY座標が画面の範囲(0 ~ 高さ)からはみ出さないように調整。
max(0, …) → 画面上に出すぎないようにする
min(frame.shape[0] – 1, …) → 画面下に出すぎないようにする
center_pos = (cx, cy_offset)
最終的に図形を描画する中心位置を
(x, y)
のタプルで保存します。cx, cy = int(c[:, 0].mean()), int(c[:, 1].mean())
マーカーの中心座標。
size_px = np.linalg.norm(c[0] - c[2]) cm_per_px = size_px / 21.0
マーカーのサイズから、ピクセルとcmの換算率を出します。
offset_y = int(-DESIRED_HEIGHT_CM * cm_per_px)
地面から90cm上に表示するためのY方向の補正。
if shape == 'star_floor' or shape == 'star_floor2': draw_star(frame, center_pos, size, (0, 255, 255), thickness=10) elif shape == 'pentagon' or shape == 'pentagon2': draw_polygon(frame, center_pos, size, 5, (255, 0, 0), thickness=10) elif shape == 'circle': draw_circle(frame, center_pos, size, (0, 0, 255), thickness=10) elif shape == 'triangle': draw_polygon(frame, center_pos, size, 3, (0, 255, 0), thickness=10) elif shape == 'star_vertical': draw_star(frame, center_pos, size, (0, 165, 255), thickness=10) elif shape == 'pyramid': draw_pyramid(frame, center_pos, size) elif shape == 'cube_transparent': draw_transparent_cube(frame, center_pos, size) elif shape == 'shadow_square': draw_shadowed_square(frame, center_pos, size)
- マーカーの種類に応じて対応する図形を描画。
distance = np.hypot(cx - frame_center[0], cy - frame_center[1])
マーカーの中心と画面中心の距離。
if distance < 100 and marker_key not in passed: score += 10 passed.add(marker_key) play_point_sound()
- 接触(100px以内)していれば、得点+音。
if marker_key in passed: cv2.putText(frame, "X", ...)
すでに通過したマーカーには「X」を描く。
cv2.putText(frame, f"Score: {score}", ...) cv2.imshow("Tello AR 7Shapes", frame) if cv2.waitKey(1) == 27: break
スコアを表示。
ウィンドウで映像を表示し、Escキーで終了。
いろいろな図形の描き方
星を描く draw_star
def draw_star(img, center, size, color, thickness): pts = [] cx, cy = center for i in range(5): angle = np.pi / 2 + i * 2 * np.pi / 5 pts.append((int(cx + size * np.cos(angle)), int(cy - size * np.sin(angle)))) cv2.polylines(img, [np.array(pts)], True, color, thickness)
中心座標 (center) から放射状に5つの点を計算
三角関数 cos と sin を使って「星形の外側の点」を決める
cv2.polylines でその5点を線で結ぶ
多角形を描く draw_polygon
def draw_polygon(img, center, size, sides, color, thickness): pts = [] cx, cy = center for i in range(sides): angle = np.pi / 2 + i * 2 * np.pi / sides pts.append((int(cx + size * np.cos(angle)), int(cy - size * np.sin(angle)))) cv2.polylines(img, [np.array(pts)], True, color, thickness)
中心から
sides
(辺の数)分の頂点を計算して、線で結ぶ例えば
sides=3
→ 三角形、sides=6
→ 六角形星と同じ仕組みですが、頂点の数を自由に指定できます
円を描く draw_circle
def draw_circle(img, center, size, color, thickness): cv2.circle(img, center, int(size), color, thickness)
cv2.circle
を使って単純に円を描きますthickness=-1
にすると塗りつぶしになります四角形 draw_shadowed_square
def draw_shadowed_square(img, center, size, square_color=(0, 128, 255), shadow_color=(50, 50, 50), thickness=4): cx, cy = center d = size // 2 offset = size // 5 # 影 shadow_pts = np.array([ (cx - d + offset, cy - d + offset), (cx + d + offset, cy - d + offset), (cx + d + offset, cy + d + offset), (cx - d + offset, cy + d + offset) ]) cv2.fillPoly(img, [shadow_pts], shadow_color) # 本体 square_pts = np.array([ (cx - d, cy - d), (cx + d, cy - d), (cx + d, cy + d), (cx - d, cy + d) ]) cv2.fillPoly(img, [square_pts], square_color)
まず少しズラした位置に「影色の四角」を塗りつぶし
その上に本体の四角を重ねて、立体感を出しています
全体コード(Telloのカメラ映像にデジタル情報を重ねて表示しています。)
import cv2 import cv2.aruco as aruco import numpy as np import socket, threading, time import pygame # --- ArUco 設定 --- aruco_dict = aruco.getPredefinedDictionary(aruco.DICT_4X4_50) parameters = aruco.DetectorParameters() # --- 通信設定 --- host = '0.0.0.0' port = 8889 tello_addr = ('192.168.10.1', 8889) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind((host, port)) def recv(): while True: try: data, _ = sock.recvfrom(1518) print(data.decode('utf-8')) except: break threading.Thread(target=recv, daemon=True).start() def send(cmd, delay=1): sock.sendto(cmd.encode(), tello_addr) print(f">>> {cmd}") time.sleep(delay) # --- Tello 飛行・映像 --- send('command') send('streamon') cap = cv2.VideoCapture("udp://192.168.10.1:11111") # --- 効果音の初期化 --- pygame.mixer.init() point_sound = pygame.mixer.Sound("point.wav") def play_point_sound(): point_sound.stop() point_sound.play() # --- 図形描画関数 --- def draw_star(img, center, size, color, thickness): pts = [] cx, cy = center for i in range(5): angle = np.pi / 2 + i * 2 * np.pi / 5 pts.append((int(cx + size * np.cos(angle)), int(cy - size * np.sin(angle)))) cv2.polylines(img, [np.array(pts)], True, color, thickness) def draw_polygon(img, center, size, sides, color, thickness): pts = [] cx, cy = center for i in range(sides): angle = np.pi / 2 + i * 2 * np.pi / sides pts.append((int(cx + size * np.cos(angle)), int(cy - size * np.sin(angle)))) cv2.polylines(img, [np.array(pts)], True, color, thickness) def draw_circle(img, center, size, color, thickness): cv2.circle(img, center, int(size), color, thickness) def draw_pyramid(img, center, size, color=(0, 200, 255), thickness=2): cx, cy = center d = size // 2 # 底面(三角) base = np.array([ (cx - d, cy + d), (cx + d, cy + d), (cx, cy - d) ]) # 頂点(上) apex = (cx, cy - int(size * 1.5)) # 三角面を線で描く for pt in base: cv2.line(img, apex, pt, color, thickness) cv2.polylines(img, [base], isClosed=True, color=color, thickness=thickness) def draw_transparent_cube(img, center, size, color=(200, 255, 200), thickness=2): cx, cy = center d = size // 2 offset = size // 3 front = np.array([ (cx - d, cy - d), (cx + d, cy - d), (cx + d, cy + d), (cx - d, cy + d) ]) back = np.array([ (cx - d + offset, cy - d - offset), (cx + d + offset, cy - d - offset), (cx + d + offset, cy + d - offset), (cx - d + offset, cy + d - offset) ]) for i in range(4): cv2.line(img, front[i], front[(i+1)%4], color, thickness) cv2.line(img, back[i], back[(i+1)%4], color, thickness) cv2.line(img, front[i], back[i], color, thickness) def draw_shadowed_square(img, center, size, square_color=(0, 128, 255), shadow_color=(50, 50, 50), thickness=4): cx, cy = center d = size // 2 offset = size // 5 # 影を描画 shadow_pts = np.array([ (cx - d + offset, cy - d + offset), (cx + d + offset, cy - d + offset), (cx + d + offset, cy + d + offset), (cx - d + offset, cy + d + offset) ]) cv2.fillPoly(img, [shadow_pts], shadow_color) # 正方形本体 square_pts = np.array([ (cx - d, cy - d), (cx + d, cy - d), (cx + d, cy + d), (cx - d, cy + d) ]) cv2.fillPoly(img, [square_pts], square_color) # --- メインループ変数 --- score = 0 passed = set() MARKERS = { 0: 'star_floor', 1: 'pentagon', 2: 'circle', 3: 'triangle', 4: 'star_vertical', 5: 'star_floor2', 6: 'pentagon2', 7: 'pyramid', 8: 'cube_transparent', 9: 'shadow_square' } DESIRED_HEIGHT_CM = 90 # --- メインループ --- while True: ret, frame = cap.read() if not ret: continue gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) corners, ids, _ = aruco.detectMarkers(gray, aruco_dict, parameters=parameters) frame_center = (frame.shape[1] // 2, frame.shape[0] // 2) cv2.circle(frame, frame_center, 10, (0, 255, 0), 2) if ids is not None: for idx, i in enumerate(ids.flatten()): c = corners[idx][0] cx, cy = int(c[:, 0].mean()), int(c[:, 1].mean()) size_px = np.linalg.norm(c[0] - c[2]) cm_per_px = size_px / 21.0 shape = MARKERS.get(i, 'unknown') size = int(25 * cm_per_px) offset_y = int(-DESIRED_HEIGHT_CM * cm_per_px) cy_offset = cy + offset_y cy_offset = max(0, min(frame.shape[0] - 1, cy_offset)) center_pos = (cx, cy_offset) # 図形描画 if shape == 'star_floor' or shape == 'star_floor2': draw_star(frame, center_pos, size, (0, 255, 255), thickness=10) elif shape == 'pentagon' or shape == 'pentagon2': draw_polygon(frame, center_pos, size, 5, (255, 0, 0), thickness=10) elif shape == 'circle': draw_circle(frame, center_pos, size, (0, 0, 255), thickness=10) elif shape == 'triangle': draw_polygon(frame, center_pos, size, 3, (0, 255, 0), thickness=10) elif shape == 'star_vertical': draw_star(frame, center_pos, size, (0, 165, 255), thickness=10) elif shape == 'pyramid': draw_pyramid(frame, center_pos, size) elif shape == 'cube_transparent': draw_transparent_cube(frame, center_pos, size) elif shape == 'shadow_square': draw_shadowed_square(frame, center_pos, size) aruco.drawDetectedMarkers(frame, corners) cv2.putText(frame, shape, (cx + 10, cy + 10), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2) # --- 接触判定とX表示 --- distance = np.hypot(cx - frame_center[0], cy - frame_center[1]) marker_key = (i, cx, cy) if distance < 100 and marker_key not in passed: score += 10 passed.add(marker_key) play_point_sound() print(f"Hit marker {i}! +10 points. Score: {score}") # 通過済みマーカーには常にXを表示 if marker_key in passed: cv2.putText(frame, "X", (center_pos[0] - 20, center_pos[1] + 20), cv2.FONT_HERSHEY_SIMPLEX, 2.0, (0, 0, 255), 5) # スコア表示 cv2.putText(frame, f"Score: {score}", (30, 50), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0, 255, 0), 3) cv2.imshow("Tello AR 7Shapes", frame) if cv2.waitKey(1) == 27: break # --- 終了処理 --- send('land') cap.release() cv2.destroyAllWindows()
-
ソケット通信について・Telloとパソコンの交信を見てみよう!
1.パソコン側の動き sock = socket.socket(socket.AF_INET, so…
PCからドローンを操作するsocket通信プログラムを少しだけ解説してみました。
import threading import socket import sys import time host = '' port = 9000 locaddr = (host,port) # Create a UDP socket sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) tello_address = ('192.168.10.1', 8889) sock.bind(locaddr) def recv(): count = 0 while True: try: data, server = sock.recvfrom(1518) print(data.decode(encoding="utf-8")) except Exception: print ('\nExit . . .\n') break
「socket通信」って何?
コンピュータ同士(ここではPC&Tello)がネットワークを通じてデータをやり取りするための方法。(コンピュータ間でデータを送受信するための仕組み)
「手紙を出す」みたいなイメージで、
送信先の住所(IP)と郵便受け(ポート)を指定して、
データ(コマンド)を送る感じです。それでは、学習をしていこう!!
まずは、大まかな流れ!!⇒それから深堀学習していきたいと思います。# Create a UDP socket sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
1.パソコン側の動き
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((”, 9000)) ←パソコン側(サーバー側)はバインド(関連付け)は必要ソケット作成時には名前がありません。アドレスがソケットに関連付けられるまで参照できない。
つまり通信プロセスはアドレスを介して接続される。これは「郵便受け(ポート9000)を開ける」こと。
Telloからの返信(データ)を受け取るための準備。IPv4とは:IPインターネットプロトコルでインターネットに接続されたコンピュータ同士がデーターをやり取りするためにデーター通信の方法を定めた規約のことで、インターネットに接続されたコンピューターを識別するためのIPアドレスが割り当てられIPv4はこのIPアドレスを32ビットのデーターとして表現するものです。
2.Telloにコマンドを送る
tello_address = (‘192.168.10.1’, 8889) → Telloの「住所(IP)」と「郵便受け(ポート)」です。
コマンドは文字列をバイト形式(b’takeoff’)に変えて送信します。3.返信を待つ(受信)
while True: try: data, server = sock.recvfrom(1518) print(data.decode(encoding="utf-8"))
Telloから返ってきたデータを受け取って表示。
Telloは “ok” と返すことが多いです。4.Tello側の動き
- Wi-Fiアクセスポイントとして動作する
Telloは自分自身がWi-Fiルーターのような存在になります。
パソコンはTelloのWi-Fi(TELLO-XXXXXX)に接続します。
- UDP通信sock.sendto(b’takeoff’, tello_address)
でパソコンからの命令を受け取る
8889番ポートでコマンドを待っています。
受け取ったら、「ok」とか「error」をUDPでポート9000番に返信します。 UDP通信とは: データを送信する側のコンピューターが、相手のコンピューターの状態にかかわらず一方的にデータを送り始めます。受信する側のコンピューターが正しくデータを受け取れたか確認する機能もないため、UDPは「コネクションレス型」のプロトコルと呼ばれています UDPはデータが宛先に届いたかどうかは関知しない。複数の相手に同時にデータを送信できる TCPよりリアルタイム性が高い。
もう少し詳しくすると。。。。
IP アドレス & ポート番号の確認は以下の Tello SDK マニュアルで確認します
tello_address = (‘192.168.10.1’, 8889)
深堀学習!!
ソケット通信一連のパソコンの内部の動きを見てよう
コマンドを送る!
sendto() が呼ばれると、以下のように 「ネットワークの階層構造」 を通ってTelloに届きます。アプリケーション層 ← Pythonの sendto() がここ
↓
トランスポート層 ← UDPプロトコル(ポート番号もここ)
↓
ネットワーク層 ← IPアドレスでTelloを指定
↓
ネットワークインターフェース層(リンク層)
↓
Wi-Fiなどの物理通信でTelloへアプリケーション層(Pythonプログラム)
sock.sendto(b’takeoff’, (‘192.168.10.1’, 8889))
ここでアプリ(Python)が「takeoff」という命令を送ります。
sendto() はソケット(socket)というインターフェースを使って次の層(UDP)へ渡します。
このとき、送信元は (”, 9000) つまり「パソコンのポート9000」として送ります。トランスポート層(UDP)
UDPヘッダが付加されます。
送信元ポート:9000(locaddr でバインド)
送信先ポート:8889(Telloのコマンド受付ポート)
この層は「信頼性より速さ」を優先していて、確認応答や再送処理をしないです。インターネット層(IP)
IPヘッダが追加されます。
送信元IP:パソコンのIP(例:192.168.10.2)
送信先IP:Tello(192.168.10.1)
この層が、「どこからどこへ」という住所情報(IPアドレス)を扱っています。ネットワークインターフェース層(Wi-Fi)
TelloのWi-Fi電波を通じて、パケットを物理的に送信します。
ここでMACアドレス(機械の番号)に変換され、電波でTelloに届きます。少し、トランスポート層(UDP)UDPヘッダが付加 と
インターネット層(IP)IPヘッダが追加を深堀してみます。UDPヘッダ/IPヘッダの追加、Wi-Fiデーターはどうなってるの?
電波の中身ってどうなっているんだろう? 世界中で使われる定番のパケットキャプチャソフト”Wireshark”を使って パソコンとTelloのWi-Fi上の交信をのぞいてみた(^^)/ パケットキャプチャソフト”Wireshark” https://www.wireshark.org/ 電波の中身って、見えないけどこれを使ったら、見える化できて仕組みをよく学べました
ここまでいろいろと調べてまとめてみて、プログラミングで指示を出し、Tello(ドローン)が飛ぶ仕組みを自分自身も深堀学習できたと思います。
- Wi-Fiアクセスポイントとして動作する
-
Tello ピンクのボールを追尾する(ピンク色のボール検出(OpenCV HSV)を使い追尾)
ピンク色のボール検出(OpenCV HSV)を使い追尾をプログラミングしてみました。Telloのカメラは解像度…
ピンク色のボール検出(OpenCV HSV)を使い追尾をプログラミングしてみました。Telloのカメラは解像度やホワイトバランスの関係で、暗めのピンク色のボールが赤っぽく映ることがあります。 この場合、純粋なピンクのHSV/BGR範囲ではうまく検出されない可能性があるので、実際に映っている色に合わせて検出範囲を調整する必要があ☛ました。手順は次に通りです。 ドローンが離陸します。 カメラ映像からピンク色のボールを認識。 見つけた瞬間に「ピンクのボール発見!追尾します」としゃべる。 (pyttsx3 の engine.say() + engine.runAndWait() を使って音声を再生) ボールの位置に合わせて、前後・左右に自動で追尾します。
コードの基本は☛顔認識での自動追尾を元にアレンジしています。
顔検出 → OpenCVでピンク色の検出に変更 音声出力 → pyttsx3
を使用して**「ピンクのボール発見!」**をしゃべらせる追尾制御 → 既存のPID制御をそのまま流用(中心点と面積) 音声出力
プログラム内での基本的な使い方
import pyttsx3 # 音声合成ライブラリを読み込み engine = pyttsx3.init() # 音声エンジンを初期化 engine.say("こんにちは、Telloが追尾します") # 読み上げたいテキスト engine.runAndWait() # 実際に再生
pyttsx3はPythonのテキスト音声変換ライブラリです。他のライブラリとは異なり、オフラインでも動作します。
import pyttsx3 #音声エンジン初期化 engine = pyttsx3.init() ball_detected = False # ボールが見つかったかどうかのフラグ
顔検出 → ピンク色検出に置き換えた関数
def findBall(img): global ball_detected hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # ピンク色のHSV範囲(明るさ・環境によって調整) lower_pink = np.array([140, 100, 100]) upper_pink = np.array([170, 255, 255]) mask = cv2.inRange(hsv, lower_pink, upper_pink) contours, _ = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) BallList_center = [] BallList_area = [] ball_center_y = 0 for cnt in contours: area = cv2.contourArea(cnt) if area > 500: # ノイズ除去 x, y, w, h = cv2.boundingRect(cnt) cx = x + w // 2 cy = y + h // 2 BallList_center.append([cx, cy]) BallList_area.append(area) cv2.rectangle(img, (x, y), (x + w, y + h), (255, 0, 255), 2) cv2.circle(img, (cx, cy), 10, (0, 0, 255), -1) cv2.putText(img, f"ballcenter: ({cx}, {cy})", (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (204, 0, 255), 1) if len(BallList_area) != 0: i = BallList_area.index(max(BallList_area)) if not ball_detected: engine.say("ピンクのボール発見!追尾します") engine.runAndWait() ball_detected = True return img, [BallList_center[i], BallList_area[i]] else: ball_detected = False return img, [[0, 0], 0]
少し改善が必要です。
問題点:engine.say(“ピンクのボール発見!追尾します”)の音声時のみ映像が、固まります。
この
runAndWait()
は 同期的に動作するので、終わるまで次の処理に進まず、映像処理も止まることになります。解決策:音声再生をスレッドで非同期に実行する
リアルタイム映像制御や物体追尾では、UI・映像処理の応答性を維持するために音声処理は必ず非同期化(スレッド化)するのが鉄則です。
完成コード
import threading import socket import time import cv2 import numpy as np import pyttsx3 # 音声再生(非同期) def speak_async(text): def _speak(): engine = pyttsx3.init() engine.say(text) engine.runAndWait() threading.Thread(target=_speak, daemon=True).start() # Telloネットワーク設定 host = '0.0.0.0' port = 8889 locaddr = (host, port) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) tello_address = ('192.168.10.1', 8889) sock.bind(locaddr) # Telloからの応答を受信 def recv(): while True: try: data, _ = sock.recvfrom(1518) print(data.decode(encoding="utf-8")) except Exception: print('\nExit . . . RECV\n') break recvThread = threading.Thread(target=recv) recvThread.daemon = True recvThread.start() # 初期化コマンド sock.sendto(b'command', tello_address) time.sleep(0.5) sock.sendto(b'streamon', tello_address) time.sleep(1) sock.sendto(b'takeoff', tello_address) time.sleep(2) # カメラ映像取得 addr = 'udp://192.168.10.1:11111' cap = cv2.VideoCapture(addr) print('start cap') # PID制御パラメータ pid = [0.4, 0.4, 0] pError = 0 w, h = 480, 360 # RCコマンド初期値 a = b = c = d = 0 ball_detected = False # 音声を一度だけ再生するためのフラグ # ピンクボール検出関数 def findBall(img): global ball_detected hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) lower_pink = np.array([140, 100, 100]) upper_pink = np.array([170, 255, 255]) mask = cv2.inRange(hsv, lower_pink, upper_pink) contours, _ = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) BallList_center = [] BallList_area = [] for cnt in contours: area = cv2.contourArea(cnt) if area > 500: x, y, w_box, h_box = cv2.boundingRect(cnt) cx = x + w_box // 2 cy = y + h_box // 2 BallList_center.append([cx, cy]) BallList_area.append(area) cv2.rectangle(img, (x, y), (x + w_box, y + h_box), (255, 0, 255), 2) cv2.circle(img, (cx, cy), 10, (0, 0, 255), -1) if len(BallList_area) != 0: i = BallList_area.index(max(BallList_area)) if not ball_detected: speak_async("ピンクのボール発見!追尾します") ball_detected = True return img, [BallList_center[i], BallList_area[i]] else: ball_detected = False return img, [[0, 0], 0] # 左右・前後制御(PID) def TrackBall(info, w, pid, pError): global b, d area = info[1] x, y = info[0] b = 0 error = x - w / 2 d = pid[0] * error + pid[1] * (error - pError) d = int(np.clip(d, -100, 100)) if 6200 < area < 6800: b = 0 elif area > 6800: b = -20 elif area < 6200 and area != 0: b = 20 if x == 0: d = 0 error = 0 return error # 上下制御(オプション) def throttle(y): global c c = 0 if 50 < y < 110: c = 20 elif 280 > y > 190: c = -20 # メインループ while True: for i in range(5): # フレームスキップで安定化 ret, frame = cap.read() if frame is None or frame.size == 0: continue frame = cv2.resize(frame, (w, h)) img, info = findBall(frame) pError = TrackBall(info, w, pid, pError) throttle(info[0][1]) rc_command = f"rc {a} {b} {c} {d}" sock.sendto(rc_command.encode('utf-8'), tello_address) print("Sending RC command with values:", a, b, c, d) # 画面に情報表示 cv2.putText(img, f'Pitch: {b} Throttle: {c} Yaw: {d}', (20, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 255), 1) cv2.imshow('Tracking', img) # ESCキーで終了 if cv2.waitKey(1) & 0xFF == 27: sock.sendto(b'land', tello_address) cap.release() cv2.destroyAllWindows() sock.close() break
- 照明の加減で色味が変わる場合 → HSVの範囲を調整:
lower_pink = np.array([140, 100, 100]) upper_pink = np.array([170, 255, 255])
試行錯誤してボールの色に合った値を調整してください。
投稿
- 空撮事始め (2021年6月18日)
- 航空法施行規則改正されました。令和3年6月1日 (2021年6月19日)
- Tello(Ryze Technology 社 POWERED BY DJI)の飛ばし方マニュアル (2021年7月5日)
- 趣味におすすめ!ドローン(Tello)のいろいろな楽しさをお伝えします。 (2021年7月5日)
- プログラミング言語って?Pythonの導入前にプログラミング言語の種類をおおまかにまとめてみました。 (2021年7月6日)
- ドローンのプログラミングらしき事をやってみる! はじめてのPython&IDEのインストール方法 (2021年7月7日)
- Tello の画像をOpenCVでPCに写してみる!!OpenCVの導入方法 (2021年7月9日)
- これだけは知っておこう!ドローンを飛ばすのに法律って関係あるの? (2021年7月15日)
- ドローンの空撮は最高!きままに撮ってみました(^^♪ 空撮Gallery集 (2021年7月30日)
- ドローンを飛ばすなら これだけは知っておこう!ドローンの飛行ルール(令和3年6月施行) (2021年7月30日)
- これだけは知っておこう!ドローン空撮には注意が必要です。映像等のインターネット上での注意事項。 (2021年7月30日)
- OpenCVで遊んでみる。 PC内蔵webカメラから動画の撮影。 (2021年9月14日)
- PCでOpenCVで顔認識して遊んでみる。(^^♪ カスケード分類器の使い方 (2021年10月30日)
- ドローン・無人航空機の登録が義務化されます。リモートIDの付け方 (2021年12月14日)
- ドローンのプログラミングでOpenCVをつかい顔認識をしてみる (2022年1月9日)
- ドローン(Tello)を飛ばしてOpenCVで顔認識してみる改良版 (2022年2月21日)
- TelloでOpenCVで顔認識させたらFLIP(フリップ)させる(^^♪ (2022年5月2日)
- Pythonを使いTello(ドローン)でOpenCV(顔認識)から自動追尾をしてみる!☺ (2022年6月13日)
- フィンガーサインでドローン(Tello)を操縦してみた!MediaPipeを使用 (2022年9月29日)
- Pythonプログラミング・OpenCv&MediaPipeを使いドローン(Tello)を自由に操縦してみた! (2022年11月21日)
- Pythonで「Tello(ドローン)で自動追尾プログラミング」改良版を紹介します!これはいける! (2023年3月22日)
- OpenCVで遊んでみる。 PC内蔵カメラから映像の表示 と ドローンからの映像を表示をする (2023年4月13日)
- 100g未満のドローン、100g以上のドローンを飛ばすには?法律や準備しないといけないことは何? (2023年4月21日)
- mediapipe(メディアパイプ)& OpenCV・ Pythonでposeランドマーク検出Telloの自動操縦 (2023年5月8日)
- プログラミングで動かせるオススメ!ドローン・ロボット ベスト2選!! (2023年6月1日)
- 海外、子供に人気!プログランミングで動かせるドローンLitebeeWing紹介(パソコン接続方法解説)します。 (2023年7月2日)
- 講座用ダウンロード サイト (2023年7月7日)
- ドローン(Tello)を音声認識コントロールできる?パソコンで2か所Wi-Fi接続してGoogle Speech to Text (2023年7月9日)
- 最近はまっている 楽しーい気分転換アイテム ドローン&プログラミング学習用教材&ラジコン (2023年7月26日)
- Scratch(スクラッチ)ビデオモーションセンサーでTello(ドローン)操作 AR「拡張現実」? (2024年1月31日)
- スクラッチ(Scratch3-Tello) & (マイコンボード)micro:bitを使って、Telllo(ドローン)をコントロールしてみた(^^♪ (2024年5月5日)
- マイコンでドローン感知ロボットを作ってみました(^^♪ microbit & arduino (2024年6月1日)
- mediapipe & Arduino で 自称ロボットのジェスチャーコントロール (2024年6月5日)
- M5Stackを内蔵のESP32でWiFiサーバーとして使い、コードレスで無線ジェスチャーコントロールをしてみました(^^)/ (2024年6月12日)
- ロボットアームのジェスチャーコントロールへの挑戦 からだアクション操作*目指せ老後の食事介助アーム* (2024年6月27日)
- LiteBee Wing/ SKY / Tello でドローンプログラミングを教えます。プログラミングしたい人集まれ(^^♪ (2024年7月3日)
- 自由研究に!「ドローンをつくろう!学ぼう!」by 神戸ロボットクラブ (2024年8月17日)
- 加速度センサーとジャイロセンサーをArduinoで使って学び、Telloの慣性ユニットに触れる(^_-)-☆ (2025年1月16日)
- RobotDogを音声でコントロールする。音声認識によりコマンドを選定し、赤外線リモコン作り赤外線を送信し、ロボットをコントロールする。 (2025年2月5日)
- Tello ピンクのボールを追尾する(ピンク色のボール検出(OpenCV HSV)を使い追尾) (2025年6月1日)
- ソケット通信について・Telloとパソコンの交信を見てみよう! (2025年8月8日)
- ArUcoマーカーの検出を使った、Telloのプログラミングゲーム (2025年8月10日)
ダウンロード
- PCパソコンweb用 (2023年7月6日)
- ロボット写真 (2023年7月7日)
- 画像認識webcamera (2023年7月7日)
- 画像認識 顔 (2023年7月7日)
- 画像認識・ 目 (2023年7月7日)
- mediapipe finger (2023年7月7日)
- mediapipe pose (2023年7月7日)
- Tello DEMO Python (2023年7月7日)
- Tello SDK (2023年7月7日)
- 無題の動画-Made-with-Clipchamp (2023年7月26日)
- IMG_1966 (2023年7月26日)
- IMG_1959-1 (2023年7月26日)
- IMG_1960-2 (2023年7月26日)
- IMG_1964-1 (2023年7月26日)
- IMG_1960-1 (2023年7月26日)
- IMG_1964 (2023年7月26日)
- IMG_1960 (2023年7月26日)
- IMG_1959 (2023年7月26日)
- viwe (2023年7月26日)
- viwe (2023年7月26日)
- Telloカメラ (2024年2月29日)
タグ
- ArUco
- IMU
- 仕組み
- NPO法人神戸ロボットクラブ
- 特定非営利活動法人神戸ロボットクラブ
- ドローンプログラミング
- python opencv
- ドローン自動追尾
- MPU6050
- ADLX345
- 加速度センサー
- ジャイロ
- 慣性ユニット
- DSO152
- 小学生
- オシロスコープ
- 自動追尾、
- pyttsx3
- python
- diy
- Drone
- ソケット通信
- UDP通信
- ArUcoマーカー
- ゲーム
- ARマーカー
- 組立
- 自由研究
- litebeewing
- ドローン
- Otto
- 楽しい
- 知育教育
- 親子でプログラミング
- マッキーの趣味のドローン
- litebee
- 神戸ロボットクラブ
- Scratch
- スクラッチ
- プログラミング 自動追尾
- 子供
- モーションセンサー
- 教育
- マイコン、microbit
- arudino
- ロボット、レーダー、トイドローン
- プログラミング、顔認識、自動操縦
- プログラミング、自動操縦
- MediaPipe
- ロボット
- OpenCv
- プログラミング
- tello
- 食事介助、ロボットアーム、mediapipe
- プログラミング、大人の趣味、老後対策
- arduino