Как создать CMRotationMatrix на устройствах без гироскопа

Я хочу создать представление дополненной реальности на iPhone. В качестве отправной точки я взглянул на демо-проект Apple pARk. Однако там свойство deviceMotion используется для получения матрицы поворота для преобразования камеры. Но поскольку в deviceMotion используется гироскоп (имеется в iPhone 4 и новее), а я хочу поддерживать и 3GS (фактически, 3GS — мое единственное устройство для разработки), я не могу использовать этот подход. Поэтому я хочу сам создать матрицу вращения, используя данные, доступные с акселерометра и компаса.

К сожалению, у меня не хватает математических способностей, чтобы сделать это самому. При поиске мне показалось, что это наиболее актуально практическое руководство по моей проблеме, но после реализации, похоже, не адаптируется к моей проблеме (представления POI появляются только на мгновение и, по-видимому, больше из-за движения устройства, чем из-за его заголовка; я опубликовал свой метод onDisplayLink ( единственный метод с серьезными изменениями) ниже). Я пытался читать соответствующую математику, но на данный момент я просто недостаточно знаю об этом, чтобы найти подход самостоятельно или найти ошибку в моем коде. Любая помощь, пожалуйста?

Правка: с тех пор я понял, что данные датчиков лучше хранить в формате double, чем в int, и добавил немного сглаживания. Теперь я более четко вижу, как POI, которые должны появляться сбоку при вращении устройства, скорее опускаются сверху. Может быть, это поможет указать на то, что не так.

CMAccelerometerData* orientation = motionManager.accelerometerData;
CMAcceleration acceleration = orientation.acceleration;

vec4f_t normalizedAccelerometer;
vec4f_t normalizedMagnetometer;

xG = (acceleration.x * kFilteringFactor) + (xG * (1.0 - kFilteringFactor));
yG = (acceleration.y * kFilteringFactor) + (yG * (1.0 - kFilteringFactor));
zG = (acceleration.z * kFilteringFactor) + (zG * (1.0 - kFilteringFactor));

xB = (heading.x * kFilteringFactor) + (xB * (1.0 - kFilteringFactor));
yB = (heading.y * kFilteringFactor) + (yB * (1.0 - kFilteringFactor));
zB = (heading.z * kFilteringFactor) + (zB * (1.0 - kFilteringFactor));

double accelerometerMagnitude = sqrt(pow(xG, 2) + pow(yG, 2) + pow(zG, 2));
double magnetometerMagnitude = sqrt(pow(xB, 2) + pow(yB, 2) + pow(zB, 2));

normalizedAccelerometer[0] = xG/accelerometerMagnitude;
normalizedAccelerometer[1] = yG/accelerometerMagnitude;
normalizedAccelerometer[2] = zG/accelerometerMagnitude;
normalizedAccelerometer[3] = 1.0f;

normalizedMagnetometer[0] = xB/magnetometerMagnitude;
normalizedMagnetometer[1] = yB/magnetometerMagnitude;
normalizedMagnetometer[2] = zB/magnetometerMagnitude;
normalizedMagnetometer[3] = 1.0f;

vec4f_t eastDirection;

eastDirection[0] = normalizedAccelerometer[1] * normalizedMagnetometer[2] - normalizedAccelerometer[2] * normalizedMagnetometer[1];
eastDirection[1] = normalizedAccelerometer[0] * normalizedMagnetometer[2] - normalizedAccelerometer[2] * normalizedMagnetometer[0];
eastDirection[2] = normalizedAccelerometer[0] * normalizedMagnetometer[1] - normalizedAccelerometer[1] * normalizedMagnetometer[0];
eastDirection[3] = 1.0f;

double eastDirectionMagnitude = sqrt(pow(eastDirection[0], 2) + pow(eastDirection[1], 2) + pow(eastDirection[2], 2));

vec4f_t normalizedEastDirection;

normalizedEastDirection[0] = eastDirection[0]/eastDirectionMagnitude;
normalizedEastDirection[1] = eastDirection[1]/eastDirectionMagnitude;
normalizedEastDirection[2] = eastDirection[2]/eastDirectionMagnitude;
normalizedEastDirection[3] = 1.0f;

vec4f_t northDirection;

northDirection[0] = (pow(normalizedAccelerometer[0], 2) + pow(normalizedAccelerometer[1],2) + pow(normalizedAccelerometer[2],2)) * xB - (normalizedAccelerometer[0] * xB + normalizedAccelerometer[1] * yB + normalizedAccelerometer[2] * zB)*normalizedAccelerometer[0];
northDirection[1] = (pow(normalizedAccelerometer[0], 2) + pow(normalizedAccelerometer[1],2) + pow(normalizedAccelerometer[2],2)) * yB - (normalizedAccelerometer[0] * xB + normalizedAccelerometer[1] * yB + normalizedAccelerometer[2] * zB)*normalizedAccelerometer[1];
northDirection[2] = (pow(normalizedAccelerometer[0], 2) + pow(normalizedAccelerometer[1],2) + pow(normalizedAccelerometer[2],2)) * zB - (normalizedAccelerometer[0] * xB + normalizedAccelerometer[1] * yB + normalizedAccelerometer[2] * zB)*normalizedAccelerometer[2];
northDirection[3] = 1.0f;

