圖像處理相關(一) —— 圖像深度相關處理簡單示例(一)

版本記錄

版本號 時間
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知道如何從JPGHEIC文件中提取數據,但不幸的是,這在模擬器中不起作用。
  • 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數據,深度和差異基本上是成反比的。

物體距離越遠,深度越大。 但是這些物體的像素之間的距離越來越接近零。 如果你打開開始項目,你可能已經注意到在選擇MaskFilter片段時屏幕底部有一個滑塊可見。

您將使用此滑塊以及深度數據為特定深度的圖像制作蒙版。 然后你將使用這個蒙版過濾原始圖像并創建一些其他的效果!


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

后記

本篇主要講述了圖像深度相關處理簡單示例,感興趣給個贊或者關注~~~

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容