iOS tap to focus

2019-01-23 14:25发布

I used this code to achieve Tap-to-Focus in iOS custom camera App, but it isn't working. Here's the code

override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
    let touchPer = touches.anyObject() as UITouch
    let screenSize = UIScreen.mainScreen().bounds.size
    var focus_x = touchPer.locationInView(self.view).x / screenSize.width
    var focus_y = touchPer.locationInView(self.view).y / screenSize.height

    if let device = captureDevice {
        if(device.lockForConfiguration(nil)) {
            device.focusMode = AVCaptureFocusMode.ContinuousAutoFocus

            device.focusPointOfInterest = CGPointMake(focus_x, focus_y)
            device.exposureMode = AVCaptureExposureMode.ContinuousAutoExposure
            device.unlockForConfiguration()
        }
    }
}

6条回答
我命由我不由天
2楼-- · 2019-01-23 15:05

You should read Apple docs on focusPointOfInterest, which says three important things:

  1. Setting a value for this property does not initiate a focusing operation. To focus the camera on a point of interest, first set this property's value, then set the focusMode property to autoFocus or continuousAutoFocus.

  2. This property's CGPoint value uses a coordinate system where {0,0} is the top left of the picture area and {1,1} is the bottom right. This coordinate system is always relative to a landscape device orientation with the home button on the right, regardless of the actual device orientation. You can convert between this coordinate system and view coordinates using AVCaptureVideoPreviewLayer methods.

  3. Before changing the value of this property, you must call lockForConfiguration() to acquire exclusive access to the device’s configuration properties. Otherwise, setting the value of this property raises an exception. When you are done configuring the device, call unlockForConfiguration() to release the lock and allow other devices to configure the settings.

Here is an implementation which does all of that:

// In your camera preview view    
@objc private func cameraViewTapped(with gestureRecognizer: UITapGestureRecognizer) {
    let location = gestureRecognizer.location(in: self)
    addFocusIndicatorView(at: location) // If you want to indicate it in the UI

    // This is the point you want to pass to your capture device
    let captureDeviceLocation = previewLayer.captureDevicePointConverted(fromLayerPoint: location)

    // Somehow pass the point to where your AVCaptureDevice is
    viewDelegate?.cameraPreviewView(self, didTapToFocusAt: captureDeviceLocation) 
}


// In your camera controller
func focus(at point: CGPoint) {
    guard let device = videoDevice else {
        return
    }

    guard device.isFocusPointOfInterestSupported, device.isExposurePointOfInterestSupported else {
        return
    }

    do {
        try device.lockForConfiguration()

        device.focusPointOfInterest = point
        device.exposurePointOfInterest = point

        device.focusMode = .continuousAutoFocus
        device.exposureMode = .continuousAutoExposure

        device.unlockForConfiguration()
    } catch {
        print(error)
    }
}
查看更多
祖国的老花朵
3楼-- · 2019-01-23 15:09
 device.focusPointOfInterest = focusPoint
 device.focusMode = AVCaptureFocusMode.AutoFocus
 device.exposurePointOfInterest = focusPoint
 device.exposureMode = AVCaptureExposureMode.ContinuousAutoExposure

I don't why this works, but it did.

查看更多
等我变得足够好
4楼-- · 2019-01-23 15:13

The better way to set focus point of interest:

  • first calculate the point of interest:

     let devicePoint: CGPoint = (self.previewView.layer as!  AVCaptureVideoPreviewLayer).captureDevicePointOfInterestForPoint(gestureRecognizer.locationInView(gestureRecognizer.view))
    
  • after that set the focus point of interest :

    let device: AVCaptureDevice! = self.videoDeviceInput!.device
    
        do {
            try device.lockForConfiguration()
    
            if device.focusPointOfInterestSupported && device.isFocusModeSupported(focusMode){
    
                device.focusPointOfInterest = devicePoint
                device.focusMode = focusMode
            }
    
            device.unlockForConfiguration()
    
        }catch{
            print(error)
        }
    
查看更多
▲ chillily
5楼-- · 2019-01-23 15:18

Swift 3.0 Solution

Converted Cody's answer into a working solution with Swift 3.

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    let touchPoint = touches.first! as UITouch
    let screenSize = cameraView.bounds.size
    let focusPoint = CGPoint(x: touchPoint.location(in: cameraView).y / screenSize.height, y: 1.0 - touchPoint.location(in: cameraView).x / screenSize.width)

    if let device = captureDevice {
        do {
            try device.lockForConfiguration()
            if device.isFocusPointOfInterestSupported {
                device.focusPointOfInterest = focusPoint
                device.focusMode = AVCaptureFocusMode.autoFocus
            }
            if device.isExposurePointOfInterestSupported {
                device.exposurePointOfInterest = focusPoint
                device.exposureMode = AVCaptureExposureMode.autoExpose
            }
            device.unlockForConfiguration()

        } catch {
            // Handle errors here
        }
    }
}
查看更多
相关推荐>>
6楼-- · 2019-01-23 15:19

You have to call the methods in the right order:

if(device.lockForConfiguration(nil)) {

    device.focusPointOfInterest = CGPointMake(focus_x, focus_y)
    device.focusMode = AVCaptureFocusMode.ContinuousAutoFocus

    device.exposureMode = AVCaptureExposureMode.ContinuousAutoExposure
    device.unlockForConfiguration()
}

Set the point of interest before setting the focus mode else the focus will be made on the previous point of interest.

The same apply for exposurePointOfInterest.

查看更多
够拽才男人
7楼-- · 2019-01-23 15:24

With a videoView: UIView displaying the video, and cameraDevice: AVCaptureDevice, the following seems to work for me:

override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
    var touchPoint = touches.first as! UITouch
    var screenSize = videoView.bounds.size
    var focusPoint = CGPoint(x: touchPoint.locationInView(videoView).y / screenSize.height, y: 1.0 - touchPoint.locationInView(videoView.x / screenSize.width)

    if let device = cameraDevice {
        if(device.lockForConfiguration(nil)) {
            if device.focusPointOfInterestSupported {
                device.focusPointOfInterest = focusPoint
                device.focusMode = AVCaptureFocusMode.AutoFocus
            }
            if device.exposurePointOfInterestSupported {
                device.exposurePointOfInterest = focusPoint
                device.exposureMode = AVCaptureExposureMode.AutoExpose
            }
            device.unlockForConfiguration()
        }
    }
} 

Note that I had to swap the x and y coordinates, and remap the x coord from 1 to 0 instead of 0 to 1 — not sure why that should be the case but it seems to be necessary to get it to work right (though it's a little tricky to test it too).

Edit: Apple's documentation explains the reason for the coordinate transformation.

In addition, a device may support a focus point of interest. You test for support using focusPointOfInterestSupported. If it’s supported, you set the focal point using focusPointOfInterest. You pass a CGPoint where {0,0} represents the top left of the picture area, and {1,1} represents the bottom right in landscape mode with the home button on the right—this applies even if the device is in portrait mode.

In my example I had been using .ContinuousAutoFocus and .ContinuousAutoExposure, but the documentation indicates .AutoFocus is the right choice. Oddly the documentation makes no mention of .AutoExpose, but I'm using it in my code and it works fine.

I also modified my example code to include .focusPointOfInterestSupported and .exposurePointOfInterestSupported tests — the documentation also mentions using the isFocusModeSupported: and isExposureModeSupported: methods for a given focus/exposure mode to test whether it is available on a given device before setting it, but I assume if the device supports the point of interest modes then it also supports the auto modes. It all seems to work fine in my app.

查看更多
登录 后发表回答