使用Switch手柄控制Tello并结合Yolov8进行目标检测

这个文章介绍了如何使用Nintendo Switch Pro手柄控制DJI Tello无人机,配合Yolov8进行目标检测与识别。

【注意】:

  1. 下面代码中的手柄控制是根据我个人喜好,参考了DJI RC2作的逻辑,如果想要自定义手柄按钮可以先参考这篇 python获取switch手柄的值 文章得到自己手柄的重映射;
  2. 在进行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以上。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值