☰
Cesium无人机航拍视频投影跟随:原理、实现与避坑指南
2026/10/19 0:43:23 网站建设 项目流程

简介:基于Cesium实现的无人机航拍视频投影跟随示例,面向WebGIS与三维可视化方向开发者,解决了将航拍视频实时投影到三维地球并随无人机模型移动旋转的问题。项目支持对投影角度、俯仰角等参数进行动态调节,适合用作Cesium视频投影、无人机轨迹模拟与交互展示的开发参考。压缩包共375个文件,大小约6.26MB,以js逻辑脚本、json参数配置、css样式文件为核心,搭配png/jpg等贴图资源与glb无人机模型,目录结构清晰,便于按模块查阅。核心逻辑通过定时器更新经纬度模拟飞行轨迹,开发者只需替换视频地址、Cesium token及无人机模型即可快速运行。目前已有935人学习下载,该示例的价值在于提供了一套完整的视频投影跟随实现思路,涵盖三维地球场景搭建、视频纹理投影、视角参数控制与轨迹更新机制,上手门槛低,适合需要快速实现无人机航拍视频与三维场景联动的开发者二次开发。

1. cesium无人机航拍视频投影跟随到底是什么:一个被误解的叠加技巧

很多人第一次接触“cesium无人机航拍视频投影跟随”这个需求时,下意识以为是把无人机视频放到三维地球表面一个矩形面上播放,就像在Cesium里贴一张动态广告牌。等真正跑起来才发现画面像是糊了一层玻璃纸,地形起伏完全对不上,飞机一转向视频就甩出几公里。其实这个标题里的“投影”不是简单的纹理贴图,而是利用无人机拍摄时的相机内外参数,把每一帧视频画面当作一束从空中射向地表的透视光线,还原出拍摄瞬间的真实覆盖范围。而“跟随”则是让三维场景里的虚拟相机始终复现无人机当时的飞行位置、朝向和视场角,让视频影像与三维地形严丝合缝地叠加在一起。

这件事的实用价值很直接:某个区域只有一次无人机巡飞的视频,没有倾斜摄影模型,也想在三维地球里看地表变化、标注目标点、测量距离,视频投影就是成本最低的替代方案。它需要的输入只有一段带GPS/IMU记录的航拍视频、一份相机参数表,以及一个能跑Cesium的前端页面。适合的读者是GIS开发、三维可视化工程师,或者是做无人机数据处理交付的团队。接下来我从原理、数据准备、核心实现到踩坑逐一拆开讲。

2. 投影跟随的原理与选型:为什么不能简单贴纹理

2.1 视频纹理贴图与相机投影的本质区别

把视频直接贴到一个平面上,在Cesium里只需要创建一个entity,把视频元素作为material的image来源,然后给它一个rectangle范围。这种方式叫“平面贴图”,它假设视频画面是垂直向下拍摄的正射影像,且地面是平的。但实际无人机航拍几乎都是带倾角的斜拍,地形也有起伏,平面贴图会把山脊拉成面条,把楼房的侧面扭曲成奇怪的形状。原因是平面贴图只有范围信息,没有姿态信息——你不知道视频帧拍摄时相机朝向哪里、视场角多大、焦距多长。

相机投影则完全不同。它根据每帧的GPS坐标、IMU姿态角、相机内参(焦距、主点、畸变系数),在三维场景里重建一个与真实拍摄瞬间完全一致的相机,然后把视频帧作为这个相机的视锥体纹理投影到地形表面。这样画面里的每一根电线杆、每一道田埂,都会落在地形上对应的真实位置。这不是Cesium独有的能力,行业里叫“视频与三维场景融合”或者“Video Georeferencing”,很多Cesium项目就是围绕这个需求在做的。

2.2 Cesium做投影的坐标换算链路

Cesium默认的坐标基准是WGS84,内部计算使用ECEF地心地固坐标系。而无人机POS数据通常给出的是经纬度、海拔、偏航角、俯仰角、翻滚角,相机的成像几何则定义在相机坐标系里。要做投影,核心是把一条坐标链打通:像素坐标 -> 相机坐标 -> 机体坐标 -> 局部地理坐标 -> ECEF -> Cesium世界坐标。

