为了账号安全,请及时绑定邮箱和手机立即绑定

如何为FPV战斗无人机打造自动避障与目标追踪的自动驾驶系统?

国防科技

让你的军队更强大,通过 Microsoft AirSimArduCopter SITLArdupilot Mission PlannerUltralytics的YOLOv8 构建并自制FPV战斗无人机自动驾驶。

MQ-9 收割者无人机正在执行作战任务。DefenseNews 来自的图片。

自从我开始开发FPV 战斗无人机,之后,我又制作了利用Yolov8物体检测的DIY间谍无人机项目之后,我收到了许多军队的信息,他们担心敌方的反无人机系统有时会阻止成功的战斗任务并导致无人机丢失。从那时起,我开始思考如何解决这个问题,从而避免无人机丢失的情况。

本文提供了一份详尽指南,介绍如何在乌克兰前线的恶劣条件下改善无人机的使用。它不仅包含构建配备计算机视觉的自动驾驶仪的说明,还提供了一个任务清单,你可以据此对自制的FPV无人机进行改装,使其减少受到俄罗斯对手方使用的_反无人机系统_的威胁。

自动导航

通常,当对手使用反无人机系统使操作员失去控制时,无人机应继续执行任务。这一概念涉及即使操作员失去了控制,无人机也能继续运行。在这种情况下,无人机应自动切换到自主飞行模式,跟踪并摧毁目标,无需操作员干预。同样,当目标在观察显示器中时,操作员可以选择将无人机切换到自主飞行模式,以便捕捉目标进行更精确的瞄准和摧毁。

本文主要关注在Microsoft AirSim中开发带有计算机视觉的自动驾驶系统的虚拟环境及其组成部分和调试过程。

该概念将Companion Computer集成到已安装好的自动驾驶仪的FPV战斗无人机中。本文将在最后解释将该概念应用到实际的FPV无人机中的所需步骤,并在本文末尾详细说明。

环境状况

自动驾驶开发和调试过程中的步骤需要一台高性能的计算机和适当的开发环境,主要包括Microsoft AirSimArduCopter SITLMission Planner

_微软AirSim_模拟器将用来调试计算机视觉模型。_ArduCopter SITL_将执行MAVLink 命令,这些命令用于控制无人机的高度、速度和方向等。任务规划器 作为地面站软件,将操控无人机的电机并切换舵机以改变飞行模式。

来看看Microsoft AirSim中的自动飞行系统是怎么工作的。

在准备发布这篇文章时,我创建了详细的部署环境说明。你可以在该 GitHub 仓库README.md 文件中找到它。此外,你还可以在那里找到其余的 Autopilot 代码。你可以随意下载并按需使用它。

建筑设计

(建筑学)

由于我们只管理一架FPV无人机,我们应该在自动飞行系统的代码中有一个单一指定点,负责发送MAVLink-commands给无人机并接收如速度、高度和方向等数据。这个模块在应用程序中被称为“命令执行器”。

此外,该应用程序使用多线程来同时处理多个任务。

自动驾驶系统架构

从根本上讲,Autopilot 的运作基于一个带有优先级的命令队列系统。这个模块是程序的核心,负责执行所有功能。TelemetryTerminator 线程只是将命令加入队列,而 Router 则负责执行。

对于更详细的Autopilot架构解释,以及其设置和运行状态,请查看名为README_DEV.md的文件,该文件位于GitHub仓库中。

模式设定

在真实的 FPV 战斗无人机 中,模式是这样控制的:通过使用一个 FPV 无线电控制器 切换预定义的开关到不同位置。通常,A2 (AUX2) 开关有三个位置,通常对应于 Betaflight 配置中 SERVO1 模式的一个位置。

为了模拟的目的,我利用了SERVO5值,可以在Servo/Relay标签页中控制该值,如下面的图片所示。

使用Mission Planner中的伺服/继电器标签来控制自动驾驶模式的选择

