在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ iOS/ iOS 上的相機(jī)捕捉
與四軸無(wú)人機(jī)的通訊
在沙盒中編寫(xiě)腳本
結(jié)構(gòu)體和值類(lèi)型
深入理解 CocoaPods
UICollectionView + UIKit 力學(xué)
NSString 與 Unicode
代碼簽名探析
測(cè)試
架構(gòu)
第二期-并發(fā)編程
Metal
自定義控件
iOS 中的行為
行為驅(qū)動(dòng)開(kāi)發(fā)
Collection View 動(dòng)畫(huà)
截圖測(cè)試
MVVM 介紹
使 Mac 應(yīng)用數(shù)據(jù)腳本化
一個(gè)完整的 Core Data 應(yīng)用
插件
字符串
為 iOS 建立 Travis CI
先進(jìn)的自動(dòng)布局工具箱
動(dòng)畫(huà)
為 iOS 7 重新設(shè)計(jì) App
XPC
從 NSURLConnection 到 NSURLSession
Core Data 網(wǎng)絡(luò)應(yīng)用實(shí)例
GPU 加速下的圖像處理
自定義 Core Data 遷移
子類(lèi)
與調(diào)試器共舞 - LLDB 的華爾茲
圖片格式
并發(fā)編程:API 及挑戰(zhàn)
IP,TCP 和 HTTP
動(dòng)畫(huà)解釋
響應(yīng)式 Android 應(yīng)用
初識(shí) TextKit
客戶端
View-Layer 協(xié)作
回到 Mac
Android
Core Image 介紹
自定義 Formatters
Scene Kit
調(diào)試
項(xiàng)目介紹
Swift 的強(qiáng)大之處
測(cè)試并發(fā)程序
Android 通知中心
調(diào)試:案例學(xué)習(xí)
從 UIKit 到 AppKit
iOS 7 : 隱藏技巧和變通之道
安全
底層并發(fā) API
消息傳遞機(jī)制
更輕量的 View Controllers
用 SQLite 和 FMDB 替代 Core Data
字符串解析
終身學(xué)習(xí)的一代人
視頻
Playground 快速原型制作
Omni 內(nèi)部
同步數(shù)據(jù)
設(shè)計(jì)優(yōu)雅的移動(dòng)游戲
繪制像素到屏幕上
相機(jī)與照片
音頻 API 一覽
交互式動(dòng)畫(huà)
常見(jiàn)的后臺(tái)實(shí)踐
糟糕的測(cè)試
避免濫用單例
數(shù)據(jù)模型和模型對(duì)象
Core Data
字符串本地化
View Controller 轉(zhuǎn)場(chǎng)
照片框架
響應(yīng)式視圖
Square Register 中的擴(kuò)張
DTrace
基礎(chǔ)集合類(lèi)
視頻工具箱和硬件加速
字符串渲染
讓東西變得不那么糟
游戲中的多點(diǎn)互聯(lián)
iCloud 和 Core Data
Views
虛擬音域 - 聲音設(shè)計(jì)的藝術(shù)
導(dǎo)航應(yīng)用
線程安全類(lèi)的設(shè)計(jì)
置換測(cè)試: Mock, Stub 和其他
Build 工具
KVC 和 KVO
Core Image 和視頻
Android Intents
在 iOS 上捕獲視頻
四軸無(wú)人機(jī)項(xiàng)目
Mach-O 可執(zhí)行文件
UI 測(cè)試
值對(duì)象
活動(dòng)追蹤
依賴(lài)注入
Swift
項(xiàng)目管理
整潔的 Table View 代碼
Swift 方法的多面性
為什么今天安全仍然重要
Core Data 概述
Foundation
Swift 的函數(shù)式 API
iOS 7 的多任務(wù)
自定義 Collection View 布局
測(cè)試 View Controllers
訪談
收據(jù)驗(yàn)證
數(shù)據(jù)同步
自定義 ViewController 容器轉(zhuǎn)場(chǎng)
游戲
調(diào)試核對(duì)清單
View Controller 容器
學(xué)無(wú)止境
XCTest 測(cè)試實(shí)戰(zhàn)
iOS 7
Layer 中自定義屬性的動(dòng)畫(huà)
第一期-更輕量的 View Controllers
精通 iCloud 文檔存儲(chǔ)
代碼審查的藝術(shù):Dropbox 的故事
GPU 加速下的圖像視覺(jué)
Artsy
照片擴(kuò)展
理解 Scroll Views
使用 VIPER 構(gòu)建 iOS 應(yīng)用
Android 中的 SQLite 數(shù)據(jù)庫(kù)支持
Fetch 請(qǐng)求
導(dǎo)入大數(shù)據(jù)集
iOS 開(kāi)發(fā)者的 Android 第一課
iOS 上的相機(jī)捕捉
語(yǔ)言標(biāo)簽
同步案例學(xué)習(xí)
依賴(lài)注入和注解,為什么 Java 比你想象的要好
編譯器
基于 OpenCV 的人臉識(shí)別
玩轉(zhuǎn)字符串
相機(jī)工作原理
Build 過(guò)程