这条链路里最容易出错的是坐标轴的朝向约定。Cesium的局部坐标系使用ENU(东-北-天),而无人机POS里的偏航角通常是相对于真北顺时针为正,俯仰角抬头为正,翻滚角右侧下沉为正。相机的光轴在机体坐标系里往往指向下方前方,与机体坐标系的x轴有一个固定的安装夹角。如果你直接用POS的欧拉角去构建旋转矩阵,不做轴对齐映射,投影出来的画面会旋转一个奇怪的角度,而且这个角度会随着飞机姿态变化而变化,非常难排查。

我一般建议的做法是:在服务端把每一帧POS数据处理成相机在地固坐标系下的位置、朝向四元数和视场角,输出成一个JSON序列,前端只管消费这些参数构建Cesium相机。这样把坐标换算的复杂度收敛到后端,前端逻辑清晰,也方便调试——你可以在Chrome的开发者工具里直接检查相机参数是否合理。

2.3 选型:实时投影与离线烘焙的取舍

实现视频投影有两条路线。一条是实时投影,每一帧都在Cesium里更新相机参数,通过修改相机投影矩阵把视频帧作为纹理投射到场景中。另一条是离线烘焙,先把视频逐帧投影到倾斜模型或地形上,生成带地理编码的纹理贴图,再一次性加载到Cesium里。

实时投影的优点是数据量小、时效性强,视频流可以直接接入,而且视角变化时能保持动态透视关系;缺点是需要高性能的着色器支持,复杂场景下帧率波动明显。离线烘焙的优点是画面质量稳定,运行时不需要复杂计算,适合交付给客户反复查看;缺点是预处理耗时长,而且一旦地形数据更新就要重新烘焙。

对于航拍视频投影跟随这个需求,我推荐优先上实时投影。原因很简单:航拍视频往往是事后处理,但你需要在地面上反复对照视频和三维场景,调整投影参数看效果,离线烘焙一次就要几分钟,迭代效率太低。实时投影把参数暴露出来,滑杆一拖就能看到结果,等所有参数调准了,再决定要不要烘焙成离线成果。这个“先实时调参、后离线交付”的流程,在模拟项目X里帮我们省了不少时间。

3. 数据准备:视频帧、POS数据与时间对齐

3.1 POS数据格式与清洗方法

无人机航拍视频的投影依赖每帧的位置姿态数据。常见的POS数据来自两个途径:一是飞机飞控记录的GPS/IMU组合导航数据,频率通常是5Hz到20Hz;二是后期用差分GNSS解算的高精度轨迹,频率可以达到50Hz。前者胜在方便,后者胜在精度,但无论哪种,第一步都是把数据读出来看格式。

最常见的POS数据格式是CSV,字段大致是:时间戳、经度、纬度、海拔(椭球高或正高)、偏航角、俯仰角、翻滚角。时间戳可能是GPS周秒,也可能是UTC毫秒,还可能是本地文件生成的序号。这里有一个隐蔽的坑:飞控记录的IMU时间戳对应的是IMU采样时刻,而视频帧的时间戳对应的是相机曝光时刻,两者之间存在一个固定的时间偏移。如果你不做对齐直接按序号配对,视频画面会和飞机位置差半秒到一秒,投影出来的画面在地面上拖出一条长长的轨迹残影。

我自己的做法是写一个Python小工具做清洗和对齐。先把POS数据读进来,剔除经纬度为0、海拔为负、姿态角超过合理范围的异常行;然后把时间戳统一换算成UTC毫秒;最后对视频帧的时间戳,在POS数据序列里做线性插值,取前后两个最近的时间点加权平均得到该帧的位置姿态。这样每一帧视频都对应一个精确的相机状态,而不是简单取最近的POS点。

import csv from datetime import datetime, timezone def parse_pos(filepath): records = [] with open(filepath, 'r') as f: reader = csv.DictReader(f) for row in reader: try: ts = int(row['timestamp_ms']) lon = float(row['lon']) lat = float(row['lat']) alt = float(row['alt']) yaw = float(row['yaw']) pitch = float(row['pitch']) roll = float(row['roll']) if abs(lon) < 0.0001 and abs(lat) < 0.0001: continue # 跳过无效定位点 if not (-180 <= lon <= 180 and -90 <= lat <= 90): continue records.append({ 'ts': ts, 'lon': lon, 'lat': lat, 'alt': alt, 'yaw': yaw, 'pitch': pitch, 'roll': roll }) except (ValueError, KeyError): continue records.sort(key=lambda r: r['ts']) return records

