简介:面向MATLAB与惯性测量单元(IMU)开发者的步态追踪实现,聚焦x-IMU设备数据从采集到可视化的完整链路,解决原始传感器信号滤波、姿态解算与运动轨迹还原等问题。压缩包共51个文件,以40个.m源码脚本为主体,配合3个CSV实测数据集、3个PNG效果示意图和1个Markdown说明文档,整体仅3.17MB,结构清晰便于查阅。源码覆盖串口数据读取、低通/卡尔曼滤波、基于四元数的AHRS姿态融合(含互补滤波思路)、六自由度动画渲染等关键模块,并附带README说明使用流程与数据格式,方便二次开发。已有1159人学习下载,适合中高级信号处理及嵌入式开发人员参考,可用于步态分析、行人航位推算、机器人姿态估计等场景;通过阅读源码可深入理解IMU校准、坐标系转换及姿态更新算法,直接复用框架并扩展至自研项目。
1. IMU的MATLAB代码:为什么拿到的数据不能直接出姿态角
大部分人拿到IMU的MATLAB代码,第一反应是跑一下看姿态角,结果经常是静止时俯仰角乱跳、航向角缓慢漂移、转个圈回来角度回不到原位。问题不在算法,而在数据进算法之前那几步——单位没换、零漂没消、时间戳没对齐,后面的滤波都是白费。MATLAB作为开发语言很适合离线数据回放和算法验证,能把这条链路完整跑通。这篇笔记按IMU数据处理的完整链路拆解:原始数据读取、单位换算与零漂校正、Mahony互补滤波与四元数更新、标定验证,以及数据回放调试技巧,适合正在做导航、机器人项目的开发者,也适合刚接触惯性测量想复现一套可用代码的新手。
2. 数据读取与预处理:单位换算、时间戳对齐与零漂校正
IMU数据处理的第一个大坑在于数据格式不统一。不同采集板存下来的数据千差万别,加速度计有的存g、有的存m/s²,陀螺仪有的给deg/s、有的给rad/s,磁力计有的直接给原始LSB值。单位不统一,后面所有解算结果都不能用。这一章按实际处理顺序,从读文件开始,把预处理这一步做扎实。
2.1 数据读入与格式识别:先看文件头再写脚本
拿到一份IMU的MATLAB代码包,第一步不是打开主函数,而是先看数据文件的格式。我一般先用下面这段脚本快速扫一遍文件前20行,确认分隔符、表头和数据列顺序:
function inspect_imu_raw(filepath) % 打开文件并预览前20行,用于确认数据格式 fid = fopen(filepath, 'r'); for i = 1:20 line = fgetl(fid); if line == -1 break; % 文件提前结束 end fprintf('%s\n', line); end fclose(fid); end这段脚本的作用是把采集文件的前20行原样打印出来,方便判断分隔符、表头和数据列顺序。大部分采集板导出的数据会带表头,比如Time、AccX、AccY、AccZ、GyrX、GyrY、GyrZ。如果前几行是注释,用%注释掉再读。参数说明:filepath是文件路径字符串,fgetl每次从文件中读一行,循环20次只做预览。这个方法比直接readtable慢,但胜在直观,不会因为猜错格式把整个数据读崩。如果文件前20行异常,可以直接中断重新确认格式。
确认格式后,用readtable或importdata批量导入。readtable能自动处理表头,importdata适合纯数值文本。我习惯用readtable,因为后面可以直接用列名索引,代码可读性强很多。下面这个函数按数据采集时的传感器配置,分别读六轴数据或九轴数据:
function imu = load_imu_stream(filepath, sensor_type) % sensor_type: 'six_axis' 或 'nine_axis' raw = readtable(filepath); if strcmpi(sensor_type, 'six_axis') imu.acc = [raw.AccX, raw.AccY, raw.AccZ]; imu.gyr = [raw.GyrX, raw.GyrY, raw.GyrZ]; imu.mag = []; else imu.acc = [raw.AccX, raw.AccY, raw.AccZ]; imu.gyr = [raw.GyrX, raw.GyrY, raw.GyrZ]; imu.mag = [raw.MagX, raw.MagY, raw.MagZ]; end imu.t = raw.Time; end这里的sensor_type参数告诉读取函数数据是六轴还是九轴。九轴多了磁力计,后面航向角修正会用到。如果采集板没有磁力计,传'six_axis'即可。一个常见的错误是直接假设列名固定,不同采集板的列名千奇百怪,我见过Acc_X、ACCX、a_x三种写法出现在不同批次的数据里。解决办法是在load_imu_stream里对列名做一次映射检查:
% 列名兼容处理 if ~isfield(raw, 'AccX') if isfield(raw, 'ACC_X') raw.AccX = raw.ACC_X; elseif isfield(raw, 'acc_x') raw.AccX = raw.acc_x; else error('无法识别加速度计列名,请检查数据格式'); end end这段容错代码不复杂,但能避免因为列名差异导致整个脚本报错。实际处理多批数据时,这种防御性写法能省很多时间。我有一个习惯,拿到新数据包先跑inspect_imu_raw看一眼结构,再决定要不要加映射,这比直接全量导入后报错再回头查要快得多。
2.2 单位统一:g、m/s²、deg/s与rad/s
读进来之后第一件事是单位换算。加速度计如果存的是g,需要乘9.80665转成m/s²。陀螺仪如果存的是deg/s,需要乘pi/180转成rad/s。在绝大多数姿态解算算法里,陀螺仪必须用rad/s,否则四元数更新会直接出问题。换算函数如下:
function imu = convert_units(imu, acc_in_g, gyr_in_deg) % acc_in_g: 加速度计数据是否为g(1是,0否) % gyr_in_deg: 陀螺仪数据是否为deg/s(1是,0否) if acc_in_g imu.acc = imu.acc * 9.80665; end if gyr_in_deg imu.gyr = imu.gyr * pi / 180; end end这段代码的核心在于两个布尔参数。如果你知道采集板配置,直接传1或0。如果不确定,可以做一个简单的静态判断:把传感器静止放置,看加速度计模长,接近1则是g,接近9.8则是m/s²。陀螺仪静态时输出应接近0,无法直接判断单位,需要查数据手册或采集软件设置。注意,单位换算必须放在零漂校正之前。如果先把陀螺仪减去均值再乘pi/180,结果其实一样,但如果在单位不一致的情况下做零漂估算,算出来的bias单位就很混乱,后续没法用。
2.3 时间戳对齐:不等间隔数据的处理
IMU采样时间戳往往不是严格等间隔的,尤其是多传感器同步采集时,每个传感器的采样时刻可能偏差几毫秒。如果直接把加速度计和陀螺仪按行号对齐,融合算法会引入额外误差。解决方式是把加速度计插值到陀螺仪的时间戳上:
function data_resampled = resample_to_target(src_t, src_data, target_t) % 将源数据插值到目标时间戳 data_resampled = interp1(src_t, src_data, target_t, 'linear'); % 处理插值边界越界:目标时间超出源时间范围时用最近邻 data_resampled = fillmissing(data_resampled, 'nearest'); end % 使用示例:把加速度计插值到陀螺仪时间戳 imu.acc = resample_to_target(imu.t_acc, imu.acc, imu.t_gyr);interp1的'linear'选项是线性插值,对IMU数据一般够用。'spline'虽然更平滑,但在数据间隙较大时会产生过冲,不利于姿态解算。fillmissing处理的是目标时间戳超出原始时间范围的情况,用最近邻填充避免NaN传播。插值之后还要检查时间戳是否单调递增。有些采集板在掉电再上电后时间戳会重置,导致时间回跳。用diff检查:
dt = diff(imu.t_gyr); if any(dt <= 0) warning('时间戳存在非单调递增段,需要检查采集板配置'); bad_idx = find(dt <= 0); imu.t_gyr(bad_idx+1:end) = imu.t_gyr(bad_idx+1:end) + median(dt); imu.t_gyr = sort(imu.t_gyr); end这里用中位数填充再排序是一种暴力的时间戳修复方式。实际项目中,更稳妥的方案是直接丢弃异常段的数据,因为修复后的时间戳物理意义不明确。但作为快速处理手段,这个办法能让链路先跑通。需要留意的是,修复后的时间戳和原始数据行的对应关系可能已经错位,后面做解算时要注意检查关键时间点的姿态是否合理。
2.4 零漂校正与噪声滤波
陀螺仪零漂是影响姿态精度的主要因素。静止时陀螺仪输出并不严格为0,而是有一个缓慢变化的偏置。如果直接积分,姿态角会随时间线性漂移。零漂估计的代码很简单,关键是静止数据要采够时长:
function gyr_bias = estimate_gyro_bias(gyr_static, fs) % gyr_static: 静止状态下采集的陀螺仪数据 % fs: 采样率,单位Hz static_duration = length(gyr_static) / fs; if static_duration < 10 warning('静止数据少于10秒,零漂估计可能不准,建议重新采集'); end gyr_bias = mean(gyr_static, 1); % 取均值作为偏置 end这里逻辑很简单:静止时理想的陀螺仪输出为零,实际输出均值就是偏置。采集时长建议不少于10秒,越长越好。消费级IMU的零漂会随温度变化,更严谨的做法是分段采集、分段补偿,这个在后面标定章节再展开。加速度计低通滤波方面,常见做法是滑动窗口平均或一阶低通,一阶低通代码如下:
function acc_filtered = lowpass_acc(acc, alpha) % alpha: 滤波系数,0~1之间,越小越平滑 acc_filtered = zeros(size(acc)); acc_filtered(1, :) = acc(1, :); for i = 2:size(acc, 1) acc_filtered(i, :) = alpha * acc_filtered(i-1, :) + (1 - alpha) * acc(i, :); end end一阶低通的alpha取值要视采样率和实际噪声频段而定。100Hz采样率下,alpha取0.7~0.9比较合适。如果alpha太小,姿态响应会明显滞后,动态场景下俯仰角跟不上真实变化。滤波器参数怎么调,避坑章节会专门展开。这轮预处理做完,数据质量和时间对齐都有了保障,接下来才进入姿态解算。
3. 姿态解算核心:Mahony互补滤波与四元数更新
预处理做完,数据干净了,接下来是姿态解算。这是IMU的MATLAB代码包里的核心内容。姿态表示方法有欧拉角、旋转矩阵、四元数三种,代码包里最常见的组合是四元数加Mahony互补滤波。这一章把四元数为什么好用、Mahony滤波每一行在干什么、以及什么时候值得换卡尔曼滤波讲清楚。
3.1 四元数为什么要用:万向锁与计算效率
欧拉角直观,但有两个问题。一是万向锁,当俯仰角达到±90°时,翻滚和航向变得不可区分,数学上表现为奇异。二是三角函数计算量大,不适合在实时解算循环里频繁调用。四元数用四个分量表示旋转,没有奇异点,而且归一化后乘法和加法就能完成姿态更新,计算效率高。四元数转欧拉角在MATLAB里可以直接用quat2eul函数:
eul = quat2eul(q, 'ZYX'); % 输出弧度 eul_deg = rad2deg(eul); % 转角度quat2eul的第二个参数指定旋转顺序,'ZYX'对应航向角、俯仰角、翻滚角的顺序,也就是常见的yaw-pitch-roll顺序。MATLAB内置的quat2eul接收的四元数格式是标量在前,即[q0 q1 q2 q3],和很多文献里的向量在前不同。如果发现姿态角完全不对,优先检查四元数分量顺序。
注意:quat2eul和quatmultiply属于MATLAB的Navigation Toolbox。如果当前License没有该工具箱,需要手写四元数乘法,本文后面提供了quat_multiply替代函数。
3.2 Mahony互补滤波实现:代码与每一步的含义
Mahony互补滤波的核心思想是:陀螺仪短时精度高但会漂移,加速度计长期稳定但有噪声,所以用加速度计来修正陀螺仪的积分误差。下面是一段完整的Mahony更新函数,代码里每一步都加了注释:
function q = mahony_update(q, gyr, acc, dt, kp, ki) % q: 四元数 [q0 q1 q2 q3],标量在前 % gyr: 陀螺仪角速度 rad/s % acc: 加速度计值 m/s^2 % dt: 采样间隔 % kp, ki: 比例项和积分项系数 % 1. 归一化加速度计测量值 acc_norm = acc / norm(acc); % 2. 由当前四元数推算重力方向在载体系中的参考向量 v = [2*(q(2)*q(4) - q(1)*q(3)); 2*(q(1)*q(2) + q(3)*q(4)); q(1)^2 - q(2)^2 - q(3)^2 + q(4)^2]; % 3. 误差向量:加速度计测量值与参考向量的叉积 e = cross(acc_norm, v); % 4. 积分项,消除稳态误差 persistent ei; if isempty(ei) ei = [0 0 0]; end ei = ei + ki * e * dt; % 5. 修正角速度 gyr_corrected = gyr + kp * e' + ei; % 6. 四元数更新,一阶龙格库塔 q_dot = 0.5 * quatmultiply(q, [0 gyr_corrected]); q = q + q_dot * dt; % 7. 归一化 q = q / norm(q); end这段代码的每一部分都值得细看。第1步归一化加速度计,是为了让测量向量和参考向量有相同的尺度,叉积误差才有意义。第2步的参考向量v,是把当前四元数姿态下重力在载体系中的投影算出来。如果姿态角正确,加速度计测量值和v方向一致,叉积为零;如果有偏差,叉积大小正比于角度误差。
第4步的persistent变量ei是积分项,用于补偿陀螺仪零漂造成的累积误差。ki取0时等效于只有比例修正的原始互补滤波,姿态会有稳态误差。调参时kp是关键,kp太小则修正很慢、姿态漂移明显,kp太大则姿态对加速度计噪声敏感、静止时抖动大。经验值是kp在0.5到2.0之间,ki取kp的0.1倍到0.05倍。这个范围不是绝对的,需要根据实际传感器噪声水平微调。
第6步用的quatmultiply如果不可用,可以替换为手写四元数乘法:
function q_out = quat_multiply(q1, q2) % 四元数乘法,q1和q2均为[q0 q1 q2 q3]格式 q0 = q1(1)*q2(1) - q1(2)*q2(2) - q1(3)*q2(3) - q1(4)*q2(4); q1_ = q1(1)*q2(2) + q1(2)*q2(1) + q1(3)*q2(4) - q1(4)*q2(3); q2_ = q1(1)*q2(3) - q1(2)*q2(4) + q1(3)*q2(1) + q1(4)*q2(2); q3_ = q1(1)*q2(4) + q1(2)*q2(3) - q1(3)*q2(2) + q1(4)*q2(1); q_out = [q0 q1_ q2_ q3_]; end四元数乘法不满足交换律,q1和q2的顺序很重要。上面的公式中,q1是当前姿态四元数,q2是角速度增量四元数,顺序不能反。如果发现姿态越转越乱,优先检查是不是把两个参数传反了。
3.3 卡尔曼滤波的取舍:什么时候才值得上
Mahony互补滤波代码量小、参数直观、计算量低,在很多嵌入式平台上都能跑得动。但它对加速度计噪声的抑制能力有限,动态性能一般。卡尔曼滤波在理论上更优,但实现复杂、需要调噪声协方差矩阵,而且在MATLAB里跑一大段代码,最后姿态精度不一定有明显提升。我的经验是:如果传感器在低速或准静态场景下工作,Mahony完全够用;如果IMU安装在高振动或强机动平台上,再考虑卡尔曼。
一段标准的扩展卡尔曼滤波实现比Mahony长四五倍,涉及状态预测、观测更新、协方差传播,调参时R和Q矩阵的每个元素都需要物理意义来解释,新手很容易调崩。一个折中方案是自适应互补滤波,根据加速度计模长动态调整kp:
acc_magnitude = norm(acc); if abs(acc_magnitude - 9.8) < 0.3 kp_adaptive = kp; % 静态或准静态,正常修正 else kp_adaptive = kp * 0.1; % 运动加速度大,降低信任 end这个自适应策略是工程实践里的常见做法。检测到运动加速度时降低kp,避免加速度计的水平分量污染姿态;恢复静态后kp回到正常值,修正陀螺仪漂移。这段逻辑在MATLAB代码包中通常放在主循环里,效果明显。
4. 避坑指南:IMU数据处理中的五个常见翻车现场
姿态解算跑通不难,跑对很难。下面五个问题是我在拆解和复现多套IMU代码包时高频遇到的场景,按现象、原因、解决来分析。
4.1 静止时航向角缓慢漂移
现象:把IMU平放在桌面上,俯仰角和翻滚角稳定,但航向角以每分钟0.5°到2°的速度持续偏移。
原因:航向角只有陀螺仪在积分,没有任何传感器做长期修正。加速度计只能约束俯仰和翻滚,磁力计才能约束航向。如果代码只用六轴数据,航向角漂移是物理上无法完全消除的。
解决:如果传感器有磁力计,在Mahony更新中加入磁力计修正项。如果代码包提供了九轴融合版本,优先用九轴。如果没有磁力计,只能通过更准确的零漂校正来减小漂移速率,但无法根除。
4.2 四元数分量顺序导致姿态角全乱
现象:俯仰角变成航向角,翻滚角数值对不上,转动传感器时三个角度交叉变化。
原因:四元数的分量顺序在不同库之间不统一。MATLAB的quat2eul和quatmultiply要求[q0 q1 q2 q3]标量在前,但很多从其他语言移植来的代码用的是[q1 q2 q3 q0]向量在前。
解决:在姿态解算入口加一个断言:
assert(length(q) == 4, '四元数必须是4维向量'); assert(norm(q) > 0.99 && norm(q) < 1.01, '四元数未归一化,检查更新步骤');更直接的验证方法:把传感器绕z轴转90°,航向角应变化约90°,同时四元数只有一个分量显著变化。这个测试能快速暴露顺序问题。
4.3 静止时姿态角高频抖动
现象:传感器静止放置,俯仰角和翻滚角以±2°到±5°的幅度上下跳动。
原因:kp参数设置过大,互补滤波对加速度计噪声过度信任。加速度计本身的噪声在静止时也不为零,kp越大,这部分噪声越容易被引入姿态估计。
解决:把kp从当前值逐步调小,每次减半,观察抖动幅度。同时确认低通滤波是否有效——alpha取0.9时,静止时加速度计的方差应明显小于原始数据。如果滤波没有明显效果,检查滤波是否作用于解算用的数据路径。
4.4 动态运动时姿态角滞后
现象:快速转动传感器时,姿态角明显跟不上真实动作,转动停止后要几百毫秒才能收敛到正确值。
原因:低通滤波的滞后和过小的kp同时作用。低通滤波把有效信号的快速变化也滤掉了,kp太小则修正很慢。
解决:把滤波器alpha从0.8调到0.9,同时把kp从1.0调到1.5,重新对比静止抖动和动态滞后。这是一个权衡,静止时越平滑,动态时越滞后。工程上更优的做法是运动加速度检测的自适应kp策略,也就是前面提到的方案。
4.5 时间戳修复不彻底导致融合输出跳变
现象:姿态角整体趋势正确,但某些时刻出现瞬间跳变,跳变位置和数据文件中的某个异常时间戳对应。
原因:时间戳修复时,用中位数填充的方式把时间戳向后推了一段,但对应的数据样本没有同步重排,造成数据和时间的错位。
解决:更稳妥的做法是直接标记并删除时间戳非单调的段落,把有效数据段的边界记录下来,解算时按段处理。虽然代码复杂度增加了,但不会引入数据错位。数据量不大的情况下,手工截断异常段也是可行的。
5. 标定与验证:从MATLAB结果到实际可用
很多开发者在仿真数据上把算法跑通了,一上实机就露馅。原因是仿真数据太干净,真实传感器有安装偏差、轴间耦合、噪声非平稳等问题。下面这套验证流程不依赖精密转台,在普通桌面上就能操作,是我在模拟项目X中实际用过的。
5.1 静态测试:零漂和噪声底
静态测试的目的是量化陀螺仪零漂和加速度计噪声水平。把传感器静置在桌面上采集一分钟数据,取后半段做统计:
function report = run_static_test(filepath, fs) data = load_imu_stream(filepath, 'six_axis'); half = floor(size(data.gyr, 1) / 2); % 取后半段,等待传感器热机稳定 gyr_tail = data.gyr(half:end, :); acc_tail = data.acc(half:end, :); report.gyr_mean = mean(gyr_tail, 1); % 零漂均值 report.gyr_std = std(gyr_tail, 0, 1); % 噪声标准差 report.acc_std = std(acc_tail, 0, 1); report.acc_mag_mean = mean(sqrt(sum(acc_tail.^2, 2))); fprintf('陀螺仪均值: [%.4f %.4f %.4f] rad/s\n', report.gyr_mean); fprintf('陀螺仪标准差: [%.4f %.4f %.4f] rad/s\n', report.gyr_std); fprintf('加速度计模长均值: %.4f m/s^2\n', report.acc_mag_mean); end判断标准:陀螺仪零漂均值应在0.01 rad/s以内,加速度计模长均值应接近9.80665。消费级IMU的陀螺仪标准差在0.005 rad/s以内算正常,超出太多说明数据质量有问题或滤波不够。
5.2 动态测试:旋转90°看姿态跟踪
动态测试不要求高精度转台,一个简单做法是手持传感器绕z轴缓慢旋转90°再停留5秒。记录数据后,把解算出的欧拉角画出来:
eul_deg = rad2deg(eul); figure; plot(t_uniform, eul_deg(:, 1), 'r'); hold on; plot(t_uniform, eul_deg(:, 2), 'g'); plot(t_uniform, eul_deg(:, 3), 'b'); grid on; legend('Yaw', 'Pitch', 'Roll'); xlabel('Time (s)'); ylabel('Angle (deg)');观察三个角度在旋转前后的差值是否接近90°。如果偏差超过±3°,要么是解算参数需要调整,要么是传感器安装误差。安装误差可以通过标定流程纠正。还有一个容易被忽略的点:旋转速度要慢。如果转得太快,加速度计会测量到很大的向心加速度,导致互补滤波的修正方向错误。
5.3 标定:加速度计六面法和陀螺仪采集
加速度计标定最简单的方案是六面法。把传感器分别以x、y、z轴的正负方向朝上放置,每个方向各采10秒取均值,得到6×3的测量矩阵。对应的理论重力向量也是6×3,然后对每个轴单独做一次线性拟合,得到scale和bias:
function [scale, bias] = accel_calib(static_data, g_ref) % static_data: 6x3矩阵,六个方向静止时的加速度计均值 % g_ref: 6x3矩阵,对应的理论重力向量 scale = zeros(1, 3); bias = zeros(1, 3); for axis = 1:3 % 单轴线性拟合:测量值 = scale * 理论值 + bias p = polyfit(g_ref(:, axis), static_data(:, axis), 1); scale(axis) = p(1); bias(axis) = p(2); end scale = diag(scale); % 转成对角矩阵方便后续使用 end调用方式:
g_ref = [9.80665 0 0; -9.80665 0 0; ... 0 9.80665 0; 0 -9.80665 0; ... 0 0 9.80665; 0 0 -9.80665]; [scale, bias] = accel_calib(static_means, g_ref);标定后重新采集数据,加速度计模长在任意静止姿态下都应接近9.80665,偏差不超过0.05 m/s²。陀螺仪标定相对简单,多段静止数据的零漂均值即可,必要时考虑温度变化的影响。消费级传感器通常标定到这里就够用了。
6. 进阶技巧:数据回放与GPS/IMU融合
最后聊两个实战中非常好用的技巧。
6.1 数据回放调试
MATLAB调试IMU算法有一个优势,数据可以离线回放。把采集的数据保存成MAT文件,之后每次改参数重新跑一遍,不用重新采集,这是最省时间的调试方式:
save('imu_capture_01.mat', 't_uniform', 'acc', 'gyr', 'mag'); load('imu_capture_01.mat'); q_result = run_solver(t_uniform, acc, gyr, mag, kp, ki);回放调试的关键是别只改一个参数看一遍就完事。我习惯把kp、ki、滤波alpha、时间戳插值方式做成一个参数结构体,每次试验的参数组合和输出姿态角一起保存,对比时直接加载不同批次。
6.2 GPS/IMU融合的一个简单实现思路
如果需要输出位置信息,可以把姿态解算结果作为输入,与GPS做松耦合融合。在MATLAB里最简方案是卡尔曼滤波,但日常开发中常用一个简化方法:IMU做高频位置增量,GPS做低频绝对修正:
if has_new_gps pos_est = pos_est + alpha_gps * (pos_gps - pos_est); else pos_est = pos_imu; endalpha_gps是GPS修正置信度系数,在0.05到0.2之间。这个简化版本作为原型验证够快,真正的深耦合融合还需要处理GPS跳变、坐标系转换等问题,篇幅远不止这些。
有件事我一直当反面教材:某次在某开发板上把IMU的安装方向装反,导致俯仰和翻滚直接互换,当时没做旋转90°验证就往下调参,浪费了一整天。从那以后每次拿到新数据包,我都先做绕z轴旋转90°的验证,确认坐标方向没问题才开始解算。希望帮到你。
本文还有配套的精品资源,点击获取