简介:本资源是一套面向高校计算机、自动化与人工智能方向本科生的毕业设计实践方案,聚焦无人机在UE4+AirSim仿真环境下的自主导航与目标跟踪问题,融合虚拟仿真、强化学习、视觉感知与飞控系统开发等关键技术。压缩包共645个文件,涵盖191个Python脚本(含PPO/DQN训练逻辑与AirSim接口)、167个C++/hpp头文件(用于UE4插件与底层通信)、116张PNG图像(含仿真场景截图与可视化结果)、65份Markdown文档(含环境搭建指南、算法说明与实验记录),整体大小为133.66MB。已有1411人学习下载,资源结构完整,包含build_docs.bat等工程构建脚本、参考文献.bib、LaTeX排版文件及可执行bin文件,支持开箱即用与模块化调试。读者可直接复现端到端训练流程,深入理解从状态观测、奖励设计、策略网络部署到实时目标跟踪的全链路实现细节。 如果你正在考虑做无人机方向的毕业设计,大概率绕不开这三个关键词:UE4、AirSim、强化学习。串起来就是——在一个用UE4渲染的仿真环境里,让一架无人机通过强化学习算法学会自主导航和目标跟踪。AirSim是微软开源的无人机/汽车仿真平台,跑在UE4引擎里,传感器模型和物理模型都做得足够真实;强化学习负责决策,让无人机从零开始试错学习。
作为过来人,我可以负责任地说:这个方向非常适合毕业设计,因为你不必买真机、不用担心炸机、不用申请飞行空域;训练数据想采多少采多少,实验想重跑就重跑,可视化效果还特别适合汇报演示。但它的难点同样很实在——环境搭建版本坑多,算法设计有大量细节,训练调试更是熬人。这篇文章我会从选型逻辑、环境搭建、状态动作设计、奖励函数、训练调参一路讲到拓展方向,把我在项目中踩过的坑和最终验证有效的做法都放出来,希望给你省掉几个月的试错时间。
1. 为什么这套技术组合值得花半年去搭
1.1 强化学习需要的试错环境,只有仿真给得起
强化学习的本质很直白:让智能体通过大量试错来最大化累积奖励。这个"大量"往往意味着几万甚至几十万次的交互。如果用真实无人机来跑,每次试错都可能伴随着坠机、桨叶损毁、电机烧坏,一次几块钱到几百块钱不等,一个项目跑下来可能比买一辆车还贵。更不用说安全问题——无人机高速旋转的桨叶在实验室里横冲直撞,万一伤到人,后果不是学生个人能承担的。
仿真环境恰恰把这些顾虑全部消解了。无人机撞了,reset一下又是一个全新的环境;训练崩溃了,重启进程重新来;想测试不同场景,用UE4编辑器几分钟改一个地形出来。AirSim在这一点上做得尤其好,它不只是提供一个好看的3D画面,还内置了IMU、GPS、深度相机、分割相机、光流传感器等多种模型,机体动力学也尽量接近真实无人机。这意味着你在仿真里训练出来的策略,至少在算法层面是可验证的,而不是纯纸面推演。
1.2 AirSim与Gazebo、真实飞控平台的真实差距
我最初也犹豫过要不要用Gazebo+ROS的方案,毕竟学术圈里这套组合更常见,教程也多。但实际对比下来,差异还是挺明显的。
| 对比维度 | AirSim + UE4 | Gazebo + ROS | 真实无人机 |
|---|---|---|---|
| 视觉逼真度 | 高,接近游戏级渲染 | 一般,模型纹理偏弱 | 最高,但不可控 |
| 传感器模型 | 丰富,深度图/分割图/光流齐全 | 依赖第三方插件 | 真实但有噪声和标定问题 |
| 上手门槛 | Python API直接调用,难度低 | 需要熟悉ROS生态,配置繁琐 | 需要飞手执照和硬件调试能力 |
| 成本 | 零成本,纯软件 | 零成本,纯软件 | 几千到几万起步 |
| 训练效率 | 可多开环境并行 | 可跑但资源占用较高 | 无法接受大规模试错 |
| 适合毕设程度 | 高,容易出效果 | 中,偏传统SLAM和规划 | 低,除非你原本就是无人机爱好者 |
对于视觉类强化学习任务,AirSim的渲染保真度是一个重要的隐性优势。视觉策略对图像分布非常敏感,Gazebo里那种"干净得像玩具"的画面,训练出来的特征很难迁移到真实环境;而UE4的材质、光照、阴影都更接近真实世界,虽然不能说完全迁移,但至少差距没有那么大。另外,AirSim的Python API设计得相当友好,client.takeoffAsync()、client.moveToPositionAsync()这类接口几行代码就能控制飞机,不需要先啃完几百页的ROS文档才能跑通第一个Demo。
1.3 UE4的一大隐性福利:场景搭建与调试效率
为什么偏偏是UE4而不是Unity?一方面是因为AirSim最初就是在UE4上开发的,官方对UE4的支持最稳定、文档最全,GitHub Issues里的大量踩坑帖也都是围绕UE4,遇到问题很容易搜到解决方案;另一方面是UE4的蓝图系统给了非美术专业学生一条很实际的场景搭建路径。
我做项目时需要临时加一个可移动的目标小车、在场景里布置几堵墙、调整光源方向,这些操作在UE4蓝图中只需要拖拖拽拽就能完成,不需要专门写C++代码。中文资料里像"UE4蓝图节点手册中文版"这类工具书积累得很厚,遇到不熟的节点随手查一下就有答案,学习成本比从零啃引擎源码低太多了。
这里也要提醒一句:别因为好奇直接上UE5。AirSim对UE5的支持几经波折,很多版本存在兼容问题,而毕业设计最怕的就是在环境工具上浪费无谓时间。用UE4求稳,是过来人的共识。
2. 环境搭建:版本选型、编译流程与PC端运行排查
2.1 版本组合是第一步,错了步步错
AirSim是一个开源项目,它和UE4的版本适配并不是"最新配最新",而是存在一个滞后期。很多同学一上来就装最新的UE4.x和最新的AirSim,结果编译报错、插件加载失败,连官方示例都跑不起来,心态直接崩了。
我最终验证稳定的版本组合是这样的:
| 组件 | 推荐版本 | 备注 |
|---|---|---|
| UE4 | 4.26 或 4.27 | AirSim官方支持最稳的版本,避开UE5 |
| AirSim | v1.7.0 或 v1.8.1 | 到GitHub Release页面下载,不要git clone master分支 |
| Python | 3.8 或 3.9 | 新版AirSim API在这两个版本下最省心 |
| PyTorch | 1.10 以上 | 按显卡驱动选择对应的CUDA版本 |
| CUDA | 11.x 系列 | 不一定要最新,匹配PyTorch即可 |
你可能会问,为什么要用Release版本而不是最新的master分支?因为master分支往往处于开发状态,可能引入了未完成的功能或临时性的Bug,而Release版本是经过测试的稳定快照。做毕业设计不是给开源项目做贡献,稳定复现比尝鲜重要得多。
2.2 从零把AirSim的PC端跑起来
整个流程可以分为五步,按顺序执行可以少踩很多坑:
第一步,安装UE4。通过Epic Games Launcher安装,注意在安装时选择目标版本对应的引擎版本。下载量比较大,建议留出充足时间和磁盘空间。
第二步,下载AirSim源码并解压。你需要关注的是AirSim/Unreal/Environments/Blocks这个官方示例项目。它是专门为AirSim准备的最简环境,网格地面加几个方块,没有任何多余的东西,非常适合拿来测试连接。
第三步:在UE4中打开Blocks工程。打开时引擎会提示你是否需要重新生成项目文件,选择是。然后等它编译一段时间,第一次编译通常在十几分钟到半小时不等。编译完成后,场景里会出现AirSim相关的Actor,说明插件已加载成功。
第四步,安装Python API。在命令行执行:
pip install airsim第五步,写一个最简单的测试脚本,验证连接:
import airsim # 创建多旋翼客户端 client = airsim.MultirotorClient() # 建立连接,失败会抛异常 client.confirmConnection() # 启用API控制权限 client.enableApiControl(True) # 解锁电机 client.armDisarm(True) # 起飞,等待任务完成 client.takeoffAsync().join() # 飞到指定坐标点,高度为-5,速度5m/s client.moveToPositionAsync(0, 0, -5, 5).join() print("Hello Drone!")这里的坐标系是NED(北东地),所以高度方向用负数表示向上飞。脚本运行后你能在UE4画面里看到无人机起飞并移到目标点。如果这一步跑通了,说明基础环境已经没问题,可以放心进入算法阶段。
2.3 遇到的报错,按频率排序给你避雷
我当初在这个阶段卡了差不多一个星期,排除掉所有坑之后,把高频问题整理成了下面这个表:
| 报错现象 | 可能原因 | 解决办法 |
|---|---|---|
| UE4加载AirSim插件失败,编辑器提示模块缺失 | 版本不匹配,或插件未正确编译 | 确认UE4和AirSim版本是否适配;删除Intermediate和Binaries目录后重新编译 |
| Python脚本执行后一直无法连接 | 仿真环境未启动,或防火墙拦截 | 先启动UE4环境,再运行Python脚本;检查Windows防火墙是否允许本地端口通信 |
| 编译报错缺少Windows SDK或C++工具链 | VS2019没有安装Desktop development with C++工作负载 | 安装对应的VS组件,注意版本位数(64位) |
| 编辑器模式下运行极卡,训练速度惨不忍睹 | UE4编辑器本身开销很大 | 用File → Package Project打包成exe运行,帧率能提升一倍以上 |
| UE4崩溃报错,类似LowLevelFatalError | 显存不足、内存不足或场景对象过多 | 降低画质选项;关闭Chrome等后台应用;减掉场景里不需要的Actor |
我自己卡最久的其实是第一项。当时直接clone了AirSim的master分支,配合当时的UE4.27,结果BUILD失败,报了一堆看不懂的C++错误。后来换成v1.7.0的Release版本,一切顺畅。所以答应我,千万别用master分支。
3. 自主导航策略的核心:状态、动作、奖励与算法组合
3.1 状态空间设计:无人机需要"看"到什么
很多初学者会误以为"端到端"就是把一帧原始图像扔给网络,网络自己就能学会一切。理论上是这样,但在实际训练中,纯图像输入的学习效率非常低,因为无人机需要同时从图像中推断自身位置、目标位置、障碍距离,再加上还要探索动作策略,信息耦合太重,收敛速度会感人到让你怀疑人生。
我最终采用的状态空间是三段式拼接:
- 第一段:第一视角RGB图像,缩放到84×84×3。这个尺寸是强化学习视觉任务里的经典配置,信息量够用,计算量又不至于太大。
- 第二段:无人机自身线速度和角速度,共6维。速度信息帮助网络判断当前运动状态,避免只靠图像产生"视觉错觉"。
- 第三段:目标相对位置的水平角度偏差和距离,共2维。这一步给任务提供了明确的指引信号,相当于给网络指了个方向。
你可能会问,直接用真实坐标值为什么不直接做?因为你的最终目标是让无人机只靠视觉做决策,而坐标值是仿真环境给你的"上帝视角"信息,在真实环境中拿不到。我在毕设里的做法是分两个阶段:先用真实坐标信息辅助训练加速收敛,再逐步增加视觉信息的权重。这种做法类似课程学习,能明显降低训练难度。
3.2 动作空间:先离散后连续,毕设别头铁
动作空间的方案直接决定了训练难度。连续动作空间(比如油门、偏航角速度、俯仰角)理论上更接近真实飞控,但探索空间太大,一集里几千步都探索不到一个有效动作,收敛非常慢。
毕设阶段,我强烈建议使用离散动作空间,定义如下:
- 前进:以2m/s速度向前飞行
- 左转:以30度/秒角速度左偏航
- 右转:以30度/秒角速度右偏航
- 悬停:保持当前高度和位置,等待下一步决策
就这么四个动作,足够完成室内场景下的避障导航和目标跟踪任务了。选四个而不是更多,是因为动作越多,策略学习的样本需求越大。实测下来,四个动作在10万步左右就能看到明显效果,而如果换成8个动作,需要的样本量几乎是翻倍的。
当然,离散动作会牺牲一些飞行平滑性,无人机看起来会一卡一卡地转向,这在演示时可能会被答辩老师质疑。我的做法是训练阶段用离散动作保证收敛,评估阶段把策略输出的离散动作解释为连续控制信号的子目标,再用PID平滑执行。这样一来,既保留了离散动作的训练效率,又避免了飞行轨迹过于生硬。
3.3 奖励函数:这是训练能否成功的最关键一环
奖励函数是强化学习里最像"玄学"的部分,但它本质上是一个工程问题。我的经验是:把你要无人机完成的意图拆解成可量化的子目标,每个子目标对应一个奖励项,然后不断调节权重。
我实际使用的奖励函数可以照抄不改:
| 奖励项 | 表达式 | 权重 | 触发条件 |
|---|---|---|---|
| 到达目标奖励 | r_goal = +10 | 1.0 | 无人机与目标点距离 < 2m |
| 距离变化奖励 | r_progress = (d_old - d_new) × 0.5 | 0.5 | 每个决策步计算 |
| 碰撞惩罚 | r_crash = -100 | 1.0 | 发生碰撞或越界,回合终止 |
| 时间惩罚 | r_timeout = -0.01 | 1.0 | 每个决策步 |
| 悬停惩罚 | r_loop = -0.1 | 0.1 | 连续悬停超过3步 |
先说为什么"到达目标"要设2m而不是精确到0m。因为AirSim的物理模型里有风阻、摩擦,PID控制器也存在超调,让无人机精确落到某个坐标点是不现实的,2m半径的容差窗口既符合实际控制精度,又能给策略一个清晰的学习信号。
d_old - d_new 这个距离变化项是"求生存"的关键。它让无人机每比上一步靠近目标一点,就能拿到正反馈,哪怕距离只有0.1m的改善,也在累积经验。如果没有这一项,无人机在空旷场景里自由探索可能几百步都碰不到目标,奖励极度稀疏,网络几乎学不到东西。这一类奖励通常被称为"势函数塑形"。
需要注意的坑是"悬停陷阱"。早期版本我没有加悬停惩罚,结果发现无人机学到一个非常鸡贼的策略:原地悬停。因为悬停既不会撞墙,也不会越界,碰撞惩罚永远触发不了,累积奖励反而比乱飞要高。加了一个连续悬停惩罚之后,这个局部最优被成功打破。
3.4 算法实现与训练循环:拿PPO开刀
算法选型上,我用的PPO(Proximal Policy Optimization),没有犹豫。
相比DQN,PPO能天然处理连续状态空间,而且在视觉输入下不需要维护复杂的经验回放缓冲区;相比DDPG,PPO对超参数的敏感度低得多,不需要精心调软更新系数,这对初学者极其友好。PPO的核心思想是限制每次策略更新的步长,防止一次更新过大导致策略崩塌,可以理解为"每次进步一小步,积少成多",这也是它训练稳定的根本原因。
网络结构是经典的Actor-Critic框架:CNN部分共三层卷积,提取84×84×3图像的视觉特征,展平后与速度和目标位置信息拼接,送入一个128维的全连接层,然后分出两个头——Actor头输出动作分布,Critic头输出状态价值V(s)。总参数量不大,在单张消费级显卡上就能训练。
训练主循环的伪代码如下:
for episode in range(MAX_EPISODES): # 重置环境,随机生成起点和目标点 state = env.reset() episode_reward = 0 done = False while not done: # 用当前策略选择动作 action = policy.act(state) # 执行动作,获取下一状态、奖励和终止信号 next_state, reward, done = env.step(action) # 存入经验缓冲区 buffer.append(state, action, reward, next_state, done) state = next_state episode_reward += reward # 每收集足够经验后,更新一次PPO策略 if len(buffer) >= BATCH_SIZE: ppo.update(buffer) buffer.clear() # 定期保存模型 if episode % SAVE_INTERVAL == 0: torch.save(policy.state_dict(), "checkpoint.pt")训练之前先跑通这个循环,再去琢磨网络结构改进。先确保数据通道没问题,再谈算法优化,这个顺序能帮你避免 "模型一直不收敛,最后发现是环境没连上" 这种极其搞笑的乌龙。
4. 目标跟踪怎么和导航共用一套训练框架
4.1 跟踪问题的本质:目标从静态变成动态
自主导航的目标是一个固定的坐标点,无人机飞过去就算完成任务。目标跟踪的差别在于,目标点不再静止,而是每一帧都在移动。如果你把上一章训练的导航策略直接拿过来用,会发现无人机永远在追一个"已经过期的目标位置",速度稍快一点就跟丢了。
想通这个问题的关键,是把目标跟踪重新定义为"对动态目标点的连续导航"。状态空间里不是传目标的绝对坐标,而是传目标相对于无人机的距离和角度偏差,这样无论目标怎么移动,状态表示都是相对量,策略的泛化能力反而更强。
4.2 跟踪任务的奖励要改三处
目标跟踪的奖励函数和静态导航有三处本质差异:
第一,跟踪不是越近越好。如果奖励设计成"距离越小奖励越高",策略很容易学会直接怼到目标身上,这在物理上既不安全也不符合"持续观测"的任务目标。我用的奖励项是:
r_dist = -|d - d_des| × 0.1其中d_des是期望跟踪距离,我设为3m。当无人机与目标距离正好是3m时,这一项为0;偏离越远,惩罚越大。这就把策略引导到一个"保持合适距离"的状态,而不是一味靠近。
第二,要奖励"目标在视野中心"。AirSim可以通过client.simGetObjectPose()获取目标的真实坐标,从而算出目标在无人机视野里的像素位置。当目标出现在画面中心区域(比如图像中心半径50像素内)时,给一个+0.2的正奖励。这一项的意义是让无人机主动把目标"框"在视野里,避免目标飞出画面导致视觉信息丢失。
第三,跟丢要重罚。如果连续20帧目标没有出现在视野内,判定为跟丢,回合终止并给予-50的惩罚。这个惩罚的力度要显著大于普通步数惩罚,让策略优先保证"不丢目标"。
4.3 任务切换:单策略还是分层策略
目标跟踪还有一个额外的设计问题:无人机一开始并不知道目标在哪,需要先搜索;发现目标后需要接近;接近到一定距离后才转入稳定跟踪。这就涉及任务切换。
我试过两种方案:
方案A,统一MDP。把模式标志(搜索/接近/跟踪)作为一个额外维度加入状态空间,让同一个策略学会三种模式。优点是实现简单,不需要多个策略之间的切换逻辑,状态转移是连续的。缺点是不够灵活,如果后续要加一个"返航"模式,整个模型要重新训练。
方案B,分层FSM。用有限状态机管理模式切换,每个模式内部是一个独立策略。搜索模式可以让无人机按固定路径移动;接近模式复用导航策略;跟踪模式用跟踪策略。优点是模块化、可扩展,缺点是状态切换边界容易抖动,无人机可能在两种模式之间反复横跳。
毕设阶段我推荐方案A。因为FSM的边界条件涉及大量手工调参,而且切换时的策略"突变"会导致奖励曲线剧烈震荡,调试成本非常高。统一MDP虽然看起来不够炫技,但训练稳定,演示效果好,完全符合毕业设计的核心诉求。
4.4 实际训练中遇到的"悬停陷阱"
目标跟踪训练中最有意思的现象,是无人机学会了一种"看似聪明其实在摸鱼"的策略:它会在距离目标大约3m的位置悬停下来,不跟目标移动。这样既满足了距离要求(r_dist为零),又不会因为动作太激进导致目标丢失。
为什么会出现这种情况?因为撤销"跟丢惩罚"的触发条件是"目标在视野内",只要目标恰好保持在画面正中央,即便无人机完全不动,奖励也能维持在一个不太低的水平。这属于典型的奖励黑客行为——策略找到了比"努力完成任务"更轻松的高分路径。
解决办法有两个思路:
- 给"目标移动但无人机没有同步移动"的情况加惩罚。具体做法是记录目标位置变化量,如果目标移动超过1m而无人机位移小于0.2m,判定为"不作为",给予-0.5的惩罚。
- 给"无人机与目标的相对位置变化"加正激励。也就是说,无人机主动跟随目标移动时,才会获得额外的跟随奖励。
这两种方法本质上是让策略明白:你不光要处在正确的位置,还要持续跟踪目标。加了这个约束之后,训练出来的行为明显更像"跟踪者",而不是"静态哨兵"。
5. 训练途中那些"看不见的敌人":收敛、性能与调参日志
5.1 训练不收敛的排查路线
训练不收敛是强化学习项目里最常见也最磨人的问题。我的做法是不要乱猜,按固定顺序排查:
第一步,检查奖励信号。打开TensorBoard看每个决策步的平均奖励,如果绝大部分都是0或者负值,说明奖励太稀疏,策略没有获得有效的学习信号。解决办法是增大距离变化奖励、缩小到达奖励触发半径。
第二步,检查观测数据。打印状态向量,确认目标坐标、速度值没有出现NaN或者异常大的数值。AirSim在某些特殊情况下会返回异常值,比如无人机碰撞后坐标飘到几千米外,这种脏数据会直接把训练搞崩。
第三步,检查网络是否真的在更新。看critic loss和actor loss曲线,如果actor loss一直不下降,可能是网络容量不足或者学习率太小;如果loss突然爆炸,可能是学习率过大或奖励尺度太大。
第四步,检查超参数。下面这个表格是我调试过程中最常用的调整方向:
| 症状 | 可能原因 | 调整方向 |
|---|---|---|
| 奖励一直很低且无增长 | 奖励过于稀疏 | 增加中间过程奖励的权重 |
| 策略退化为单一动作 | 探索不足或奖励设计问题 | 增大熵奖励系数,降低探索衰减速度 |
| Actor Loss爆炸 | 学习率过大 | 学习率从3e-4调到1e-4 |
| Critic Loss震荡严重 | 奖励尺度差异过大 | 对奖励做标准化或裁剪 |
| 策略在多个动作间反复横跳 | 网络容量不足 | 增加全连接层宽度,从128调到256 |
这里要特别强调一个习惯:每次只改一个变量。不要同时调学习率、奖励权重、batch size,否则你根本不知道是哪一个改动起了作用。改完一个变量,记录结果,再改下一个。
5.2 仿真环境跑太慢,先优化性能再谈算法
AirSim的物理渲染引擎很吃性能,默认的编辑器模式下,我的RTX 3060显卡跑起来只有每秒5到10帧,训练一个回合动辄要一分钟。这个速度根本不可能支撑上百万步的训练。
我做了下面这些优化,把训练速度提了将近4倍:
- 图像分辨率从256×256降到84×84。视觉信息量虽然减少了,但强化学习任务对分辨率的需求没有分类任务那么高,卷积网络照样能提取到关键特征。
- 关闭阴影和抗锯齿。在UE4编辑器里打开项目设置,把阴影质量调到低,关闭抗锯齿。这些渲染细节对视觉策略训练没有帮助,只会拖慢渲染速度。
- 使用打包后的exe运行环境。编辑器模式本身占用大量资源,打包成exe后帧率能提升30%到50%。
- 多环境并行。我同时启动4个AirSim环境,每个环境各跑一个训练进程,用Python的multiprocessing把经验数据汇总到主进程,再用PPO更新策略。这个策略把数据采集速度从"逐步爬行"提升到了"小跑"。
很多同学的毕设之所以卡在训练阶段,不是算法问题,而是数据采集速度跟不上。先把渲染开销压到最低,再谈调参优化,顺序别反。
5.3 实验记录与Checkpoint管理
训练到后期,你会发现自己面临的是一个"调参迷宫":改了奖励函数,模型不收敛;改回去,又发现之前记录的数据不够完整,不知道是哪个参数组合导致上次训练效果好。
血的教训告诉我,实验记录要认真做。不需要多花哨,一张Excel表就够了:
日期 | 算法 | 状态维度 | 动作数 | 奖励版本 | 学习率 | BatchSize | 回合数 | 最终奖励 | 备注每次实验都要填这一行,尤其是奖励函数的版本,我强烈建议把每个版本命名成v1、v2、v3,并保留对应的代码文件。不要在原代码上原地修改,因为一次改动可能引起"蝴蝶效应",你不记录的话根本不知道是哪一步把效果改好了还是改坏了。
Checkpoint保存也不要只存一个文件。我习惯每隔一定回合数保存一份,并按"回合数-平均奖励"命名,比如checkpoint_50000_78.5.pt。这样一来,如果训练后期发生崩溃或者策略退化,随时可以回滚到之前效果最好的版本继续调整。
6. 做完这个毕设后,我建议你这样扩展
6.1 从仿真到真实环境的Sim2Real三步走
仿真训练的策略直接搬上真机,几乎是必翻车的。UE4的渲染再真实,和真实世界也有差距;AirSim的动力学模型再精细,也不可能和真实飞控完全一致。想让仿真策略迁移到真实世界,一般要走三步:
第一步,域随机化。训练时随机化光照强度、目标颜色、地面纹理、障碍物位置,让策略不要过度依赖某个特定的视觉特征,这样迁移到新环境时才不至于"换个背景
本文还有配套的精品资源,点击获取