센서 퓨전 진짜 오랜만에 보는데 

일단 먼저 자이로 각속도로 오일러 각 구하기 구현 시작

 

 

1. 자이로로 부터 얻은 각속도로 오일러 각 구하기

일단 자이로 각속도 데이터부터 읽어오기 구현

제너레이터로 루프마다 가져오게 만듬

 

각속도 wx, wy, wz

import scipy.io

gyro = scipy.io.loadmat("ArsGyro.mat")
def get_gyro():
    wx = gyro["wx"]
    wy = gyro["wy"]
    wz = gyro["wz"]

    gyro_len = len(wx)
    for i in range(gyro_len):
        yield wx[i], wy[i], wz[i]

for wx, wy, wz in get_gyro():
    print(wx, wy, wz)


get_gyro()

 

xyz니까 한번 플로팅해주려고 하는데 할때마다 서브플롯 어캐햇는지 잊어먹는다

import matplotlib.pyplot as plt

plt.subplot(311)
ax1 = plt.subplot(3,1,1)
ax2 = plt.subplot(3,1,2)
ax2 = plt.subplot(3,1,3)

 

대충 이거랑 get gyro랑 섞으면 이래 나온다.

 

 

wx_list = []
wy_list = []
wz_list = []

for wx, wy, wz in get_gyro():
    wx_list.append(wx)
    wy_list.append(wy)
    wz_list.append(wz)

plt.subplot(311)
ax1 = plt.subplot(3,1,1)
ax2 = plt.subplot(3,1,2)
ax3 = plt.subplot(3,1,3)
ax1.plot(wx_list)
ax2.plot(wy_list)
ax3.plot(wz_list)

 

2. 각속도 -> 오일러각 변환하기

이번에는 각속도 wx,wy,wz를 pqr이라 하자

이걸 오일러각 phi theta psi 로 바꾸도록 구현

 

얻어내고자 하는 오일러각(변화량)은

기존의 오일러 각으로 아래 3 x 3 행렬 구하고 p q r 과 곱하면 나옴

매트랩 코드를 파이썬으로 구현하는데

이전 파이 세타 프사이를 매개변수로 넣어버릴까 싶다가

그냥 메트랩 코드 따라서 전역으로 나둠

 

일단 대충 구현하면 이런식이다

 

from math import cos, sin, tan

prev_phi = 0
prev_theta = 0
prev_psi = 0 
def gyro_euler(p, q, r, dt):
    sin_phi = sin(prev_phi)
    cos_phi = cos(prev_phi)
    cos_theta = cos(prev_theta)
    tan_theta = tan(prev_theta)

    phi = prev_phi + dt * (p + q * sin_phi * tan_theta + r * cos_phi * tan_theta)
    theta = prev_theta + dt * (q * cos_phi - r * sin_phi)
    psi = prev_psi + dt * (q * sin_phi / cos_theta + r * cos_phi / cos_theta)

    return phi, theta, psi

 

각속도 -> 오일러 변환 모듈을 플로팅함수랑 합치자

 

위 그래프 3개는 각속도

아래 3개는 오일러각

 

z축 회전은 그렇다 쳐도

x, y를 보면 오차가 누적되서 롤 피치각이 기울어진 모양을 하고있음

 

3. 가속도계 값 가져오기

자이로는 됫으니 가속도계 가져올 차례

자이로랑 별차이 없다 이름만 조금 고쳐줌

import scipy.io

accel = scipy.io.loadmat("ArsAccel.mat")
def get_accel():
    ax = accel["fx"]
    ay = accel["fy"]
    az = accel["fz"]

    accel_len = len(ax)
    for i in range(accel_len):
        yield ax[i], ay[i], az[i]

for ax, ay, az in get_accel():
    print(ax, ay, az)


get_accel()

 

이것도 플로팅해보면

잘 나온다.

ax_list = []
ay_list = []
az_list = []

for ax, ay, az in get_accel():
    ax_list.append(ax)
    ay_list.append(ay)
    az_list.append(az)

plt.subplot(311)
ax1 = plt.subplot(3,1,1)
ax2 = plt.subplot(3,1,2)
ax3 = plt.subplot(3,1,3)
ax1.plot(ax_list)
ax2.plot(ay_list)
ax3.plot(az_list)

 

 

 

4. 회전하지 않고, 등속도 운동 상황 가정시 자세 구하기

이제 가속도를 쓰려고 하는데 좀 복잡하게 생김

가속도에는 회전 영향도 있고, 중력 영향도 있음

 

등속운동 중이고, 회전을 안한다 가정시, 위 식을 단순하게 만들어서

fx, fy, fz를 가지고 파이와 세타를 구할 수 있음

 

xyz 가속도로 파이 세타 가져오는 함수 구현하면 이럼

 

