Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AVFoundation - How to control exposure

AFTER tapping to take picture, I want to lock exposure and turn off torch as soon as exposure is no longer adjusting. So, I added an observer to handle adjustingExposure:

- (IBAction)configureImageCapture:(id)sender
{
    [self.session beginConfiguration];

    [self.cameraController device:self.inputDevice exposureMode:AVCaptureExposureModeAutoExpose];
    [self.cameraController device:self.inputDevice torchMode:AVCaptureTorchModeOn torchLevel:0.8f];

    [self.session commitConfiguration];

    [(AVCaptureDevice *)self.inputDevice addObserver:self forKeyPath:@"adjustingExposure" options:NSKeyValueObservingOptionNew context:MyAdjustingExposureObservationContext];        
}

Here is the observeValueForKeyPath method:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if (context == MyAdjustingExposureObservationContext) {
        if( [keyPath isEqualToString:@"adjustingExposure"] )
        {
            BOOL adjustingExposure = [ [change objectForKey:NSKeyValueChangeNewKey] isEqualToNumber:[NSNumber numberWithInt:1] ];

            if (!adjustingExposure)
            {
                [(AVCaptureDevice *)self.cameraController.inputDevice removeObserver:self forKeyPath:@"adjustingExposure"];

                if ([self.inputDevice isExposureModeSupported:AVCaptureExposureModeLocked]) {
                    dispatch_async(dispatch_get_main_queue(),
                                   ^{
                                       NSError *error = nil;
                                       if ([self.inputDevice lockForConfiguration:&error]) {
                                           // 5) lock the exposure
                                           [self.cameraController device:self.inputDevice exposureMode:AVCaptureExposureModeLocked];

                                           // 6) turn off the Torch
                                           [self.cameraController device:self.inputDevice torchMode:AVCaptureTorchModeOn torchLevel:0.0001f];

                                           [self.inputDevice unlockForConfiguration];
                                       }
                                   });
                }                    
            }
        }
    } else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

@user3115647 posted this information, which is exactly what I am trying to do.

But my picture is taken BEFORE the torch is turned off.

Here is my captureStillImageAsynchronouslyFromConnection:self.captureConnection completionHandler. This block occurs after the image is taken. The observeValueForKeyPath is supposed to occur while the camera is adjusting exposure BEFORE the image is taken. But my torch is not going low BEFORE the image is being taken. Either this is a timing issue or I'm not setting up the camera configuration correctly.

- (void)captureImage
{
    // configureImageCapture has already been done
    [self.stillImageOutput captureStillImageAsynchronouslyFromConnection:self.captureConnection completionHandler: ^(CMSampleBufferRef imageSampleBuffer, NSError *error)
     {
         if (imageSampleBuffer != NULL)
         {
             // Log the image properties
             CFDictionaryRef attachmentsRef = CMGetAttachment(imageSampleBuffer, kCGImagePropertyExifDictionary, NULL);
             NSDictionary *properties = (__bridge NSDictionary *)(attachmentsRef);
             NSLog(@"Image Properties => %@", (properties.count) ? properties : @"none");
like image 262
Patricia Avatar asked Feb 26 '14 00:02

Patricia


2 Answers

I got something similar to happen by using flash instead of the torch. I have an observer for @"videoDevice.flashActive" as well. I did try using exposureModeLocked first, but it didn't work for me either.

  1. Take a photo with flash on
  2. Then instantly turn flash off and take another photo before the exposure has time to adjust

The code below probably doesn't just work on its own, but it's simplified from what I did.

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void*)context
{
    if (context == AdjustingExposureContext)
    {
        self.isAdjustingExposure = [change[NSKeyValueChangeNewKey] boolValue];
    }
    else if (context == FlashModeChangedContext)
    {
        self.isFlashActive = [change[NSKeyValueChangeNewKey] boolValue];
        if (!self.flashActive)
        {
            [self captureImage];  // QUICKLY! capture 2nd image without
        }                         // flash before exposure adjusts
    }
    if (!self.isAdjustingExposure && self.flashActive)
    {
        [self removeObserver:self forKeyPath:@"videoDevice.adjustingExposure" context:AdjustingExposureContext];
        [self captureImage];  // capture 1st image with the flash on
    }
}

Now in the callback for captureStillImageAsynchronouslyFromConnection:,

if (self.isFlashActive)
    [self.videoDeviceInput.device setFlashMode:NO];

However, if you need to take more than one photo without flash at the lowered exposure, this strategy may not work.

like image 83
EthanP Avatar answered Oct 04 '22 13:10

EthanP


It is almost certainly a timing issue. Call captureStillImageAsynchronouslyFromConnection:completionHandler: inside your if block. Then the capture will always be executed after exposure has been locked.

if ([self.inputDevice isExposureModeSupported:AVCaptureExposureModeLocked]) {
    dispatch_async(dispatch_get_main_queue(), 
        ^{
            NSError *error = nil;
            if ([self.inputDevice lockForConfiguration:&error]) {
                //code to lock exposure here
                //take photo here
            }
        });
}
like image 35
Pranav Avatar answered Oct 04 '22 12:10

Pranav