Buy us Beer
Categories
Pages

Augmented reality on the iPhone using NyARToolkit [w/ code]

nyarrrHi

I saw the stats for the blog a while ago and it seems that the augmented reality topic is hot! 400 clicks/day, that’s awesome!

So I wanted to share with you my latest development in this field – cross compiling the AR app to the iPhone. A job that proved easier than I originally thought, although it took a while to get it working smoothly.

Basically all I did was take NyARToolkit, compile it for armv6 arch, combine it with Norio Namura’s iPhone camera video feed code, slap on some simple OpenGL ES rendering, and bam – Augmented Reality on the iPhone.

Update: Apple officially supports camera video pixel buffers in iOS 4.x using AVFoundation, here’s sample code from Apple developer.

This is how I did it…

I recommend you read my last post on this matter. I have some insights, however superficial, to working with NyARToolkit implementation for C++, that I also use here.

Getting NyARToolkit C++ to compile on iPhone

First of all, I needed to cross-compile NyARToolkit for iPhone’s CPU architecture (Arm), but this was a very simple task – it just compiled off the bat! No tweaking done, what so ever.
But that’s only the beginning, as iPhone apps are built using Objective-C and not C++ (maybe they can, but all the documentation is in obj-c). So I needed to write an Obj-C wrapper around NyARTk to allow my iPhone app to interact with it.

I only needed a very small set of functions out of NyARTk to get Aug.Reality – those that have to do with marker detection. I ended up with a lean API:

@interface NyARToolkitWrapper : NSObject {
	bool wasInit;
}

-(void)initNyARTwithWidth:(int)width andHeight:(int)height;
-(bool)detectMarker:(float[])resultMat;
-(void)setNyARTBuffer:(Byte*)buf;
-(void)getProjectionMatrix:(float[])m;

I also have some functions I used for debugging, and non-optimized stages. The inner works of the wrapper are not very interesting (and you can see them in the code yourself), they are mainly invoking NyARSingleDetectMarker functions.

In the beginning – there was only marker detection

OK, to get AR basically what I need to do is:

  1. initialize NyARTk inner structs
  2. set NyARTk’s RGBA buffer with each frame’s pixles
  3. get the extrinsic parameters of the camera, and draw the OpenGL scene accordingly

This is for full fledged AR, but let me start with a simpler case – detecting the market in a single image read from a file. No OpenGL, no camera. Just reading the file’s pixels data, and feeding it to NyARTk.

Now this is far more simple:

CGImageRef img = [[UIImage imageNamed:@"test_marker.png"] CGImage];
int width = CGImageGetWidth(img);
int height = CGImageGetHeight(img);
Byte* brushData = (Byte *) malloc(width * height * 4);
CGContextRef cgctx = CGBitmapContextCreate(brushData, width, height, 8, width * 4, CGImageGetColorSpace(img), kCGImageAlphaPremultipliedLast);
CGContextDrawImage(cgctx, CGRectMake(0, 0, (CGFloat)width, (CGFloat)height), img);
CGContextRelease(cgctx);

[nyartwrapper initNyARTwithWidth:width andHeight:height];
[nyartwrapper setNyARTBuffer:brushData];
[nyartwrapper detectMarker:ogl_camera_matrix];

First I read the image to UIImage, then get it’s respective CGImage. But what I need are bytes, so I create a temporary CGBitmapContext, draw the image into it and use the context pixel data (allocated by me).

Adding the 3D rendering

This is nice, but nothing is shown to the screen, which sux. So the next step will be to create an OpenGL scene, and draw some 3D using the calibration we now have. To do this I used EAGLView from Apple’s OpenGL ES docs.
This view will setup an environment to draw a 3D scene, by giving you a delegate to do the actual drawing while hiding all the perepherial code (frame buffers… and other creatures you wouldn’t want to meet in a dark 3D alley scene).

All I needed to implement in my code were two functions defined in the protocol:

@protocol _DGraphicsViewDelegate<NSObject>

@required

// Draw with OpenGL ES
-(void)drawView:(_DGraphicsView*)view;

@optional
-(void)setupView:(_DGraphicsView*)view;

@end

‘setupView’ will initialize the scene, and ‘drawView’ will draw each frame. In setupView we’ll have the viewport setting, lighting, generating texture buffers etc., You can see all that in the code, it’s not very interesting…