1100 对应模式 OFF1500 对应模式 READY1900 对应模式 DESTROY。你可以通过点击 SERVO5 前面的相应按钮来切换这些模式。在 Autopilot 应用程序中,这段逻辑是在 router.py 文件中实现的。

    def command_telemetry_mode_change(telemetry):  
        servo5_raw = int(telemetry['servo5_raw'])  
        autopilot_mode = autopilot.state['bee_state']  
        if servo5_raw == 1100:  
            autopilot_mode = 'OFF'  
        elif servo5_raw == 1500:  
            autopilot_mode = 'READY'  
        elif servo5_raw == 1900:  
            autopilot_mode = 'DESTROY'  
        elif servo5_raw == 0:  
            if autopilot.state['bee_state'] == 'READY' and autopilot.state['altitude'] > 1:  
                messages.display(messages.anti_drone_system_is_detected)  
                time.sleep(2)  
                autopilot_mode = 'KILL'  
            elif autopilot.state['bee_state'] != 'KILL':  
                messages.display(messages.rc_lost_can_not_switch_to_kill_mode)  

        if autopilot_mode != autopilot.state['bee_state']:  
            autopilot.state['bee_state'] = autopilot_mode  
            messages.display(messages.bee_state_changed_to, [autopilot_mode])  
            command_queue.queue.clear()

模式解释:

  • OFF — 自动驾驶仪等待无线电控制器指令切换至任何模式。除了 监听模式切换(遥测)记录 之外,没有其他模块在工作。
  • READY — 除了上一模式中的所有模块外,还包括 反无人机系统工作识别,进一步切换到模式 KILL 并检查无线电信号仍未建立。
  • KILL — 因检测到反无人机系统而自动切换到此模式。任务与模式 DESTROY 相同。
  • DESTROY — 寻找目标并摧毁它。在此模式下,自动驾驶仪开启摄像头寻找目标。锁定目标后,它将跟随并摧毁目标。

关闭,就绪,摧毁,这些模式由无线电控制器控制。

击杀 — 自动模式(类似于DESTROY),但在无线电信号恢复时会自动取消。

数据遥测

遥测线程 每 4 秒向队列添加系统监控和遥测请求命令。这些命令旨在为自动驾驶仪提供当前的高度和飞机的速度的关键信息,以帮助自动驾驶仪做出决策。

...
如果未设置停止命令:
    messages.display(messages.telemetry_process_connected, [vars.mavlink_url])

    while not stop_command.is_set():
        try:
            if int(autopilot.state['altitude']) > 1:  # 高度大于1
                router.put_command(router.Command(3,'MONITOR',{'target':'SYS_STATUS'}))
            router.put_command(router.Command(2,'TELEMETRY',{'target':'SERVO_OUTPUT_RAW'}))  # 发送传感器数据
            router.put_command(router.Command(1,'TELEMETRY',{'target':'LOCAL_POSITION_NED'}))
            time.sleep(4)  # 暂停4秒
        except Exception:  # 捕获所有异常
            pass

...

在执行这些命令时,Router 会用当前的飞机速度和高度,剩余的电池百分比,以及整个 Autopilot mode 更新自动驾驶仪的状态。尤其是当飞机处于 DESTROY 模式时,及时接收遥测信息至关重要,因为这会直接影响飞机的移动和方向。

天网终结者

终结者线程添加了这样的命令,用于定位、追踪和消灭目标。只要当前自动驾驶模式为销毁,并且炸弹未被释放(毁坏属性为未毁掉),它就会每秒生成并将这些命令输入到系统路由器中。

如以下列表所示,在执行时,系统尝试使用vision模块定位目标。它会确认目标是否仍然开启,根据视频摄像头中的目标位置计算NED坐标,并持续跟踪目标直到其到达投弹点。