double northDirectionMagnitude;

northDirectionMagnitude = sqrt(pow(northDirection[0], 2) + pow(northDirection[1], 2) + pow(northDirection[2], 2));

vec4f_t normalizedNorthDirection;

normalizedNorthDirection[0] = northDirection[0]/northDirectionMagnitude;
normalizedNorthDirection[1] = northDirection[1]/northDirectionMagnitude;
normalizedNorthDirection[2] = northDirection[2]/northDirectionMagnitude;
normalizedNorthDirection[3] = 1.0f;

CMRotationMatrix r;
r.m11 = normalizedEastDirection[0];
r.m21 = normalizedEastDirection[1];
r.m31 = normalizedEastDirection[2];
r.m12 = normalizedNorthDirection[0];
r.m22 = normalizedNorthDirection[1];
r.m32 = normalizedNorthDirection[2];
r.m13 = normalizedAccelerometer[0];
r.m23 = normalizedAccelerometer[1];
r.m33 = normalizedAccelerometer[2];

transformFromCMRotationMatrix(cameraTransform, &r);

[self setNeedsDisplay];

Когда устройство помещается на стол и примерно (с помощью Compass.app) указывает на север, я регистрирую следующие данные:

Accelerometer: x: -0.016692, y: 0.060852, z: -0.998007
Magnetometer: x: -0.016099, y: 0.256711, z: -0.966354
North Direction x: 0.011472, y: 8.561041, z:0.521807
Normalized North Direction x: 0.001338, y: 0.998147, z:0.060838
East Direction x: 0.197395, y: 0.000063, z:-0.003305
Normalized East Direction x: 0.999860, y: 0.000319, z:-0.016742

Это кажется разумным?

Редактировать 2: я обновил назначение r на такое, которое, по-видимому, ведет меня на полпути к моей цели: когда устройство находится в вертикальном положении, я теперь вижу ориентиры рядом с горизонтальной плоскостью; однако они находятся примерно на 90º по часовой стрелке от ожидаемого местоположения. Кроме того, вывод после движения, предложенного Бетой:

Accelerometer: x: 0.074289, y: -0.997192, z: -0.009475
Magnetometer: x: 0.031341, y: -0.986382, z: -0.161458
North Direction x: -1.428996, y: -0.057306, z:-5.172881
Normalized North Direction x: -0.266259, y: -0.010678, z:-0.963842
East Direction x: 0.151658, y: -0.011698, z:-0.042025
Normalized East Direction x: 0.961034, y: -0.074126, z:-0.266305

person mss    schedule 17.04.2012    source источник
comment
У меня нет возможности протестировать ваш код, но я вижу некоторые возможности. Можете ли вы убедиться, что eastDirection и northDirection работают так, как вы предполагали?   -  person Beta    schedule 18.04.2012
comment
Я не уверен, поэтому я добавил некоторые зарегистрированные данные вместе с правкой выше. Это помогает?   -  person mss    schedule 18.04.2012
comment
Выглядит неплохо; Север +Y, Восток +X. Вы должны убедиться, что когда вы поднимаете северный край, поворачивая его вокруг южного края на 90 градусов, восток по-прежнему +X, а север -Z. Теперь, что вы хотите сделать с матрицей вращения, и как вы узнаете, что она работает?   -  person Beta    schedule 18.04.2012
comment
После выполнения этого подъема северного края я получаю вывод, добавленный в Edit 2 выше, что выглядит нормально, учитывая ваши критерии. Дальнейшие действия показаны здесь в метод drawRect, который я оставил нетронутым. (Я просто не мог использовать код этого класса, связанный с гироскопом.) Используя приведенный выше код, все мои POI появляются на 90º от их ожидаемого местоположения (они находятся за пределами моего окна), поэтому я буду знать, что это работает, когда они совпадают с их местонахождение в реальной жизни.   -  person mss    schedule 19.04.2012
comment
Я разобрался. Спасибо за вашу помощь!   -  person mss    schedule 25.04.2012


Ответы (1)


Получив iPhone 4, я смог сравнить данные, сгенерированные приведенным выше кодом, с выводом данных об ориентации CoreMotion. При этом я обнаружил, что должен присваивать значения моей матрице вращения следующим образом:

CMRotationMatrix r;
r.m11 = normalizedNorthDirection[0];
r.m21 = normalizedNorthDirection[1];
r.m31 = normalizedNorthDirection[2];
r.m12 = 0 - normalizedEastDirection[0];
r.m22 = normalizedEastDirection[1];
r.m32 = 0 - normalizedEastDirection[2];
r.m13 = 0 - normalizedAccelerometer[0];
r.m23 = 0 - normalizedAccelerometer[1];
r.m33 = 0 - normalizedAccelerometer[2];

Это дает примерно аналогичные значения, но, конечно, данные, полученные CoreMotion с использованием гироскопа, намного лучше. Во всяком случае, это отправная точка для разумной поддержки 3GS. Возможно, благодаря какой-то фильтрации можно получить дополнительное качество, но я еще не решил, стоит ли это усилий.

person mss    schedule 25.04.2012