这段代码做三件事。第一,只保留字段完整的行,避免缺列导致后续数组越界。第二,过滤经纬度接近零的异常定位点,这类数据在飞控刚启动搜星时很常见。第三,按时间戳排序,保证后续插值时数据是单调递增的,否则二分查找会出错。参数上需要注意,判断无效定位点用的阈值0.0001度约等于10米,这个阈值要根据你的项目精度要求调整,如果飞的是小型多旋翼,可以放宽到0.0005度,但如果是固定翼高速飞行,阈值太松会把起飞前漂移的数据放进来。

3.2 视频帧抽取与时间戳绑定

视频文件本身不直接暴露每一帧的精确拍摄时间,通常的做法是用FFmpeg按一定帧率抽帧,同时记录每一帧在视频流中的PTS(显示时间戳)。PTS与真实拍摄时间之间有一个偏移量,但只要偏移是常数,就可以用视频第一帧的拍摄时间加上PTS差来还原每一帧的拍摄时刻。

这里有一个细节:无人机消费级产品的视频PTS不一定从0开始,有些机型的起始PTS是一个随机大数,有些则包含B帧导致PTS不单调。稳妥的做法是先用FFmpeg探测视频流的start_time,然后在处理时把每一帧的PTS减去start_time,得到一个相对时间,再加上首帧的UTC时间。

ffprobe -v quiet -print_format json -show_streams input.mp4

这条命令输出视频流信息,重点看start_time和time_base两个字段。start_time是首帧相对文件起始的时间,time_base是时间戳的单位,通常是1/1000秒或1/90000秒。拿到这两个值,配合飞控记录的首帧曝光时刻,就能把视频帧时间戳和POS时间统一到同一个参考系。如果飞控记录的时间用的是GPS周秒,而视频时间是基于UTC的,中间还隔着闰秒,需要查一下拍摄当天的闰秒数手动加上。

抽帧这一步不需要把每一帧都抽出来。考虑到投影时画面连续性要求和性能平衡,我一般会抽2到4帧每秒,生成一个带时间戳的帧列表。这个帧列表就是后续投影的最小时间粒度:时间上落在相邻两帧之间的视频时刻,用前一帧的相机参数代替,或者做参数插值。插值带来的画面平滑度提升很明显,但IMU姿态数据的插值不能直接用线性插值,要做四元数的球面线性插值,否则在飞机转弯时相机会出现抖动。

# 帧列表结构示意 frame_list = [ {"frame_id": 0, "pts_ms": 0, "timestamp": 1690000000000}, {"frame_id": 1, "pts_ms": 500, "timestamp": 1690000005000}, ]

帧列表里只需要保留帧序号、相对时间、绝对时间三个字段。实际投影时,Cesium根据当前时钟找到最近的帧索引,用它的相机参数构建投影。如果要做平滑过渡,再取前后两帧做四元数插值。这个环节最容易忽略的是视频I帧间隔:如果视频的关键帧间隔是2秒,而抽帧频率是25FPS,那么PTS到真实时间的映射就要考虑解码延迟,实际拍摄时刻与PTS之间的偏移并不是严格常数。对于这种情况,工程上一般用飞控记录的首帧和末帧时间做线性校准,把视频的整段时长映射到真实时长上,可以消除累计误差。

4. 在Cesium中实现视频投影:相机矩阵、着色器与跟随逻辑

4.1 最小实现:用Cesium的frustum完成单帧投影

Cesium本身提供了完善的视锥体(frustum)和相机概念,实现视频投影最直接的方式就是利用它现有的相机结构。我们不需要自己写投影矩阵的数学运算,而是把无人机每一帧的位姿换算成Cesium相机的position、direction、up参数,再把视频帧作为一张纹理通过自定义材质贴到场景中的Geometry上。

这里的关键在于“贴到场景中的Geometry上”怎么理解。Cesium的地形是高度场,没有统一的UV坐标,不能直接给地形贴一张普通纹理。业界常用的方案是创建一个覆盖投影区域的Geometry,它模拟相机视锥体与地表的交叠范围。更成熟的做法是自定义一个Primitive,在顶点着色器里根据当前片元的世界坐标反算它在相机画面里的UV坐标,然后去采样视频纹理。这个方案对地形的起伏是自适应的,因为着色器逐像素做投影反算,山脊山谷都能覆盖到。