如果它失去了目标,它会后退2米,重新找到目标并调整飞机位置准备轰炸。

    def command_kill(params):  
        ruined = autopilot.state['ruined']  
        frame = autopilot.state['frame']  
        altitude = autopilot.state['altitude']  
        speed = autopilot.state['speed']  

        if ruined == False:  
            # 如果无人机着陆,则不要跟随  
            if altitude == 0:  
                return  
            # 或者即将到达  
            if frame != {} and speed > 0:  
                return  

            # 寻找目标并跟随  
            result = vision.get_camera_image()  
            if result != {}:  
                d = 0  
                boxes = result.boxes.xyxy.tolist()  
                if len(boxes) > 0:  
                    autopilot.state['target_lost'] = 0  
                    x1, y1, x2, y2 = boxes[0]  

                    if vision.is_target_close_enough(x1, y1, x2, y2) == False:  
                        n, e, d, y = vision.get_ned_target(x1, y1, x2, y2, altitude)  
                        autopilot.state['frame'] = (n, e, d, y)  
                        messages.display(messages.command_kill_following_target,  
                                                            [n, e, d, y])  
                        mavs.follow_target(n, e, d, y)  
                    else:  
                        # 如果足够近可以发动攻击,则采取攻击位置  
                        messages.display(messages.command_kill_preparing_for_attack)  
                        mavs.prepare_for_attack(altitude)  
                        # 然后实施攻击  
                        res = mavs.attack()  
                        if res == True:  
                            messages.display(messages.command_kill_target_attacked)  
                            autopilot.state['ruined'] = True  
                            # 攻击后我们撤退  
                            messages.display(messages.command_kill_fallback)  
                            mavs.fallback()  
                            # 任务完成  
                            command_queue.queue.clear()  
                            messages.display(messages.command_kill_mission_completed)  
                else:  
                    tl_count = int(autopilot.state['target_lost'])  
                    messages.display(messages.command_kill_target_lost)  
                    autopilot.state['target_lost'] = int(tl_count + 1)  
                    D_coord = vision.get_altitude_correction(altitude)  
                    d = mavs.target_search(tl_count, D_coord)  

                # 如果高度发生变化,我们需要紧急更新状态  
                if d != 0:  
                    put_command(  
                        Command(0,'TELEMETRY',{'target':'LOCAL_POSITION_NED'}))

一旦飞机处于轰炸位置,自动驾驶仪将飞机高度调整至5米,做好攻击准备。然后,它对目标发起攻击,释放炸弹,并执行一个“撤退”机动。这包括将飞机提升至10米高度并引导其远离轰炸点10米。

MAVLink 指令

所有这些操作都是通过 命令路由器命令处理器) 执行的,它是应用程序的核心组件,并执行所有加入命令队列的命令。

另一个帮助伴随飞行计算机(自动飞行员或autopilot)和无人机之间通信的重要组件是Commands模块,包含所有的_MAVLink命令_的。

import time  
import definitions as vars  
from pymavlink import mavutil  

command_delays = {  
    'disconnect': 0.2,  
    'land': 10,  
    'fallback': 3,  
    'prepare_for_attack': 1,  
    'attack': 1,  
    'target_search': 0.5  
}  

def wait_for_execution(target, delay=0):  
    if delay == 0:  
        delay = command_delays.get(target)  
    time.sleep(delay)  

def connect(system=255):  
    master = mavutil.mavlink_connection(vars.mavlink_url,   
                                        source_system=system)  
    vehicle = master.wait_heartbeat()  
    return master, vehicle  

def disconnect(master):  
    wait_for_execution('disconnect')  
    master.close()  

def send_message_to_gc(message):  
    master, vehicle = connect(1)  
    master.mav.statustext_send(  
        mavutil.mavlink.MAV_SEVERITY_NOTICE, message.encode('utf-8'))  
    disconnect(master)  

def copter_init():  
    master, vehicle = connect(1)  
    master.mav.request_data_stream_send(  
    master.target_system,  
    master.target_component,  
    mavutil.mavlink.MAV_DATA_STREAM_ALL, 1, 1)  
    disconnect(master)  