iOS 上的相機(jī)捕捉

第一臺(tái) iPhone 問(wèn)世就裝有相機(jī)。在第一個(gè) SKDs 版本中,在 app 里面整合相機(jī)的唯一方法就是使用 UIImagePickerController,但到了 iOS 4,發(fā)布了更靈活的 AVFoundation 框架。

在這篇文章里,我們將會(huì)看到如何使用 AVFoundation 捕捉圖像,如何操控相機(jī),以及它在 iOS 8 的新特性。

概述

AVFoundation vs. UIImagePickerController

UIImagePickerController 提供了一種非常簡(jiǎn)單的拍照方法。它支持所有的基本功能,比如切換到前置攝像頭,開(kāi)關(guān)閃光燈,點(diǎn)擊屏幕區(qū)域?qū)崿F(xiàn)對(duì)焦和曝光,以及在 iOS 8 中像系統(tǒng)照相機(jī)應(yīng)用一樣調(diào)整曝光。

然而,當(dāng)有直接訪問(wèn)相機(jī)的需求時(shí),也可以選擇 AVFoundation 框架。它提供了完全的操作權(quán),例如,以編程方式更改硬件參數(shù),或者操縱實(shí)時(shí)預(yù)覽圖。

AVFoundation 相關(guān)類(lèi)

AVFoundation 框架基于以下幾個(gè)類(lèi)實(shí)現(xiàn)圖像捕捉 ,通過(guò)這些類(lèi)可以訪問(wèn)來(lái)自相機(jī)設(shè)備的原始數(shù)據(jù)并控制它的組件。

  • AVCaptureDevice 是關(guān)于相機(jī)硬件的接口。它被用于控制硬件特性,諸如鏡頭的位置、曝光、閃光燈等。
  • AVCaptureDeviceInput 提供來(lái)自設(shè)備的數(shù)據(jù)。
  • AVCaptureOutput 是一個(gè)抽象類(lèi),描述 capture session 的結(jié)果。以下是三種關(guān)于靜態(tài)圖片捕捉的具體子類(lèi):
    • AVCaptureStillImageOutput 用于捕捉靜態(tài)圖片
    • AVCaptureMetadataOutput 啟用檢測(cè)人臉和二維碼
    • AVCaptureVideoOutput 為實(shí)時(shí)預(yù)覽圖提供原始幀
  • AVCaptureSession 管理輸入與輸出之間的數(shù)據(jù)流,以及在出現(xiàn)問(wèn)題時(shí)生成運(yùn)行時(shí)錯(cuò)誤。
  • AVCaptureVideoPreviewLayerCALayer 的子類(lèi),可被用于自動(dòng)顯示相機(jī)產(chǎn)生的實(shí)時(shí)圖像。它還有幾個(gè)工具性質(zhì)的方法,可將 layer 上的坐標(biāo)轉(zhuǎn)化到設(shè)備上。它看起來(lái)像輸出,但其實(shí)不是。另外,它擁有 session (outputs 被 session 所擁有)。

設(shè)置

讓我們看看如何捕獲圖像。首先我們需要一個(gè) AVCaptureSession 對(duì)象:

let session = AVCaptureSession()

