13

I am trying to write an iPhone application which will do some real-time camera image processing. I used the example presented in the AVFoundation docs as a starting point: setting a capture session, making a UIImage from the sample buffer data, then drawing an image at a point via -setNeedsDisplay, which I call on the main thread.

This works, but it is fairly slow (50 ms per frame, measured between -drawRect: calls, for a 192 x 144 preset) and I've seen applications on the App Store which work faster than this.
About half of my time is spent in -setNeedsDisplay.

How can I speed up this image processing?

Brad Larson
  • 170,088
  • 45
  • 397
  • 571
grunge fightr
  • 1,360
  • 2
  • 19
  • 38
  • 1
    If you need fast image processing such as filtering the image then using OpenGL would be your best bet. See the answer to this post [link](http://stackoverflow.com/questions/5156872/how-to-apply-filters-to-avcapturevideopreviewlayer). – Steve McFarlin Mar 09 '11 at 18:50
  • 1
    @James - They aren't really asking about how to do recognition or interpretation of the images here, but how to speed up the processing and display of them. OpenCV is not necessarily the most performant approach for processing images, and it doesn't address the bottleneck of displaying the processed frames to the screen. – Brad Larson Mar 09 '11 at 19:28

2 Answers2

22

As Steve points out, in my answer here I encourage people to look at OpenGL ES for the best performance when processing and rendering images to the screen from the iPhone's camera. The reason for this is that using Quartz to continually update a UIImage onto the screen is a fairly slow way to send raw pixel data to the display.

If possible, I encourage you to look to OpenGL ES to do your actual processing, because of how well-tuned GPUs are for this kind of work. If you need to maintain OpenGL ES 1.1 compatibility, your processing options are much more limited than with 2.0's programmable shaders, but you can still do some basic image adjustment.

Even if you're doing all of your image processing using the raw data on the CPU, you'll still be much better off by using an OpenGL ES texture for the image data, updating that with each frame. You'll see a jump in performance just by switching to that rendering route.

(Update: 2/18/2012) As I describe in my update to the above-linked answer, I've made this process much easier with my new open source GPUImage framework. This handles all of the OpenGL ES interaction for you, so you can just focus on applying the filters and other effects that you'd like to on your incoming video. It's anywhere from 5-70X faster than doing this processing using CPU-bound routines and manual display updates.

Community
  • 1
  • 1
Brad Larson
  • 170,088
  • 45
  • 397
  • 571
  • i am ALSO asking if the way i am doing it - getting UIImage from SampleBuffer through CGImage (like the examples in avfoundation) and then calling performSelectorOnMainThread with setNeedsDisplay (then drawImageAtPoint) is CORRECT (?) - drawing takes about 30 ms and performSelector also 30 ms so i get <20 fps for no processing and minimal camera resolution - maybe i do something wrong - especially i am not sure about this performSelector code – grunge fightr Mar 10 '11 at 09:18
  • @user641443 - Your process of updating your image view is correct, because you need to run updates like this for standard UI elements on the main thread (thus the `-performSelectorOnMainThread:`). What's taking the time is the Quartz drawing to the screen of your image. As I suggest, you can significantly speed up this display process by using OpenGL ES to render your image as a texture. You won't even need to call back to the main thread to update such a texture, leading to a more responsive interface. I highly recommend looking at this for your kind of video display. – Brad Larson Mar 10 '11 at 14:13
  • much tnx for answers - but listen to this: my measurings show that drawing (scalling included) takes about 30 ms - not so fast but maybe ok - and waiting on perform selector on main thread also takes about next 30 ms - is that correct value for such 'perform selecor'? a would optymise drawing but it is only half a time – grunge fightr Mar 10 '11 at 15:05
  • @user641443 - I'm not sure how you're arriving at these numbers, but `-performSelectorOnMainThread:` is calling your drawing routines, so it has to be at least as slow from start to finish as they are. If you're talking about the time from when you send the `-performSelectorOnMainThread:` message to when drawing first starts, that can be due to something else blocking your main thread temporarily. Only one action can be executing on the main thread at a time. You might even be waiting on the previous frame to be drawn on the main thread before the new one starts. – Brad Larson Mar 10 '11 at 15:21
  • @user641443 - Again, using OpenGL ES would be much faster, and you can run updates on a background thread so you won't need to rely on the main thread being available. – Brad Larson Mar 10 '11 at 15:22
  • (sorry for my bad english) - i 'know' open gl but i am not to much experienced in it - it would take to much time now;; as to perform selector - i am caling 'perform selector on main thread to only call one line: 'set needs display' - so it does not include drawing time i think - as to my measurments it is like: uint64_t time_a = mach_absolute_time(); [self performSelectorOnMainThread:@selector(setNeedsDisplay) withObject:nil waitUntilDone:YES]; //about 20-30 ms (variable) uint64_t time_b = mach_absolute_time(); – grunge fightr Mar 11 '11 at 12:20
  • @user641443 - Because you are taking a timestamp before and after the `-performSelectorOnMainThread:` call, and `waitUntilDone` is set to YES, you are also timing the duration of the method that is being called by that. Therefore, `-performSelectorOnMainThread:` will take at least as long as `-setNeedsDisplay`, because `-setNeedsDisplay` runs within it. Thus, your numbers are exactly what you'd expect. – Brad Larson Mar 11 '11 at 15:20
  • @brad should `-setNeedsdisplay` take so long? it probably doeas almost nothing except setting some flag - it is `drawRect` that does real work - or am i wrong? much time for `perform selector` on `do set display` worries me – grunge fightr Apr 20 '11 at 14:42
  • @user641443 - `-setNeedsDisplay` triggers the actual drawing, if needed, and this doesn't happen on an different thread, so the method will only complete when the drawing is done. Therefore, measuring the time for this and for `-performSelectorOnMainThread:` really is just measuring how long it takes for `-drawRect:` to run. Thus, these numbers make perfect sense if you have a slow drawing routine. – Brad Larson Apr 20 '11 at 14:46
  • @Brad Larson Can you elaborate, how exactly _fast_ Open GL ES texture upload can be done? – noop Feb 18 '12 at 14:02
  • @noop - Are you talking about the new iOS 5.0 upload capabilities? If so, they're not documented well, but Apple has two example applications, GLCameraRipple: https://developer.apple.com/library/ios/#samplecode/GLCameraRipple/Introduction/Intro.html#//apple_ref/doc/uid/DTS40011222 and RosyWriter: https://developer.apple.com/library/ios/#samplecode/RosyWriter/Introduction/Intro.html#//apple_ref/doc/uid/DTS40011110 which show how this works. I'll be incorporating this into my GPUImage framework soon, as well, so you could wait for that. – Brad Larson Feb 18 '12 at 17:41
  • @Brad Larson Thanks for the information! But, in fact, I just meant that words "fast" and "texture upload" sounded weird together, based on my previous experience :) Thanks again for great post. – noop Feb 19 '12 at 19:26
  • @Brad I just have a quick question, If i want to perform image processing on the gpu trough opengl es 2.0, would this hit my performance of me actually using opengl to display agumented reality stuff?, I am not using patterns so i have to track points of interest which i asume require a heavy processing. Thanks – Pochi Feb 29 '12 at 04:59
  • @LuisOscar - Like with the CPU, there's only a finite amount of processing power, so if you were doing too many intensive tasks on the GPU, it could slow down other processing. However, unless you're doing intense image processing (edge detection, some more artistic shaders, etc.) I don't imagine it would dramatically slow the overlay of 3-D objects on your scene if done right. – Brad Larson Feb 29 '12 at 16:33
  • @BradLarson thanks for your reply, Actually yes, i plan on doing some heavy image processing, more specifically capturing video and using the gpu to process in real time to have points of interest tracking. Which is why i was wondering if it would hinder either my performance on the detection or on the rendering. I will see how it goes since i also have to research for a very efficient algorithm. Using Open CV sounds attractive, personally i haven't tried it, but according to everyone it lacks speed and wont work on real time video. – Pochi Mar 01 '12 at 01:00