In drawView we’ll draw the background and the 3D scene. Now this took some trickery. First I though i’ll take the easy route and just have the 3D scene be transparent, draw the view using a simple UIView of some kind, and overlay the 3D over it. I didn’t manage to get it to work, so I took a different path (harder? don’t know) and I decided to paint the background over a 3D plane, in the 3D scene itself, using textures. This is how I did it in all my AR app on other devices.
Now, the camera video feed is 304×400 pixels, and OpenGL textures are best optimized at power-of-2 sizes, so I created a 512×512 texture. But for now we’re talking about a single frame.

const GLfloat spriteTexcoords[] = {0,0.625f,   0.46f,0.625f,   0,0,   0.46f,0,};
const GLfloat spriteVertices[] =  {0,0,0,   1,0,0,   0,1,0   ,1,1,0};

glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glOrthof(0, 1, 0, 1, -1000, 1);
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();

// Sets up pointers and enables states needed for using vertex arrays and textures
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLOAT, 0, spriteVertices);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glTexCoordPointer(2, GL_FLOAT, 0, spriteTexcoords);	

glBindTexture(GL_TEXTURE_2D, spriteTexture);
glEnable(GL_TEXTURE_2D);

glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);

glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glPopMatrix();

Basically, I go into orthographic mode and draw a rectangle with the texture on it, nothing fancy.

Next up – drawing the perspective part of the scene, the part that aligns with the actual camera…

//Load the projection matrix (intrinsic parameters)
glMatrixMode(GL_PROJECTION);
glLoadMatrixf(ogl_projection_matrix);

//Load the "camera" matrix (extrinsic parameters)
glMatrixMode(GL_MODELVIEW);
glLoadMatrixf(ogl_camera_matrix);

glLightfv(GL_LIGHT0, GL_POSITION, lightPosition);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);

glDisable(GL_TEXTURE_2D);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);

glPushMatrix();	

glScalef(kTeapotScale, kTeapotScale, kTeapotScale);

{
        static GLfloat spinZ = 0.0;
        glRotatef(spinZ, 0.0, 0.0, 1.0);
        glRotatef(90.0, 1.0, 0.0, 0.0);
        spinZ += 1.0;
}

glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY);
glVertexPointer(3 ,GL_FLOAT, 0, teapot_vertices);
glNormalPointer(GL_FLOAT, 0, teapot_normals);
glEnable(GL_NORMALIZE);

for(int i = 0; i < num_teapot_indices; i += new_teapot_indicies[i] + 1)
{
        glDrawElements(GL_TRIANGLE_STRIP, new_teapot_indicies[i], GL_UNSIGNED_SHORT, &new_teapot_indicies[i+1]);
}

glPopMatrix();

For this also I learned from Apple’s OpenGL ES docs (find it here). I ended up with this:
Picture 5

Tying it together with the camera

This runs on the simulator, since the camera is not involved just yet. I used it to fix the lighting and such, before moving to the device. But we’re here to get it work on the device, so next I plugged in the code from Norio Nomura.
Some people have asked me to post up a working version of Nomura’s code, so you can get it with the code for this app (scroll down). Nomura was kind enough to make it public under MIT license.

First, I set up a timer to fire in ~11fps, and initialize the camera hook to grab the frames from the internal buffers:

repeatingTimer = [NSTimer scheduledTimerWithTimeInterval:0.0909 target:self selector:@selector(load2DTexFromFile:) userInfo:nil repeats:YES];

ctad = [[CameraTestAppDelegate alloc] init];
[ctad doInit];

And then I take the pixel data and use it for the background texture and the marker detection:

-(void)load2DTexWithBytes:(NSTimer*) timer {
	if([ctad getPixelData] != NULL) {
		CGSize s = [ctad getVideoSize];
		glBindTexture(GL_TEXTURE_2D, spriteTexture);
		glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, s.width, s.height, GL_BGRA, GL_UNSIGNED_BYTE, [ctad getPixelData]);

		if(![nyartwrapper wasInit]) {
			[nyartwrapper initNyARTwithWidth:s.width andHeight:s.height];
			[nyartwrapper getProjectionMatrix:ogl_projection_matrix];

			[nyartwrapper setNyARTBuffer:[ctad getPixelData]];
		}

		[nyartwrapper detectMarker:ogl_camera_matrix];
	}
}

