版本記錄
版本號 | 時間 |
---|---|
V1.0 | 2018.10.15 星期一 |
前言
App中很多時候都需要進行圖像處理,包括各種縮放、濾鏡和壓縮等處理,好的圖像處理不僅可以提高App的性能,也會給用戶帶來耳目一新的感覺,這里重新開了一個專題,專門講述對圖像的各種處理。
開始
首先看一下寫作環境。
Swift 4, iOS 11, Xcode 9
說實話。 作為人類,我們最終會創造出能夠接管世界的機器人,對吧? 對我們最終的機器人大師來說,最重要的一件事就是良好的深度感知。 沒有它,他們怎么會知道它是真的是一個人還是只是一個紙板剪影? 他們可以做到這一點的一種方法是使用深度圖。
但是在機器人能夠做到這一點之前,他們首先需要按照這種方式進行編程,這就是你進來的原因! 在本教程中,您將了解Apple為圖像深度貼圖(image depth maps)
提供的API。 你會:
- 了解iPhone如何生成深度信息。
- 從圖像中讀取深度數據。
- 將此深度數據與濾鏡相結合,以創建整潔的效果。
在開始之前,您需要確保運行Xcode 9或更高版本。 另外,我強烈建議您直接在設備上運行本教程。 這意味著您需要運行iOS 11或更高版本的iPhone。 在撰寫本文時,模擬器的速度非常慢。
打開入門項目。里面的圖像包括與教程一起使用的深度信息。
如果您愿意并且擁有雙攝像頭iPhone,則可以使用自己的圖像來使用本教程。 要拍攝包含深度數據的圖片,iPhone需要運行iOS 11或更高版本。 并且不要忘記在Camera
應用程序中使用Portrait
模式。
您將在入門項目中看到三個警告。 不要擔心它們,因為您將在本教程中修復它們。
Build并運行項目。 你應該看到這個:
點擊圖像循環到下一個。 如果添加自己的圖片,則需要遵循命名約定test##.jpg
。 數字從00
開始并按順序遞增。
在本教程中,您將填寫Depth
, Mask
, 和Filtered
部分的功能。
Reading Depth Data - 讀取深度數據
深度數據最重要的類是AVDepthData
。
不同的圖像格式稍微不同地存儲深度數據。 在HEIC
中,它存儲為元數據。 但在JPG
中,它被存儲為JPG
中的第二個圖像。
您通常使用AVDepthData
從圖像中提取此輔助數據,這是第一步。 打開DepthReader.swift
并將以下方法添加到DepthReader
:
func depthDataMap() -> CVPixelBuffer? {
// 1
guard let fileURL = Bundle.main.url(forResource: name, withExtension: ext) as CFURL? else {
return nil
}
// 2
guard let source = CGImageSourceCreateWithURL(fileURL, nil) else {
return nil
}
// 3
guard let auxDataInfo = CGImageSourceCopyAuxiliaryDataInfoAtIndex(source, 0,
kCGImageAuxiliaryDataTypeDisparity) as? [AnyHashable : Any] else {
return nil
}
// 4
var depthData: AVDepthData
do {
// 5
depthData = try AVDepthData(fromDictionaryRepresentation: auxDataInfo)
} catch {
return nil
}
// 6
if depthData.depthDataType != kCVPixelFormatType_DisparityFloat32 {
depthData = depthData.converting(toDepthDataType: kCVPixelFormatType_DisparityFloat32)
}
// 7
return depthData.depthDataMap
}
好的,這是相當多的代碼,下面細分講解:
- 1) 首先,您獲取圖像文件的URL并安全地將其轉換為
CFURL
。 - 2) 然后,您可以從此文件創建
CGImageSource
。 - 3) 從索引0處的圖像源,您可以從其輔助數據中復制
disparity
數據(更多關于以后的含義,但您現在可以將其視為深度數據)。索引為0,因為圖像源中只有一個圖像。 iOS知道如何從JPG
和HEIC
文件中提取數據,但不幸的是,這在模擬器中不起作用。 - 4) 您為深度數據準備了屬性。如前所述,您使用
AVDepthData
從圖像中提取輔助數據。 - 5) 您可以從讀入的輔助數據中創建
AVDepthData
實體。 - 6) 您可以確保深度數據是您需要的格式:32位浮點
disparity
信息。 - 7) 最后,返回此深度數據
map
。
現在,在運行此之前,您需要更新DepthImageViewController.swift
。
找到loadCurrent(image:withExtension :)
并將以下代碼行添加到開頭:
// 1
let depthReader = DepthReader(name: name, ext: ext)
// 2
let depthDataMap = depthReader.depthDataMap()
// 3
depthDataMap?.normalize()
// 4
let ciImage = CIImage(cvPixelBuffer: depthDataMap)
depthDataMapImage = UIImage(ciImage: ciImage)
使用此代碼:
- 1) 您可以使用當前圖像創建
DepthReader
實體。 - 2) 使用新的
depthDataMap
方法,將深度數據讀入CVPixelBuffer
。 - 3) 然后使用提供的
CVPixelBuffer
擴展來標準化深度數據。 這可以確保所有像素都在0.0和1.0之間,其中0.0是最遠的像素,1.0是最近的像素。 - 4) 然后,將深度數據轉換為
CIImage
,然后轉換為UIImage
,并將其保存到屬性中。
如果您對
normalize
方法的工作方式感興趣,請查看CVPixelBufferExtension.swift
。 它循環遍歷2D數組中的每個值,并跟蹤所看到的最小值和最大值。 然后它再次循環遍歷所有值,并使用最小值和最大值來計算介于0.0和1.0之間的新值。
Build并運行項目,然后點擊底部分段控件的Depth
分段。
真棒! 還記得你對深度數據進行標準化嗎? 這是視覺表現。 像素越白,越近,像素越暗,距離越遠。
做的很好!
How Does the iPhone Do This? - iPhone如何做到這一點?
簡而言之,iPhone的雙攝像頭正在模仿立體視覺。
試試這個。 將食指緊緊貼在鼻子前方并指向上方。 閉上你的左眼。 不要移動手指或頭部,同時打開左眼并閉上右眼。
現在快速來回關閉一只眼睛并打開另一只眼睛。 注意手指相對于背景中物體的相對位置。 看看你的手指如何與遠處的物體相比左右跳躍?
物體離眼睛越近,其相對位置與背景相比的變化越大。 這聽起來很熟悉嗎? 這是一個視差效應!
iPhone的雙攝像頭就像它的眼睛一樣,看著兩張相互略微偏移的圖像。 它對應兩個圖像中的特征并計算它們移動了多少像素。 像素的這種變化稱為視差disparity。
1. Depth vs Disparity - 深度與視差
到目前為止,我們主要使用術語depth data
,但在您的代碼中,您請求了kCGImageAuxiliaryDataTypeDisparity
數據,深度和差異基本上是成反比的。
物體距離越遠,深度越大。 但是這些物體的像素之間的距離越來越接近零。 如果你打開開始項目,你可能已經注意到在選擇Mask和Filter片段時屏幕底部有一個滑塊可見。
您將使用此滑塊以及深度數據為特定深度的圖像制作蒙版。 然后你將使用這個蒙版過濾原始圖像并創建一些其他的效果!
Creating a Mask - 創建一個蒙層
打開DepthImageFilters.swift
并找到createMask(for:withFocus:andScale :)
。 然后將以下代碼添加到其頂部:
let s1 = MaskParams.slope
let s2 = -MaskParams.slope
let filterWidth = 2 / MaskParams.slope + MaskParams.width
let b1 = -s1 * (focus - filterWidth / 2)
let b2 = -s2 * (focus + filterWidth / 2)
這些常量將定義我們如何將深度數據轉換為圖像蒙版。
將深度數據圖想象為以下函數:
深度圖圖像的像素值等于標準化視差。 請記住,像素值1.0是白色,視差值1.0是最接近相機的。 在比例的另一側,像素值0.0是黑色,并且視差值0.0離相機最遠。
當您從深度數據創建蒙版時,您將更改此函數以使其更有趣。
使用斜率4.0,寬度0.1和0.75作為焦點,createMask(for:withFocus:andScale :)
將在您完成后使用以下函數:
這意味著最白像素(值1.0)將是具有0.75±0.05
(焦點±寬度/ 2)的視差的像素。 然后,對于高于和低于該范圍的視差值,像素將快速漸變為黑色。 斜率越大,它們褪色到黑色的速度越快。
在常量添加此行之后:
let mask0 = depthImage
.applyingFilter("CIColorMatrix", parameters: [
"inputRVector": CIVector(x: s1, y: 0, z: 0, w: 0),
"inputGVector": CIVector(x: 0, y: s1, z: 0, w: 0),
"inputBVector": CIVector(x: 0, y: 0, z: s1, w: 0),
"inputBiasVector": CIVector(x: b1, y: b1, z: b1, w: 0)])
.applyingFilter("CIColorClamp")
該濾波器將所有像素乘以斜率s1
。 由于蒙版是灰度的,因此您需要確保所有顏色通道具有相同的值。 使用CIColorClamp
將值鉗位在0.0和1.0之間后,此過濾器將應用以下函數:
s1
越大,線的斜率越陡。 常數b1
向左或向右移動線。
要處理遮罩函數的另一面,請添加以下內容:
let mask1 = depthImage
.applyingFilter("CIColorMatrix", parameters: [
"inputRVector": CIVector(x: s2, y: 0, z: 0, w: 0),
"inputGVector": CIVector(x: 0, y: s2, z: 0, w: 0),
"inputBVector": CIVector(x: 0, y: 0, z: s2, w: 0),
"inputBiasVector": CIVector(x: b2, y: b2, z: b2, w: 0)])
.applyingFilter("CIColorClamp")
由于斜率s2
為負,因此濾波器應用以下函數:
現在,把兩個masks
放在一起:
let combinedMask = mask0.applyingFilter("CIDarkenBlendMode", parameters: ["inputBackgroundImage" : mask1])
let mask = combinedMask.applyingFilter("CIBicubicScaleTransform", parameters: ["inputScale": scale])
您可以使用CIDarkenBlendMode
過濾器組合蒙版,該過濾器選擇輸入蒙版的兩個值中的較低者。
然后縮放蒙版以匹配圖像大小。
最后,用以下代碼替換返回行:
return mask
Build并運行您的項目。 點擊Mask
分段部分并使用滑塊進行播放。
警告:如果您在模擬器中運行,這將是無法忍受的緩慢。 如果您希望看到此改進,請在bugreport.apple.com上復制此open radar。
你應該看到這樣的東西:
1. Your First Depth-Inspired Filter - 你的第一個深度激發過濾器
接下來,您將創建一個有點模仿聚光燈的過濾器。 “聚光燈”將照射在選定深度的物體上,并從那里淡化為黑色。
而且因為你已經在深度數據和創建mask中進行了艱苦的閱讀,所以它將變得非常簡單。
打開DepthImageFilters.swift
并添加以下內容:
func spotlightHighlight(image: CIImage, mask: CIImage, orientation: UIImageOrientation = .up) -> UIImage? {
// 1
let output = image.applyingFilter("CIBlendWithMask", parameters: ["inputMaskImage": mask])
// 2
guard let cgImage = context.createCGImage(output, from: output.extent) else {
return nil
}
// 3
return UIImage(cgImage: cgImage, scale: 1.0, orientation: orientation)
}
以下是您在這三行中所做的工作:
- 1) 您使用了
CIBlendWithMask
過濾器并傳入了您在上一節中創建的mask。 濾波器實質上將像素的alpha值設置為對應的mask像素值。 因此,當mask像素值是1.0時,圖像像素是完全不透明的,并且當mask像素值是0.0時,圖像像素是完全透明的。 由于UIImageView
背后的UIView
有黑色,因此黑色就是您從圖像后面看到的。 - 2) 使用
CIContext
創建CGImage
。 - 3) 然后創建一個
UIImage
并將其返回。
要查看此過濾器的運行情況,首先需要DepthImageViewController
在適當時調用此方法。
打開DepthImageViewController.swift
并轉到updateImageView
。 在主switch語句的.filtered
的case中,您將找到selectedFilter
的嵌套switch語句。
將.spotlight
案例的代碼替換為:
finalImage = depthFilters?.spotlightHighlight(image: filterImage, mask: mask, orientation: orientation)
Build并運行您的項目! 點按Filtered
的部分,并確保在頂部選擇Spotlight
。 滑動滑塊。 你應該看到這樣的東西:
恭喜! 您已經編寫了第一個深度激發的圖像濾鏡。
2. Color Highlight Filter - 顏色突出顯示過濾器
打開DepthImageFilters.swift
,然后在剛剛編寫的spotlightHighlight(image:mask:orientation :)
下面添加以下新方法:
func colorHighlight(image: CIImage, mask: CIImage, orientation: UIImageOrientation = .up) -> UIImage? {
let greyscale = image.applyingFilter("CIPhotoEffectMono")
let output = image.applyingFilter("CIBlendWithMask", parameters: ["inputBackgroundImage" : greyscale,
"inputMaskImage": mask])
guard let cgImage = context.createCGImage(output, from: output.extent) else {
return nil
}
return UIImage(cgImage: cgImage, scale: 1.0, orientation: orientation)
}
這應該看起來很熟悉。 它幾乎與您剛寫的spotlightHighlight(image:mask:orientation :)
過濾器完全相同。 一個區別是,這次您將背景圖像設置為原始圖像的灰度版本。
此濾鏡將根據滑塊位置在焦點處顯示全色,并從那里淡入灰色。
打開DepthImageViewController.swift
并在selectedFilter
的相同switch
語句中,將.color
大小寫的代碼替換為:
finalImage = depthFilters?.colorHighlight(image: filterImage, mask: mask, orientation: orientation)
這將調用您的新過濾器方法并顯示結果。
Build并運行以查看魔法:
當你拍照只是為了稍后發現相機聚焦在錯誤的物體上時,你不討厭它嗎? 如果你能在事后改變焦點怎么樣?
這正是您接下來要寫的深度激發過濾器!
3. Change the Focal Length - 改變焦距
在DepthImageFilters.swift
中的colorHightlight(image:mask:orientation :)
方法下,添加:
func blur(image: CIImage, mask: CIImage, orientation: UIImageOrientation = .up) -> UIImage? {
// 1
let invertedMask = mask.applyingFilter("CIColorInvert")
// 2
let output = image.applyingFilter("CIMaskedVariableBlur", parameters: ["inputMask" : invertedMask,
"inputRadius": 15.0])
// 3
guard let cgImage = context.createCGImage(output, from: output.extent) else {
return nil
}
// 4
return UIImage(cgImage: cgImage, scale: 1.0, orientation: orientation)
}
這個過濾器與其他兩個過濾器略有不同。
- 1) 首先,您反轉mask。
- 2) 然后應用
CIMaskedVariableBlur
過濾器,這是iOS 11中的新過濾器。此過濾器將使用等于inputRadius * mask pixel value
的半徑進行模糊。 因此,當掩碼像素值為1.0時,模糊處于最大值,這就是您需要首先反轉掩碼的原因。 - 3) 再次,您使用
CIContext
生成CGImage
... - 4) ...并使用它來創建
UIImage
并將其返回。
注意:如果遇到性能問題,可以嘗試減少
inputRadius
。 高斯模糊計算成本高,模糊半徑越大,需要進行的計算越多。
在運行之前,需要再次更新selectedFilter switch
語句。 要使用閃亮的新方法,請將.blur
情況下的代碼更改為:
finalImage = depthFilters?.blur(image: filterImage, mask: mask, orientation: orientation)
Build并運行
很完美,對吧!
More About AVDepthData - 有關AVDepthData
的更多信息
你還記得你如何在createMask(for:withFocus:andScale:)
中縮放mask嗎?原因是iPhone捕獲的深度數據的分辨率低于傳感器分辨率。與相機可拍攝的1200萬像素相比,它更接近0.5百萬像素。
另一個重要的事情是數據可以過濾或未過濾。未過濾的數據可能具有由NaN
表示的空洞(非數字 - 浮點數據類型中的可能值)。如果手機無法關聯兩個像素,或者某些東西只遮擋其中一個攝像頭,則會導致這些NaN值出現disparity
。
值為NaN的像素將顯示為黑色。由于乘以NaN始終為NaN,因此這些黑色像素將傳播到最終圖像。它們看起來就像是圖像中的洞。
由于這可能很難處理,Apple會在可用時為您提供過濾數據,以填補這些空白并平滑數據。
如果您不確定,則應始終檢查isDepthDataFiltered
屬性,以確定您是否正在處理已過濾或未過濾的數據。
還有更多的Core Image過濾器可用。 點擊here查看完整列表。 當與深度數據結合時,許多這些濾鏡可以創建有趣的效果。
源碼
1. Swift
首先看一下文檔結構
看一下sb中的內容
下面就是看代碼了
1. ControlEnums.swift
enum ImageMode: Int {
case original = 0
case depth = 1
case mask = 2
case filtered = 3
}
enum FilterType: Int {
case spotlight = 0
case color = 1
case blur = 2
}
2. DepthImageFilters.swift
import UIKit
enum MaskParams {
static let slope: CGFloat = 4.0
static let width: CGFloat = 0.1
}
class DepthImageFilters {
var context: CIContext
init(context: CIContext) {
self.context = context
}
init() {
context = CIContext()
}
func createMask(for depthImage: CIImage, withFocus focus: CGFloat, andScale scale: CGFloat) -> CIImage {
let s1 = MaskParams.slope
let s2 = -MaskParams.slope
let filterWidth = 2 / MaskParams.slope + MaskParams.width
let b1 = -s1 * (focus - filterWidth / 2)
let b2 = -s2 * (focus + filterWidth / 2)
let mask0 = depthImage
.applyingFilter("CIColorMatrix", parameters: [
"inputRVector": CIVector(x: s1, y: 0, z: 0, w: 0),
"inputGVector": CIVector(x: 0, y: s1, z: 0, w: 0),
"inputBVector": CIVector(x: 0, y: 0, z: s1, w: 0),
"inputBiasVector": CIVector(x: b1, y: b1, z: b1, w: 0)])
.applyingFilter("CIColorClamp")
let mask1 = depthImage
.applyingFilter("CIColorMatrix", parameters: [
"inputRVector": CIVector(x: s2, y: 0, z: 0, w: 0),
"inputGVector": CIVector(x: 0, y: s2, z: 0, w: 0),
"inputBVector": CIVector(x: 0, y: 0, z: s2, w: 0),
"inputBiasVector": CIVector(x: b2, y: b2, z: b2, w: 0)])
.applyingFilter("CIColorClamp")
let combinedMask = mask0.applyingFilter("CIDarkenBlendMode", parameters: ["inputBackgroundImage" : mask1])
let mask = combinedMask.applyingFilter("CIBicubicScaleTransform", parameters: ["inputScale": scale])
return mask
}
func spotlightHighlight(image: CIImage, mask: CIImage, orientation: UIImageOrientation = .up) -> UIImage? {
let output = image.applyingFilter("CIBlendWithMask", parameters: ["inputMaskImage": mask])
guard let cgImage = context.createCGImage(output, from: output.extent) else {
return nil
}
return UIImage(cgImage: cgImage, scale: 1.0, orientation: orientation)
}
func colorHighlight(image: CIImage, mask: CIImage, orientation: UIImageOrientation = .up) -> UIImage? {
let greyscale = image.applyingFilter("CIPhotoEffectMono")
let output = image.applyingFilter("CIBlendWithMask", parameters: ["inputBackgroundImage" : greyscale,
"inputMaskImage": mask])
guard let cgImage = context.createCGImage(output, from: output.extent) else {
return nil
}
return UIImage(cgImage: cgImage, scale: 1.0, orientation: orientation)
}
func blur(image: CIImage, mask: CIImage, orientation: UIImageOrientation = .up) -> UIImage? {
let invertedMask = mask.applyingFilter("CIColorInvert")
let output = image.applyingFilter("CIMaskedVariableBlur", parameters: ["inputMask" : invertedMask,
"inputRadius": 15.0])
guard let cgImage = context.createCGImage(output, from: output.extent) else {
return nil
}
return UIImage(cgImage: cgImage, scale: 1.0, orientation: orientation)
}
}
3. DepthImageViewController.swift
import AVFoundation
import UIKit
class DepthImageViewController: UIViewController {
@IBOutlet weak var imageView: UIImageView!
@IBOutlet weak var imageModeControl: UISegmentedControl!
@IBOutlet weak var filterControl: UISegmentedControl!
@IBOutlet weak var depthSlider: UISlider!
@IBOutlet weak var filterControlView: UIView!
var origImage: UIImage?
var depthDataMapImage: UIImage?
var filterImage: CIImage?
var bundledJPGs = [String]()
var current = 0
let context = CIContext()
var depthFilters: DepthImageFilters?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
depthFilters = DepthImageFilters(context: context)
// Figure out which images are bundled in the app
bundledJPGs = getAvailableImages()
// Load current image
loadCurrent(image: bundledJPGs[current], withExtension: "jpg")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
// MARK: Depth Data Methods
extension DepthImageViewController {
}
// MARK: Helper Methods
extension DepthImageViewController {
func getAvailableImages() -> [String] {
var availableImages = [String]()
var base = "test"
var name = "\(base)00"
var num = 0 {
didSet {
name = "\(base)\(String(format: "%02d", num))"
}
}
while Bundle.main.url(forResource: name, withExtension: "jpg") != nil {
availableImages.append(name)
num += 1
}
return availableImages
}
func loadCurrent(image name: String, withExtension ext: String) {
let depthReader = DepthReader(name: name, ext: ext)
// Read the depth data from the image
let depthDataMap = depthReader.depthDataMap()
// Normalize the depth data to be between 0.0 -> 1.0
depthDataMap?.normalize()
let ciImage = CIImage(cvPixelBuffer: depthDataMap)
depthDataMapImage = UIImage(ciImage: ciImage)
// Create the original unmodified image
origImage = UIImage(named: "\(name).\(ext)")
filterImage = CIImage(image: origImage)
// Set the segmented control to point to the original image
imageModeControl.selectedSegmentIndex = ImageMode.original.rawValue
// Update the image view
updateImageView()
}
func updateImageView() {
depthSlider.isHidden = true
filterControlView.isHidden = true
imageView.image = nil
let selectedImageMode = ImageMode(rawValue: imageModeControl.selectedSegmentIndex) ?? .original
switch selectedImageMode {
case .original:
// Original
imageView.image = origImage
case .depth:
// Depth
#if IOS_SIMULATOR
guard let orientation = origImage?.imageOrientation,
let ciImage = depthDataMapImage?.ciImage,
let cgImage = context.createCGImage(ciImage, from: ciImage.extent) else {
return
}
imageView.image = UIImage(cgImage: cgImage, scale: 1.0, orientation: orientation)
#else
imageView.image = depthDataMapImage
#endif
case .mask:
// Mask
depthSlider.isHidden = false
guard let depthImage = depthDataMapImage?.ciImage else {
return
}
let maxToDim = max((origImage?.size.width ?? 1.0), (origImage?.size.height ?? 1.0))
let maxFromDim = max((depthDataMapImage?.size.width ?? 1.0), (depthDataMapImage?.size.height ?? 1.0))
let scale = maxToDim / maxFromDim
guard let mask = depthFilters?.createMask(for: depthImage, withFocus: CGFloat(depthSlider.value), andScale: scale) else {
return
}
guard let cgImage = context.createCGImage(mask, from: mask.extent),
let origImage = origImage else {
return
}
imageView.image = UIImage(cgImage: cgImage, scale: 1.0, orientation: origImage.imageOrientation)
case .filtered:
// Filtered
depthSlider.isHidden = false
filterControlView.isHidden = false
guard let depthImage = depthDataMapImage?.ciImage else {
return
}
let maxToDim = max((origImage?.size.width ?? 1.0), (origImage?.size.height ?? 1.0))
let maxFromDim = max((depthDataMapImage?.size.width ?? 1.0), (depthDataMapImage?.size.height ?? 1.0))
let scale = maxToDim / maxFromDim
guard let mask = depthFilters?.createMask(for: depthImage, withFocus: CGFloat(depthSlider.value), andScale: scale),
let filterImage = filterImage,
let orientation = origImage?.imageOrientation else {
return
}
let finalImage: UIImage?
let selectedFilter = FilterType(rawValue: filterControl.selectedSegmentIndex) ?? .spotlight
switch selectedFilter {
case .spotlight:
finalImage = depthFilters?.spotlightHighlight(image: filterImage, mask: mask, orientation: orientation)
case .color:
finalImage = depthFilters?.colorHighlight(image: filterImage, mask: mask, orientation: orientation)
case .blur:
finalImage = depthFilters?.blur(image: filterImage, mask: mask, orientation: orientation)
}
imageView.image = finalImage
}
}
}
// MARK: Slider Methods
extension DepthImageViewController {
@IBAction func sliderValueChanged(_ sender: UISlider) {
updateImageView()
}
}
// MARK: Segmented Control Methods
extension DepthImageViewController {
@IBAction func segementedControlValueChanged(_ sender: UISegmentedControl) {
updateImageView()
}
@IBAction func filterTypeChanged(_ sender: UISegmentedControl) {
updateImageView()
}
}
// MARK: Gesture Recognizor Methods
extension DepthImageViewController {
@IBAction func imageTapped(_ sender: UITapGestureRecognizer) {
current = (current + 1) % bundledJPGs.count
loadCurrent(image: bundledJPGs[current], withExtension: "jpg")
}
}
4. DepthReader.swift
#if !IOS_SIMULATOR
import AVFoundation
struct DepthReader {
var name: String
var ext: String
func depthDataMap() -> CVPixelBuffer? {
// Create a CFURL for the image in the Bundle
guard let fileURL = Bundle.main.url(forResource: name, withExtension: ext) as CFURL? else {
return nil
}
// Create a CGImageSource
guard let source = CGImageSourceCreateWithURL(fileURL, nil) else {
return nil
}
guard let auxDataInfo = CGImageSourceCopyAuxiliaryDataInfoAtIndex(source, 0, kCGImageAuxiliaryDataTypeDisparity) as? [AnyHashable : Any] else {
return nil
}
// This is the star of the show!
var depthData: AVDepthData
do {
// Get the depth data from the auxiliary data info
depthData = try AVDepthData(fromDictionaryRepresentation: auxDataInfo)
} catch {
return nil
}
// Make sure the depth data is the type we want
if depthData.depthDataType != kCVPixelFormatType_DisparityFloat32 {
depthData = depthData.converting(toDepthDataType: kCVPixelFormatType_DisparityFloat32)
}
return depthData.depthDataMap
}
}
#endif
5. DepthReaderSimulatorHack.swift
#if IOS_SIMULATOR
import AVFoundation
import UIKit
struct DepthReader {
var name: String
var ext: String
func depthDataMap() -> CVPixelBuffer? {
// Create a CFURL for the image in the Bundle
guard let fileURL = Bundle.main.url(forResource: name, withExtension: ext) as CFURL? else {
return nil
}
// Create a CGImageSource
guard let source = CGImageSourceCreateWithURL(fileURL, nil) else {
return nil
}
guard let cgImage = CGImageSourceCreateImageAtIndex(source, 1, nil) else {
return nil
}
let depthDataMap = cgImage.pixelBuffer()?.convertToDisparity32()
depthDataMap?.normalize()
return depthDataMap
}
}
#endif
6. CGImageExtension.swift
import CoreVideo
import CoreGraphics
extension CGImage {
func pixelBuffer() -> CVPixelBuffer? {
var pxbuffer: CVPixelBuffer?
guard let dataProvider = dataProvider else {
return nil
}
let dataFromImageDataProvider = CFDataCreateMutableCopy(kCFAllocatorDefault, 0, dataProvider.data)
CVPixelBufferCreateWithBytes(
kCFAllocatorDefault,
width,
height,
kCVPixelFormatType_32ARGB,
CFDataGetMutableBytePtr(dataFromImageDataProvider),
bytesPerRow,
nil,
nil,
nil,
&pxbuffer
)
return pxbuffer
}
}
7. CIImageExtension.swift
import UIKit
extension CIImage {
convenience init?(image: UIImage?) {
guard let image = image else {
return nil
}
self.init(image: image)
}
convenience init?(cvPixelBuffer: CVPixelBuffer?) {
guard let cvPixelBuffer = cvPixelBuffer else {
return nil
}
self.init(cvPixelBuffer: cvPixelBuffer)
}
convenience init?(cgImage: CGImage?) {
guard let cgImage = cgImage else {
return nil
}
self.init(cgImage: cgImage)
}
}
8. CVPixelBufferExtension.swift
import AVFoundation
import UIKit
extension CVPixelBuffer {
func normalize() {
let width = CVPixelBufferGetWidth(self)
let height = CVPixelBufferGetHeight(self)
CVPixelBufferLockBaseAddress(self, CVPixelBufferLockFlags(rawValue: 0))
let floatBuffer = unsafeBitCast(CVPixelBufferGetBaseAddress(self), to: UnsafeMutablePointer<Float>.self)
var minPixel: Float = 1.0
var maxPixel: Float = 0.0
for y in 0 ..< height {
for x in 0 ..< width {
let pixel = floatBuffer[y * width + x]
minPixel = min(pixel, minPixel)
maxPixel = max(pixel, maxPixel)
}
}
let range = maxPixel - minPixel
for y in 0 ..< height {
for x in 0 ..< width {
let pixel = floatBuffer[y * width + x]
floatBuffer[y * width + x] = (pixel - minPixel) / range
}
}
CVPixelBufferUnlockBaseAddress(self, CVPixelBufferLockFlags(rawValue: 0))
}
func printDebugInfo() {
let width = CVPixelBufferGetWidth(self)
let height = CVPixelBufferGetHeight(self)
let bytesPerRow = CVPixelBufferGetBytesPerRow(self)
let totalBytes = CVPixelBufferGetDataSize(self)
print("Depth Map Info: \(width)x\(height)")
print(" Bytes per Row: \(bytesPerRow)")
print(" Total Bytes: \(totalBytes)")
}
func convertToDisparity32() -> CVPixelBuffer? {
let width = CVPixelBufferGetWidth(self)
let height = CVPixelBufferGetHeight(self)
var dispartyPixelBuffer: CVPixelBuffer?
let _ = CVPixelBufferCreate(nil, width, height, kCVPixelFormatType_DisparityFloat32, nil, &dispartyPixelBuffer)
guard let outputPixelBuffer = dispartyPixelBuffer else {
return nil
}
CVPixelBufferLockBaseAddress(outputPixelBuffer, CVPixelBufferLockFlags(rawValue: 0))
CVPixelBufferLockBaseAddress(self, CVPixelBufferLockFlags(rawValue: 1))
let outputBuffer = unsafeBitCast(CVPixelBufferGetBaseAddress(outputPixelBuffer), to: UnsafeMutablePointer<Float>.self)
let inputBuffer = unsafeBitCast(CVPixelBufferGetBaseAddress(self), to: UnsafeMutablePointer<UInt8>.self)
for y in 0 ..< height {
for x in 0 ..< width {
let pixel = inputBuffer[y * width + x]
outputBuffer[y * width + x] = (Float(pixel) / Float(UInt8.max))
}
}
CVPixelBufferUnlockBaseAddress(self, CVPixelBufferLockFlags(rawValue: 1))
CVPixelBufferUnlockBaseAddress(outputPixelBuffer, CVPixelBufferLockFlags(rawValue: 0))
return dispartyPixelBuffer
}
}
9. UIImageExtension.swift
import UIKit
extension UIImage {
convenience init?(ciImage: CIImage?) {
guard let ciImage = ciImage else {
return nil
}
self.init(ciImage: ciImage)
}
}
后記
本篇主要講述了圖像深度相關處理簡單示例,感興趣給個贊或者關注~~~