5

Set sessionPresent of capture session to AVCaptureSessionPresetLow as shown in the below sample code, this will increase the processing speed, but image from the buffer will be of low quality.



- (void)initCapture {
    AVCaptureDeviceInput *captureInput = [AVCaptureDeviceInput 
                                          deviceInputWithDevice:[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo] 
                                          error:nil];
    AVCaptureVideoDataOutput *captureOutput = [[AVCaptureVideoDataOutput alloc] init] ;
    captureOutput.alwaysDiscardsLateVideoFrames = YES; 
    captureOutput.minFrameDuration = CMTimeMake(1, 25);
    dispatch_queue_t queue;
    queue = dispatch_queue_create("cameraQueue", NULL);
    [captureOutput setSampleBufferDelegate:self queue:queue];
    dispatch_release(queue);
    NSString* key = (NSString*)kCVPixelBufferPixelFormatTypeKey; 
    NSNumber* value = [NSNumber numberWithUnsignedInt:kCVPixelFormatType_32BGRA]; 
    NSDictionary* videoSettings = [NSDictionary dictionaryWithObject:value forKey:key]; 
    [captureOutput setVideoSettings:videoSettings]; 
    self.captureSession = [[AVCaptureSession alloc] init] ;
    [self.captureSession addInput:captureInput];
    [self.captureSession addOutput:captureOutput];
    self.captureSession.sessionPreset=AVCaptureSessionPresetLow;
    /*sessionPresent choose appropriate value to get desired speed*/

    if (!self.prevLayer) {
        self.prevLayer = [AVCaptureVideoPreviewLayer layerWithSession:self.captureSession];
    }
    self.prevLayer.frame = self.view.bounds;
    self.prevLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
    [self.view.layer addSublayer: self.prevLayer];

}



pradeepa
  • 4,104
  • 5
  • 31
  • 41