All this happens 11 times per second, so it must be concise.

Video proof time…

Well, looks like we are pretty much done! time for a video…

How did you get the phone to stand still so nicely?

An important issue… when it comes to shooting the phone w/o holding it.
Well I used a little piece of metal that’s used to block the PCI docks in the PC. In hebrew will call these scrap metal “Flakch”s (don’t try to pronounce this at home). I bended it in the middle to create a kind of “leg”, and the ledge to hold the phone already exists.
metal iPhone stand

The code

As promised, here’s the code (I omitted some files whose license is questionable).

That’s all folks!
See you when I get this to work on the Android…
Roy.

  • Share/Bookmark

90 Responses to “Augmented reality on the iPhone using NyARToolkit [w/ code]”

  • Ray Lee:

    Dear Roy,

    I think your work is amazing, however I don’t know how to implement it, actually I just need the marker detect part, so what should I do??

    Ray

  • FlimFlam:

    Brilliant stuff Roy – but I’ve been really struggling today to get this implemented. And it’s get to be in my 3dGraphicsView where I am going wrong. Am I right in using GLGravityView.h / .m – I’ve changed there names to 3dGraphicsView to avoid conflict and then added the protocol at the end of the header file?

    I’ve learnt a lot implementing this so far but seem to be falling at the final hurdle.

  • Roy:

    Keep in mind that when you rename the class to 3DGraphicsView – XCode will automatically change it to _DGraphicsView, since you can’t have a number as the first letter of a class. Perhaps this is the problem.

    Roy.

  • Alex:

    Hello,

    i tried to compile the NyArtToolkit and all your code, but i got only Warnings and 135 Errors, it seem like the cross-compiling doesn’t work. Can you please describe how to compile the NyArtToolkit for the Iphone?

  • FlimFlam:

    I got this working! My issue’s were just my lack of knowledge about delegates and how to implement them correctly, but worked through it and figured it out.

    Now…does anyone know how to get my own marker in there? I’ve created the marker, changed all references to the old patt.hiro to my patt.metro marker and yet it still only looks for the patt.hiro marker?! Anyone got any nuggets of info that might help me out?

    Cheers

  • Fastrak:

    FlimFlam,

    Can you please elaborate on exactly what you did?

    I think I am stuck at the same point you were,

    glView.delegate = self;
    glView.animationInterval = 1.0 / kRenderingFrequency;

    are both giving errors about not a structure or union

  • I’m having the same problem as FlimFlam. I can’t change delegates at runtime. Googled it everywhere , i’ll keep on the net trying to look for an answer, but if you can share some light i’l appreciate it a lot.

  • John:

    Does anyone have a complete example project source? Not just the classes etc..

  • FlimFlam:

    The best advice I can give is to head here for some brilliant tutorials on opengl on the iphone. Read the tutorials, download the source files and you’ll quickly figure out what all this is about and solve the issues you are having:

    http://iphonedevelopment.blogspot.com/2009/05/opengl-es-from-ground-up-table-of.html

  • Alex:

    Hi guys, if anyone got complete source please share it

  • Juliano:

    Hi Roy,

    I’m trying to compile the code, made all the right answers, but I’m getting an error

    “Unknown class NyARToolkitCrossCompileAppDelegate in Interface Builder”

  • Gearge:

    I’m just trying to work through your tutorial on this brilliant website, just getting slightly confused where some of the coding goes, what files they go in, is there any chance u can help?

  • Tarkrat:

    Some source files (specifically NyARToolkitWrapper.m) seem to be written in Objective-C++! It #include’s cstdio, iostream, and fstream and contains try-catch blocks (instead of the #try-#catch blocks in Objective-C), and Xcode is moaning about all of this with compile-errors.
    I thought only Objective-C code could be compiled to run on the iPhone!
    What must I do? I’m very confused! Must I download another compiler?

  • Roy:

    XCode can compile c++ just fine for the iPhone, there’s nothing wrong with the compiler. However it could be outdated, you can try and update to a new version of the iPhone SDK.
    For mixing objective-C and C++ you must create an “.mm” file.

  • Tarkrat:

    Oh I see, so it must be NyARToolkitWrapper.mm with two m’s for Objective-C++ code.
    Now that file compiles, thanks!

  • Gemma:

    Brilliant advice and source thanks ever so much. I have an error I can’t seem to budge

    extra qualification ‘NyARToolkitCPP::NyARDoubleMatrix33::’ on member ‘createArray’

    I have tinkered with the code, and not too sure what the problem might be. Any chance you might no reasons why or how i can get rid of it?

    Also I’m using iPhone OS 3.0 is this tutorial compatible with it?

    Hope to hear back :)
    Gemma

  • Tarkrat:

    Suppose I’ve got my own marker image in a .png-file.
    How must I process this image for use with this program?
    Which file in the data directory is read by the program for recognition?

  • Gemma:

    Hi there, what a great site this is!! It’s fantastic. I just wondered if you could help me. I have 4 errors all to do with:

    extra qualification on a member, for example:

    error: extra qualification ‘NyARToolkitCPP::NyARLabelingLabelStack::’ on member ‘NyARLabelingLabelStack’

    Also is this compatible on iPhone OS 3.1?

    Thanks ever so much for tutorial it’s great! :)

    Gemma

  • Gem:

    Hi there, I just wondered if you could help, I have 4 errors to do with

    extra qualification on member, for example:
    error: extra qualification ‘NyARToolkitCPP::NyARLabelingLabelStack::’ on member ‘NyARLabelingLabelStack’

    Just wondered if you could help?
    Also is this compatible on iphone os 3.1?

    Gem

  • Tarkrat:

    Now another thing. I’m trying to see how I would adapt the code in NyARToolkitWrapper.mm to do multiple marker detection.
    I could construct multiple NyARSingleMarkerDetect objects – one for each marker I’d like to detect – but I think that would be inefficient.
    From other sources I’ve determined that the NyARToolkit also contains an NyARMarkerDetect class. Apparently this class contains a constructor that takes as its parameters an ARParams, an array of Codes, a marker width, number of Codes, and the raster buffer type.
    This class also appears in the source code for this project, but have a look at this class’ declaration:

    class NyARDetectMarker
    {
    public:
    NyARDetectMarker(void);
    virtual ~NyARDetectMarker(void);
    };

    This class doesn’t seem to do anything. What’s going on here?
    How do I do multiple marker detection?

  • Tarkrat:

    Hi Gemma,
    in which source file does this error occur?
    Did you try removing the “NyARToolkitCPP::”-part?

  • Ok, I can confirm I’ve got this working with a static image on my iPhone running OS 3.1.3. However, because the camera loophole was closed, the only authorised method to access the camera is “UIGetScreen” method which, unfortunately, grabs the entire screen, including the OpenGL layer we are drawing, therefore it is pretty useless. There may very well be a hack to get around this but I don’t know it :(

  • Jimmy:

    Hi,

    I’m compiling for OS 3.1.3, I now have 39 errors (started with about 130 errors) left to fix. It looks like 38 of the errors are a variation of the following:

    Extra qualification ‘NyARToolkitCPP::NyARDoubleMatrix33:: on member ‘createarray’

    In xCode I am using C/C++ compiler version GCC 4.2. Any advice on how to remove this error would be great. I’ve googled the issue several times and I still can’t fix it.

    The other error is in NyARToolkitWrapper.mm:
    colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);

    I’m receiving a ‘kCGColorSpaceGenericRGB is unavailable’ error. Yes, I do have the CoreGraphics.framework included in the project.

    Any help would be greatly appreciated.

  • Jimmy:

    Hi, I’m still fighting with this. I’ve got it down to 7 errors now. This is one of the errors.

    In

    glView.delegate = self;
    glView.animationInterval = 1.0 / kRenderingFrequency;

    // I receive the error “request for ‘blah’ in something not a structure or union” for both of those lines.

    The other errors are:

    Extra qualification ‘NyARToolkitCPP::NyARDoubleMatrix33:: on member ‘createarray’

    The other error is in NyARToolkitWrapper.mm:
    colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);

    I’m receiving a ‘kCGColorSpaceGenericRGB is unavailable’ error.

    I’ll keep fighting with this but any suggestions would be greatly appreciated.

  • Jimmy:

    Okay, down to a couple of errors:

    1. Duplicate symbol _spriteVertices (referring to the 3DGraphicsView & NyARToolKitCrossCompileAppDelegate).

    2. glView.delegate = self; (error in NyARToolKitCrossCompileAppDelegate.m)

    Almost there. Again, any help would be appreciated.

  • pablo:

    Hi, could you upload the whole project? I can’t make it work properly, i’ve got tons of errors like the ones described above. thanks

  • Rob:

    I have a ridiculous question.

    How do I start? I’ve created a project with all files from the NyARToolkit-iPhone folder.
    This got me 136 errors and a feeling of been too dumb to figure it out by myself.

    The first error is it cannot find 3DGraphicsView.h.
    Like Duke would say, “Where is it?”

  • Yjnn:

    Where can I get the file 3DGraphicsView.h/m from?

  • Roy:

    Hi Rob
    As I mentioned in the article, the 3DGraphicsView is a copy of Apple Docs’ EAGLView class, here.
    The rest of the errors you’ll have to figure out on your own, but there are many answers already in the comments…
    Roy.

  • Roy:

    Use this and change the name to 3DGraphicsView.

    Roy.

  • Zander Cage:

    hi,
    I had 135 errors initially.i brought it down to 1 error which i cannot understand. Can somebody please help.
    This is the error.

    Line Location Tool:0: Command /Developer/Platforms/iPhoneSimulator.platform/Developer/usr/bin/g++-4.0 failed with exit code 1

  • Zander Cage:

    hi,
    I am getting error because of this line.
    glView.animationInterval = 1.0 / kRenderingFrequency;

    error:request for member ‘animationInterval’ is something not a structure or union.

  • Mark:

    Thanks for the great work and generous post Roy. However, I can’t even get past square one when it comes to compiling the CPP. It sounds like a lot of other people are also getting stuck at this early stage. Do you have an .xcodeproj that can just be loaded in and run/compiled? If not, perhaps you can discuss in more detail how “it just compiled off the bat! No tweaking done, what so ever.” Thanks again!

  • Emilia:

    Hi Roy!

    Great work you have here.
    I was wondering if you planing to update this code for working with the new SDK 4?
    As now is possible to get real video stream from the iPhone. I would like to work on some AR for the iPhone using the new SDK.
    For the moment I have acces to video frames and I also display the video on the window.
    Do you think it s possible to modify your code in order to get the video frame buffer as input device?
    If so, wich classes should I modify? CameraTestAppDelegate?

    Many thanks for sharing your work.

    Best regards.

    Emilia

  • Greensource:

    Hi folks!
    First, Roy thanks for this articles and the web site in general. And aslo congrats for MIT ;)

    Emilia, I’m currently working on AR on iOS too. I get video frame just like you and now I try to make Roy sample code functional with it ;)
    It’s not totally “off the bat” (I don’t know this expression, i’m French ^^) but I try to work on it.

    I let you my email, if we work on the same thing may be we can help each other: g r e e n s o u r c e [at]gmail.com

    Regards,
    Pierre DUCHENE

  • Max:

    hi Roy, Brilliant tutorial for those who are interested in Augmented Reality, far one of the few tutorial websites i can search in the net! Im interested more on android because i have android, and in the country I stay, iPhone is very rare still. So hope u could share the android version if you had done. :) Cheers!

  • Domsou:

    @Zander Cage:

    replacing ‘animationInterval’ by ‘animationFrameInterval’ solved this error in my case.

  • Roger:

    Hi Roy, I managed to correct every error and compile correctly, but when running the simulator displays the following message on the console:
    “Failed to make complete framebuffer object 0″

    Whose operation is in this function “- (BOOL) createFramebuffer” of the library GLGravity

    If someone did the same hope to guide me.
    Greetings.

  • Roger:

    Hello Roy
    That so, by testing and research, I finally achieved correct what the console message “Failed to make complete framebuffer object 0″

    The solution was to see an older version of GLGravityView, and take for example the protocol call, my mistake was to declare the protocol.

    Now my next step is to get to work on my iphone, that the console gives me a 501 error.

    In the list of comments on this post is a solution for this problem, I hope I serve.

    Roy thank you very much, which helped me a lot.

    Sorry for my English.

  • Nish:

    Hello,

    Can anybody send the full source code of this application please ?
    Email : xpert.developer@gmail.com

    Thanks.

Leave a Reply