現(xiàn)在我們需要一個(gè)相機(jī)設(shè)備輸入。在大多數(shù) iPhone 和 iPad 中,我們可以選擇后置攝像頭或前置攝像頭 -- 又稱(chēng)自拍相機(jī) (selfie camera) -- 之一。那么我們必須先遍歷所有能提供視頻數(shù)據(jù)的設(shè)備 (麥克風(fēng)也屬于 AVCaptureDevice,因此略過(guò)不談),并檢查 position 屬性:

let availableCameraDevices = AVCaptureDevice.devicesWithMediaType(AVMediaTypeVideo)
for device in availableCameraDevices as [AVCaptureDevice] {
  if device.position == .Back {
    backCameraDevice = device
  }
  else if device.position == .Front {
    frontCameraDevice = device
  }
}

然后,一旦我們發(fā)現(xiàn)合適的相機(jī)設(shè)備,我們就能獲得相關(guān)的 AVCaptureDeviceInput 對(duì)象。我們會(huì)將它設(shè)置為 session 的輸入:

var error:NSError?
let possibleCameraInput: AnyObject? = AVCaptureDeviceInput.deviceInputWithDevice(backCameraDevice, error: &error)
if let backCameraInput = possibleCameraInput as? AVCaptureDeviceInput {
  if self.session.canAddInput(backCameraInput) {
    self.session.addInput(backCameraInput)
  }
}

注意當(dāng) app 首次運(yùn)行時(shí),第一次調(diào)用 AVCaptureDeviceInput.deviceInputWithDevice() 會(huì)觸發(fā)系統(tǒng)提示,向用戶請(qǐng)求訪問(wèn)相機(jī)。這在 iOS 7 的時(shí)候只有部分國(guó)家會(huì)有,到了 iOS 8 拓展到了所有地區(qū)。除非得到用戶同意,否則相機(jī)的輸入會(huì)一直是一個(gè)黑色畫(huà)面的數(shù)據(jù)流。

對(duì)于處理相機(jī)的權(quán)限,更合適的方法是先確認(rèn)當(dāng)前的授權(quán)狀態(tài)。要是在授權(quán)還沒(méi)有確定的情況下 (也就是說(shuō)用戶還沒(méi)有看過(guò)彈出的授權(quán)對(duì)話框時(shí)),我們應(yīng)該明確地發(fā)起請(qǐng)求。

let authorizationStatus = AVCaptureDevice.authorizationStatusForMediaType(AVMediaTypeVideo)
switch authorizationStatus {
case .NotDetermined:
  // 許可對(duì)話沒(méi)有出現(xiàn),發(fā)起授權(quán)許可
  AVCaptureDevice.requestAccessForMediaType(AVMediaTypeVideo,
    completionHandler: { (granted:Bool) -> Void in
    if granted {
      // 繼續(xù)
    }
    else {
      // 用戶拒絕,無(wú)法繼續(xù)
    }
  })
case .Authorized:
  // 繼續(xù)
case .Denied, .Restricted:
  // 用戶明確地拒絕授權(quán),或者相機(jī)設(shè)備無(wú)法訪問(wèn)
}

如果能繼續(xù)的話,我們會(huì)有兩種方式來(lái)顯示來(lái)自相機(jī)的圖像流。最簡(jiǎn)單的就是,生成一個(gè)帶有 AVCaptureVideoPreviewLayer 的 view,并使用 capture session 作為初始化參數(shù)。

previewLayer = AVCaptureVideoPreviewLayer.layerWithSession(session) as AVCaptureVideoPreviewLayer
previewLayer.frame = view.bounds
view.layer.addSublayer(previewLayer)

AVCaptureVideoPreviewLayer 會(huì)自動(dòng)地顯示來(lái)自相機(jī)的輸出。當(dāng)我們需要將實(shí)時(shí)預(yù)覽圖上的點(diǎn)擊轉(zhuǎn)換到設(shè)備的坐標(biāo)系統(tǒng)中,比如點(diǎn)擊某區(qū)域?qū)崿F(xiàn)對(duì)焦時(shí),這種做法會(huì)很容易辦到。之后我們會(huì)看到具體細(xì)節(jié)。

