AirSim多台无人机第一视角键盘控制进阶版
本文实现的效果
前言
本篇文章实现的键盘控制较上一篇主要有三个地方的改进。第一个,更换控制API实现更自由的控制,第二是在该基础上增加图像读取与实时刷新显示功能,第三个是在两台及以上的无人机中自由切换控制对象以及对应的FPV视角。
一、环境依赖
除了上篇文章依赖的pygame库外,还需要安装opencv-python库,用来对AirSim图像API接口返回的数据进行图像编码,以及保存图像。
pip3 install opencv-python
pip3 install pygame
二、图像读取与显示
1.使用的API
python安装的AirSim库中提供了两种获取图像的API,一个是simGetImage,另外一个是simGetImages。对于后者,我们可以对一台无人机同时读取多个摄像头的图像,以及设置是否压缩,多次测试发现使用不压缩的数据类型可以提高图像读取的速率,在读取多个图像时十分管用。在这里为了降低程序阅读门槛,遵循循序渐进的方法,先介绍使用simGetImage接口来读取并处理一帧图像数据。
image = AirSim_client.simGetImage('0', airsim.ImageType.Scene, vehicle_name="Drone1")
第一个参数表示要读取的摄像头编号,'0’号代表的是正前方,此外默认的模型还有左前方右前方,正下方以及正后方的摄像头,具体对应关系如下。
'0': front_center '1': front_right '2': front_left '3': bottom_center '4': back_center
2.实时显示的一种方法
利用pygame库的图像加载API将实时的第一视角图像读取,然后在pygame主窗口实时更新,对于第一视角的图像使用opencv里面的imwrite函数进行图像保存,基本实现如下。
cv2.imwrite('visual.png', image)
# 利用pygame库加载保存的第一视角图像
screen_image = pygame.image.load("visual.png")
三、键盘控制改进
本次无人机各个方向控制使用了速率量,这样能有更大的自由空间,可以自行调节参数来获得更好的效果。具体请参照代码注释,不懂可留言提问。
import sys
import cv2
import time
import airsim
import pygame
# >------>>> pygame settings <<<------< #
pygame.init()
screen = pygame.display.set_mode((320, 240))
pygame.display.set_caption('keyboard ctrl @FPV')
screen.fill((0, 0, 0))
# >------>>> AirSim settings <<<------< #
# 这里改为你要控制的无人机名称(settings文件里面设置的)
base_name = "Drone"
vehicle_index = [1, 2]
now_index = vehicle_index[0]
AirSim_client = airsim.MultirotorClient()
AirSim_client.confirmConnection()
AirSim_client.enableApiControl(True, vehicle_name=base_name + str(vehicle_index[0]))
AirSim_client.enableApiControl(True, vehicle_name=base_name + str(vehicle_index[1]))
AirSim_client.armDisarm(True, vehicle_name=base_name + str(vehicle_index[0]))
AirSim_client.armDisarm(True, vehicle_name=base_name + str(vehicle_index[1]))
AirSim_client.takeoffAsync(vehicle_name=base_name + str(vehicle_index[0]))
AirSim_client.takeoffAsync(vehicle_name=base_name + str(vehicle_index[1])).join()
image_types = {
"scene": airsim.ImageType.Scene,
"depth": airsim.ImageType.DepthVis,
"seg": airsim.ImageType.Segmentation,
"normals": airsim.ImageType.SurfaceNormals,
"segmentation": airsim.ImageType.Segmentation,
"disparity": airsim.ImageType.DisparityNormalized
}
# 基础的控制速率
base_rate = 0.2
# 悬停时的油门
base_throttle = 0.55
# 设置临时加速比例
speedup_ratio = 4.0
# 用来设置临时加速
speedup_flag = False
change_time = 0.0
# 防止来回切换控制对象
enable_change = True
# 用来标记当前是否进行键盘控制
control_iteration = False
while True:
pitch_rate = 0.0
yaw_rate = 0.0
roll_rate = 0.0
throttle = base_throttle
control_iteration = False
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
scan_wrapper = pygame.key.get_pressed()
# 按下空格键加速10倍
if scan_wrapper[pygame.K_SPACE]:
scale_ratio = speedup_ratio
else:
scale_ratio = speedup_ratio / speedup_ratio
# 切换两秒后方可再次切换
if time.time() - change_time > 2:
enable_change = True
# 需要加上enable_change变量判断,否则会来回切换
if scan_wrapper[pygame.K_LCTRL] and scan_wrapper[pygame.K_c] and enable_change:
enable_change = False
change_time = time.time()
if now_index == vehicle_index[0]:
now_index = vehicle_index[1]
else:
now_index = vehicle_index[0]
print(f"change to drone{now_index} ···")
time.sleep(0.2)
# 根据 'A' 和 'D' 按键来设置偏航速率变量
if scan_wrapper[pygame.K_a] or scan_wrapper[pygame.K_d]:
control_iteration = True
yaw_rate = (scan_wrapper[pygame.K_a] - scan_wrapper[pygame.K_d]) * scale_ratio * base_rate
# 根据 'UP' 和 'DOWN' 按键来设置pitch轴速率变量(NED坐标系,pitch为机头向前)
if scan_wrapper[pygame.K_UP] or scan_wrapper[pygame.K_DOWN]:
control_iteration = True
pitch_rate = (scan_wrapper[pygame.K_UP] - scan_wrapper[pygame.K_DOWN]) * scale_ratio * base_rate
# 根据 'LEFT' 和 'RIGHT' 按键来设置roll轴速率变量(NED坐标系,roll为正右方)
if scan_wrapper[pygame.K_LEFT] or scan_wrapper[pygame.K_RIGHT]:
control_iteration = True
roll_rate = -(scan_wrapper[pygame.K_LEFT] - scan_wrapper[pygame.K_RIGHT]) * scale_ratio * base_rate
# 根据 'W' 和 'S' 按键来设置z轴速率变量(NED坐标系,z轴向上为负,油门为正数)
if scan_wrapper[pygame.K_w] or scan_wrapper[pygame.K_s]:
control_iteration = True
throttle = base_throttle + (scan_wrapper[pygame.K_w] - scan_wrapper[pygame.K_s]) * scale_ratio * base_rate
# 速率需要限幅
if pitch_rate > 1.0:
pitch_rate = 1.0
elif pitch_rate < -1.0:
pitch_rate = -1.0
if yaw_rate > 1.0:
yaw_rate = 1.0
elif yaw_rate < -1.0:
yaw_rate = -1.0
if roll_rate > 1.0:
roll_rate = 1.0
elif roll_rate < -1.0:
roll_rate = -1.0
if throttle > 1.0:
throttle = 1.0
elif throttle < 0.0:
throttle = 0.0
# 不控制的时候保持悬停,也可以用于刹车
if control_iteration:
# 设置速率控制以及设置偏航控制
AirSim_client.moveByRollPitchYawrateThrottleAsync(pitch=pitch_rate, roll=roll_rate, yaw_rate=yaw_rate,
throttle=throttle, duration=0.05,
vehicle_name=base_name + str(now_index))
else:
# 保持当前位置
AirSim_client.hoverAsync(vehicle_name=base_name + str(now_index))
# 获取正前方的一帧图像
temp_image = AirSim_client.simGetImage('0', image_types["scene"], vehicle_name=base_name + str(now_index))
if temp_image is None:
print("Warning: Failed to read a frame!! ")
pygame.quit()
else:
pass
# 将图像进行解码,变成像素值为0-255的范围,保存路径自行修改
image = cv2.imdecode(airsim.string_to_uint8_array(temp_image), cv2.IMREAD_COLOR)
cv2.imwrite('../data/screen/visual.png', image)
# 利用pygame库加载保存的第一视角图像,
screen_image = pygame.image.load("../data/screen/visual.png")
# 图像坐标系,左上角为(0, 0),在此放置图片
screen.blit(screen_image, (0, 0))
pygame.display.flip()
pygame.display.update()
# press 'Esc' to quit
if scan_wrapper[pygame.K_ESCAPE]:
pygame.quit()
sys.exit()
总结
本文使用更底层的控制API来实现无人机的运动控制,并使用opencv和pygame结合的方式来实现实时的图像读取与显示,较上一篇文章有更多拓展,只要自己能将各个参数调好,也能达到一种不错的控制效果。下一篇博客将开始使用程序收集图像数据集用于yolov5模型的训练,实现在AirSim中识别无人机以及其它需要识别的目标。