图像处理相关(一) —— 图像深度相关处理简单示例(一)

版本记录

版本号 时间
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)
  }
}

后记

本篇主要讲述了图像深度相关处理简单示例,感兴趣给个赞或者关注~~~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容