第二種方法是從輸出數(shù)據(jù)流捕捉單一的圖像幀,并使用 OpenGL 手動(dòng)地把它們顯示在 view 上。這有點(diǎn)復(fù)雜,但是如果我們想要對(duì)實(shí)時(shí)預(yù)覽圖進(jìn)行操作或使用濾鏡的話,就是必要的了。

為獲得數(shù)據(jù)流,我們需要?jiǎng)?chuàng)建一個(gè) AVCaptureVideoDataOutput,這樣一來(lái),當(dāng)相機(jī)在運(yùn)行時(shí),我們通過(guò)代理方法 captureOutput(_:didOutputSampleBuffer:fromConnection:) 就能獲得所有圖像幀 (除非我們處理太慢而導(dǎo)致掉幀),然后將它們繪制在一個(gè) GLKView 中。不需要對(duì) OpenGL 框架有什么深刻的理解,我們只需要這樣就能創(chuàng)建一個(gè) GLKView

glContext = EAGLContext(API: .OpenGLES2)
glView = GLKView(frame: viewFrame, context: glContext)
ciContext = CIContext(EAGLContext: glContext)

現(xiàn)在輪到 AVCaptureVideoOutput

videoOutput = AVCaptureVideoDataOutput()
videoOutput.setSampleBufferDelegate(self, queue: dispatch_queue_create("sample buffer delegate", DISPATCH_QUEUE_SERIAL))
if session.canAddOutput(self.videoOutput) {
  session.addOutput(self.videoOutput)
}

以及代理方法:

func captureOutput(captureOutput: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, fromConnection connection: AVCaptureConnection!) {
  let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)
  let image = CIImage(CVPixelBuffer: pixelBuffer)
  if glContext != EAGLContext.currentContext() {
    EAGLContext.setCurrentContext(glContext)
  }
  glView.bindDrawable()
  ciContext.drawImage(image, inRect:image.extent(), fromRect: image.extent())
  glView.display()
}

一個(gè)警告:這些來(lái)自相機(jī)的樣本旋轉(zhuǎn)了 90 度,這是由于相機(jī)傳感器的朝向所導(dǎo)致的。AVCaptureVideoPreviewLayer 會(huì)自動(dòng)處理這種情況,但在這個(gè)例子,我們需要對(duì) GLKView 進(jìn)行旋轉(zhuǎn)。

馬上就要搞定了。最后一個(gè)組件 -- AVCaptureStillImageOutput -- 實(shí)際上是最重要的,因?yàn)樗试S我們捕捉靜態(tài)圖片。只需要?jiǎng)?chuàng)建一個(gè)實(shí)例,并添加到 session 里去:

stillCameraOutput = AVCaptureStillImageOutput()
if self.session.canAddOutput(self.stillCameraOutput) {
  self.session.addOutput(self.stillCameraOutput)
}

配置

現(xiàn)在我們有了所有必需的對(duì)象,應(yīng)該為我們的需求尋找最合適的配置。這里又有兩種方法可以實(shí)現(xiàn)。最簡(jiǎn)單且最推薦是使用 session preset:

session.sessionPreset = AVCaptureSessionPresetPhoto

AVCaptureSessionPresetPhoto 會(huì)為照片捕捉選擇最合適的配置,比如它可以允許我們使用最高的感光度 (ISO) 和曝光時(shí)間,基于相位檢測(cè) (phase detection)的自動(dòng)對(duì)焦, 以及輸出全分辨率的 JPEG 格式壓縮的靜態(tài)圖片。

然而,如果你需要更多的操控,可以使用 AVCaptureDeviceFormat 這個(gè)類(lèi),它描述了一些設(shè)備使用的參數(shù),比如靜態(tài)圖片分辨率,視頻預(yù)覽分辨率,自動(dòng)對(duì)焦類(lèi)型,感光度和曝光時(shí)間限制等。每個(gè)設(shè)備支持的格式都列在 AVCaptureDevice.formats 屬性中,并可以賦值給 AVCaptureDeviceactiveFormat (注意你并不能修改格式)。

操作相機(jī)

