A magnetic field measured in any 3-dimensional plane with a magnetometer can be represented as: Where represents the Earth’s magnetic field, which is spherical in its nature. Therefore, if our samples taken from the magnetometer are taken in the shape of a sphere, we are able to extrapolate more information from our sensors.
Similarly to the Accelerometer’s calibration algorithm, the Magnetometer may be modeled as having scale-factor noise, , non-orthogonality of the sensor axis, , and an internal sensor bias or offset, .
However, the magnetometer differs from the Accelerometer in that it may have additional sources of noise from external magnetic fields. We are able to model any sort of Hard-Iron (permanent) sources of noise as a bias, , and Soft-Iron (induced) sources of noise as a 3x3 matrix, .
Therefore, we can combine these sources of noise into the following equation:
Where represents the sensor’s measured magnetic field, and represents the true magnetic field, without noise sources. We are able to re-write this equation as:
Now we can re-write the equation for as follows:
Now, we must write this equation in the quadratic form(spherical representation) as described by : Where:
Using techniques found in Calibration of a magnetometer in combination with inertial sensors, we can come to the solutions:
Which are then used to solve for our calibrated values of the magnetometer:
The equations refered to in this section are found in Least squares ellipsoid specific fitting.
The Magnetometer calibration technique involves taking unfiltered and uncalibrated data and fitting the data points to a quadratic surface using multiple matrices.
In our case, we are going to set .
Using Equation (11), solve for .
Where the element is equal to the transpose of , written as: .
Next, solve for the Eigenvalue and Eigenvector of the system to determine as shown below:
Next we solve for using equation (13), as shown below:
Now, we can find by concactenating and , as done in Equation (11).
The values in by indices are then assigned to vectors , , and .
Using the values of , , and , we can determine matrices and that will be used in calibrating magnetometer readings. The multiplication between matrices used in and is dot multiplication, not matrix multiplication.
Where is the magnitude, commonly normalized to 1.
Now that we have determined the matrices used for calibration, we can use the equation below to determine the calibrated readings from the Magnetometer.
Where are the calibrated readings, and are the raw sensor readings.
The code to this algorithm is found below:
def Calibrate_Mag(magX, magY, magZ):
x2 = (magX ** 2)
y2 = (magY ** 2)
z2 = (magZ ** 2)
yz = 2*np.multiply(magY, magZ)
xz = 2*np.multiply(magX, magZ)
xy = 2*np.multiply(magX, magY)
x = 2*magX
y = 2*magY
z = 2*magZ
d_tmp = np.ones(len(magX))
d = np.expand_dims(d_tmp, axis=1)
D = np.array([x2, y2, z2, yz, xz, xy, x, y, z, d])
D = D[:,:, 0]
C1 = np.array([[-1, 1, 1, 0, 0, 0],
[1, -1, 1, 0, 0, 0],
[1, 1, -1, 0, 0, 0],
[0, 0, 0, -4, 0, 0],
[0, 0, 0, 0, -4, 0],
[0, 0, 0, 0, 0, -4]])
# Equation 11 --- S = D(D.T)
#D_T = np.transpose(D, (1, 0, 2))
S = np.matmul(D, D.T)
print("S Shape: ", S.shape)
S11 = S[:6, :6]
S12 = S[:6, 6:]
S21 = S[6:, :6]
S22 = S[6:, 6:]
print(S22.shape)
# Equation 15
tmp1 = np.matmul(S12, np.matmul(np.linalg.inv(S22), S12.T))
tmp = np.matmul(np.linalg.inv(C1), S11 - tmp1)
eigenValue, eigenVector = np.linalg.eig(tmp)
v1 = eigenVector[:, np.argmax(eigenValue)]
if v1[0] < 0: v1 = -v1
# Equation 13
v2 = np.matmul(-np.matmul(np.linalg.inv(S22), S12.T), v1)
# Equation 11 (part 2)
v = np.concatenate([v1, v2]).T
M = np.array([[v[0], v[5], v[4]],
[v[5], v[1], v[3]],
[v[4], v[3], v[2]]])
n = np.array([[v[6]],
[v[7]],
[v[8]]])
d = v[9]
Minv = np.linalg.inv(M)
b = -np.dot(Minv, n)
Ainv = np.real(1 / np.sqrt(np.dot(n.T, np.dot(Minv, n)) - d) * linalg.sqrtm(M))
return Minv, b, Ainv
Plotting this content should result to something similar as shown below. It appears that I extended the magnetometer alittle bit more than average in parts of the rotation of the IMU.
Here’s some of the code used to plot the magnetometer calibration.
fig = plt.figure(1)
ax = fig.add_subplot(111, projection='3d')
ax.scatter(mX, mY, mZ, s=5, color='r')
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
u = np.linspace(0, 2 * np.pi, 100)
v = np.linspace(0, np.pi, 100)
x = np.outer(np.cos(u), np.sin(v))
y = np.outer(np.sin(u), np.sin(v))
z = np.outer(np.ones(np.size(u)), np.cos(v))
ax.plot_wireframe(x, y, z, rstride=10, cstride=10, alpha=0.5)
ax.plot_surface(x, y, z, alpha=0.3, color='b')
print("A_inv: ")
print(Ainv)
print()
print("b")
print(b)
print()
calibratedX = np.zeros(mX.shape)
calibratedY = np.zeros(mY.shape)
calibratedZ = np.zeros(mZ.shape)
totalError = 0
for i in range(len(mX)):
h = np.array([[mX[i], mY[i], mZ[i]]]).T
hHat = np.matmul(Ainv, h-b)
hHat = hHat[:, :, 0]
calibratedX[i] = hHat[0][0]
calibratedY[i] = hHat[0][1]
calibratedZ[i] = hHat[0][2]
mag = np.dot(hHat.T, hHat)
err = (mag[0][0] - 1)**2
totalError += err
print("Total Error: %f" % totalError)
fig2 = plt.figure(2)
ax2 = fig2.add_subplot(111, projection='3d')
ax2.scatter(calibratedX, calibratedY, calibratedZ, s=1, color='r')
ax2.set_xlabel('X')
ax2.set_ylabel('Y')
ax2.set_zlabel('Z')
# plot unit sphere
u = np.linspace(0, 2 * np.pi, 100)
v = np.linspace(0, np.pi, 100)
x = np.outer(np.cos(u), np.sin(v))
y = np.outer(np.sin(u), np.sin(v))
z = np.outer(np.ones(np.size(u)), np.cos(v))
ax2.plot_wireframe(x, y, z, rstride=10, cstride=10, alpha=0.5)
ax2.plot_surface(x, y, z, alpha=0.3, color='b')
plt.show()