The code is available here
With the advent of iOS4.0 we now have proper access to the real time camera stream and the use of UIGetScreenImage is not allowed anymore.
To access the camera we now use the AV Foundation framework. There's a number of really good resources on how to use this. The WWDC video "Session 409 - Using the Camera with AV Foundation" along with the sample code from that session is a great resource (registered iPhone devs can download all the WWDC videos and sample code from iTunes by clicking here: https://developer.apple.com/videos/wwdc/2010/).
To access the camera we need to add the following frameworks to our application:
- AVFoundation
- CoreVideo
- CoreMedia
// Create the AVCapture Session session = [[AVCaptureSession alloc] init];If you want to show what the camera is seeing then you can create a view preview layer - this is handy if you just want to draw stuff on top of the camera preview.
// create a preview layer to show the output from the camera AVCaptureVideoPreviewLayer *previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:session]; previewLayer.frame = previewView.frame; [previewView.layer addSublayer:previewLayer];In the code above I've assumed that you've created a view called previewView in your view hierarchy and positioned it where you want the camera preview to appear. The preview image will be aspect scaled to fit in the frame you specify. The next step is to get hold of the capture device - here we just ask the OS to give us the default device that supports video. This will normally be the back camera. You can use the AVCaptureDevice class to query what devices are available using the devicesWithMediaType method.
// Get the default camera device AVCaptureDevice* camera = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];We now need to create a capture input with our camera
// Create a AVCaptureInput with the camera device NSError *error=nil; AVCaptureInput* cameraInput = [[AVCaptureDeviceInput alloc] initWithDevice:camera error:&error];And configure the capture output. This a bit more complicated. We allocate an instance of AVCaptureViewDataOutput and set ourself as the delegate to receive sample buffers - our class will need to implements the AVCaptureVideoDataOutputSampleBufferDelegate protocol. We need to provide a dispatch queue to run the output delegate on - you can't use the default queue. We also set our desired pixel format - there are a couple of recommended output formats, CVPixelFormatType_32BGRA on all devices, kCVPixelFormatType_422YpCbCr8 on the 3G device and kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange on the 3GS and 4 devices. In this example we'll just use CVPixelFormatType_32BGRA.
// Set the output
AVCaptureVideoDataOutput* videoOutput = [[AVCaptureVideoDataOutput alloc] init];
// create a queue to run the capture on
dispatch_queue_t captureQueue=dispatch_queue_create("catpureQueue", NULL);
// setup our delegate
[videoOutput setSampleBufferDelegate:self queue:captureQueue];
// configure the pixel format
videoOutput.videoSettings = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedInt:kCVPixelFormatType_32BGRA], (id)kCVPixelBufferPixelFormatTypeKey,
nil];
We can also configure the resolution of the images we want to capture. | Preset | 3G | 3GS | 4 back | 4 front |
| AVCaptureSessionPresetHigh | 400x304 | 640x480 | 1280x720 | 640x480 |
| AVCaptureSessionPresetMedium | 400x304 | 480x360 | 480x360 | 480x360 |
| AVCaptureSessionPresetLow | 400x306 | 192x144 | 192x144 | 192x144 |
| AVCaptureSessionPreset640x480 | NA | 640x480 | 640x480 | 640x480 |
| AVCaptureSessionPreset1280x720 | NA | NA | 1280x720 | NA |
| AVCaptureSessionPresetPhoto | NA | NA | NA | NA |
// and the size of the frames we want [session setSessionPreset:AVCaptureSessionPresetMedium];Finally we need to add the input and outputs to the session and start it running:
// Add the input and output [session addInput:cameraInput]; [session addOutput:videoOutput]; // Start the session [session startRunning];We'll now start to receive images from the capture session on our implementation of didOutputSampleBuffer.
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
To access the video data we need to use some of the functions from CoreVideo and CoreMedia. First we get hold of the image buffer from the sample buffer. We then need to lock the image buffer and then we can access its data. // this is the image buffer CVImageBufferRef cvimgRef = CMSampleBufferGetImageBuffer(sampleBuffer); // Lock the image buffer CVPixelBufferLockBaseAddress(cvimgRef,0); // access the data int width=CVPixelBufferGetWidth(cvimgRef); int height=CVPixelBufferGetHeight(cvimgRef); // get the raw image bytes uint8_t *buf=(uint8_t *) CVPixelBufferGetBaseAddress(cvimgRef); size_t bprow=CVPixelBufferGetBytesPerRow(cvimgRef); .... do something useful with the image here .....With the image data you have a number of options, you can turn it into a UIImage:
CGColorSpaceRef colorSpace=CGColorSpaceCreateDeviceRGB(); CGContextRef context=CGBitmapContextCreate(buf, width, height, 8, bprow, colorSpace, kCGBitmapByteOrder32Little|kCGImageAlphaNoneSkipFirst); CGImageRef image=CGBitmapContextCreateImage(context); CGContextRelease(context); CGColorSpaceRelease(colorSpace); UIImage *resultUIImage=[UIImage imageWithCGImage:image]; CGImageRelease(image);Or you can just process the data directly. The image will be stored in BGRA format, so each row of image data has pixels consisting of 4 bytes each - blue,green,red,alpha, blue,green,red,alpha, blue,green,red,alpha etc...
If you're doing some image processing then you'll probably not bother with creating a UIImage and just go straight for crunching the raw bytes.
I've put together a simple demo project here. The image processing is just a demo - don't try and use if for anything important.
Great Demo for Augmented Reality.
ReplyDeleteThanks a lot.
Excellent!!!!!
ReplyDeleteThx a bunch!!!!!
Hi Chris, what about a marker recognition? Do you have some place to point for that? Have you some snippet code to show?
ReplyDeleteReally Thanks!!
So glad to find this. And I echo Chris's question about marker recognition. Thanks for this.
ReplyDeleteI'm sorry, I meant Giasone's question about marker recognition.
ReplyDelete@Giasone and Alyoshak - there's a pretty good toolkit available here that does what you want: http://www.hitl.washington.edu/artoolkit/. Unfortunately for commercial use you have to pay.
ReplyDeleteWhat you could try doing is identifying the square in the image that contains the marker, pull it out and then pass it through one of the QR libraries - something like http://code.google.com/p/zxing/ would work.
You might want to take a look at my post about Sudoku Grab - there's some information there about pulling out regions from images and perspective correcting them - http://sudokugrab.blogspot.com/2009/07/how-does-it-all-work.html
Chris, Thanks for the toolkit suggestion. By the way, I haven't been able to overcome build errors in ARDemo, all of them stemming from the inability to correctly find header files in AVFoundation framework. I know this is some kind of problem on my end, but did you have any problems with this? The AVFoundation framework is definitely added to the project. I simply downloaded the demo, expanded the compressed files, and opened it with Xcode. Any suggestions? 58 errors of stuff like "AVFoundation/AVCaptureSession.h: No such file or directory".
ReplyDelete@Alyoshak Sounds like it's not finding the AVFoundation framework - are you building for 4.0? it wasn't introduced until then.
ReplyDeleteAlso, make sure you are building for the device - it won't build for or work on the simulator.
ReplyDeleteI figured out a little earlier that I wasn't building for the device, but when I proceeded to build for it, I got even more errors (went from 19 to 58). I finally got rid of the problem, but I don't know why what I did worked. I just created a new, basic project from scratch, added the needed frameworks and added a line of code that tested whether it was finding everything it needed from the AVFoundation framework. It found it fine. So I proceeded to reconstruct the app either cutting and pasting code or importing the needed class files from the ARDemo project. She's working fine now. Sorry I couldn't figure it out. Oh, I'm building for 4.1 to answer your question.
ReplyDeleteChris, I have another question if you have time. I've tried several times while inside captureOutput to make invocations of methods that modify ARView, but to no avail. For example, now I'm trying to change the color of a rectangle being drawn in ARView in response to having identified a certain pattern in the sampleBuffer. I try this by calling a BOOL setter on ARView and then sending it the setNeedsDisplay method so that it will redraw itself. Nothing happens because its drawRect method never gets invoked. But I learned that if I simply put the same two lines of code in the viewWasTapped method, ARView behaves as expected (updates its view with rectangle of changed color). Is there something about the captureOutput method that renders it incapable of communicating with objects of different classes? Can't find anything online about this.
ReplyDelete@AlyoshaK - the captureOutput is running on a thread so cannot access UIKit objects. You need to use performSelectorOnMainThread.
ReplyDeleteHi Chris nice work ! i was wondering how can i make a button that is like the shoot button of the iphone, a button somewhere that when you click it saves the image in a UIImage variable
ReplyDeleteThanks for the great code!
Thanks a lot, very helpful, any tip how to continue to recognize a marker forexample? how to analize the raw data?
ReplyDeleteThanks
Thanks a lot for this demo. To recognition question, try OpenCV library..
ReplyDeleteThank you so much!
ReplyDeleteThank you for this code.
ReplyDelete