When Apple released the 3.1 update to the iPhone operating system they added some extra properties to the UIImagePickerController allowing you to add your own camera overlay and hide the camera controls. Before this developers had to dive into the UIView hierarchy and hack things around as detailed here. Fortunately the new API is a lot simpler and quite a few applications have been released that take advantage of this to do some Augmented Reality.
Unfortunately one thing that is still lacking is access to the real time video feed from the camera. This limits what you can currently do with the iPhone in terms of real time video processing.
There have been various attempts to access the camera, unfortunately they all seem to fall outside of the public SDK so using them in an app destined for the app store is not possible.
However, there was a recent announcement from Apple that they would be allowing applications to use the UIGetScreenImage API call which has previously been a private function. This opens up some possibilities for accessing the real time video feed. Unfortunately the function is an all or nothing screen grab - which makes it a bit difficult to draw data on top of the camera view and access the real time feed.
Fortunately as you can see from the screen shot and the video you can still do some cool stuff. I've used it in my App Sudoku Grab to great effect.
If you look carefully at the screenshot you'll see that I'm actually drawing to the screen using a checkerboard pattern - this gives a good enough image for the user to see, but still allows enough of the camera preview image to show through to be usable. Hopefully this blog post will provide enough information to get you started on implementing you own Augmented Reality app. I've attached a link to a sample application at the end of the post.
So, how does it all work?
The first thing that we need to do any useful image processing is a way to get at the pixels of an image. These two utility functions here give you that:
- Image *fromCGImage(CGImageRef srcImage, CGRect srcRect) {
- Image *result=createImage(srcRect.size.width, srcRect.size.height);
- // get hold of the image bytes
- CGColorSpaceRef colorSpace=CGColorSpaceCreateDeviceGray();
- CGContextRef context=CGBitmapContextCreate(result->rawImage,
- result->width,
- result->height,
- 8,
- result->width,
- colorSpace,
- kCGImageAlphaNone);
- // lowest possible quality for speed
- CGContextSetInterpolationQuality(context, kCGInterpolationNone);
- CGContextSetShouldAntialias(context, NO);
- // get the rectangle of interest from the image
- CGImageRef subImage=CGImageCreateWithImageInRect(srcImage, srcRect);
- // draw it into our bitmap context
- CGContextDrawImage(context, CGRectMake(0,0, result->width, result->height), subImage);
- // cleanup
- CGContextRelease(context);
- CGColorSpaceRelease(colorSpace);
- CGImageRelease(subImage);
- return result;
- }
- CGImageRef toCGImage(Image *srcImage) {
- // generate space for the result
- uint8_t *rgbData=(uint8_t *) calloc(srcImage->width*srcImage->height*sizeof(uint32_t),1);
- // process the greyscale image back to rgb
- for(int i=0; i<srcimage->height*srcImage->width; i++) {
- // no alpha
- rgbData[i*4]=0;
- int val=srcImage->rawImage[i];
- // rgb values
- rgbData[i*4+1]=val;
- rgbData[i*4+2]=val;
- rgbData[i*4+3]=val;
- }
- // create the CGImage from this data
- CGColorSpaceRef colorSpace=CGColorSpaceCreateDeviceRGB();
- CGContextRef context=CGBitmapContextCreate(rgbData,
- srcImage->width,
- srcImage->height,
- 8,
- srcImage->width*sizeof(uint32_t),
- colorSpace,
- kCGBitmapByteOrder32Little|kCGImageAlphaNoneSkipLast);
- // cleanup
- CGImageRef image=CGBitmapContextCreateImage(context);
- CGContextRelease(context);
- CGColorSpaceRelease(colorSpace);
- free(rgbData);
- return image;
- }
- </srcimage->
- typedef struct {
- uint8_t *rawImage; // the raw pixel data
- uint8_t **pixels; // 2D array of pixels e.g. use pixels[y][x]
- int width;
- int height;
- } Image;
- Image *createImage(int width, int height) {
- Image *result=(Image *) malloc(sizeof(Image));
- result->width=width;
- result->height=height;
- result->rawImage=(uint8_t *) calloc(result->width*result->height, 1);
- // create a 2D aray - this makes using the data a lot easier
- result->pixels=(uint8_t **) malloc(sizeof(uint8_t *)*result->height);
- for(int y=0; y<result->height; y++) {
- result->pixels[y]=result->rawImage+y*result->width;
- }
- return result;
- }
- void destroyImage(Image *image) {
- free(image->rawImage);
- free(image->pixels);
- free(image);
- }
- </result->
We can now use these classes to start doing something useful. The first thing we are going to need is a view that can draw using the checkerboard mask.
- - (id)initWithFrame:(CGRect)frame {
- if (self = [super initWithFrame:frame]) {
- // create the mask image
- Image *checkerBoardImage=createImage(self.bounds.size.width, self.bounds.size.height);
- for(int y=0;y<checkerboardimage->height; y+=2) {
- for(int x=0; x<checkerboardimage->width; x+=2) {
- checkerBoardImage->pixels[y][x]=255;
- }
- }
- for(int y=1;y<checkerboardimage->height; y+=2) {
- for(int x=1; x<checkerboardimage->width; x+=2) {
- checkerBoardImage->pixels[y][x]=255;
- }
- }
- // convert to a CGImage
- maskImage=toCGImage(checkerBoardImage);
- // cleanup
- destroyImage(checkerBoardImage);
- }
- return self;
- }
- - (void)drawRect:(CGRect)rect {
- // we're going to draw into an image using our checkerboard mask
- UIGraphicsBeginImageContext(self.bounds.size);
- CGContextRef context=UIGraphicsGetCurrentContext();
- CGContextClipToMask(context, self.bounds, maskImage);
- // do your drawing here
- ////////
- UIImage *imageToDraw=UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
- // now do the actual drawing of the image
- CGContextRef drawContext=UIGraphicsGetCurrentContext();
- CGContextTranslateCTM(drawContext, 0.0, self.bounds.size.height);
- CGContextScaleCTM(drawContext, 1.0, -1.0);
- // very important to switch these off - we don't wnat our grid pattern to be disturbed in any way
- CGContextSetInterpolationQuality(drawContext, kCGInterpolationNone);
- CGContextSetShouldAntialias(drawContext, NO);
- CGContextDrawImage(drawContext, self.bounds, [imageToDraw CGImage]);
- // stash the results of our drawing so we can remove them later
- if(drawnImage) destroyImage(drawnImage);
- drawnImage=fromCGImage([imageToDraw CGImage], self.bounds);
- }
- </checkerboardimage-></checkerboardimage-></checkerboardimage-></checkerboardimage->
- CGContextClipToMask(context, self.bounds, maskImage);
Now in our view controller where we launch the image picker we can use this view as the camera overlay:
- -(IBAction) runAugmentedReality {
- // set up our camera overlay view
- // tool bar - handy if you want to be able to exit from the image picker...
- UIToolbar *toolBar=[[[UIToolbar alloc] initWithFrame:CGRectMake(0, 480-44, 320, 44)] autorelease];
- NSArray *items=[NSArray arrayWithObjects:
- [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil] autorelease],
- [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(finishedAugmentedReality)] autorelease],
- nil];
- [toolBar setItems:items];
- // create the overlay view
- overlayView=[[[OverlayView alloc] initWithFrame:CGRectMake(0, 0, 320, 480-44)] autorelease];
- // important - it needs to be transparent so the camera preview shows through!
- overlayView.opaque=NO;
- overlayView.backgroundColor=[UIColor clearColor];
- // parent view for our overlay
- UIView *parentView=[[[UIView alloc] initWithFrame:CGRectMake(0,0,320, 480)] autorelease];
- [parentView addSubview:overlayView];
- [parentView addSubview:toolBar];
- // configure the image picker with our overlay view
- UIImagePickerController *picker=[[UIImagePickerController alloc] init];
- picker.sourceType = UIImagePickerControllerSourceTypeCamera;
- UIImagePickerControllerSourceTypePhotoLibrary;
- // hide the camera controls
- picker.showsCameraControls=NO;
- picker.delegate = nil;
- picker.allowsImageEditing = NO;
- // and put our overlay view in
- picker.cameraOverlayView=parentView;
- [self presentModalViewController:picker animated:YES];
- [picker release];
- // start our processing timer
- processingTimer=[NSTimer scheduledTimerWithTimeInterval:1/5.0f target:self selector:@selector(processImage) userInfo:nil repeats:YES];
- }
- // and put our overlay view in
- picker.cameraOverlayView=parentView;
- // this is where is all happens
- CGImageRef UIGetScreenImage();
- -(void) processImage {
- // grab the screen
- CGImageRef screenCGImage=UIGetScreenImage();
- // turn it into something we can use
- Image *screenImage=fromCGImage(screenCGImage, overlayView.frame);
- CGImageRelease(screenCGImage);
- // process the image to remove our drawing - WARNING the edge pixels of the image are not processed
- Image *drawnImage=overlayView.drawnImage;
- for(int y=1;y<screenimage->height-1; y++) {
- for(int x=1; x<screenimage->width-1; x++) {
- // if we draw to this pixel replace it with the average of the surrounding pixels
- if(drawnImage->pixels[y][x]!=0) {
- screenImage->pixels[y][x]=(screenImage[y-1][x]+screenImage[y+1][x]+
- screenImage[y][x-1]+screenImage[y][x+1])/4;
- }
- }
- }
- // do something clever with the image here and tell the overlay view to draw stuff
- // simple edge detection and following:
- CGMutablePathRef pathRef=CGPathCreateMutable();
- int lastX=-1000, lastY=-1000;
- for(int y=0; y<screenimage->height-1; y++) {
- for(int x=0; x<screenimage->width-1; x++) {
- int edge=(abs(screenImage->pixels[y][x]-screenImage->pixels[y][x+1])+
- abs(screenImage->pixels[y][x]-screenImage->pixels[y+1][x]))/2;
- if(edge>10) {
- int dist=(x-lastX)*(x-lastX)+(y-lastY)*(y-lastY);
- if(dist>50) {
- CGPathMoveToPoint(pathRef, NULL, x, y);
- lastX=x;
- lastY=y;
- } else if(dist>10) {
- CGPathAddLineToPoint(pathRef, NULL, x, y);
- lastX=x;
- lastY=y;
- }
- }
- }
- }
- // update the overlay view
- [overlayView setPath:pathRef];
- //////////////
- // finished with the screen image
- destroyImage(screenImage);
- }
- </screenimage-></screenimage-></screenimage-></screenimage->
The important lines of code are here. We ask the overlay view for the image it drew to the screen, then we go through each pixel to see if we drew to it and if we did replace it with the average of the surrounding pixels. This way we remove any artefacts from out drawing at the loss of some screen resolution:
- // process the image to remove our drawing - WARNING the edge pixels of the image are not processed
- Image *drawnImage=overlayView.drawnImage;
- for(int y=1;y<screenimage->height-1; y++) {
- for(int x=1; x<screenimage->width-1; x++) {
- // if we draw to this pixel replace it with the average of the surrounding pixels
- if(drawnImage->pixels[y][x]!=0) {
- screenImage->pixels[y][x]=(screenImage[y-1][x]+screenImage[y+1][x]+
- screenImage[y][x-1]+screenImage[y][x+1])/4;
- }
- }
- }
- /screenimage-></screenimage->
16 comments:
nice work and thanks for sharing.that helps me a lot
Thanks for sharing this, I really appreciate it. Incredibly helpful.
Thanks for writing this, I found it very helpful. One suggestion, if you are releasing source code to the public then you should include a software license. Otherwise no one knows the legal ways they can use the code.
Thanks for sharing helpful information.
By the way, I have ported your sample code to C# (MonoTouch). I have written a blog entry how to start augmented reality using MonoTouch. May I have your permission to publish it?
http://blog.reinforce-lab.com/2010/02/monotouchaugmented-reality-how-to.html
Thank you for sharing. This example is invaluable for me.
Neat! Thanks for your work!! ^^
wow great work!
Great job, and thanks for your sharing code!!!
Great job! Thank you for this sharing... web development
Thanks for your sharing code.
and I have one Question.
How convert image file to video file?
Please Help me.
my email address is gilgoona@gmail.com.
thanks for reading.
Thanx a lot! I have been searching for this for ages! Nobody was able to tell me where to start with augmented reality and here it is all together with an example. Great job and thanx a lot once more!
I am so thankful! You are truly a role model for developers!
Wow, this is so cool, thanks for sharing! I have been studying computer imaging and did my thesis on augmented reality some years ago, and has also written a simple sudoku solver in php once upon a time. I got the idea to add those two together in an iPhone app, but after a little googling I can see that you've done this in a much more nice-looking way than I could ever do. Now I have to come up with something else :)
Very useful post!
Very very useful post!
So useful, I bought the app, even though I don't play Soduko anymore... :)
Full Service iPhone + Smart Phone Development
Mobile Kingdoms is ideally positioned to handle all steps of launching a successful app. Choose us for your app's strategy, development, marketing, or all three. We are equally accustomed to handling everything ourselves or collaborating with your organization's existing technology & design resources. Contact us at support@mobilekingdoms.com
Strategy
The Opportunity
* Migrate an existing web service to mobile or start anew
* Leverage the salience of mCommerce, the ubiquity of smart phones, and a real-time communication medium
* Create a plan for app deployment in less than month!
Mobile Brand Reach
* Reaching new demographics
* Cutting through the clutter
* Understanding the audience
The Broader Context
* What are the business objectives?
* How does this fit in mobile today?
* What are the key success metrics?
* Explore other opportunities
Development
Timeline & Planning
* Resource breakdown for app and server components
* Finalizing the scope of the project
* Wire-framing & prototyping for client review and sign off
Coding & UI Design
* Programming and creation of graphical elements in tandem
* Integration with existing or new APIs
* Stellar and intuitive UI that is fully Apple Human Interface Guideline compliant.
Testing and Improvement
* Deploy completed versions of the app on up to 100 iPhones or smart phones for testing
* Stress testing of server side elements
* Last minute app “tweaks” and graphical improvements
Marketing
App Store or Enterprise Deployment
* Leverage natural App Store “SEO”
* Catalyze adoption through mobile ad placement
* Contact relevant PR and media channels
Iterate
* Collect real-time user data including: app use duration, GPS location, and user demographics
* Collaborative moderated feedback mechanisms
* Build loyalty by engaging users directly within applications
Analyze
* Measure and adjust against initial goals
* Daily analytic reports
* Track conversions across systems for comprehensive ROI
http://www.mobilekingdoms.com
Love the sound track to the video, what's it from?
Post a Comment