先看相机参数的换算。Cesium的Camera对象有position(Cartesian3)、direction(Cartesian3)、up(Cartesian3)三个核心属性。position来自POS的经纬度和海拔,换算成Cartesian3很简单。direction和up则要根据姿态角来算:默认情况下,direction指向正北水平方向,up指向天顶。无人机朝下拍摄时,direction应当指向地面下方,up应当指向机头方向。

const position = Cesium.Cartesian3.fromDegrees( lon, lat, altitude ); const heading = Cesium.Math.toRadians(yaw); const pitch = Cesium.Math.toRadians(pitch); const roll = Cesium.Math.toRadians(roll); const hpr = new Cesium.HeadingPitchRoll(heading, pitch, roll); const orientation = Cesium.Transforms.headingPitchRollQuaternion( position, hpr ); const matrix = Cesium.Matrix4.fromRotationTranslation( Cesium.Matrix3.fromQuaternion(orientation), position );

这段代码把经纬度海拔和欧拉角转换成一个4x4变换矩阵。Cesium的HeadingPitchRoll约定与无人机POS的习惯略有出入:这里的pitch正值表示向下看,而无人机POS里的pitch是抬头为正。所以传入之前要做一次取反。heading使用的是相对真北的偏航角,这个是一致的。roll的符号同样要留意,Cesium的roll正值是向右倾斜,与IMU定义的右侧下沉方向恰好一致,但不同厂商的IMU输出定义可能有差异,最好用一帧试验画面做方向校核。

相机参数换算之后,还要设置frustum。Cesium的PerspectiveFrustum需要设置fov,这个fov是垂直视场角,而且要求的是弧度值。无人机的相机内参通常会给出水平视场角和垂直视场角,或者给出焦距和传感器尺寸,通过公式换算。这里要特别注意,有些镜头是广角,畸变明显,如果直接用原始视场角投影,边缘区域会明显偏出去。我一般会在设置fov之前先用畸变系数做一次补偿,或者干脆只使用画面中心区域,避开边缘畸变。

4.2 用自定义材质实现逐像素投影

相机参数设置好之后,下一步是把视频帧变成一张可采样的纹理。Cesium的Material支持自定义着色器,也可以直接用ImageMaterial,把video元素作为image来源。但image材质只能做平面贴图,所以要想让视频画面跟随地形起伏,必须走自定义Primitive路线。

这里给出的是一个简化思路:创建一个Primitive,它渲染一个覆盖投影区域的平面网格,顶点着色器把每个顶点的世界坐标传递到片元着色器,片元着色器里根据相机的位置、朝向、视场角反算该世界坐标点对应的画面UV坐标,然后采样视频纹理。这个方案的巧妙之处在于,地形起伏造成的高度差,会在片元着色器的反算中被天然考虑进去。

const vertexShader = ` attribute vec3 position3DHigh; attribute vec3 position3DLow; varying vec3 vWorldPosition; void main() { vec3 worldPosition = position3DHigh + position3DLow; vWorldPosition = worldPosition; gl_Position = czm_viewProjection * vec4(worldPosition, 1.0); } `; const fragmentShader = ` uniform sampler2D videoTexture; uniform vec3 cameraPosition; uniform mat3 cameraRotation; uniform float fov; varying vec3 vWorldPosition; void main() { vec3 delta = vWorldPosition - cameraPosition; vec3 cameraSpace = cameraRotation * delta; float tanHalfFov = tan(fov / 2.0); float u = cameraSpace.x / (cameraSpace.z * tanHalfFov * aspect); float v = cameraSpace.y / (cameraSpace.z * tanHalfFov); if (u < 0.0 || u > 1.0 || v < 0.0 || v > 1.0) { discard; } vec4 color = texture2D(videoTexture, vec2(u, 1.0 - v)); gl_FragColor = color; } `;

着色器里的CameraRotation矩阵,含义是把ECEF坐标变换到相机坐标系——相机光轴指向z轴。计算出来的u和v经过视场角归一化后,就映射到纹理的UV范围。这里有一个很容易搞反的地方:纹理坐标的v轴在WebGL里默认向下增长,但画面里y轴向上,所以采样时要对v做一次翻转。很多人在这一步踩坑,投影出来的画面上下颠倒,还以为是自己矩阵算错了。

