我使用此代码在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()
}
}
}
随着
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()
}
}
}
请注意,我必须交换
x
和 y
坐标,并将 x
坐标从 1 重新映射到 0,而不是 0 到 1 — 编辑:Apple的文档解释了坐标变换的原因。
此外,设备可能支持兴趣焦点。您可以使用 focusPointOfInterestSupported 测试支持。如果支持,您可以使用 focusPointOfInterest 设置焦点。您传递一个 CGPoint,其中 {0,0} 表示图片区域的左上角,{1,1} 表示横向模式下的右下角,主页按钮位于右侧 - 即使设备处于纵向模式,这也适用.
在我的示例中,我一直使用
.ContinuousAutoFocus
和 .ContinuousAutoExposure
,但文档表明 .AutoFocus
是正确的选择。奇怪的是,文档没有提及 .AutoExpose
,但我在代码中使用它并且工作正常。
我还修改了示例代码以包含
.focusPointOfInterestSupported
和 .exposurePointOfInterestSupported
测试 - 文档还提到对给定的焦点/曝光模式使用 isFocusModeSupported:
和 isExposureModeSupported:
方法来测试它是否在给定设备上可用。设置它,但我假设如果设备支持兴趣点模式,那么它也支持自动模式。这一切似乎在我的应用程序中运行良好。
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
}
}
}
您应该阅读 Apple docs on
focusPointOfInterest
,其中说了三件重要的事情:
为此属性设置值不会启动聚焦操作。要将相机聚焦在兴趣点上,请首先设置此属性的值,然后将 focusMode 属性设置为 autoFocus 或 ContinuousAutoFocus。
此属性的 CGPoint 值使用坐标系,其中 {0,0} 是图片区域的左上角,{1,1} 是右下角。 此坐标系始终相对于主页按钮位于右侧的横向设备方向,无论实际设备方向如何。您可以使用 AVCaptureVideoPreviewLayer 方法在此坐标系和视图坐标之间进行转换。
在更改此属性的值之前,必须调用 lockForConfiguration() 来获取对设备配置属性的独占访问权限。否则,设置此属性的值会引发异常。完成设备配置后,调用unlockForConfiguration()释放锁定并允许其他设备配置设置。
这是一个实现所有这些的实现:
// 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)
}
}
device.focusPointOfInterest = focusPoint
device.focusMode = AVCaptureFocusMode.AutoFocus
device.exposurePointOfInterest = focusPoint
device.exposureMode = AVCaptureExposureMode.ContinuousAutoExposure
我不知道为什么会这样,但它确实有效。
设置焦点兴趣点的更好方法:
首先计算兴趣点:
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 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
}
}
}
您必须按正确的顺序调用方法:
if(device.lockForConfiguration(nil)) {
device.focusPointOfInterest = CGPointMake(focus_x, focus_y)
device.focusMode = AVCaptureFocusMode.ContinuousAutoFocus
device.exposureMode = AVCaptureExposureMode.ContinuousAutoExposure
device.unlockForConfiguration()
}
在设置对焦模式之前先设置兴趣点,否则焦点将聚焦在前一个兴趣点上。
同样适用于
exposurePointOfInterest
。
斯威夫特 4:
public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
var touchPoint = touches.first as! UITouch
let cameraView = cameraViewController.view
var screenSize = cameraView!.bounds.size
var focusPoint = CGPoint(x: touchPoint.location(in: cameraView).y / screenSize.height, y: 1.0 - touchPoint.location(in: cameraView).x / screenSize.width)
if #available(iOS 10.0, *) {
let device = AVCaptureDevice.default(.builtInWideAngleCamera,
for: .video, position: .unspecified)
do{
try device?.lockForConfiguration()
if device!.isFocusPointOfInterestSupported {
device!.focusPointOfInterest = focusPoint
device!.focusMode = AVCaptureDevice.FocusMode.autoFocus
}
if device!.isExposurePointOfInterestSupported {
device!.exposurePointOfInterest = focusPoint
device!.exposureMode = AVCaptureDevice.ExposureMode.autoExpose
}
device!.unlockForConfiguration()
}catch{
}
} else {
// Fallback on earlier versions
}
}
除了提供的让您在特定点聚焦/曝光区域/对象的答案之外,如果该对象已移开,或者相机视图发生变化,此时我们需要将 focusMode 设置回 .continuousAutoFocus 并曝光模式返回 .ContinousAutoExposure 让系统处理焦点/曝光
首先,我们需要观察主题区域变更通知:
cameraDevice.isSubjectAreaChangeMonitoringEnabled = true
NotificationCenter.default.addObserver(self, selector: #selector(setDefaultFocusAndExposure), name: .AVCaptureDeviceSubjectAreaDidChange, object: nil)
并在收到通知时配置摄像头设备:
@objc private func setDefaultFocusAndExposure() {
guard let cameraDevice else { return }
try? cameraDevice.lockForConfiguration()
if cameraDevice.isFocusModeSupported(.continuousAutoFocus) {
cameraDevice.focusMode = .continuousAutoFocus
}
if cameraDevice.isExposureModeSupported(.continuousAutoExposure) {
cameraDevice.exposureMode = .continuousAutoExposure
}
cameraDevice.unlockForConfiguration()
}
最后,不要忘记删除观察者,对我来说,我是在 deinit 中完成的:
deinit {
NotificationCenter.default.removeObserver(self)
}