iOS中基于CMDeviceMotion获取世界坐标系下设备绝对旋转速率的方法
嘿,这个问题我之前做运动传感相关功能时刚好研究过,其实核心就是利用CMAttitude里的旋转矩阵或四元数做坐标变换,把设备本地坐标系的旋转速率映射到你设定的世界坐标系上,我给你一步步拆解下:
先明确两个坐标系
首先得搞清楚我们要转换的两个空间:
- 设备本地坐标系:iOS标准的右手坐标系——x轴向右,y轴向上,z轴垂直屏幕向外
- 世界坐标系(以
xMagneticNorthZVertical为例):X轴指向磁北,Z轴垂直地面向上,Y轴则垂直于X-Z平面(同样是右手坐标系)
CMRotationRate默认给出的是设备相对于自身本地坐标系的旋转速率,我们要做的就是把这个“相对速率”转换成世界坐标系下的“绝对速率”。
具体实现步骤(代码示例)
1. 初始化运动管理器,设置目标参考帧
首先得让CMMotionManager以你需要的世界参考帧来输出数据:
import CoreMotion let motionManager = CMMotionManager() motionManager.deviceMotionUpdateInterval = 0.01 // 100Hz更新,根据需求调整 // 设置参考帧为磁北+垂直地面的世界坐标系 motionManager.startDeviceMotionUpdates(using: .xMagneticNorthZVertical)
2. 利用姿态的逆旋转矩阵做坐标变换
CMAttitude的rotationMatrix是世界坐标系→设备本地坐标系的转换矩阵,那反过来,要把本地的旋转速率转到世界坐标系,就需要用它的逆矩阵(对于正交矩阵来说,逆矩阵等于转置矩阵,用转置计算更高效)。
在更新回调里做转换:
motionManager.startDeviceMotionUpdates(to: .main) { motion, error in guard let motion = motion else { print("获取设备运动数据失败:\(error?.localizedDescription ?? "未知错误")") return } // 本地坐标系下的旋转速率(x/y/z对应设备自身的三个轴) let localRate = motion.rotationRate // 当前设备的姿态信息 let attitude = motion.attitude // 取旋转矩阵的转置(等价于逆矩阵,正交矩阵特性) let rotationMatrix = attitude.rotationMatrix let transposedMatrix = CMRotationMatrix( m11: rotationMatrix.m11, m12: rotationMatrix.m21, m13: rotationMatrix.m31, m21: rotationMatrix.m12, m22: rotationMatrix.m22, m23: rotationMatrix.m32, m31: rotationMatrix.m13, m32: rotationMatrix.m23, m33: rotationMatrix.m33 ) // 矩阵乘法:将本地旋转速率向量转换为世界坐标系下的向量 let worldRateX = transposedMatrix.m11 * localRate.x + transposedMatrix.m12 * localRate.y + transposedMatrix.m13 * localRate.z let worldRateY = transposedMatrix.m21 * localRate.x + transposedMatrix.m22 * localRate.y + transposedMatrix.m23 * localRate.z let worldRateZ = transposedMatrix.m31 * localRate.x + transposedMatrix.m32 * localRate.y + transposedMatrix.m33 * localRate.z // 现在worldRateX就是绕世界X轴(磁北)的旋转速率,worldRateZ是绕世界Z轴(垂直地面)的速率 print("绕磁北轴旋转速率:\(worldRateX) rad/s,绕垂直轴旋转速率:\(worldRateZ) rad/s") }
替代方案:用四元数变换(更高效)
如果你的场景对性能要求更高,也可以用四元数的共轭来做向量变换,避免矩阵运算的开销:
// 在回调里获取姿态四元数 let quat = attitude.quaternion // 四元数的共轭(w不变,x/y/z取反) let conjugateQuat = CMQuaternion(x: -quat.x, y: -quat.y, z: -quat.z, w: quat.w) // 本地旋转速率向量 let vx = localRate.x let vy = localRate.y let vz = localRate.z // 四元数旋转向量的计算逻辑 let tempX = conjugateQuat.w * vx + conjugateQuat.y * vz - conjugateQuat.z * vy let tempY = conjugateQuat.w * vy + conjugateQuat.z * vx - conjugateQuat.x * vz let tempZ = conjugateQuat.w * vz + conjugateQuat.x * vy - conjugateQuat.y * vx let tempW = -conjugateQuat.x * vx - conjugateQuat.y * vy - conjugateQuat.z * vz // 最终世界坐标系下的旋转速率 let worldRateX = tempX * quat.w + tempW * (-quat.x) + tempY * (-quat.z) - tempZ * (-quat.y) let worldRateY = tempY * quat.w + tempW * (-quat.y) + tempZ * (-quat.x) - tempX * (-quat.z) let worldRateZ = tempZ * quat.w + tempW * (-quat.z) + tempX * (-quat.y) - tempY * (-quat.x)
几个注意点
- 确保设备的磁力计正常工作(
xMagneticNorthZVertical依赖磁力计),如果出现数据异常,可能需要提示用户校准设备 - 旋转速率的单位是弧度每秒(rad/s),如果需要转换成角度每秒,乘以
180 / .pi即可 - 正交矩阵的逆等于转置,所以用转置矩阵代替逆矩阵计算,结果完全一致,且计算成本更低
内容的提问来源于stack exchange,提问作者Ze Jibe
相关产品推荐
相关产品推荐