此外,discard指令的使用有讲究。如果片元的投影UV落在0到1之外,说明这个地形点不在当前相机画面里,直接丢弃而不是输出黑色,否则覆盖区域边缘会看到一整片黑影。实测中,丢弃指令会带来一些性能开销,但对于单视频场景完全可接受。

4.3 跟随逻辑:让虚拟相机复现无人机轨迹

投影跟随的“跟随”体现在两个层面。第一个层面是相机视角跟随,也就是在Cesium里把场景相机绑定到无人机位置,让用户以无人机视角看地面。第二个层面是投影范围跟随,也就是视频投影的区域随着无人机飞行实时更新,用户可以在上帝视角下一边看三维场景一边看视频覆盖范围。

两个层面是正交的。相机视角跟随实现简单,在Cesium的clock tick回调里持续更新相机参数即可。投影范围跟随则需要动态更新Primitive的几何覆盖范围,因为随着无人机向前飞,投影区域的四边形也在变化。如果直接把Primitive的geometry每次重建,会频繁触发VBO更新,导致卡顿。常见优化是提前创建一个较大范围的网格,网格覆盖整条航线的投影范围,着色器里根据当前帧的相机参数实时判断哪些区域需要显示。这样网格只创建一次,视频纹理每一帧更新,性能表现好得多。

viewer.clock.onTick.addEventListener(function(clock) { const frame = findNearestFrame(clock.currentTime); if (!frame) return; const cameraParams = interpolateCamera(frame); updateProjectionPrimitive(cameraParams); if (followCamera) { viewer.camera.position = cameraParams.position; viewer.camera.direction = cameraParams.direction; viewer.camera.up = cameraParams.up; } });

在这个回调里,findNearestFrame根据当前场景时间找到最近的视频帧索引,interpolateCamera对前后帧做四元数插值得到平滑的相机参数。followCamera是一个布尔开关,决定是否强制把场景相机绑定到无人机视角。在实际交付的项目里,这个开关是必备的——有的客户只想看投影覆盖效果,有的想沉浸式跟着飞机飞一遍。

headingPitchRollQuaternion转换成四元数后做插值有一个好处:插值结果天然是单位四元数,不会出现欧拉角插值时常见的万向锁问题。但要注意,如果相邻两帧的yaw角相差超过180度,直接做四元数插值会绕远路,导致画面快速旋转一圈。解决方法是插值前判断yaw差值,超过180度就加上360度再插值,本质上是对角度做unwrap。

5. 投影跟随的避坑指南:从纹理闪烁到高程悬浮

5.1 影像边缘出现黑边或拉伸变形

现象:投影区域边缘出现明显的黑色锯齿边,或者在楼房、树木边缘看到横向拉伸的彩色条纹。

原因:最常见的是畸变问题。无人机的广角镜头在画面边缘的畸变很大,直接使用理想的针孔相机模型投影,边缘像素会偏离真实地理位置几十米。另一个原因是视场角参数不准确,帧画面实际覆盖范围比计算范围窄,边缘部分投影到了视锥体外。

解决:先校正畸变。用OpenCV的摄像机标定求得畸变系数后,对每一帧视频做去畸变处理再送入Cesium。如果实时去畸变性能扛不住,至少把视场角向内收缩5%到10%,牺牲边缘画面保证主体区域的形变准确。我一般用后者做快速验证,确认投影位置正确后再做全程去畸变。

5.2 投影画面像悬浮在空中的贴纸,没有贴在地表

现象:视频画面看起来贴在了地形上方的一层透明玻璃上,无人机飞过山坡时,道路和房屋错位明显。

原因:高程基准不一致。飞控给出的海拔通常是椭球高,而Cesium地形服务使用的是大地高(基于EGM2008或EGM96水准面模型),两者之间相差一个高程异常值,通常在十几米到几十米之间。如果你直接把POS里的椭球高传给Cesium的position,整个投影面会被抬起来。

解决:在服务端对POS数据做高程转换。查拍摄区域的高程异常值,统一减去这个值再送入Cesium。更稳妥的办法是使用自动服务查询高程异常格网,或者用Cesium的地形抬升功能做整体修正。要注意的是,高程异常值不是常数,在山区变化可能很快,一个固定值只能保证中心区域准确。

5.3 相机跟随模式在转弯时画面剧烈旋转