iPhone 和 iPad 中內(nèi)置的相機(jī)或多或少跟其他相機(jī)有相同的操作,不同的是,一些參數(shù)如對(duì)焦、曝光時(shí)間 (在單反相機(jī)上的模擬快門(mén)的速度),感光度是可以調(diào)節(jié),但是鏡頭光圈是固定不可調(diào)整的。到了 iOS 8,我們已經(jīng)可以對(duì)所有這些可變參數(shù)進(jìn)行手動(dòng)調(diào)整了。

我們之后會(huì)看到細(xì)節(jié),不過(guò)首先,該啟動(dòng)相機(jī)了:

sessionQueue = dispatch_queue_create("com.example.camera.capture_session", DISPATCH_QUEUE_SERIAL)
dispatch_async(sessionQueue) { () -> Void in
  self.session.startRunning()
}

在 session 和相機(jī)設(shè)備中完成的所有操作和配置都是利用 block 調(diào)用的。因此,建議將這些操作分配到后臺(tái)的串行隊(duì)列中。此外,相機(jī)設(shè)備在改變某些參數(shù)前必須先鎖定,直到改變結(jié)束才能解鎖,例如:

var error:NSError?
if currentDevice.lockForConfiguration(&error) {
  // 鎖定成功,繼續(xù)配置
  // currentDevice.unlockForConfiguration()
}
else {
  // 出錯(cuò),相機(jī)可能已經(jīng)被鎖
}

對(duì)焦

在 iOS 相機(jī)上,對(duì)焦是通過(guò)移動(dòng)鏡片改變其到傳感器之間的距離實(shí)現(xiàn)的。

自動(dòng)對(duì)焦是通過(guò)相位檢測(cè)和反差檢測(cè)實(shí)現(xiàn)的。然而,反差檢測(cè)只適用于低分辨率和高 FPS 視頻捕捉 (慢鏡頭)。

編者注 關(guān)于相位對(duì)焦和反差對(duì)焦,可以參看[這篇文章](http://ask.zealer.com/post/149)。

AVCaptureFocusMode 是個(gè)枚舉,描述了可用的對(duì)焦模式:

  • Locked 指鏡片處于固定位置
  • AutoFocus 指一開(kāi)始相機(jī)會(huì)先自動(dòng)對(duì)焦一次,然后便處于 Locked 模式。
  • ContinuousAutoFocus 指當(dāng)場(chǎng)景改變,相機(jī)會(huì)自動(dòng)重新對(duì)焦到畫(huà)面的中心點(diǎn)。

設(shè)置想要的對(duì)焦模式必須在鎖定之后實(shí)施:

let focusMode:AVCaptureFocusMode = ...
if currentCameraDevice.isFocusModeSupported(focusMode) {
  ... // 鎖定以進(jìn)行配置
  currentCameraDevice.focusMode = focusMode
  ... // 解鎖
  }
}

通常情況下,AutoFocus 模式會(huì)試圖讓屏幕中心成為最清晰的區(qū)域,但是也可以通過(guò)變換 “感興趣的點(diǎn) (point of interest)” 來(lái)設(shè)定另一個(gè)區(qū)域。這個(gè)點(diǎn)是一個(gè) CGPoint,它的值從左上角 {0,0} 到右下角 {1,1},{0.5,0.5} 為畫(huà)面的中心點(diǎn)。通??梢杂靡曨l預(yù)覽圖上的點(diǎn)擊手勢(shì)識(shí)別來(lái)改變這個(gè)點(diǎn),想要將 view 上的坐標(biāo)轉(zhuǎn)化到設(shè)備上的規(guī)范坐標(biāo),我們可以使用 AVVideoCaptureVideoPreviewLayer.captureDevicePointOfInterestForPoint()

var pointInPreview = focusTapGR.locationInView(focusTapGR.view)
var pointInCamera = previewLayer.captureDevicePointOfInterestForPoint(pointInPreview)
...// 鎖定,配置

// 設(shè)置感興趣的點(diǎn)
currentCameraDevice.focusPointOfInterest = pointInCamera

// 在設(shè)置的點(diǎn)上切換成自動(dòng)對(duì)焦
currentCameraDevice.focusMode = .AutoFocus

...// 解鎖

在 iOS 8 中,有個(gè)新選項(xiàng)可以移動(dòng)鏡片的位置,從較近物體的 0.0 到較遠(yuǎn)物體的 1.0 (不是指無(wú)限遠(yuǎn))。