def land():  
    master, vehicle = connect()  
    print('准备降落!')  
    master.mav.command_long_send(  
        master.target_system,  
        master.target_component,  
        mavutil.mavlink.MAV_CMD_NAV_LAND,  
        0,  
        0, 0, 0, 0, 0, 0, 0)  

    wait_for_execution('land')  
    disconnect(master)  

def telemetry(target):  
    master, vehicle = connect(1)  
    msg = master.recv_match(type=target, blocking=False)  
    telemetry = {}  
    if msg is not None and msg.get_srcSystem() == 1 \  
        and msg.get_srcComponent() == 1:  
            telemetry = msg.to_dict()  
    disconnect(master)  
    return telemetry  

def fallback():  
    master, vehicle = connect()  
    print('回退至初始位置,向后退10米,向左移动10米,上升10米!')  
    msg = master.mav.set_position_target_local_ned_encode(  
        0, 0, 0, 9, 0b110111111000, -10, -10, -10, 0, 0, 0, 0, 0, 0, 0, 0  
    )  
    master.mav.send(msg)  

    wait_for_execution('fallback')  
    disconnect(master)  

def prepare_for_attack(altitude):  
    master, vehicle = connect()  
    target_altitude = altitude - 5  
    print(f'准备攻击,目标高度{target_altitude}米')  
    msg = master.mav.set_position_target_local_ned_encode(  
        0, 0, 0, 9, 0b110111111000, 0, 0, target_altitude, 0, 0, 0, 0, 0, 0, 0, 0  
    )  
    master.mav.send(msg)  

    wait_for_execution('prepare_for_attack')  
    disconnect(master)  

def attack():  
    master, vehicle = connect()  
    msg = master.mav.command_long_encode(  
            master.target_system,  
            master.target_component,  
            mavutil.mavlink.MAV_CMD_DO_SET_SERVO,  
            0,  
            6,  # 参数1(辅助通道号)  
            1100,  # 参数2,  
            0, 0, 0, 0, 0)  
    master.mav.send(msg)      

    wait_for_execution('attack')  
    disconnect(master)  
    return True  

def follow_target(n_coord, e_coord, d_coord, yaw_angle):  
    master, vehicle = connect()  
    msg = master.mav.set_position_target_local_ned_encode(  
        0, 0, 0, 9, 3576,  
        n_coord,  
        e_coord,  
        d_coord,  
        1, 0, 0, 0, 0, 0, yaw_angle, 0)  
    master.mav.send(msg)  

    # 等待位置调整执行  
    delay = int(n_coord / 7 + 1)  
    wait_for_execution(None, delay)  

    disconnect(master)  

def target_search(target_lost_count, d_coord):  
    master, vehicle = connect()  

    yaw_angle = 0   
    d = 0  
    if target_lost_count > vars.target_lost_limit:  
        yaw_angle = 0.7854 # 15度  
        d = d_coord  

    msg = master.mav.set_position_target_local_ned_encode(  
        0, 0, 0, 9, 3576,  
        -2,  
        0,  
        d,  
        1, 0, 0, 0, 0, 0, yaw_angle, 0)  
    master.mav.send(msg)  

    wait_for_execution('target_search')  
    disconnect(master)  
    return d

这些命令执行多种功能,比如向Ardupilot地面站发送通知,请求无人机降落,获取遥测数据,监控系统状态,以及控制无人机的高度和动作。

我想强调,这条指令并没有提供自动驾驶系统结构和操作的完整解释。开发人员想要了解更多细节,可以参考我们GitHub上的详细指南。

电脑看