现象:开启相机跟随模式后,无人机每次转弯,三维场景视角就像被人猛甩了一下,画面瞬间旋转接近180度。

原因:yaw角在-180度和180度之间跳变。无人机机头从5度转到-175度,如果插值逻辑只按数值差处理,就会认为转了350度,导致视角逆着实际转弯方向狂转一圈。

解决:对偏航角做unwrap处理。在时间序列上检测相邻帧的yaw差,如果差值超过180度,就加上或减去360度,让角度序列保持连续。这个处理要在服务端生成相机参数时完成,前端拿到的是已经unwrap过的连续角度,插值就不会出问题。

5.4 视频纹理闪烁,像是有两张图在快速交替

现象:播放过程中影像每隔几百毫秒闪一次,有些画面停留时间长有些短,整体看起来不流畅。

原因:抽帧频率与视频帧率不匹配。如果视频是25FPS,而你抽帧只抽了2FPS,前端显示时每一帧视频要持续500毫秒,但场景时钟的刷新频率是60FPS,持续的静止画面与三维场景的连续动态产生冲突,视觉上就是一顿一顿的闪烁。

解决:把抽帧频率提高到8到10FPS,并开启帧间四元数插值。插值让相机参数连续变化,视频画面本身还是2到4帧每秒也没太大问题。如果确实想保持低抽帧率,可以在展示端对视频帧做透明度渐变过渡,用交叉淡化消除跳变感。

5.5 投影区域在远处看是正常的,拉近看却出现大片空洞

现象:切换到近景视角后,原本覆盖完整的地面出现很多不规则的空白区域,像是地面破了很多洞。

原因:投影区域是用一个与视线方向对齐的四边形网格模拟的。从近处看时,相机视角发生了变化,原本的网格四边形部分区域被地形前的物体遮挡,或者网格密度不够,在近处抽样不足导致空洞。根本原因是投影网格的分辨率是固定,没有根据视距做细分(LOD)。

解决:更新Primitive时动态调整网格密度。离相机近的区域加密网格顶点,远的地方保持疏网格。Cesium的CustomShader支持根据相机距离设置不同的细分级别,但实现成本较高。如果项目场景不大,简单粗暴的做法是把网格整体细分化到足够密,比如每10米一个顶点,这样即使近距离看也不会出现明显空洞,代价是每帧渲染顶点数增加,需要用性能测试来平衡。

6. 进阶:多视频接缝融合与投影质量自检

当一个项目需要投影多段航拍视频,接缝处理就成了绕不开的话题。相邻航段覆盖的区域有重叠,如果两段视频相邻帧曝光差异大,接缝位置会出现明显的色彩跳跃,像一条明亮的线划过地表。常用做法是对重叠区域做alpha加权融合,权重根据片元到接缝中心的距离计算。Cesium里实现起来很简单,一个视频Primitive的着色器里增加一个uniform blendingWidth,片元在边缘一定范围内线性降低透明度,两个视频的投影结果在重叠区域自然叠加。要小心的是,两个Primitive在同一片区域都要输出颜色时,要统一用混合模式而不是覆盖模式,否则后加载的视频会把先加载的完全盖掉,看不到过渡效果。

投影质量的自检方法其实很朴素:把一个带地理坐标的矢量图层叠加到三维场景里,比如道路中心线或者地块边界线,然后在播放视频时观察道路走向投影与矢量线是否重合。重合度稳定说明坐标系统和相机参数都正确;偏差方向随视频播放逐渐增大,优先怀疑时间戳累计误差;偏差只出现在视频边缘,优先怀疑畸变未校正。你也可以把同一个位置在不同时间段的航拍帧投影结果切片保存下来,在GIS软件里叠加对比,能发现很细微的高程误差。

有一个细节值得养成习惯:所有测试都要用同一段航线数据跑两遍,一遍使用原始POS数据,一遍使用修正后的数据,输出两套截图对比。这种做法能帮助你快速判断问题究竟是出在数据上还是出在代码上。某次在模拟项目X里花了一个通宵排查投影位置偏移,最后发现是草稿箱里的坐标轴约定和一版旧代码的注释不一致导致旋转矩阵用错,那以后我养成了把姿态角符号约定写进代码注释第一行的习惯。希望这篇笔记能帮你少走一些弯路,把时间花在真正有价值的调参上。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询