... // 鎖定,配置
var lensPosition:Float = ... // 0.0 到 1.0的float
currentCameraDevice.setFocusModeLockedWithLensPosition(lensPosition) {
  (timestamp:CMTime) -> Void in
  // timestamp 對(duì)應(yīng)于應(yīng)用了鏡片位置的第一張圖像緩存區(qū)
}
... // 解鎖

這意味著對(duì)焦可以使用 UISlider 設(shè)置,這有點(diǎn)類(lèi)似于旋轉(zhuǎn)單反上的對(duì)焦環(huán)。當(dāng)用這種相機(jī)手動(dòng)對(duì)焦時(shí),通常有一個(gè)可見(jiàn)的輔助標(biāo)識(shí)指向清晰的區(qū)域。AVFoundation 里面沒(méi)有內(nèi)置這種機(jī)制,但是比如可以通過(guò)顯示 "對(duì)焦峰值 (focus peaking)"(一種將已對(duì)焦區(qū)域高亮顯示的方式) 這樣的手段來(lái)補(bǔ)救。我們?cè)谶@里不會(huì)討論細(xì)節(jié),不過(guò)對(duì)焦峰值可以很容易地實(shí)現(xiàn),通過(guò)使用閾值邊緣 (threshold edge) 濾鏡 (用自定義 CIFilterGPUImageThresholdEdgeDetectionFilter),并調(diào)用 AVCaptureAudioDataOutputSampleBufferDelegate 下的 captureOutput(_:didOutputSampleBuffer:fromConnection:) 方法將它覆蓋到實(shí)時(shí)預(yù)覽圖上。

曝光

在 iOS 設(shè)備上,鏡頭上的光圈是固定的 (在 iPhone 5s 以及其之后的光圈值是 f/2.2,之前的是 f/2.4),因此只有改變曝光時(shí)間和傳感器的靈敏度才能對(duì)圖片的亮度進(jìn)行調(diào)整,從而達(dá)到合適的效果。至于對(duì)焦,我們可以選擇連續(xù)自動(dòng)曝光,在“感興趣的點(diǎn)”一次性自動(dòng)曝光,或者手動(dòng)曝光。除了指定“感興趣的點(diǎn)”,我們可以通過(guò)設(shè)置曝光補(bǔ)償 (compensation) 修改自動(dòng)曝光,也就是曝光檔位的目標(biāo)偏移。目標(biāo)偏移在曝光檔數(shù)里有講到,它的范圍在 minExposureTargetBiasmaxExposureTargetBias 之間,0為默認(rèn)值 (即沒(méi)有“補(bǔ)償”)。

var exposureBias:Float = ... // 在 minExposureTargetBias 和 maxExposureTargetBias 之間的值
... // 鎖定,配置
currentDevice.setExposureTargetBias(exposureBias) { (time:CMTime) -> Void in
}
... // 解鎖

使用手動(dòng)曝光,我們可以設(shè)置 ISO 和曝光時(shí)間,兩者的值都必須在設(shè)備當(dāng)前格式所指定的范圍內(nèi)。

var activeFormat = currentDevice.activeFormat
var duration:CTime = ... //在activeFormat.minExposureDuration 和 activeFormat.maxExposureDuration 之間的值,或用 AVCaptureExposureDurationCurrent 表示不變
var iso:Float = ... // 在 activeFormat.minISO 和 activeFormat.maxISO 之間的值,或用 AVCaptureISOCurrent 表示不變
... // 鎖定,配置
currentDevice.setExposureModeCustomWithDuration(duration, ISO: iso) { (time:CMTime) -> Void in
}
... // 解鎖

如何知道照片曝光是否正確呢?我們可以通過(guò) KVO,觀察 AVCaptureDeviceexposureTargetOffset 屬性,確認(rèn)是否在 0 附近。

白平衡

