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

iOS 上的相機捕捉

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

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

概述

AVFoundation vs. UIImagePickerController

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

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

AVFoundation 相關(guān)類

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

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

設(shè)置

讓我們看看如何捕獲圖像。首先我們需要一個 AVCaptureSession 對象:

let session = AVCaptureSession()

現(xiàn)在我們需要一個相機設(shè)備輸入。在大多數(shù) iPhone 和 iPad 中,我們可以選擇后置攝像頭或前置攝像頭 -- 又稱自拍相機 (selfie camera) -- 之一。那么我們必須先遍歷所有能提供視頻數(shù)據(jù)的設(shè)備 (麥克風(fēng)也屬于 AVCaptureDevice,因此略過不談),并檢查 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)合適的相機設(shè)備,我們就能獲得相關(guān)的 AVCaptureDeviceInput 對象。我們會將它設(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)
  }
}

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

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

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

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

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

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

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

為獲得數(shù)據(jù)流,我們需要創(chuàng)建一個 AVCaptureVideoDataOutput,這樣一來,當相機在運行時,我們通過代理方法 captureOutput(_:didOutputSampleBuffer:fromConnection:) 就能獲得所有圖像幀 (除非我們處理太慢而導(dǎo)致掉幀),然后將它們繪制在一個 GLKView 中。不需要對 OpenGL 框架有什么深刻的理解,我們只需要這樣就能創(chuàng)建一個 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()
}

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

馬上就要搞定了。最后一個組件 -- AVCaptureStillImageOutput -- 實際上是最重要的,因為它允許我們捕捉靜態(tài)圖片。只需要創(chuàng)建一個實例,并添加到 session 里去:

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

配置

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

session.sessionPreset = AVCaptureSessionPresetPhoto

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

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

操作相機

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

我們之后會看到細節(jié),不過首先,該啟動相機了:

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

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

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

對焦

在 iOS 相機上,對焦是通過移動鏡片改變其到傳感器之間的距離實現(xiàn)的。

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

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

AVCaptureFocusMode 是個枚舉,描述了可用的對焦模式:

  • Locked 指鏡片處于固定位置
  • AutoFocus 指一開始相機會先自動對焦一次,然后便處于 Locked 模式。
  • ContinuousAutoFocus 指當場景改變,相機會自動重新對焦到畫面的中心點。

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

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

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

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

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

// 在設(shè)置的點上切換成自動對焦
currentCameraDevice.focusMode = .AutoFocus

...// 解鎖

在 iOS 8 中,有個新選項可以移動鏡片的位置,從較近物體的 0.0 到較遠物體的 1.0 (不是指無限遠)。

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

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

曝光

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

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

使用手動曝光,我們可以設(shè)置 ISO 和曝光時間,兩者的值都必須在設(shè)備當前格式所指定的范圍內(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
}
... // 解鎖

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

白平衡

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

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

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

以下是全部過程:

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
    }
  }
... // 解鎖

實時人臉檢測

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

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)于人臉檢測與識別的內(nèi)容請查看 Engin 的文章

捕捉靜態(tài)圖片

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

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

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è)置中明確進行了設(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)")
    }
  }
}

當圖片被捕捉的時候,有視覺上的反饋是很好的體驗。想要知道何時開始以及何時結(jié)束的話,可以使用 KVO 來觀察 AVCaptureStillImageOutputisCapturingStillImage 屬性。

分級捕捉

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

以下是代碼實現(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)

    // 當計數(shù)為0,捕捉完成
    counter--

  }
}

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

總結(jié)

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

上一篇:在沙盒中編寫腳本下一篇:Artsy