这款软件最重要的一部分是对象检测部分,让应用能够定位和追踪目标。核心是Ultralytics公司的YOLOv8。在微软AirSim模拟中,我们使用yolov8n.pt模型和2 - Car类进行对象检测。


    import cv2  
    import airsim  
    import definitions as vars  

    from datetime import datetime  
    from ultralytics import YOLO  

    model = YOLO(vars.vision_model)  
    image_width = vars.camera_width  
    image_height = vars.camera_height  
    following_altitude = vars.following_altitude  
    yaw_conversion_factor = 0.002  
    threshold_percentage = 0.03  
    approach_factor = 0.8  

    def get_camera_image():  
        result = {}  
        png_file_name = f'logs/img_{datetime.now().strftime("%Y-%m-%d_%H-%M-%S")}.png'  

        if vars.airsim_camera:  
            client = airsim.MultirotorClient() # 模拟  
            png_image = client.simGetImage(vars.video_source, airsim.ImageType.Scene)  
            if png_image is not None:  
                img = cv2.imdecode(airsim.string_to_uint8_array(png_image), cv2.COLOR_BGR2RGB)  

                results = model(img, classes=vars.vision_classes)  
                frame = results[0].plot()  
                cv2.imwrite(png_file_name, frame)  
                result = results[0]  
            client.enableApiControl(False)  
        else:  
            cam = cv2.VideoCapture(vars.video_source)  
            if not cam.isOpened():  
                return result  
            success, frame = cam.read()  
            if success:  
                results = model(frame, classes=vars.vision_classes)  
                anotated_frame = results[0].plot()  
                cv2.imwrite(png_file_name, anotated_frame)  
                result = results[0]  
            cam.release()  
            cv2.destroyAllWindows()  

        return result  

    def get_ned_coordinates(x1, y1, x2, y2, altitude):  
        target_x = (x1 + x2) / 2  
        target_y = (y1 + y2) / 2  

        relative_x = (2 * target_x / image_width) - 1  
        relative_y = (2 * target_y / image_height) - 1  

        N_coord = relative_y * altitude  
        E_coord = relative_x * altitude  

        D_coord = get_altitude_correction(altitude)  

        return N_coord, E_coord, D_coord  # 获取NED坐标

    def get_yaw_angle(x1, y1, x2, y2):  
        target_x = (x1 + x2) / 2  
        yaw_angle = (target_x - image_width / 2) * yaw_conversion_factor  

        return yaw_angle  # 获取偏航角度

    def get_target_threshold_area(x1, y1, x2, y2):  
        target_area = (x2 - x1) * (y2 - y1)  
        threshold_area = image_width * image_height * threshold_percentage  # 获取目标阈值区域

        return target_area, threshold_area  

    def is_target_close_enough(x1, y1, x2, y2):  
        target_area, threshold_area = get_target_threshold_area(x1, y1, x2, y2)  # 目标是否足够接近

        return target_area > threshold_area  

    def get_ned_target(x1, y1, x2, y2, altitude):  
        N_coord, E_coord, D_coord = get_ned_coordinates(x1, y1, x2, y2, altitude)  
        yaw_angle = get_yaw_angle(x1, y1, x2, y2)  
        target_area, threshold_area = get_target_threshold_area(x1, y1, x2, y2)  
        long_factor = threshold_area / target_area  

        return round(N_coord * long_factor * approach_factor, 4), \  
            round(E_coord, 4), round(D_coord, 4), round(yaw_angle, 4)  # 获取NED目标

    def get_altitude_correction(altitude):  
        D_coord = 0  
        if altitude > 0 and altitude not in [following_altitude, following_altitude-1, following_altitude+1]:  
            D_coord = int(altitude - following_altitude)  
            if D_coord > following_altitude:  
                D_coord = following_altitude  
        return D_coord  # 获取海拔修正值

如上所述,计算机视觉模块 包括将目标在视频画面中的位置转换为 笛卡尔坐标(NED) ,根据需要调整无人机的飞行高度,并考虑比例和转换因子,以更有效地跟踪目标。

