这个文章介绍了如何使用Nintendo Switch Pro手柄控制DJI Tello无人机,配合Yolov8进行目标检测与识别。
【注意】:
- 下面代码中的手柄控制是根据我个人喜好,参考了DJI RC2作的逻辑,如果想要自定义手柄按钮可以先参考这篇 python获取switch手柄的值 文章得到自己手柄的重映射;
- 在进行Yolo目标检测与识别的时候建议提前下载号模型,否则运行后会自动开启下载;
import djitellopy
import pygame
import cv2
from ultralytics import YOLO
'''
TelloPy API RC控制模式接口
send_rc_control(self, left_right_velocity: int, forward_backward_velocity: int, up_down_velocity: int, yaw_velocity: int):
left_right_velocity: -100~100 (左/右)
forward_backward_velocity: -100~100 (前/后)
up_down_velocity: -100~100 (上/下)
yaw_velocity: -100~100 (偏航)
两个遥感
axes[1] z轴需要取相反值
axes[0] yaw轴需要取相反值
axes[3] x轴需要取相反值
axes[2] y轴需要取相反值
中间四个按键:
button[10] 起飞
button[11] 降落
button[4] 录制数据
button[9] 结束录制
上下左右按键:
hat(0)[0] 左右滚翻
hat(0)[1] 前后滚翻
'''
tello = djitellopy.Tello()
tello.connect() # 连接Tello
tello.streamon() # 打开图像视频流
pygame.init()
joystick = pygame.joystick.Joystick(4) # 连接手柄
joystick.init()
terminate = False
remap_factor = 100 # 重映射参数,因为joystick手柄的遥感值是 [-1.0, 1.0] ,但tello api中的速度值范围为 [-100,100]
joy_dead_region = 0.015 # 游戏手柄死区值
# 在晃动手柄摇杆后复位,手柄是有可能一直输出特别小的一个值,我在这里将其称为死区,该值需要根据不同手柄试出来,可以用上面提到的那个文章连接进行测试。对于死区而言进行一次简单的截断滤波即可
# 修正摇杆值, 防止死区导致机体频繁移动
def modify_axis_value(origin_value, dead_value, factor) -> int:
if abs(origin_value) < dead_value:
return 0
return int(factor * origin_value)
x_value = 0
y_value = 0
z_value = 0
yaw_value = 0
frame_read = tello.get_frame_read()
# 下载好的yolo模型路径,这里假设使用的是 yolov8.pt
g_yolo_model_name = "path of yolov8n.pt"
g_yolo_model = YOLO(g_yolo_model_name)
print(g_yolo_model)
# Yolo 目标检测与识别
def yolo_predic(img):
results = g_yolo_model(img)
annotated_image = results[0].plot()
return results, annotated_image
while not terminate:
print("========================================================")
for event in pygame.event.get():
if event.type == pygame.QUIT:
terminate = True
break
try:
# 起飞/降落
if event.type == pygame.JOYBUTTONDOWN:
if joystick.get_button(10): # + 键起飞
tello.takeoff()
print("+ button has been pressed, taking off...")
break
if joystick.get_button(11): # home 键降落
tello.land()
print("Home button has been pressed, landing...")
break
# 四个方向上的翻滚
hat_command = joystick.get_hat(0)
if 0.5 < hat_command[1]: # 前滚翻
tello.flip_forward()
break
elif -0.5 > hat_command[1]: # 后滚翻
tello.flip_back()
break
if -0.5 > hat_command[0]: # 左滚翻
tello.flip_left()
break
elif 0.5 < hat_command[0]: # 右滚翻
tello.flip_right()
break
# 手柄控制
if event.type == pygame.JOYAXISMOTION:
z_value = modify_axis_value(joystick.get_axis(1), joy_dead_region, -1.0*remap_factor)
x_value = modify_axis_value(joystick.get_axis(3), joy_dead_region, -1.0*remap_factor)
y_value = modify_axis_value(joystick.get_axis(2), joy_dead_region, 1.0*remap_factor)
yaw_value = modify_axis_value(joystick.get_axis(0), joy_dead_region, 1.0*remap_factor)
# 对四个方向上的速度进行一次逻辑处理
if 0 != z_value and 0!=yaw_value:
if joystick.get_axis(1) / joystick.get_axis(0) > 10: # z/yaw
yaw_value = 0
elif joystick.get_axis(0) / joystick.get_axis(1) > 10: # yaw/z
z_value = 0
if 0!=x_value and 0!= y_value:
if joystick.get_axis(2) / joystick.get_axis(3) > 10: # x/y
y_value = 0
elif joystick.get_axis(3) / joystick.get_axis(2) > 10: # y/x
x_value = 0
print(f"Move command: x={x_value}, y={y_value}, z={z_value}, yaw={yaw_value}")
tello.send_rc_control(y_value, x_value, z_value, yaw_value)
# 电池电量
print("Basic information:")
print("Battery: ", tello.get_battery())
print("Height : ", tello.get_height())
print(f"Velocity: x=[{tello.get_speed_x}], y=[{tello.get_speed_y}], z=[{tello.get_speed_z}]")
print(f"Accelera: x=[{tello.get_acceleration_x()}], y=[{tello.get_acceleration_y()}], z=[{tello.get_acceleration_z()}]")
print(f"Temperature: {tello.get_temperature()}")
# yolo predict
img = frame_read.frame
# 执行yolo目标检测
detect_result, rendered_img = yolo_predic(img)
rendered_img = cv2.cvtColor(rendered_img, cv2.COLOR_RGB2BGR)
cv2.imshow("drone", rendered_img)
key = cv2.waitKey(1) & 0xff
if key == 27: # ESC
break
except:
print("Catch an exception:")
pygame.time.wait(50) # 非常重要
pygame.quit()
在上面的代码中有一点非常重要,位置在末尾处:
pygame.time.wait(50)
这句话表示等待手柄动作被激活,因为与Tello无人机UDP通讯相比,USB直连的手柄通讯频率相对较高,如果不在循环中进行一定程度的阻塞会导致CPU被手柄大量占用,而传输进来的图像存在明显卡顿和延时,我这里笔记本画面会滞后10s以上。