18

I'm working on an iOS-app where one of the features is scanning QR-codes. For this I'm using the excellent library, ZBar. The scanning works fine and is generally really quick. However when you use smaller QR-codes it takes a bit longer to scan, mostly due to the fact that the autofocus needs some time to adjust. I was experimenting and noticed that the focus could be locked using the following code:

AVCaptureDevice *cameraDevice = readerView.device;
if ([cameraDevice lockForConfiguration:nil]) {
     [cameraDevice setFocusMode:AVCaptureFocusModeLocked];
     [cameraDevice unlockForConfiguration];
}

When this code is used after a successful scan, the coming scans are really quick. That made me wonder, could I somehow lock the focus before even scanning one code? The app will only scan rather small QR-codes so there will never be a need for focusing on something far away. Sure, I could implement something like tap to focus, but preferably I would like to avoid that extra step. Is there a way to achieve this? Or are there maybe another way of speeding things up when dealing with smaller QR-codes?

// Alexander

Mike Abdullah
  • 14,933
  • 2
  • 50
  • 75
Ryuu Tora
  • 591
  • 1
  • 4
  • 12
  • 1
    Related: http://stackoverflow.com/questions/8488736/is-there-a-way-to-programmatically-set-the-camera-focus-of-an-ios-device-to-infi?rq=1 – Prof. Falken Apr 03 '13 at 10:30
  • @amigable-clark-kant, yes that seems to be the basically the same issue, only he wants to lock focus as far away as possible. It seems that either my current solution (lock focus after first successful scan) or "tap to focus" is the best solution right now. – Ryuu Tora Apr 04 '13 at 10:40

2 Answers2

23

In iOS7 this is now possible!

Apple has added the property autoFocusRangeRestriction to the AVCaptureDevice class. This property is of the enum AVCaptureAutoFocusRangeRestriction which has three different values:

  1. AVCaptureAutoFocusRangeRestrictionNone - Default, no restrictions
  2. AVCaptureAutoFocusRangeRestrictionNear - The subject that matters is close to the camera
  3. AVCaptureAutoFocusRangeRestrictionFar - The subject that matters is far from the camera

To check if the method is available we should first check if the property autoFocusRangeRestrictionSupported is true. And since it's only supported in iOS7 an onwards we should also use respondsToSelector so we don't get an exception on earlier iOS-versions.
So the resulting code should look something like this:

AVCaptureDevice *cameraDevice = zbarReaderView.device;
if ([cameraDevice respondsToSelector:@selector(isAutoFocusRangeRestrictionSupported)] && cameraDevice.autoFocusRangeRestrictionSupported) {
    // If we are on an iOS version that supports AutoFocusRangeRestriction and the device supports it
    // Set the focus range to "near"
    if ([cameraDevice lockForConfiguration:nil]) {
        cameraDevice.autoFocusRangeRestriction = AVCaptureAutoFocusRangeRestrictionNear;
        [cameraDevice unlockForConfiguration];
    }
}

This seems to somewhat speed up the scanning of small QR-codes according to my initial tests :)

Update - iOS8

With iOS8, Apple has given us lots of new camera API's to play with. One of this new methods is this one:

- (void)setFocusModeLockedWithLensPosition:(float)lensPosition completionHandler:(void (^)(CMTime syncTime))handler

This method locks focus by moving the lens to a position between 0.0 and 1.0. I played around with the method, locking the lens at close values. However, in general it caused more problems then it solved. You had to keep the QR-codes/barcodes at a very specific distance, which could cause issues when you had codes of different sizes.
But. I think I have found a pretty good alternative to locking focus altogether. When the user press the scan button, I lock the lens to a close distance, and when it's finished I switch the camera back to auto focus. This gives us the benefits of keeping auto focus on, but forces the camera to begin at a close distance where a QR-code/barcode is likely to be found. This in combination with:

cameraDevice.autoFocusRangeRestriction = AVCaptureAutoFocusRangeRestrictionNear;

And:

cameraDevice.focusPointOfInterest = CGPointMake(0.5,0.5);

Results in a pretty snappy scanner. I also built a custom scanner with the API's introduced in iOS7, instead of using ZBar. Mostly because the ZBar-libs are quite outdated and as when iPhone 5 introduced ARMv7s I now had to recompile it again for ARM64.

// Alexander

Ryuu Tora
  • 591
  • 1
  • 4
  • 12
  • Now how can I force the photo app to focus closely? :-) Tapping the screen always wants to focus on the background. I've learned to lock focus for macro shots, but I wish I could use a focus restrictor instead. Also, is there something in the API that can return the focus distance? This could be useful in creating an app to add location information to a picture, where you want the subject location, not the device location. – Victor Engel Jan 09 '14 at 00:52
6

iOS 8 recently added this configuration! It is almost like they read stack overflow

    /*!
 @method setFocusModeLockedWithLensPosition:completionHandler:
 @abstract
    Sets focusMode to AVCaptureFocusModeLocked and locks lensPosition at an explicit value.

 @param lensPosition
    The lens position, as described in the documentation for the lensPosition property. A value of AVCaptureLensPositionCurrent can be used
    to indicate that the caller does not wish to specify a value for lensPosition.
 @param handler
    A block to be called when lensPosition has been set to the value specified and focusMode is set to AVCaptureFocusModeLocked. If
    setFocusModeLockedWithLensPosition:completionHandler: is called multiple times, the completion handlers will be called in FIFO order. 
    The block receives a timestamp which matches that of the first buffer to which all settings have been applied. Note that the timestamp 
    is synchronized to the device clock, and thus must be converted to the master clock prior to comparison with the timestamps of buffers 
    delivered via an AVCaptureVideoDataOutput. The client may pass nil for the handler parameter if knowledge of the operation's completion 
    is not required.

 @discussion
    This is the only way of setting lensPosition.
    This method throws an NSRangeException if lensPosition is set to an unsupported level.
    This method throws an NSGenericException if called without first obtaining exclusive access to the receiver using lockForConfiguration:.
*/
- (void)setFocusModeLockedWithLensPosition:(float)lensPosition completionHandler:(void (^)(CMTime syncTime))handler NS_AVAILABLE_IOS(8_0);

EDIT: this is a method of AVCaptureDevice

Michael Lorenzo
  • 628
  • 10
  • 20
  • 1
    Yeah it's almost as if they actually listen :) I played around with locking focus using that method. But most of the time it caused more problems than it solved. You had to keep the QR-codes/barcodes at a very specific distance which could cause issues when you had codes of different sizes. However, I think I found a pretty good alternative to locking the focus completely. When the user press the scan button, I lock the focus at a close distance and as soon as it's finished I switch the camera back to auto focus. This way we can use auto focus but the camera gets a good starting position. – Ryuu Tora Dec 15 '14 at 15:44