from math import asin, acos

def accel_euler(ax, ay, az):
    g = 9.8
    theta = asin( ax / g)
    phi = asin(-ay / (g * cos(theta)))
    return phi, theta

 

이것도 그래프 플로팅 시키면

파이, 세타

롤피치요 니까

아래 2개는 롤각과 피치각을 나타냄

 

import numpy as np

def accel_euler(ax, ay, az):
    g = 9.8
    theta = asin( ax / g)
    phi = asin(-ay / (g * cos(theta)))
    return phi, theta

dt = 0.01

t_list = np.linspace(0, 415, 41500)
ax_list, ay_list, az_list = [], [], []
phi_list, theta_list = [], []


for ax, ay, az in get_accel():
    phi, theta = accel_euler(ax, ay, az)
    ax_list.append(ax)
    ay_list.append(ay)
    az_list.append(az)

    phi_list.append(phi)
    theta_list.append(theta)


plt.subplot(511)
ax1 = plt.subplot(5,1,1)
ax2 = plt.subplot(5,1,2)
ax3 = plt.subplot(5,1,3)
ax1.plot(t_list, ax_list)
ax2.plot(t_list, ay_list)
ax3.plot(t_list, az_list)
ax4 = plt.subplot(5,1,4)
ax5 = plt.subplot(5,1,5)
ax4.plot(t_list, phi_list)
ax5.plot(t_list, theta_list)

 

5. 센서 퓨전을 통한 자세 구하기

앞서 자이로만 가지고 파이 세타 프사이 자세를

가속도계만 가지고 파이 세타 자세를 계산해봄

이번에는 kf로 자이로와 가속도 데이터 센서 퓨전함

 

자이로, 가속도 계 데이터 보면

자이로로 구한 자세는 오차가 누적

가속도계로 구한 자세는 오차 누적되진 않음

=> 자이로 데이터로 자세를 먼저 구하고, 누적되는 오차는 가속도 계로 보정

 

! 오일러각 대신 쿼터니언 사용

X를 파이 세타 프사이로 놓으면 시스템 행렬 A를 만들수가 없다

대신 쿼터니언을 사용하면 각속도 pqr과 쿼터니언을 이런식으로 정리 가능

 

k+1, k 이산 시간에 대해 수정하면 이런식으로 정리해서 A를 구할수 있음

 

 

쿼터니언과 오일러각 사이 관계는 이런식이라고 함

https://powerofsummary.tistory.com/103

 

지금까지 구현한 kf에서는 

시스템 행렬 A가 고정 되어있었는데

지금 시스템 행렬 A는 각속도 pqr에 따라 계속 변해서 이전 구현 내용과는 살짝 다름

이전 kf에서는 z만 넣어줬찌만

이번 kf에는 A, z를 같이 넣는다

 

잠깐 구현 중에 다시 정리한다

1. 각속도 -> 시스템 행렬 A 만듬

2. 가속도계 -> 오일러각 변환 phi, theta 취득 -> 쿼터니언으로 변환하여 z로 사용

 

미완 코드

def kf_euler(A, z):
    X_pred = A @ X
    P_pred = A @ P @ A.T + Q
    K = P_pred @ H.T @ np.linalg.inv(H @ P_pred @ H.T + R)
    X_est = X_pred + K @ (z - H @ X_pred)
    P_est = P_pred - K @ H @ P_pred
    return X_est, P_est

dt = 0.01


X = np.array([[1, 0, 0, 0]]).T
H = np.array([
    [1, 0, 0, 0],
    [0, 1, 0, 0],
    [0, 0, 1, 0],
    [0, 0, 0, 1]
])
Q = 0.0001 * np.eye(4)
R = 10 * np.eye(4)
P = np.eye(4)


for (p, q, r), (ax, ay, az) in zip(get_gyro(), get_accel()):

    A = np.array([
        [0, -p, -q, -r],
        [p, 0, r, -q],
        [q, -r, 0, p],
        [r, q, -p, 0]
    ])
    A = np.eye(4) + dt * 1/2 * A

    phi, theta = accel_euler(ax, ay, az)

 

 

내가 여기서 빠트린게 오일러각으로 쿼터니언 변환을 구현안했다

아까 그림대로 만들면 되는데 되게 복잡하네

 

구현하다보니 kf 결과는 쿼터니언으로 반환하니

알아보게 시각화하려면 쿼터니언을 오일러각으로 다시 바꿔줘야 한다..

이건 엄두가 안나는데 메트랩코드 그대로 파이썬으로 고쳐줌

