Структура публикации
- Оговорка про крен
- Подготовка GPS-трека
- Как из массива векторов получить углы Крылова-Эйлера
- Имитация показаний гироскопа
- Вектор ускорения свободного падения и направление «на север»
- Имитация показаний акселерометра, компаса и барометра
Для отладки алгоритма, работающего с датчиками инерциальной навигации, может потребоваться имитировать показания этих самых датчиков. Например, вы имеете отладочную последовательность точек пути, имитирующую определённую ситуацию. Вы можете иметь некий GPS-трек, имеющий особенности, или напротив их не имеющий. В моём случае результат полевых испытаний есть, а плата ещё не готова — нужно чем-то заняться.
Оговорка про крен
Стоит сразу отметить, что перемещаясь, 3D точка не даёт нам информации о положении тела относительно оси перемещения. Если представить, что между точками пути натянута нитка, а наш объект — это бусинка, то вдоль нитки она будет двигаться чётко, а вокруг оси движения может свободно вращаться. В качестве направления движения будем использовать вектор скорости и будем помнить оговорку про крен в результате.
Подготовка GPS-трека
Если у вас в качестве исходных данных GPS-трек, то нужно его сначала подготовить. Требуется преобразовать имеющийся файл к формату, из которого можно получить данные. Я преобразовывал к GPX (так как внутри это XML).
<trkpt lat="12.345678" lon="87.654321">
<ele>839</ele>
<time>2013-05-09T11:24:28.776Z</time>
<extensions>
<mytracks c="194.9" s="9.25" />
</extensions>
</trkpt>
. . .
<trkpt lat="12.345678" lon="87.654321">
<ele>837</ele>
<time>2013-05-09T11:24:31.779Z</time>
<extensions>
<mytracks c="195.7" s="8.68" />
</extensions>
</trkpt>
Далее берём любую доступную БД (например MySQL), создаём табличку и заполняем её данными из полученного XML. Формат XML может отличаться — главное найти широту, долготу, высоту и время. Создаём первую табличку, например 'xml_src'. Все столбцы для простоты загрузки делаем строковыми.
Немного причешем данные. Для удобства создадим вторую табличку, например 'points'. Код для MySQL:
insert into points (lat,lon,h,dt)
SELECT cast(xx.lat AS DECIMAL(11, 6))
, cast(xx.lon AS DECIMAL(11, 6))
, cast(xx.ele AS DECIMAL(11, 6))
, cast(replace(replace(xx.`time`, "T", " "), "Z", "") AS DATETIME)
FROM
xml_src AS xx;
В результате имеем следующее:
Затем переводим широту и долготу в метры (см. статью «Занимательная геодезия» или используем средства БД, например в MSSQL см. метод ShortestLineTo). Конвертировать в метры можно следующим образом. Мы считаем что координаты первой точки равны X = 0, Y = 0. Координаты каждой последующей точки считаем относительно первой. Определяем расстояние между точками сначала по вертикали, затем по горизонтали в метрах. Функция для рассчёта растояния есть в статье «Определение расстояния между географическими точками в MySQL».
Время переводим в секунды так, чтобы в первой строке получилось 0 секунд (просто вычитаем значение первой строки из остальных).
FUNCTION geodist(src_lat DECIMAL(9, 6),
src_lon DECIMAL(9, 6),
dst_lat DECIMAL(9, 6),
dst_lon DECIMAL(9, 6)
)
RETURNS decimal(11,3)
DETERMINISTIC
BEGIN
SET @dist := 6371000 * 2 * asin(sqrt(
power(sin((src_lat - abs(dst_lat)) * pi() / 180 / 2), 2) +
cos(src_lat * pi() / 180) *
cos(abs(dst_lat) * pi() / 180) *
power(sin((src_lon - dst_lon) * pi() / 180 / 2), 2)
));
RETURN @dist;
END
Затем выберем начальную точку и используем её координаты в запросе. Мы будем мерить расстояние от этой точки до каждой следующей по вертикали и горизонтали в метрах.
INSERT INTO track (x, y, z, dt)
SELECT if(42.302929 > pp.lat, 1, -1) * geodist(42.302929, 18.891985, pp.lat, 18.891985)
, if(18.891985 > pp.lon, -1, 1) * geodist(42.302929, 18.891985, 42.302929, pp.lon)
, h
, dt
FROM
points AS pp;
В результате имеем следующее:
Теперь нам нужно получить координаты точки в равные промежутки времени. То, что вторая точка появились через 4 секунды после первой, третья через 2 секунды, а следующая через секунду – нас так не устраивает. Мы же собираемся имитировать датчики? А они измеряют значения в равные промежутки времени.
Для получения координат точки в равные промежутки времени используем интерполяцию. В качестве инструмента интерполяции используем одномерный кубический сплайн. У меня под рукой был Excel, я в нём макрос написал (см. спойлер). На этом этапе мы решаем с какой частотой будет работать каждый «датчик». Например, все «датчики» будут давать значения 10 раз в секунду. То есть интервал между измерениями равен 0,1 секунды.
Public Sub interpolate()
'------------------------
Dim i As Integer
Const start_n As Integer = 0
Const n As Integer = 1718
Dim src_x(n) As Double
Dim src_y(n) As Double
Dim spline_x(n) As Double
Dim spline_a(n) As Double
Dim spline_b(n) As Double
Dim spline_c(n) As Double
Dim spline_d(n) As Double
For i = start_n To n - 1
spline_x(i) = Application.ActiveWorkbook.ActiveSheet.Cells(i + 1, 1).Value
spline_a(i) = Application.ActiveWorkbook.ActiveSheet.Cells(i + 1, 2).Value
src_x(i) = spline_x(i)
src_y(i) = spline_a(i)
Next
spline_c(0) = 0
Dim alpha(n - 1) As Double
Dim beta(n - 1) As Double
Dim a As Double
Dim b As Double
Dim c As Double
Dim F As Double
Dim h_i As Double
Dim h_i1 As Double
Dim z As Double
Dim x As Double
alpha(0) = 0
beta(0) = 0
For i = start_n + 1 To n - 2
h_i = src_x(i) - src_x(i - 1)
h_i1 = src_x(i + 1) - src_x(i)
If (h_i = 0) Or (h_i1 = 0) Then
MsgBox ("ОШИБКА! Строка " + CStr(i + 1) + " Нет изменения по координате X! Это одномерный сплайн!")
Exit Sub
End If
a = h_i
c = 2 * (h_i + h_i1)
b = h_i1
F = 6 * ((src_y(i + 1) - src_y(i)) / h_i1 - (src_y(i) - src_y(i - 1)) / h_i)
z = (a * alpha(i - 1) + c)
alpha(i) = -b / z
beta(i) = (F - a * beta(i - 1)) / z
Next
spline_c(n - 1) = (F - a * beta(n - 2)) / (c + a * alpha(n - 2))
For i = n - 2 To start_n + 1 Step -1
spline_c(i) = alpha(i) * spline_c(i + 1) + beta(i)
Next
For i = n - 1 To start_n + 1 Step -1
h_i = src_x(i) - src_x(i - 1)
spline_d(i) = (spline_c(i) - spline_c(i - 1)) / h_i
spline_b(i) = h_i * (2 * spline_c(i) + spline_c(i - 1)) / 6 + (src_y(i) - src_y(i - 1)) / h_i
Next
'-------------------------------
' my
Dim dx As Double
Dim j As Integer
Dim k As Integer
Dim y As Double
row_num = 1
For x = 0 To 3814 Step 0.1
i = 0
j = n - 1
Do While i + 1 < j
k = i + (j - i) / 2
If x <= spline_x(k) Then
j = k
Else
i = k
End If
Loop
dx = x - spline_x(j)
y = spline_a(j) + (spline_b(j) + (spline_c(j) / 2 + spline_d(j) * dx / 6) * dx) * dx
Application.ActiveWorkbook.ActiveSheet.Cells(row_num, 3).Value = x
Application.ActiveWorkbook.ActiveSheet.Cells(row_num, 4).Value = y
row_num = row_num + 1
Next
End Sub
Интерполируем парами:
- Время – X
- Время – Y
- Время – Z
В итоге получается таблица с координатами точек «объекта», в котором находятся наши «датчики». Временной интервал между точками составляет 0,1 секунды. Время появления координат конкретной точки вычисляется по формуле t = n / 10, где n — это номер строки.
Как из массива векторов получить углы Крылова-Эйлера
Возьмём перемещение носа самолёта из моей предыдущей статьи:
Координаты точки, означающей нос равны:
Давайте определим все три поворота. Для этого получим ось первого вращения v и угол первого поворота alf вокруг оси. Пусть точки пути – это вершины векторов. Тогда ось получим путём умножения соседних векторов.
v12 = v1 * v2 = (1; 0; 0) * (0; 1; 0) = (0; 0; 1)
Угол вычисляется следующим образом:
Public Function vectors_angle(v1 As TVector, v2 As TVector) As Double
v1 = normal(v1)
v2 = normal(v2)
vectors_angle = Application.WorksheetFunction.Acos(v1.x * v2.x + v1.y * v2.y + v1.z * v2.z)
End Function
Alf12 = 90. Теперь создаём кватернион на основе полученных осей и углов:
Public Function create_quat(rotate_vector As TVector, rotate_angle As Double) As TQuat
rotate_vector = normal(rotate_vector)
create_quat.w = Cos(rotate_angle / 2)
create_quat.x = rotate_vector.x * Sin(rotate_angle / 2)
create_quat.y = rotate_vector.y * Sin(rotate_angle / 2)
create_quat.z = rotate_vector.z * Sin(rotate_angle / 2)
End Function
Получаем первый кватернион:
(w=0,7071; x=0; y=0; z=0,7071)
Из кватерниона получим компоненты поворота:
sqw = w * w
sqx = x * x
sqy = y * y
sqz = z * z
bank = atan2(2 * (w * x + y * z), 1 - 2 * (sqx + sqy))
altitude = Application.WorksheetFunction.Asin(2 * (w * y - x * z))
heading = atan2(2 * (w * z + x * y), 1 - 2 * (sqy + sqz))
Результат определения первого поворота: курс = 90, тангаж = 0, крен = 0.
Остальные повороты мы так посчитать не можем, так как после первого поворота локальная система координат самолёта перестала совпадать с глобальной системой координат. Для определения второго поворота от положения носа самолёта №2 к положению №3 нужно сначала отменить первый поворот, то есть вернуть систему отчёта на место и вместе с ней развернуть новый результирующий вектор. Для этого требуется получить обратный кватернион первого разворота и применить его на вектора №2 и №3 (получение обратного кватерниона и поворот вектора кватернионом – см. в статье).
Обратный кватернион первого разворота (поворот против часовой вдоль оси Z):
(w=0,7071; x=0; y=0; z=-0,7071)
После применения данного кватерниона второй и третий вектора равны:
v2 = (x=1; y=0; z=0)
v3 = (x=0; y=0; z=-1)
Когда локальная система координат совмещена с глобальной, можно вышеописанным способом вычислить второй кватернион и компоненты второго поворота:
(w=0,7071; x=0; y=0; z=-0,7071),
курс = 0, тангаж = 90, крен = 0.
Дальше мы должны запоминать совершённые развороты в локальной системе координат. Для этого заведём отдельный кватернион и в него будем умножать кватернионы поворотов:
Затем из кватерниона серии совершённых разворотов будем получать обратный кватернион для совмещения систем отчёта.
Подведём итог. Чтобы получить компоненты поворотов по списку векторов, обозначающих направление движения, требуется выполнить следующее:
' кватернион "нет поворота"
q_mul.w = 1
q_mul.x = 0
q_mul.y = 0
q_mul.z = 0
For row_n = M To N
' текущее положение поворачиваемого вектора
v1.x = Application.ActiveWorkbook.ActiveSheet.Cells(row_n - 1, 1).Value
v1.y = Application.ActiveWorkbook.ActiveSheet.Cells(row_n - 1, 2).Value
v1.z = Application.ActiveWorkbook.ActiveSheet.Cells(row_n - 1, 3).Value
' следующее целевое положение вектора
v2.x = Application.ActiveWorkbook.ActiveSheet.Cells(row_n, 1).Value
v2.y = Application.ActiveWorkbook.ActiveSheet.Cells(row_n, 2).Value
v2.z = Application.ActiveWorkbook.ActiveSheet.Cells(row_n, 3).Value
' совмещаем системы отчёта для чего разворачиваем вектора
q_inv = myMath.quat_invert(q_mul) ' получение обратного кватерниона
v1 = myMath.quat_transform_vector(q_inv, v1) ' разворот вектора кватернионом
' в этом примере для всех шагов получим (1; 0; 0) – его исходное положение
v2 = myMath.quat_transform_vector(q_inv, v2) ' разворот вектора кватернионом
' ищем кватернион нового разворота
r12 = myMath.vecmul(v1, v2) ' умножение векторов
alf = myMath.vectors_angle(v1, v2) ' получаем угол между векторами
q12 = myMath.create_quat(r12, alf) ' создаём кватернион на основе оси и угла разворота
' получаем компоненты углов
ypr = myMath.quat_to_krylov(q12)
Application.ActiveWorkbook.ActiveSheet.Cells(row_n, 4).Value = ypr.heading
Application.ActiveWorkbook.ActiveSheet.Cells(row_n, 5).Value = ypr.altitude
Application.ActiveWorkbook.ActiveSheet.Cells(row_n, 6).Value = ypr.bank
' добавляем поворот в серию
q_mul = myMath.quat_mul_quat(q_mul, q12) ' умножение кватернионов
Next
В данном макросе для Excel-я вектора считываются с первых трёх столбиков. Результат пишется в 4, 5, 6 столбик.
Имитация показаний гироскопа
Как вы понимаете, координаты точек в качестве входных данных не годятся — нам нужны вектора скорости.
Получить скорость и ускорение из подготовленных данных легко – разница между соседними строчками таблицы координат точек – это скорость, разница между соседними строчками скорости – это ускорение. Только нужно помнить про частоту замеров. В данном случае у нас 10 «измерений» в секунду. Это значит, чтобы получить, например, скорость в метрах в секунду, нужно значение соответствующей ячейки умножить на 10.
Берём вектора скорости и получаем компоненты поворота.
Теперь про гироскоп. Гироскоп показывает угловую скорость. Поэтому берём разницу между соседними значениями углов и получаем угловую скорость.
Кроме получения угловой скорости, нужно ещё прибавить шум. В шуме гироскопа присутствует два ощутимых компонента — собственный шум датчика (равномерное распределение) и влияние вращения земли (если ваш «датчик» не в космосе). Уровень собственного шума датчика описан в спецификации (изучаем datasheet на имитируемый датчик). Мой датчик имеет разрешение 16 бит и имеет 4 режима измерения. У каждого режима свой максимум: у первого ±250º/сек,… у четвёртого ±2000º/сек. Чувствительность для первого режима 131 LSB/(º/s). Чтобы посчитать это в градусах, нужно воспользоваться формулой:
Чувствительность = Чувствительность_LSB * Максимум / Разрешение = 131 * ±250 / (2 ^ 16) = 131 * ±250 / 65536 = ±0,49972534 º/сек.
То есть величина шума в пределах одного градуса. Формула для Excel-я:
=(СЛЧИС()-0,5)/N, где N — это число имитируемых «измерений» в секунду.
Земля вращается со скоростью 15 градусов в час. Это 0,0041667 градусов в секунду, что сильно меньше погрешности измерения. Забавы ради можно рассчитать и это.
Предположим, что ось вращения Земли совпадает с осью Z. Предположим также, что тело находится на экваторе и ось Y тела ориентировано строго на север (по касательной). Тогда ось Х тела совпадает с касательной направления вращения. В этом случае ось вращения Земли и тела вместе с ним совпадает с осью Z тела. Зрительно представим смещение по широте нашего тела к северу. Тогда ось вращения повернётся вокруг оси Х по часовой стрелке на число градусов, равное значению новой широты. При смещении в северное полушарие — это знак плюс, в южное — знак минус. Когда тело свободно ориентировано в пространстве, нам поможет вектор ускорения свободного падения — g.
Для вычисления оси вращения Земли в локальной системе координат нужно:
- Умножить вектор g на вектор показания компаса «на север».
- Создать кватернион на основе получившегося вектора и угла. В качестве значения угла берём широту.
- Развернуть получившимся кватернионом вектор компаса «на север».
Для получения значений разворота нашего тела планетой в момент каждого замера делаем:
- Вычисляем вектор оси вращения планеты используя текущую широту из исходных данных.
- Вычисляем на сколько повернулась планета с момента последнего измерения. В нашем случае при измерении 10 раз в секунду это будет 0,0041667 / 10 = 0,00041667.
- Строим кватернион на основе вектора оси и угла разворота.
- Получаем три компоненты разворота (курс, тангаж, крен) и прибавляем к показаниям «датчика».
В общем в итоге всего получим таблицу:
Последние три столбца — это «показания гироскопа».
Вектор ускорения свободного падения и направление «на север»
Предположим, что есть единичный вектор С, который совпадает с касательной к меридиану и направлением «юг» -> «север». Пусть ось Y нашей глобальной системы координат совпадает с вектором С = (0, 1, 0). А вектор ускорения свободного падения g направлен вдоль оси Z, но в противоположную сторону. g = (0, 0, -1). Тогда, если явно указать вектор скорости тела в начальный момент времени как Н = (1, 0, 0), то все развороты, полученные на основе вращений вектора скорости будут применимы и для вращений векторов g и С. Вращать эти вектора нужно обратным кватернионом. Обратный от кватерниона, который накапливает все совершённые повороты вектора скорости (q_mul).
Достаточно добавить пару строк в процедуру расчёта углов поворота. Теперь она выглядит так:
Public Sub calc_all()
Dim v1 As myMath.TVector
Dim v2 As myMath.TVector
Dim r12 As myMath.TVector
Dim m12 As myMath.TMatrix
Dim q12 As myMath.TQuat
Dim q_mul As myMath.TQuat
Dim q_inv As myMath.TQuat
Dim ypr As myMath.TKrylov
Dim alf As Double
Dim row_n As Long
Dim g As myMath.TVector
Dim nord As myMath.TVector
g.x = 0
g.y = 0
g.z = -1
nord.x = 0
nord.y = 1
nord.z = 0
q_mul.w = 1
q_mul.x = 0
q_mul.y = 0
q_mul.z = 0
For row_n = 3 To 38142
v1.x = Application.ActiveWorkbook.ActiveSheet.Cells(row_n - 1, 1).Value
v1.y = Application.ActiveWorkbook.ActiveSheet.Cells(row_n - 1, 2).Value
v1.z = Application.ActiveWorkbook.ActiveSheet.Cells(row_n - 1, 3).Value
v2.x = Application.ActiveWorkbook.ActiveSheet.Cells(row_n, 1).Value
v2.y = Application.ActiveWorkbook.ActiveSheet.Cells(row_n, 2).Value
v2.z = Application.ActiveWorkbook.ActiveSheet.Cells(row_n, 3).Value
q_inv = myMath.quat_invert(q_mul)
v1 = myMath.quat_transform_vector(q_inv, v1)
v2 = myMath.quat_transform_vector(q_inv, v2)
g = myMath.quat_transform_vector(q_inv, g)
nord = myMath.quat_transform_vector(q_inv, nord)
r12 = myMath.vecmul(v1, v2)
alf = myMath.vectors_angle(v1, v2)
q12 = myMath.create_quat(r12, alf)
ypr = myMath.quat_to_krylov(q12)
Application.ActiveWorkbook.ActiveSheet.Cells(row_n, 4).Value = ypr.heading
Application.ActiveWorkbook.ActiveSheet.Cells(row_n, 5).Value = ypr.altitude
Application.ActiveWorkbook.ActiveSheet.Cells(row_n, 6).Value = ypr.bank
Application.ActiveWorkbook.ActiveSheet.Cells(row_n - 1, 7).Value = g.x
Application.ActiveWorkbook.ActiveSheet.Cells(row_n - 1, 8).Value = g.y
Application.ActiveWorkbook.ActiveSheet.Cells(row_n - 1, 9).Value = g.z
Application.ActiveWorkbook.ActiveSheet.Cells(row_n - 1, 10).Value = nord.x
Application.ActiveWorkbook.ActiveSheet.Cells(row_n - 1, 11).Value = nord.y
Application.ActiveWorkbook.ActiveSheet.Cells(row_n - 1, 12).Value = nord.z
q_mul = myMath.quat_mul_quat(q_mul, q12)
Next
End Sub
Первые три столбца — вектор скорости, с явно указанным направлением в первой точке. Это входные данные. Затем три столбца — расчётные углы поворота. Далее вектор направления «на север» и вектор ускорения свободного падения. И наконец три столбика — это имитируемые показания гироскопа.
Имитация показаний акселерометра, компаса и барометра
Так как у нас всё движение происходит вдоль вектора скорости, то и ускорение у нас направлено так же вдоль вектора скорости — это значит вдоль оси X. Показания датчика ускорения складываются из трёх ощутимых компонентов: ускорение свободного падения, шумы и собственно ускорение тела:
a = (x = length(a), 0, 0);
A = a + (9,8 / N) * g + rnd,
где a — расчётный по входным данным вектор ускорения,
N — количество имитируемых измерений в секунду,
g — вектор ускорения свободного падения,
rnd — шум.
Величина шума снова зависит от имитируемого датчика. Для расчёта смотрим в спецификацию и используем ту же формулу:
Чувствительность = Чувствительность_LSB * Максимум / Разрешение = 16,384 * ±2 / 65536 = ±0,0005 g = ±0,0049 м/с2.
Тогда можно немножко ухудшить результат и выразить имитируемые показания датчика ускорения простыми формулами:
Ax = length(a) + gx * 9,8 / N + random(0,01) — 0,005
Ay = gy * 9,8 / N + random(0,01) — 0,005
Az = gz * 9,8 / N + random(0,01) — 0,005
Диапазон измерений компаса в спецификациях дают в Теслах. Магнитное поле Земли будет на уровне 0,00005 T = 50 uT. Посчитаем чувствительность по той же формуле:
Чувствительность = 15uT * ±4800uT / 65536 = ±1uT.
Это даёт разброс в пределах 4%. Поскольку показания компаса так или иначе приводятся к вектору «на север», то в качестве показаний компаса можно просто взять уже рассчитанный вектор и «ухудшить» каждую его составляющую на 4%:
mx = Cx + random(2) — 1
my = Cy + random(2) — 1
mz = Cz + random(2) — 1.
С барометром всё ещё проще. Их показания приводятся к высоте в метрах, разброс обычно в пределах метра (см. спецификацию, бывает меньше). Если нужны прям показания датчика в Па — см. барометрическую формулу. А так
h = Z + random(1) — 0,5.
Вот собственно и всё. Кому понравилось — не забывайте ставить плюсики. Если хотите статью на тему обратной задачи (от датчиков к точкам пути) — пишите в комментариях.
Автор: 1div0