數(shù)碼相機(jī)為了適應(yīng)不同類(lèi)型的光照條件需要補(bǔ)償。這意味著在冷光線的條件下,傳感器應(yīng)該增強(qiáng)紅色部分,而在暖光線下增強(qiáng)藍(lán)色部分。在 iPhone 相機(jī)中,設(shè)備會(huì)自動(dòng)決定合適的補(bǔ)光,但有時(shí)也會(huì)被場(chǎng)景的顏色所混淆失效。幸運(yùn)地是,iOS 8 可以里手動(dòng)控制白平衡。

自動(dòng)模式工作方式和對(duì)焦、曝光的方式一樣,但是沒(méi)有“感興趣的點(diǎn)”,整張圖像都會(huì)被納入考慮范圍。在手動(dòng)模式,我們可以通過(guò)開(kāi)爾文所表示的溫度來(lái)調(diào)節(jié)色溫和色彩。典型的色溫值在 2000-3000K (類(lèi)似蠟燭或燈泡的暖光源) 到 8000K (純凈的藍(lán)色天空) 之間。色彩范圍從最小的 -150 (偏綠) 到 150 (偏品紅)。

溫度和色彩可以被用于計(jì)算來(lái)自相機(jī)傳感器的恰當(dāng)?shù)?RGB 值,因此僅當(dāng)它們做了基于設(shè)備的校正后才能被設(shè)置。

以下是全部過(guò)程:

var incandescentLightCompensation = 3_000
var tint = 0 // 不調(diào)節(jié)
let temperatureAndTintValues = AVCaptureWhiteBalanceTemperatureAndTintValues(temperature: incandescentLightCompensation, tint: tint)
var deviceGains = currentCameraDevice.deviceWhiteBalanceGainsForTemperatureAndTintValues(temperatureAndTintValues)
... // 鎖定,配置
currentCameraDevice.setWhiteBalanceModeLockedWithDeviceWhiteBalanceGains(deviceGains) {
        (timestamp:CMTime) -> Void in
    }
  }
... // 解鎖

實(shí)時(shí)人臉檢測(cè)

AVCaptureMetadataOutput 可以用于檢測(cè)人臉和二維碼這兩種物體。很明顯,沒(méi)什么人用二維碼 (編者注: 因?yàn)樵跉W美現(xiàn)在二維碼不是很流行,這里是一個(gè)惡搞。鏈接的這個(gè) tumblr 博客的主題是 “當(dāng)人們?cè)趻叨S碼時(shí)的圖片”,但是 2012 年開(kāi)博至今沒(méi)有任何一張圖片,暗諷二維碼根本沒(méi)人在用,這和以中日韓為代表的亞洲用戶群體的使用習(xí)慣完全相悖),因此我們就來(lái)看看如何實(shí)現(xiàn)人臉檢測(cè)。我們只需通過(guò) AVCaptureMetadataOutput 的代理方法捕獲的元對(duì)象:

var metadataOutput = AVCaptureMetadataOutput()
metadataOutput.setMetadataObjectsDelegate(self, queue: self.sessionQueue)
if session.canAddOutput(metadataOutput) {
  session.addOutput(metadataOutput)
}
metadataOutput.metadataObjectTypes = [AVMetadataObjectTypeFace]
func captureOutput(captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [AnyObject]!, fromConnection connection: AVCaptureConnection!) {
    for metadataObject in metadataObjects as [AVMetadataObject] {
      if metadataObject.type == AVMetadataObjectTypeFace {
        var transformedMetadataObject = previewLayer.transformedMetadataObjectForMetadataObject(metadataObject)
      }
    }

更多關(guān)于人臉檢測(cè)與識(shí)別的內(nèi)容請(qǐng)查看 Engin 的文章。

捕捉靜態(tài)圖片

最后,我們要做的是捕捉高分辨率的圖像,于是我們調(diào)用 captureStillImageAsynchronouslyFromConnection(connection, completionHandler)。在數(shù)據(jù)時(shí)被讀取時(shí),completion handler 將會(huì)在某個(gè)未指定的線程上被調(diào)用。

如果設(shè)置使用 JPEG 編碼作為靜態(tài)圖片輸出,不管是通過(guò) session .Photo 預(yù)設(shè)設(shè)定的,還是通過(guò)設(shè)備輸出設(shè)置設(shè)定的,sampleBuffer 都會(huì)返回包含圖像的元數(shù)據(jù)。如果在 AVCaptureMetadataOutput 中是可用的話,這會(huì)包含 EXIF 數(shù)據(jù),或是被識(shí)別的人臉等:

dispatch_async(sessionQueue) { () -> Void in

  let connection = self.stillCameraOutput.connectionWithMediaType(AVMediaTypeVideo)

  // 將視頻的旋轉(zhuǎn)與設(shè)備同步
  connection.videoOrientation = AVCaptureVideoOrientation(rawValue: UIDevice.currentDevice().orientation.rawValue)!

  self.stillCameraOutput.captureStillImageAsynchronouslyFromConnection(connection) {
    (imageDataSampleBuffer, error) -> Void in

    if error == nil {

      // 如果使用 session .Photo 預(yù)設(shè),或者在設(shè)備輸出設(shè)置中明確進(jìn)行了設(shè)置
      // 我們就能獲得已經(jīng)壓縮為JPEG的數(shù)據(jù)

      let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(imageDataSampleBuffer)

      // 樣本緩沖區(qū)也包含元數(shù)據(jù),我們甚至可以按需修改它

      let metadata:NSDictionary = CMCopyDictionaryOfAttachments(nil, imageDataSampleBuffer, CMAttachmentMode(kCMAttachmentMode_ShouldPropagate)).takeUnretainedValue()

      if let image = UIImage(data: imageData) {
        // 保存圖片,或者做些其他想做的事情
        ...
      }
    }
    else {
      NSLog("error while capturing still image: \(error)")
    }
  }
}

當(dāng)圖片被捕捉的時(shí)候,有視覺(jué)上的反饋是很好的體驗(yàn)。想要知道何時(shí)開(kāi)始以及何時(shí)結(jié)束的話,可以使用 KVO 來(lái)觀察 AVCaptureStillImageOutputisCapturingStillImage 屬性。

分級(jí)捕捉

在 iOS 8 還有一個(gè)有趣的特性叫“分級(jí)捕捉”,可以在不同的曝光設(shè)置下拍攝幾張照片。這在復(fù)雜的光線下拍照顯得非常有用,例如,通過(guò)設(shè)定 -1、0、1 三個(gè)不同的曝光檔數(shù),然后用 HDR 算法合并成一張。

以下是代碼實(shí)現(xiàn):

dispatch_async(sessionQueue) { () -> Void in
  let connection = self.stillCameraOutput.connectionWithMediaType(AVMediaTypeVideo)
  connection.videoOrientation = AVCaptureVideoOrientation(rawValue: UIDevice.currentDevice().orientation.rawValue)!

  var settings = [-1.0, 0.0, 1.0].map {
    (bias:Float) -> AVCaptureAutoExposureBracketedStillImageSettings in

    AVCaptureAutoExposureBracketedStillImageSettings.autoExposureSettingsWithExposureTargetBias(bias)
  }

  var counter = settings.count

  self.stillCameraOutput.captureStillImageBracketAsynchronouslyFromConnection(connection, withSettingsArray: settings) {
    (sampleBuffer, settings, error) -> Void in

    ...
    // 保存 sampleBuffer(s)

    // 當(dāng)計(jì)數(shù)為0,捕捉完成
    counter--

  }
}

這很像是單個(gè)圖像捕捉,但是不同的是 completion handler 被調(diào)用的次數(shù)和設(shè)置的數(shù)組的元素個(gè)數(shù)一樣多。

總結(jié)

我們已經(jīng)詳細(xì)看到如何在 iPhone 應(yīng)用里面實(shí)現(xiàn)拍照的基礎(chǔ)功能(呃…不光是 iPhone,用 iPad 拍照其實(shí)也是不錯(cuò)的)。你也可以查看這個(gè)例子。最后說(shuō)下,iOS 8 允許更精確的捕捉,特別是對(duì)于高級(jí)用戶,這使得 iPhone 與專(zhuān)業(yè)相機(jī)之間的差距縮小,至少在手動(dòng)控制上。不過(guò),不是任何人都喜歡在日常拍照時(shí)使用復(fù)雜的手動(dòng)操作界面,因此請(qǐng)合理地使用這些特性。

上一篇:在沙盒中編寫(xiě)腳本下一篇:Artsy