Please bear with my long question, I am trying to make it as clear as possible.
What i am trying to do is, get the attitude(roll pitch and yaw) when a picture is taken using camera and then save the attitude values to nsuserdefaults. After saving, the orientation is changed and then try to bring the phone to the same attitude the picture was taken by constantly comparing the attitude values(saved and current).
For the purpose of interface, the user interface has 3 dots (one for each attitude parameter)on screen which guide to the correct orientation the picture was taken. On reaching the correct attitude the match flag is shown on screen.
I have been finding the roll, pitch and yaw values as:
CMQuaternion quat = self.motionManager.deviceMotion.attitude.quaternion;
myRoll = radiansToDegrees(atan2(2*(quat.y*quat.w - quat.x*quat.z), 1 - 2*quat.y*quat.y - 2*quat.z*quat.z)) ;
myPitch = radiansToDegrees(atan2(2*(quat.x*quat.w + quat.y*quat.z), 1 - 2*quat.x*quat.x - 2*quat.z*quat.z));
myYaw = radiansToDegrees(2*(quat.x*quat.y + quat.w*quat.z));
When i noticed there is some disparity in the yaw values, i searched and could find from here:link, that
yaw, pitch and roll from a quaternion you will have the same
problem as if you were using just yaw, pitch and roll. You have to use
quaternions EVERYWHERE in your code and forget about yaw, pitch and
roll
So now i guess i have to code everything again... Please could you be so kind to point me to a sample code for using Quaternion for this purpose?
Here is the code i am working on:
In the ViewController.m, within the image:didFinishSavingWithError:
[self.motionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue mainQueue] withHandler:^(CMDeviceMotion *motion, NSError *error) {
CMQuaternion quat = self.motionManager.deviceMotion.attitude.quaternion;
double tempYaw = radiansToDegrees(asin(2*(quat.x*quat.y + quat.w*quat.z)));
double tempRoll = radiansToDegrees(atan2(2*(quat.y*quat.w - quat.x*quat.z), 1 - 2*quat.y*quat.y - 2*quat.z*quat.z)) ;
double tempPitch = radiansToDegrees(atan2(2*(quat.x*quat.w + quat.y*quat.z), 1 - 2*quat.x*quat.x - 2*quat.z*quat.z));
if (savingGyroOrientation == YES) {
NSLog(#"Roll = %f degrees",tempRoll);
NSLog(#"Pitch = %f degrees",tempPitch);
NSLog(#"Yaw = %f degrees",tempYaw);
[self.deviceStatus setDouble:tempRoll forKey:#"DeviceRoll"];
[self.deviceStatus setDouble:tempPitch forKey:#"DevicePitch"];
[self.deviceStatus setDouble:tempYaw forKey:#"DeviceYaw"];
[self.deviceStatus synchronize];
savingGyroOrientation = NO;
checkingGyroOrientation = YES;
self.savingLabel.hidden = YES;
self.startTimerButton.hidden = NO;
}
savingGyroOrientation = NO;
checkingGyroOrientation = YES;
self.savingLabel.hidden = YES;
self.startTimerButton.hidden = NO;
}
if (timerRunning == YES) {
if (checkingGyroOrientation == YES) {
self.takePicButton.hidden = YES;
int xRoll, yPitch, xYaw, yYaw;
// Roll Checking
if (tempRoll >= [self.deviceStatus doubleForKey:#"DeviceRoll"]-1 && tempRoll <= [self.deviceStatus doubleForKey:#"DeviceRoll"]+1 ) {
[self.rollDot setFrame:CGRectMake(150, 195, 20, 20)];
self.rollToR.hidden = YES;
self.rollToL.hidden = YES;
self.rollDot.hidden = NO;
rollOk = YES;
}else{
rollOk = NO;
self.rollDot.hidden = YES;
self.rollToR.hidden = NO;
self.rollToL.hidden = NO;
if (tempRoll - [self.deviceStatus doubleForKey:#"DeviceRoll"] < 0) {
xRoll = 150 + (tempRoll - [self.deviceStatus doubleForKey:#"DeviceRoll"]);
self.rollToR.hidden = YES;
if (xRoll <= 0) {
[self.rollToL setFrame:CGRectMake(0, 195, 20, 20)];
}else if (xRoll>= 300){
[self.rollToL setFrame:CGRectMake(300, 195, 20, 20)];
}else{
[self.rollToL setFrame:CGRectMake(xRoll, 195, 20, 20)];
}
}
if (tempRoll - [self.deviceStatus doubleForKey:#"DeviceRoll"] > 0){
xRoll = 150 + (tempRoll - [self.deviceStatus doubleForKey:#"DeviceRoll"]);
self.rollToL.hidden = YES;
if (xRoll <= 0) {
[self.rollToR setFrame:CGRectMake(0, 195, 20, 20)];
}else if (xRoll>=300){
[self.rollToR setFrame:CGRectMake(300, 195, 20, 20)];
}else{
[self.rollToR setFrame:CGRectMake(xRoll, 195, 20, 20)];
}
}
if (tempRoll - [self.deviceStatus doubleForKey:#"DeviceRoll"] > 180){
xRoll = 150 + (tempRoll - [self.deviceStatus doubleForKey:#"DeviceRoll"]-360);
self.rollToR.hidden = YES;
if (xRoll <= 0) {
[self.rollToL setFrame:CGRectMake(0, 195, 20, 20)];
}else if (xRoll>=300){
[self.rollToL setFrame:CGRectMake(300, 195, 20, 20)];
}else{
[self.rollToL setFrame:CGRectMake(xRoll, 195, 20, 20)];
}
}
if (tempRoll - [self.deviceStatus doubleForKey:#"DeviceRoll"] < -180){
xRoll = 150 + (tempRoll - [self.deviceStatus doubleForKey:#"DeviceRoll"]+360);
self.rollToL.hidden = YES;
if (xRoll <= 0) {
[self.rollToR setFrame:CGRectMake(0, 195, 20, 20)];
}else if (xRoll >= 300){
[self.rollToR setFrame:CGRectMake(300, 195, 20, 20)];
}else{
[self.rollToR setFrame:CGRectMake(xRoll, 195, 20, 20)];
}
}
}
//Pitch Checking
if (tempPitch >= [self.deviceStatus doubleForKey:#"DevicePitch"]-1 && tempPitch <= [self.deviceStatus doubleForKey:#"DevicePitch"]+1) {
[self.pitchDot setFrame:CGRectMake(150, 195, 20, 20)];
self.pitchDot.hidden = NO;
self.pitchToDown.hidden = YES;
self.pitchToUp.hidden = YES;
pitchOk = YES;
}else{
pitchOk = NO;
self.pitchDot.hidden = YES;
self.pitchToDown.hidden = NO;
self.pitchToUp.hidden = NO;
if (tempPitch - [self.deviceStatus doubleForKey:#"DevicePitch"] < 0) {
yPitch = 195+(tempPitch - [self.deviceStatus doubleForKey:#"DevicePitch"]);
// NSLog(#"tempPitch is %0.02f Difference is %0.02f, yPitch is %0.02f",tempPitch, tempPitch-[self.deviceStatus doubleForKey:#"DevicePitch"],195+(tempPitch - [self.deviceStatus doubleForKey:#"DevicePitch"]));
self.pitchToDown.hidden = YES;
if (yPitch <= 0) {
[self.pitchToUp setFrame:CGRectMake(150, 0, 20, 20)];
}else if (yPitch >= 390) {
[self.pitchToUp setFrame:CGRectMake(150, 390, 20, 20)];
}else{
[self.pitchToUp setFrame:CGRectMake(150, yPitch, 20, 20)];
}
}
if (tempPitch - [self.deviceStatus doubleForKey:#"DevicePitch"] > 0){
yPitch = 195+(tempPitch - [self.deviceStatus doubleForKey:#"DevicePitch"]);
// NSLog(#"tempPitch is %0.02f Difference is %0.02f, yPitch is %0.02f",tempPitch, tempPitch-[self.deviceStatus doubleForKey:#"DevicePitch"],195+(tempPitch - [self.deviceStatus doubleForKey:#"DevicePitch"]));
self.pitchToUp.hidden = YES;
if (yPitch <= 0) {
[self.pitchToDown setFrame:CGRectMake(150, 0, 20, 20)];
}else if (yPitch >= 390) {
[self.pitchToDown setFrame:CGRectMake(150, 390, 20, 20)];
}else{
[self.pitchToDown setFrame:CGRectMake(150, yPitch, 20, 20)];
}
}
if (tempPitch - [self.deviceStatus doubleForKey:#"DevicePitch"] < -180){
yPitch = 195+tempPitch - [self.deviceStatus doubleForKey:#"DevicePitch"] + 360;
// NSLog(#"tempPitch is %0.02f Difference is %0.02f, yPitch is %0.02f",tempPitch, tempPitch-[self.deviceStatus doubleForKey:#"DevicePitch"],195+(tempPitch - [self.deviceStatus doubleForKey:#"DevicePitch"]));
// NSLog(#"*yPitch is %d",yPitch);
self.pitchToUp.hidden = YES;
self.pitchToDown.hidden = NO;
if (yPitch <= 0 ) {
[self.pitchToDown setFrame:CGRectMake(150, 0, 20, 20)];
}else if (yPitch >= 390) {
[self.pitchToDown setFrame:CGRectMake(150, 390, 20, 20)];
}else{
[self.pitchToDown setFrame:CGRectMake(150, yPitch, 20, 20)];
}
}
}
if (tempYaw >= [self.deviceStatus doubleForKey:#"DeviceYaw"]-2 && tempYaw <= [self.deviceStatus doubleForKey:#"DeviceYaw"]+2) {
[self.yawDot setFrame:CGRectMake(150, 195, 20, 20)];
self.yawDot.hidden = NO;
self.rotateRight.hidden = YES;
self.rotateLeft.hidden = YES;
yawOk = YES;
}else{
yawOk = NO;
self.yawDot.hidden = YES;
self.rotateRight.hidden = NO;
self.rotateLeft.hidden = NO;
if (tempYaw - [self.deviceStatus doubleForKey:#"DeviceYaw"] < 0 ) {
xYaw = 150+(tempYaw - [self.deviceStatus doubleForKey:#"DeviceYaw"]);
yYaw = 195-1.3*(tempYaw - [self.deviceStatus doubleForKey:#"DeviceYaw"]);
NSLog(#"current yaw is %0.02f Difference is %0.02f",tempYaw, tempYaw-[self.deviceStatus doubleForKey:#"DeviceYaw"]);
NSLog(#"saved Yaw is %0.02f",[self.deviceStatus doubleForKey:#"DeviceYaw"]);
NSLog(#"xYaw is %d, yYaw is %d",xYaw,yYaw);
self.rotateRight.hidden = YES;
if (xYaw <=0 && yYaw >=390) {
[self.rotateLeft setFrame:CGRectMake(0, 390, 20, 20)];
}else{
[self.rotateLeft setFrame:CGRectMake(xYaw, yYaw, 20, 20)];
}
}if (tempYaw - [self.deviceStatus doubleForKey:#"DeviceYaw"] > 0){
xYaw = 150+(tempYaw - [self.deviceStatus doubleForKey:#"DeviceYaw"]);
yYaw = 195-1.3*(tempYaw - [self.deviceStatus doubleForKey:#"DeviceYaw"]);
NSLog(#"current yaw is %0.02f Difference is %0.02f",tempYaw, tempYaw-[self.deviceStatus doubleForKey:#"DeviceYaw"]);
NSLog(#"saved Yaw is %0.02f",[self.deviceStatus doubleForKey:#"DeviceYaw"]);
NSLog(#"*xYaw is %d, yYaw is %d",xYaw,yYaw);
self.rotateLeft.hidden = YES;
if (xYaw >=300 && yYaw <=0) {
[self.rotateRight setFrame:CGRectMake(300, 0, 20, 20)];
}else{
[self.rotateRight setFrame:CGRectMake(xYaw, yYaw, 20, 20)];
}
}
}
if (rollOk == YES && pitchOk == YES && yawOk ==YES) {
self.orientationOkay.hidden = NO;
self.centerCircle.hidden = YES;
self.rollDot.hidden = YES;
self.pitchDot .hidden =YES;
self.yawDot.hidden = YES;
[self.clickTimer invalidate];
self.clickTimer = nil;
self.takePicButton.hidden = NO;
timerRunning = NO;
[self.motionManager stopDeviceMotionUpdates];
[self.deviceStatus removeObjectForKey:#"DeviceRoll"];
[self.deviceStatus removeObjectForKey:#"DevicePitch"];
[self.deviceStatus removeObjectForKey:#"DeviceYaw"];
[self.deviceStatus removeObjectForKey:#"DeviceAngle"];
}else{
self.orientationOkay.hidden = YES;
if (flagger == YES) {
self.centerCircle.hidden = NO;
}
else{
self.centerCircle.hidden = YES;
}
}
}
}else{
self.rotateRight.hidden = YES;
self.rotateLeft.hidden = YES;
self.rollToL.hidden = YES;
self.rollToR.hidden = YES;
self.pitchToDown.hidden = YES;
self.pitchToUp.hidden = YES;
self.rollDot.hidden = NO;
self.pitchDot .hidden =NO;
self.yawDot.hidden = NO;
[self.yawDot setFrame:CGRectMake(0, 390, 20, 20)];
[self.rollDot setFrame:CGRectMake(0, 195, 20, 20)];
[self.pitchDot setFrame:CGRectMake(150, 0, 20, 20)];
}
}];
Please let me know if any further details are needed on this.
Any suggestions or advice is always welcome, :) I am a noob to programming and to ios.
Thanks!!
I think the following things are necessary to manage to task:
First of all you need a good understanding of quaternions (skip this if you already made friends with them). I recommend OpenGL:Tutorials:Using Quaternions to represent rotation or The Matrix and Quaternions FAQ. It helps to bear in mind that (x, y, z) represent the axis to rotate around (not normalised) and w = cos (alpha/2) i.e. stands approximately for the amount of rotation.
As CMQuaternion is just a struct thus it's hard to do all the calculations. Use a full functional quaternion class instead like cocoamath (you need at least Quaternion.h, .m and QuaternionOperation.m from trunk).
Now the basic considerations:
The difference (or sometimes stated as division) between two quaternions being defined as the angular displacement from one orientation to another might be the way to go. It is defined as
d = a-1 * b
So this expresses the delta from the current position to the target position.
Having this delta you need to define the conditions to met for considering the target orientation as reached. My first idea is to use the delta angle. This can be easily retrieved from the w component of the above calculated d quaternion by:
alpha = 2 * arccos (w)
The domain of arccos is restricted but this shouldn't be a problem in this case as we are especially interested in small values.
Maybe it's worth to emphasize that every 3D rotation has two unit quaternion representations, q and -q. This might be confusing but doesn't matter.
Update:
So a bit of pseudo code would look something like:
CMQuaternion cmQ = attitude.quaternion;
// Get an instance of cocoamath's Quaternion for our currently reported quaternion
Quaternion current = [Quaternion initWithRe:(double)cmQ.w i:(double)cmQ.x j:(double)cmQ.y k:(double)cmQ.z];
// the complex conjugate and normalised to be on the safe side
Quaternion inverse = [current inverse];
// build the delta, assuming you have your stored direction as class member targetQuaternion
Quaternion diff = [inverse multiply:targetQuaternion];
float alpha = 2 * acos (diff.Re);
// maxDeltaAngle is your class member variable defining the angle from which you assume the position as restored (radians)
if (fabs (alpha) < maxDeltaAngle) {
// do my fancy camera things
}
Yes it's not that trivial at the beginning. As Ali stated in his answer, visualisation is an important issue as well. My solution just solves the raw maths part.
I am not saying that the following is the solution to your problem, I have no idea how user-friendly this will be but I believe it is worth a shot. I see two issues: how you store the attitude and how you visualize them.
Storage. I would save the attitude either as a quaternion or as a rotation matrix; CMAttitude provides both. (I personally prefer rotation matrices to quaternions as rotation matrices are easier to understand in my opinion. It is just a personal preference.)
Visualization. You are using 3 markers to bring the phone to the same attitude as the saved one. These 3 markers came from yaw, pitch and roll, effectively ruining the stability of your application. Instead of these 3 markers I would visualize the rotation between the saved attitude and the current one. One way of doing it is to project the rotated 3 basis vectors onto the phone's screen. The nice thing about rotation matrices is that they give you exactly this without any extra computation. For example, to visualize the
| 1 0 0 |
| 0 1 0 |
| 0 0 1 |
rotation this rotation matrix represents, I only need to plot 3 arrows from [0, 0, 0] to (i) [1, 0, 0], (ii) [0, 1, 0] and (iii) [0, 0, 1]. That is, I only need to plot either the rows or the columns of the matrix (also depends on what you want whether the rows or the columns are better).
To get the rotation between a saved rotation matrix S and the current rotation matrix C you need to compute CTS (in words: C transpose times S). You have aligned the phone with the saved attitude if your rotation matrix is the one shown above.
An excellent tutorial on rotation matrices is the Direction Cosine Matrix IMU: Theory manuscript.
Another way of visualizing the rotation between the saved and the current attitude is to transform it to angle-axis form. Then, I would project the axis to the phone's screen. The user first aligns the phone with the axis, than needs to rotate around the axis to match the saved attitude. Quaternions basically represent the axis-angle form, although in a nontrivial way.
It requires significant further work on your part to alter this idea according to your needs; this is definitely not a complete answer. Nevertheless, I hope this help a bit.
Im showing some data in a simple bar that charts some values, all is working, but now to build the scale for my Y axis, im having some problems with some basic stuff,
I get the ceiling value for my chart with scaleMaxInt in this case for testing = 900
then according to some simple test, I get for example 9 divisions [plus zero]...
so to make the thing work, I create the int pp=100
- (void) drawScaleLabels:(int)scaleMaxInt
{
//temp division for scale, NOTE WHERE TO USE 9 DIVISIONS!!
int scaleStep = scaleMaxInt/9;
NSLog(#"va ::%d", scaleStep);
// case scaleMax : 0 < scaleMax < 1000
float scaleDiv = 31.5;
for (int i = 0; i<9; i++)
{ // is 8, to 9 only for testing!!
int pp = 100;
self.divScaleLabel = [[[UILabel alloc] initWithFrame:CGRectMake(469, scaleDiv+286, 60, 14)]autorelease];
self.divScaleLabel.font = [UIFont fontWithName:#"FS Albert" size:14];
self.divScaleLabel.textColor = [UIColor whiteColor];
self.divScaleLabel.textAlignment = UITextAlignmentRight;
self.divScaleLabel.text =[NSString stringWithFormat:#"$%d",scaleMaxInt-scaleStep];
self.divScaleLabel.backgroundColor =[UIColor clearColor];
[self.view addSubview:self.divScaleLabel];
scaleDiv = scaleDiv + 31.5;
scaleStep = scaleStep+pp;
}
}
wich shows the list of values in my Y axis: 900 , 800 , 700 ,600... 0
but if I use the result of the int scaleStep = scaleMaxInt/9
it gives me the list but with the value*2
- (void) drawScaleLabels:(int)scaleMaxInt
{
//temp division for scale, NOTE WHERE TO USE 9 DIVISIONS!!
int scaleStep = scaleMaxInt/9;
NSLog(#"va ::%d", scaleStep);
// case scaleMax : 0 < scaleMax < 1000
float scaleDiv = 31.5;
for (int i = 0; i<9; i++)
{ // is 8, to 9 only for testing!!
//int pp = 100;
self.divScaleLabel = [[[UILabel alloc] initWithFrame:CGRectMake(469, scaleDiv+286, 60, 14)]autorelease];
self.divScaleLabel.font = [UIFont fontWithName:#"FS Albert" size:14];
self.divScaleLabel.textColor = [UIColor whiteColor];
self.divScaleLabel.textAlignment = UITextAlignmentRight;
self.divScaleLabel.text =[NSString stringWithFormat:#"$%d",scaleMaxInt-scaleStep];
self.divScaleLabel.backgroundColor =[UIColor clearColor];
[self.view addSubview:self.divScaleLabel];
scaleDiv = scaleDiv + 31.5;
NSLog(#"va ::%d", scaleStep);
scaleStep = scaleStep+scaleStep;
}
}
so Y axis is, 900, 800 , 700, 500 , 100, -700 ...-24700
Im stupidly stuck with this!,
how can i generate the list for Y axis?, with a dynamic value, dependent of scaleMaxInt
that doesn't get confused??
thanks a lot!
You have this in your loop:
scaleStep = scaleStep+scaleStep;
That means you are doubling scaleStep on each pass through the loop. Remove that line. Instead, set self.divScaleLabel.text like this:
self.divScaleLabel.text =[NSString stringWithFormat:#"$%d", scaleMaxInt - scaleStep * i];
I have a struct where I define the size (width, height) of a square, and I don't know why the code doesn't work well. Here's the code that I'm using:
.h
struct size{
int width;
int height;
};
.m
struct size a;
a.width = 508;
a.height = 686;
// I use it here.
Any ideas?
If you want to use Apple provided types, you have:
CGSize for sizes (with width and height)
CGPoint for locations (with x and y)
and CGRect, which combines the two.
Example usage:
CGPoint p;
CGSize s;
CGRect r;
p.x = 1;
p.y = 2;
// or:
p = CGPointMake(1, 2);
s.width = 3;
s.height = 4;
// or:
s = CGSizeMake(3, 4);
r.origin.x = 1;
r.origin.y = 2;
r.size.width = 3;
r.size.height = 4;
// or:
r.origin = p;
r.size = s;
// or:
r = CGRectMake(1, 2, 3, 4);
i'm creating a bunch of textfields. i am stacking them vertical, so i use:
CGRectMake(193, ((i * 45) + 45), 240, 30)
then after each textbox is created, i increment i.
int i = 0
CGRect myRect = CGRectMake(193, ((i * 45) + 45), 240, 30)
UITextField *myTextField01 = [[UITextField alloc] initWithFrame:myRect];
//format and add to view text field
i++;
//create next text field
myRect = CGRectMake(193, ((i * 45) + 45), 240, 30) //can i get rid of this line?
UITextField *myTextField01 = [[UITextField alloc] initWithFrame:myRect];
//format and add to view text field
i++;
i need to reassign the myRect to get i updated to the value of 1.
is there a better way to do this so i don't need to reassign myRect to get the update value of i?
CGRect is a struct, so you can increment the internal fields directly:
CGRect myRect = CGRectMake(193, 45, 240, 30);
UITextField *myTextField01 = [[UITextField alloc] initWithFrame:myRect];
myRect.origin.y += 45;
UITextField *myTextField02 = [[UITextField alloc] initWithFrame:myRect];
myRect.origin.y += 45;
UITextField *myTextField03 = [[UITextField alloc] initWithFrame:myRect];
I would also use a loop and some kind of container (probably NSArray), but that's a side topic ;)
Use myRect = CGRectOffset(myRect, 0, 45); each time. "i" is not used here because you offset the already offset rectangle.
What kind of trick can I use to speed up and improve an image sequence animation?
I'm using the code below, but then just running through the codes below takes a couple of seconds to finish.
Once the animations have been created, my phone seems to get much slower too.
Please enlight.
Thanks,
Tee
NSMutableArray * sequenceArray;
sequenceArray = [[NSMutableArray alloc] init];
int k;
int xPos = 0;
int yPos = 50;
for (k = 1; k <= 48; k++)
{
UIImageView* dynamicView = [[UIImageView alloc] initWithFrame:self.view.frame];
[sequenceArray addObject:dynamicView];
int i;
NSMutableArray *a = [NSMutableArray array];
for (i = 1; i <= 102; i++)
{
[a addObject:[UIImage imageNamed:[NSString stringWithFormat:#"loop2 %d.png", i]]];
}
dynamicView.animationImages = a;
//[a release];
// all frames will execute in 1.75 seconds
dynamicView.animationDuration = 1.6;
// repeat the annimation forever
dynamicView.animationRepeatCount = 0;
CGRect frame1 = dynamicView.frame;
frame1.size.width = 34;
frame1.size.height = 34;
frame1.origin.x = xPos;
frame1.origin.y = yPos;
dynamicView.frame = frame1;
if (k % 8 == 0) {
xPos = 0;
yPos += 40;
}
xPos += 40;
NSLog(#"step last");
// start animating
[dynamicView startAnimating];
// add the animation view to the main window
[self.view addSubview:dynamicView];
[dynamicView release];
}
So, you're creating 48 UIImageViews, each with an animation of the same 102 frames? Am I reading that right?
The first thing that occurs to me is to pull this out of the other loop, since it's loading the same array each time, and just assign it to each view:
int i;
NSMutableArray *a = [NSMutableArray array];
for (i = 1; i <= 102; i++)
{
[a addObject:[UIImage imageNamed:[NSString stringWithFormat:#"loop2 %d.png", i]]];
}
But, really, why are you doing this?