iOS 点击聚焦

iOS tap to focus

我使用此代码在 iOS 自定义相机应用程序中实现了点击对焦,但它不起作用。这是代码

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()
        }
    }
}
 device.focusPointOfInterest = focusPoint
 device.focusMode = AVCaptureFocusMode.AutoFocus
 device.exposurePointOfInterest = focusPoint
 device.exposureMode = AVCaptureExposureMode.ContinuousAutoExposure

我不明白为什么会这样,但确实如此。

videoView: UIView 显示视频,cameraDevice: AVCaptureDevice,以下似乎对我有用:

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()
        }
    }
} 

请注意,我必须交换 xy 坐标,并将 x 坐标从 1 重新映射到 0,而不是从 0 到 1 — 不确定为什么会这样,但似乎有必要让它正常工作(尽管测试它也有点棘手).

编辑:Apple's documentation 解释坐标转换的原因。

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.

在我的示例中,我一直使用 .ContinuousAutoFocus.ContinuousAutoExposure,但文档表明 .AutoFocus 是正确的选择。奇怪的是文档没有提到 .AutoExpose,但我在我的代码中使用它并且工作正常。

我还修改了我的示例代码以包含 .focusPointOfInterestSupported.exposurePointOfInterestSupported 测试 — 文档还提到对给定的 [=38] 使用 isFocusModeSupported:isExposureModeSupported: 方法=] 模式来测试它是否在设置之前在给定设备上可用,但我假设如果设备支持兴趣点模式,那么它也支持自动模式。在我的应用程序中似乎一切正常。

您必须按正确的顺序调用方法:

if(device.lockForConfiguration(nil)) {

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

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

在设置焦点模式之前先设置兴趣点,否则焦点将放在之前的兴趣点上。

同样适用于exposurePointOfInterest

设置兴趣点的更好方法:

  • 首先计算兴趣点:

     let devicePoint: CGPoint = (self.previewView.layer as!  AVCaptureVideoPreviewLayer).captureDevicePointOfInterestForPoint(gestureRecognizer.locationInView(gestureRecognizer.view))
    
  • 之后设置兴趣点:

    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)
        }
    

Swift 3.0 解

使用 Swift 3.

将 Cody 的答案转换为有效的解决方案
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
        }
    }
}

您应该在 focusPointOfInterest 上阅读 Apple docs,其中说明了三件重要的事情:

  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.

这是一个完成所有这些的实现:

// 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)
    }
}

Swift 5.0版本

// The back camera as default device
var captureDevice: AVCaptureDevice? {
    return AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back)
}

// The camera view.
var cameraView: UIView!

// The layer that contains the camera output
var previewLayer: AVCaptureVideoPreviewLayer

// The focus square view - the yellow one ;)
var squareFocusView: UIView

// User taps on screen to select focus
@IBAction func tapToFocus(_ sender: UITapGestureRecognizer) {
    // make sure we capture one tap only
    if (sender.state == .ended) {
        
        guard let captureDevice = captureDevice else {
            return
        }
        
        let tappedFocusPoint = sender.location(in: cameraView)
        
        // we need to move the focus point to be the center of the tap instead of (0.0, 0.0)
        let centerX = tappedFocusPoint.x - (squareFocusView.frame.size.width / 2.0)
        let centerY = tappedFocusPoint.y - (squareFocusView.frame.size.height / 2.0)

        let focusPoint = CGPoint(x: centerX, y: centerY)
        
        // we need to remap the point because of different coordination systems.
        let convertedFocusPoint = previewLayer.captureDevicePointConverted(fromLayerPoint: focusPoint)
        
        do {
            // changing focusMode and exposureMode requires the device config to be locked.
            try captureDevice.lockForConfiguration()
            
            if (captureDevice.isFocusModeSupported(.autoFocus) && captureDevice.isFocusPointOfInterestSupported) {
                captureDevice.focusPointOfInterest = convertedFocusPoint
                captureDevice.focusMode = .autoFocus
            }
            
            if (captureDevice.isExposureModeSupported(.autoExpose) && captureDevice.isExposurePointOfInterestSupported) {
                captureDevice.exposurePointOfInterest = convertedFocusPoint
                captureDevice.exposureMode = .autoExpose
            }
            
            // unlocks device config
            captureDevice.unlockForConfiguration()
            
        } catch {
            // handle error here
        }
    }
}