def quaternion_to_euler(X):
    x1 = X[0][0]
    x2 = X[1][0]
    x3 = X[2][0]
    x4 = X[3][0]

    phi =  atan2( 2*(x3*x4 + x1*x2), 1 - 2*(x2**2 + x3**2) )
    theta = -asin(  2*(x2*x4 - x1*x3))
    psi =  atan2( 2*(x2*x3 + x1*x4), 1 - 2*(x3**2 + x4**2))
    return phi, theta, psi

 

 

아 하다가 겟 자이로, 가속도에서 좀 잘못구현한거같은데

wx[i] 이던걸 뒤에 [0] 인덱싱 추가해줘야 kf 계산에서 잘못안댐

import scipy.io

gyro = scipy.io.loadmat("ArsGyro.mat")
def get_gyro():
    wx = gyro["wx"]
    wy = gyro["wy"]
    wz = gyro["wz"]

    gyro_len = len(wx)
    for i in range(gyro_len):
        yield wx[i][0], wy[i][0], wz[i][0]

for wx, wy, wz in get_gyro():
    print(wx, wy, wz)


get_gyro()


import scipy.io

accel = scipy.io.loadmat("ArsAccel.mat")
def get_accel():
    ax = accel["fx"]
    ay = accel["fy"]
    az = accel["fz"]

    accel_len = len(ax)
    for i in range(accel_len):
        yield ax[i][0], ay[i][0], az[i][0]

for ax, ay, az in get_accel():
    print(ax, ay, az)

get_accel()

 

 

 

 

 

 

 

 

자이로, 가속도계를 이용해서 노이즈를 제거한채 자세 추정이 잘 되었다

 

 

 

 

아래 그림은 아까 자이로 각속도에서 오일러각 계산한 그래프인데

4, 5번째 파이 세타가 오차 누적되서 기울어졌었지만 지금은 가속도 계 덕분에 누적 오차를 제거해서 윗 그림처럼 원하는 결과 얻어내었다.

from math import atan2
from math import pi

def euler_to_quaternion(phi, theta, psi):
    sin_phi = sin(phi / 2)
    cos_phi = cos(phi / 2)
    sin_theta = sin(theta / 2)
    cos_theta = cos(theta / 2)
    sin_psi = sin(psi / 2)
    cos_psi = cos(psi / 2)

    z = np.array([
        [
            cos_phi * cos_theta * cos_psi + sin_phi * sin_theta * sin_psi,
            sin_phi * cos_theta * cos_psi - cos_phi * sin_theta * sin_psi,
            cos_phi * sin_theta * cos_psi + sin_phi * cos_theta * sin_psi,
            cos_phi * cos_theta * sin_psi - sin_phi * sin_theta * cos_psi
        ]
    ]).T
    return z

def quaternion_to_euler(X):
    x1 = X[0][0]
    x2 = X[1][0]
    x3 = X[2][0]
    x4 = X[3][0]

    phi =  atan2( 2*(x3*x4 + x1*x2), 1 - 2*(x2**2 + x3**2) )
    theta = -asin(  2*(x2*x4 - x1*x3))
    psi =  atan2( 2*(x2*x3 + x1*x4), 1 - 2*(x3**2 + x4**2))
    return phi, theta, psi

def kf_euler(A, z):
    X_pred = A @ X
    P_pred = A @ P @ A.T + Q
    K = P_pred @ H.T @ np.linalg.inv(H @ P_pred @ H.T + R)
    X_est = X_pred + K @ (z - H @ X_pred)
    P_est = P_pred - K @ H @ P_pred
    return X_est, P_est

dt = 0.01
t_list = np.linspace(0, 415, 41500)
phi_list, theta_list, psi_list = [], [], []

X = np.array([[1, 0, 0, 0]]).T
H = np.array([
    [1, 0, 0, 0],
    [0, 1, 0, 0],
    [0, 0, 1, 0],
    [0, 0, 0, 1]
])
Q = 0.0001 * np.eye(4)
R = 10 * np.eye(4)
P = np.eye(4)

for (p, q, r), (ax, ay, az) in zip(get_gyro(), get_accel()):

    A = np.array([
        [0, -p, -q, -r],
        [p, 0, r, -q],
        [q, -r, 0, p],
        [r, q, -p, 0]
    ])
    A = np.eye(4) + dt * 1/2 * A
    phi, theta = accel_euler(ax, ay, az)
    z = euler_to_quaternion(phi, theta, 0)
    X, P = kf_euler(A, z)
    phi, theta, psi = quaternion_to_euler(X)

    phi_list.append(phi * 180 / pi)
    theta_list.append(theta * 180 / pi)
    psi_list.append(psi * 180 / pi)
    

plt.subplot(311)
ax1 = plt.subplot(3,1,1)
ax2 = plt.subplot(3,1,2)
ax3 = plt.subplot(3,1,3)
ax1.plot(t_list, phi_list)
ax2.plot(t_list, theta_list)
ax3.plot(t_list, psi_list)

 

+ Recent posts