对于真正的FPV作战无人机来说,你可能需要训练和编译一个额外的空中识别模型,使其能够识别士兵、坦克、军用卡车等这类军事目标。在这种情形下,请阅读《DIY间谍实用指南:在军事操作中利用YOLOv8目标检测技术》中的说明。了解更多,请参阅DIY间谍实用指南:在军事操作中利用YOLOv8目标检测技术

通知与记录

在其运行过程中,Autopilot 会生成并向不同目标发送消息,包括 应用控制台(即应用界面)、地面控制(例如实机无人机上的 FPV 眼镜)以及 AirSim 调试控制台 等,并将整个路径跟踪记录保存在日志文件中。

目标按日志里的路径行动

The 自动驾驶 存储从参与目标跟踪过程的视频摄像头接收到的每一帧图像到 Logs 文件夹里。该文件夹包含了所有用于对象检测的 PNG 图像,以及一个单一的日志文件,详细记录了无人机飞行期间的所有操作。

日志文件里保存了无人机的飞行记录

以日志形式的飞行记录极具价值,用于调查飞行和战斗任务(任务)。

飞行日志对调查飞行和作战任务非常有用。

我总是建议将日志器切换到DEBUG模式以便从这些记录中获得最多的学习。这样做有助于提升你的自动飞行助手,每次飞行都能让它的表现更上一层楼。

你在考虑真正的 FPV 战斗无人机吗?

在你完成AirSim模拟环境中使用自定义Autopilot和图像识别模型的测试后,你就可以准备好迈出任务的下一阶段:将你的Autopilot集成到真实的FPV战斗无人机,第一人称视角。

以下是一个分步指南,帮助你完成这个目标。

  • 集成与控制。 将飞行控制器与辅助计算机连接;探索从FPV遥控器(FPV无线电控制器)识别命令并进行模式切换的方法;研究使用伺服电机和继电器机制释放炸弹的机制。
  • 通信与遥测。 探索反无人机系统对FPV无人机遥测影响的效果;研究FPV眼镜或FPV头盔显示器向操作员发送消息的通知机制;研究与视频摄像头的通信,连接到辅助计算机或通过FPV无人机上的MAVLink协议。
  • 计算机视觉开发。 创建空中数据集并开发对象识别模型,用于识别军事目标;在选定的辅助计算机上实验计算机视觉算法以评估速度;如有必要,考虑使用C边缘TPU(Edge TPU)进行加速。
  • 无人机组装与测试。 组装FPV无人机,安装辅助计算机和必要组件;在FPV测试场(FPV靶场)进行调试,进行初始测试和故障排查。
  • 使用与评估。 准备初始产品版本,启用DEBUG模式日志记录;将产品交付给乌克兰军队进行实战测试;从军方获取飞行日志,用于进一步分析评估。

您可以自由下载并使用从Github上的自动驾驶仓库的代码,您可以随意使用此软件,没有任何限制。

支持乌克兰的战士们

按照本文提供的说明来做实验。为您的战斗无人机添加一个具有_目标追踪_功能的_自动导航_系统,并将这个结果捐赠给乌克兰的领土防卫部队以协助他们保卫乌克兰的领土。

乌克兰是如何用DIY无人机击落俄罗斯坦克的 https://www.wsj.com/video/series/in-depth-features/how-ukrainian-diy-drones-are-taking-out-russian-tanks/3912093C-EDC6-4EDE-806D-57C558C8E8DB

有兴趣的话,可以看看我之前写的文章《如何为军事用途构建FPV作战无人机》(https://medium.com/@dmytrosazonov/how-to-build-an-fpv-combat-drone-for-military-purposes-ce549f24efca),学习如何从零开始自己动手组装一台DIY作战无人机。掌握之后,为你的无人机添加最新的“目标跟随”功能,给他们发过去,让他们在乌克兰前线试试,并享受这种成就感。

来联系吧

如果有问题,不要犹豫在 Twitter 上提问。

推特: https://twitter.com/dmytro_sazonov (可在该链接查看)

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消