视频处理相关(一) —— 视频深度相关处理简单示例(一)

版本记录

版本号 时间
V1.0 2018.10.17 星期三

前言

App中很多时候都需要进行视频处理,包括各种滤镜以及编解码等处理,好的视频处理不仅可以提高App的性能,也会给用户带来耳目一新的感觉,这里重新开了一个专题,专门讲述对视频的各种处理。

开始

首先看一下写作环境

Swift 4, iOS 11, Xcode 9

在这个iOS视频深度图教程中,学习如何利用iOS 11强大的视频深度贴图来应用实时视频滤镜并创建特效杰作!

在这个视频深度图教程中,您将学习如何:

  • 请求视频Feed的深度信息。
  • 操纵深度信息。
  • 将视频Feed与深度数据和滤镜相结合,以创建SFX杰作。

对于此视频深度贴图教程,您将需要Xcode 9或更高版本。 你还需要一部背面有双摄像头的iPhone,这就是iPhone生成深度信息的方式。 还需要Apple Developer帐户,因为您需要在设备上运行此应用程序,而不是模拟器。

准备好一切后,下载并浏览本教程的材料(您可以在本教程的顶部或底部找到一个链接)。

打开入门项目,然后在您的设备上Build并运行它。 你会看到这样的事情:

注意:为了捕捉深度信息,iPhone必须设置广角相机变焦以匹配远摄相机变焦。 因此,与相机应用程序相比,应用程序中的视频被放大。

此时,该应用程序没有做太多。 现在需要一起来完善!


Capturing Video Depth Maps Data - 捕获视频深度图数据

捕获视频的深度数据需要将AVCaptureDepthDataOutput对象添加到AVCaptureSession

正如其名称所示,AVCaptureDepthDataOutput在iOS 11中被添加,专门用于处理深度数据。

打开DepthVideoViewController.swift并将以下行添加到configureCaptureSession()的底部:

// 1
let depthOutput = AVCaptureDepthDataOutput()

// 2
depthOutput.setDelegate(self, callbackQueue: dataOutputQueue)

// 3
depthOutput.isFilteringEnabled = true

// 4
session.addOutput(depthOutput)

// 5
let depthConnection = depthOutput.connection(with: .depthData)

// 6
depthConnection?.videoOrientation = .portrait

以下是逐步细分:

  • 1) 您创建一个新的AVCaptureDepthDataOutput对象
  • 2) 然后将当前视图控制器设置为新对象的委托。callbackQueue参数是调用委托方法的调度队列。 现在,忽略错误;你以后会解决的。
  • 3) 您可以对深度数据启用过滤,以利用Apple的算法填充数据中的任何漏洞。
  • 4) 此时,您已准备好将配置的AVCaptureDepthDataOutput添加到AVCaptureSession
  • 5) 在这里,您可以获得深度输出的AVCaptureConnection,以便...
  • 6) ...确保深度数据的视频方向与视频输入相匹配。

简单吧?

但坚持下去! 在构建和运行项目之前,首先需要告诉应用程序如何处理此深度数据。 这就是委托方法的用武之地。

仍然在DepthVideoViewController.swift中,在文件末尾添加以下扩展名和委托方法:

// MARK: - Capture Depth Data Delegate Methods

extension DepthVideoViewController: AVCaptureDepthDataOutputDelegate {

  func depthDataOutput(_ output: AVCaptureDepthDataOutput,
                       didOutput depthData: AVDepthData,
                       timestamp: CMTime,
                       connection: AVCaptureConnection) {

    // 1
    if previewMode == .original {
      return
    }

    var convertedDepth: AVDepthData

    // 2
    if depthData.depthDataType != kCVPixelFormatType_DisparityFloat32 {
      convertedDepth = depthData.converting(toDepthDataType: kCVPixelFormatType_DisparityFloat32)
    } else {
      convertedDepth = depthData
    }

    // 3
    let pixelBuffer = convertedDepth.depthDataMap

    // 4
    pixelBuffer.clamp()

    // 5
    let depthMap = CIImage(cvPixelBuffer: pixelBuffer)

    // 6
    DispatchQueue.main.async { [weak self] in
      self?.depthMap = depthMap
    }
  }
}

下面详细分述:

  • 1) 仅当当前预览模式是使用深度贴图的任何内容时,才优化此函数以创建深度贴图。
  • 2) 接下来,确保深度数据是您需要的格式:32位浮点disparity信息。
  • 3) 您将AVDepthData对象中的深度数据映射另存为CVPixelBuffer
  • 4) 使用项目中包含的扩展,然后将像素缓冲区中的像素钳位在0.0到1.0之间。
  • 5) 您将像素缓冲区转换为CIImage并...
  • 6) ...然后将其存储在类变量中供以后使用。

你可能现在想要运行它,但在你做之前,你需要做一个小的补充来查看深度图,你需要显示它!

找到AVCaptureVideoDataOutputSampleBufferDelegate扩展,并在captureOutput(_:didOutput:from :)中查找switch语句。 添加以下案例:

case .depth:
  previewImage = depthMap ?? image

构建并运行项目,然后点击底部Depth控件的深度段。

这是与视频数据一起捕获的深度数据的视觉表示。


Video Resolutions And Frame Rates - 视频分辨率和帧速率

关于您正在捕获的深度数据,您应该了解一些事项。你的iPhone需要做很多工作来关联两个摄像头之间的像素并计算disparity

为了向您提供最佳的实时数据,iPhone限制了它返回的深度数据的分辨率和帧速率。

例如,您可以在iPhone 7 Plus上接收的最大深度数据量为320 x 240,每秒24帧。 iPhone X能够以30 fps的速度提供数据。

AVCaptureDevice不允许您设置独立于视频帧速率的深度帧速率。深度数据必须以相同的帧速率或视频帧速率的偶数部分传送。否则,会出现深度数据但没有视频数据的情况,这很奇怪。

因此,你需要做两件事:

  • 1) 设置视频帧速率以确保最大可能的深度数据帧速率。
  • 2) 确定视频数据和深度数据之间的比例因子。开始创建蒙版和滤镜时,比例因子很重要。

是时候让你的代码更好了!

再次在DepthVideoViewController.swift中,将以下内容添加到configureCaptureSession()的底部:

// 1
let outputRect = CGRect(x: 0, y: 0, width: 1, height: 1)
let videoRect = videoOutput.outputRectConverted(fromMetadataOutputRect: outputRect)
let depthRect = depthOutput.outputRectConverted(fromMetadataOutputRect: outputRect)

// 2
scale = max(videoRect.width, videoRect.height) / max(depthRect.width, depthRect.height)

// 3    
do {
  try camera.lockForConfiguration()

  // 4
  if let frameDuration = camera.activeDepthDataFormat?
    .videoSupportedFrameRateRanges.first?.minFrameDuration {
    camera.activeVideoMinFrameDuration = frameDuration
  }

  // 5
  camera.unlockForConfiguration()
} catch {
  fatalError(error.localizedDescription)
}

下面进行细分:

  • 1) 您计算一个CGRect,以像素为单位定义视频和深度输出。 这些方法将完整的元数据输出rect映射到视频和数据输出的完整分辨率。
  • 2) 使用CGRect进行视频和数据输出,可以计算它们之间的比例因子。 您获取尺寸的最大值,因为深度数据实际上已旋转90度。
  • 3) 在这里您可以更改AVCaptureDevice配置,因此您需要锁定它,这可能会引发异常
  • 4) 然后,将AVCaptureDevice的最小帧持续时间(最大帧速率的倒数)设置为等于深度数据支持的帧速率
  • 5) 然后解锁在步骤#3中锁定的配置。

好的,构建并运行项目。 无论您是否看到差异,您的代码现在都更加强大且面向未来。


What Can You Do With This Depth Data? - 你可以用这个深度数据做什么?

您可能已经注意到屏幕底部有一个用于MaskFiltered片段的滑块。 此滑块控制mask的深度焦点。

目前,该滑块似乎什么都不做。 那是因为屏幕上没有可视化的mask。 你现在要改变它!

返回到AVCaptureDepthDataOutputDelegate扩展中的depthDataOutput(_:didOutput:timestamp:connection :)。 在DispatchQueue.main.async之前,添加以下内容:

// 1
if previewMode == .mask || previewMode == .filtered {

  //2
  switch filter {

  // 3
  default:
    mask = depthFilters.createHighPassMask(for: depthMap,
                                           withFocus: sliderValue,
                                           andScale: scale)
  }  
}

在这段代码中:

  • 1) 如果MaskFiltered段处于活动状态,您只创建一个mask - 对您有好处!
  • 2) 然后,您可以打开所选过滤器的类型(位于iPhone屏幕顶部)。
  • 3) 最后,创建一个高通mask作为默认情况。 你很快就会填写其他case。

您仍然需要将mask连接到UIImageView才能看到它。

返回到AVCaptureVideoDataOutputSampleBufferDelegate扩展,并在captureOutput(_:didOutput:from :)中查找switch语句。 添加以下case:

case .mask:
  previewImage = mask ?? image

构建并运行项目,然后点击Mask段。

将滑块向左拖动时,屏幕的更多变为白色。 那是因为你实现了一个高通mask,

做得好! 您为本教程中最激动人心的部分奠定了基础:过滤器!

1. Comic Background Effect - 漫画背景效果

iOS SDK捆绑了一堆Core Image过滤器。 特别突出的是CIComicEffect。 此滤镜为图像提供印刷的漫画外观。

您将使用此过滤器将视频流的背景变为漫画。

打开DepthImageFilters.swift。 这个类是所有mask和过滤器的所在。

将以下方法添加到DepthImageFilters类:

func comic(image: CIImage, mask: CIImage) -> CIImage {

  // 1
  let bg = image.applyingFilter("CIComicEffect")

  // 2
  let filtered = image.applyingFilter("CIBlendWithMask",
                                      parameters: ["inputBackgroundImage": bg,
                                                   "inputMaskImage": mask])

  // 3
  return filtered
}

下面细分说明:

  • 1) 您将CIComicEffect应用于输入图像。
  • 2) 然后使用输入mask将原始图像与漫画图像混合。
  • 3) 最后,返回已过滤的图像。

现在,要使用过滤器,请打开DepthVideoViewController.swift并找到captureOutput(_:didOutput:from :)。 删除switch语句中的default case并添加以下case:

case .filtered:

  // 1
  if let mask = mask {

    // 2
    switch filter {

    // 3
    case .comic:
      previewImage = depthFilters.comic(image: image, mask: mask)

    // 4
    default:
      previewImage = image
    }
  } else {

    // 5
    previewImage = image
  }

这段代码很简单。 下面细分说明:

  • 1) 你检查是否有mask,因为没有mask你不能过滤!
  • 2) 您可以打开UI中选择的filter
  • 3) 如果所选过滤器是comic,则根据漫画过滤器创建新图像,并将其作为预览图像。
  • 4) 否则,您只需保持视频图像不变。
  • 5) 最后,你处理mask为nil的情况。

在运行代码之前,还应该做一件事,以便更轻松地添加未来的过滤器。

找到depthDataOutput(_:didOutput:timestamp:connection),并将以下case添加到switch filter语句中:

case .comic:
  mask = depthFilters.createHighPassMask(for: depthMap,
                                         withFocus: sliderValue,
                                         andScale: scale)

在这里,您创建一个高通mask。

这看起来与default情况完全相同。 添加其他过滤器后,您将删除default case,因此最好确保漫画case现在在那里。

前进。 我知道你很高兴能够这样做。 构建并运行项目,然后点击Filtered segment。

很棒的工作! 你觉得自己是漫画书中的超级英雄吗?

2. No Green Screen? No Problem! - 没有绿屏? 没问题!

这很好,但也许你不想在超级英雄电影上工作? 也许你更喜欢科幻小说?

别担心。 下一个过滤器将让你在月球上欢呼雀跃! 为此,您需要创建一个临时的绿屏效果。

打开DepthImageFilters.swift并将以下方法添加到类中:

func greenScreen(image: CIImage, background: CIImage, mask: CIImage) -> CIImage {

  // 1
  let crop = CIVector(x: 0,
                      y: 0,
                      z: image.extent.size.width,
                      w: image.extent.size.height)

  // 2
  let croppedBG = background.applyingFilter("CICrop",
                                            parameters: ["inputRectangle": crop])

  // 3
  let filtered = image.applyingFilter("CIBlendWithMask",
                                      parameters: ["inputBackgroundImage": croppedBG,
                                                   "inputMaskImage": mask])

  // 4
  return filtered
}

在此过滤器中:

  • 1) 您可以创建一个4D CIVector来定义与输入图像相等的裁剪边界。
  • 2) 然后将背景图像裁剪为与输入图像相同的大小 - 这对下一步非常重要
  • 3) 接下来,通过基于mask参数混合输入和背景图像来组合它们。
  • 4) 最后,返回已过滤的图像

现在你只需要在DepthVideoViewController.swift中连接mask和过滤逻辑,你就可以开始了。

DepthVideoViewController.swift中找到captureOutput(_:didOutput:from :)并将以下case添加到switch filter语句中:

case .greenScreen:

  // 1
  if let background = background {

    // 2
    previewImage = depthFilters.greenScreen(image: image,
                                            background: background,
                                            mask: mask)
  } else {

    // 3
    previewImage = image
  }

这里:

  • 1) 您确保背景图像存在。 它在viewDidLoad()中创建。
  • 2) 如果存在,请使用刚刚编写的新函数使用背景和mask过滤输入图像。
  • 3) 否则,只需使用输入视频图像。

接下来,找到depthDataOutput(_:didOutput:timestamp:connection)并将以下情况添加到switch语句中:

case .greenScreen:
  mask = depthFilters.createHighPassMask(for: depthMap,
                                         withFocus: sliderValue,
                                         andScale: scale,
                                         isSharp: true)

此代码创建高通mask,但使截取更清晰(更陡峭的斜率)。

构建并运行项目。 移动滑块,看看你可以在月球上放置什么物体。

3. Dream-like Blur Effect - 梦幻般的模糊效果

也许你不喜欢超级英雄或科幻小说类型。 我知道了。 你更像是一个艺术电影类型的人。 如果是这样,那么下一个过滤器来了。

使用此过滤器,除了与相机之间的距离很窄的物体外,您将模糊除了物体之外的任何物体。 这可以给你的电影带来梦幻般的感觉。

返回到DepthImageFilters.swift并向该类添加一个新函数:

func blur(image: CIImage, mask: CIImage) -> CIImage {

  // 1
  let blurRadius: CGFloat = 10

  // 2
  let crop = CIVector(x: 0,
                      y: 0,
                      z: image.extent.size.width,
                      w: image.extent.size.height)

  // 3
  let invertedMask = mask.applyingFilter("CIColorInvert")

  // 4
  let blurred = image.applyingFilter("CIMaskedVariableBlur",
                                     parameters: ["inputMask": invertedMask,
                                                  "inputRadius": blurRadius])

  // 5
  let filtered = blurred.applyingFilter("CICrop",
                                        parameters: ["inputRectangle": crop])

  // 6
  return filtered
}

这个有点复杂,但这就是你做的:

  • 1) 您可以定义要使用的模糊半径 - 半径越大,模糊越多,模糊越慢!
  • 2) 再次,您创建一个4D CIVector来定义裁剪区域。 这是因为模糊将有效地增加边缘处的图像,您只需要原始尺寸。
  • 3) 然后你反转mask,因为你使用的模糊滤镜在mask是白色的地方模糊。
  • 4) 接下来,使用反转mask和模糊半径作为参数,将CIMaskedVariableBlur滤镜应用于图像。
  • 5) 您裁剪模糊图像以保持所需的大小。
  • 6) 最后,返回已过滤的图像。

打开DepthVideoViewController.swift并在captureOutput(_:didOutput:from :)中的switch语句中添加一个新case:

case .blur:
  previewImage = depthFilters.blur(image: image, mask: mask)

这将在UI中选择时创建模糊过滤器。 当你在那里时,你可以删除default情况,因为switch filter语句现在是详尽的。

现在为mask

将以下情况的default case替换为depthDataOutput(_:didOutput:timestamp:connection)内的switch语句:

case .blur:
  mask = depthFilters.createBandPassMask(for: depthMap,
                                         withFocus: sliderValue,
                                         andScale: scale)

在这里,您可以为要使用的模糊滤镜创建带通mask。

构建并运行此项目。 尝试调整Mask & Filtered部分中的滑块以及更改过滤器以查看可以创建的效果。


源码

1. Swift

首先看一下文档结构

看一下sb中的内容

1. CVPixelBufferExtension.swift
import AVFoundation
import UIKit

extension CVPixelBuffer {
  
  func clamp() {
    
    let width = CVPixelBufferGetWidth(self)
    let height = CVPixelBufferGetHeight(self)
    
    CVPixelBufferLockBaseAddress(self, CVPixelBufferLockFlags(rawValue: 0))
    let floatBuffer = unsafeBitCast(CVPixelBufferGetBaseAddress(self), to: UnsafeMutablePointer<Float>.self)
    
    for y in 0 ..< height {
      for x in 0 ..< width {
        let pixel = floatBuffer[y * width + x]
        floatBuffer[y * width + x] = min(1.0, max(pixel, 0.0))
      }
    }
    
    CVPixelBufferUnlockBaseAddress(self, CVPixelBufferLockFlags(rawValue: 0))
  }
}
2. ControlEnums.swift
enum PreviewMode: Int {
  case original
  case depth
  case mask
  case filtered
}

enum FilterType: Int {
  case comic
  case greenScreen
  case blur
}
3. DepthImageFilters.swift
import UIKit

enum MaskParams {
  static let slope: CGFloat = 4.0
  static let sharpSlope: CGFloat = 10.0
  static let width: CGFloat = 0.1
}

class DepthImageFilters {

  func createHighPassMask(for depthImage: CIImage,
                          withFocus focus: CGFloat,
                          andScale scale: CGFloat,
                          isSharp: Bool = false) -> CIImage {

    let s = isSharp ? MaskParams.sharpSlope : MaskParams.slope
    let filterWidth =  2 / s + MaskParams.width
    let b = -s * (focus - filterWidth / 2)

    let mask = depthImage
      .applyingFilter("CIColorMatrix", parameters: [
        "inputRVector": CIVector(x: s, y: 0, z: 0, w: 0),
        "inputGVector": CIVector(x: 0, y: s, z: 0, w: 0),
        "inputBVector": CIVector(x: 0, y: 0, z: s, w: 0),
        "inputBiasVector": CIVector(x: b, y: b, z: b, w: 0)])
      .applyingFilter("CIColorClamp")
      .applyingFilter("CIBicubicScaleTransform",
                      parameters: ["inputScale": scale])

    return mask
  }

  func createBandPassMask(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 comic(image: CIImage, mask: CIImage) -> CIImage {

    let bg = image.applyingFilter("CIComicEffect")

    let filtered = image.applyingFilter("CIBlendWithMask",
                                        parameters: ["inputBackgroundImage": bg,
                                                     "inputMaskImage": mask])

    return filtered
  }

  func greenScreen(image: CIImage, background: CIImage, mask: CIImage) -> CIImage {

    let crop = CIVector(x: 0,
                        y: 0,
                        z: image.extent.size.width,
                        w: image.extent.size.height)

    let croppedBG = background.applyingFilter("CICrop",
                                              parameters: ["inputRectangle": crop])

    let filtered = image.applyingFilter("CIBlendWithMask",
                                        parameters: ["inputBackgroundImage": croppedBG,
                                                     "inputMaskImage": mask])

    return filtered
  }

  func blur(image: CIImage, mask: CIImage) -> CIImage {

    let blurRadius: CGFloat = 10
    let crop = CIVector(x: 0,
                        y: 0,
                        z: image.extent.size.width,
                        w: image.extent.size.height)

    let invertedMask = mask.applyingFilter("CIColorInvert")

    let blurred = image.applyingFilter("CIMaskedVariableBlur",
                                       parameters: ["inputMask": invertedMask,
                                                    "inputRadius": blurRadius])

    let filtered = blurred.applyingFilter("CICrop",
                                          parameters: ["inputRectangle": crop])

    return filtered
  }
}
4. DepthVideoViewController.swift
import UIKit
import AVFoundation

class DepthVideoViewController: UIViewController {

  @IBOutlet weak var previewView: UIImageView!
  @IBOutlet weak var previewModeControl: UISegmentedControl!
  @IBOutlet weak var filterControl: UISegmentedControl!
  @IBOutlet weak var filterControlView: UIView!
  @IBOutlet weak var depthSlider: UISlider!

  var sliderValue: CGFloat = 0.0
  var previewMode = PreviewMode.original
  var filter = FilterType.comic

  let session = AVCaptureSession()

  let dataOutputQueue = DispatchQueue(label: "video data queue",
                                      qos: .userInitiated,
                                      attributes: [],
                                      autoreleaseFrequency: .workItem)

  var background: CIImage?
  var depthMap: CIImage?
  var mask: CIImage?

  var scale: CGFloat = 0.0

  var depthFilters = DepthImageFilters()

  override func viewDidLoad() {
    super.viewDidLoad()

    if let bgImage = UIImage(named: "earth_rise.jpg") {
      background = CIImage(image: bgImage)
    }

    filterControlView.isHidden = true
    depthSlider.isHidden = true

    previewMode = PreviewMode(rawValue: previewModeControl.selectedSegmentIndex) ?? .original
    filter = FilterType(rawValue: filterControl.selectedSegmentIndex) ?? .comic
    sliderValue = CGFloat(depthSlider.value)

    configureCaptureSession()

    session.startRunning()
  }

  override var shouldAutorotate: Bool {
    return false
  }
}

// MARK: - Helper Methods

extension DepthVideoViewController {

  func configureCaptureSession() {

    guard let camera = AVCaptureDevice.default(.builtInDualCamera,
                                               for: .video,
                                               position: .unspecified) else {

                                                fatalError("No depth video camera available")
    }

    session.sessionPreset = .photo

    do {
      let cameraInput = try AVCaptureDeviceInput(device: camera)
      session.addInput(cameraInput)
    } catch {
      fatalError(error.localizedDescription)
    }

    let videoOutput = AVCaptureVideoDataOutput()
    videoOutput.setSampleBufferDelegate(self, queue: dataOutputQueue)
    videoOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA]

    session.addOutput(videoOutput)

    let videoConnection = videoOutput.connection(with: .video)
    videoConnection?.videoOrientation = .portrait

    let depthOutput = AVCaptureDepthDataOutput()
    depthOutput.setDelegate(self, callbackQueue: dataOutputQueue)
    depthOutput.isFilteringEnabled = true

    session.addOutput(depthOutput)

    let depthConnection = depthOutput.connection(with: .depthData)
    depthConnection?.videoOrientation = .portrait

    let outputRect = CGRect(x: 0, y: 0, width: 1, height: 1)
    let videoRect = videoOutput.outputRectConverted(fromMetadataOutputRect: outputRect)
    let depthRect = depthOutput.outputRectConverted(fromMetadataOutputRect: outputRect)

    scale = max(videoRect.width, videoRect.height) / max(depthRect.width, depthRect.height)

    do {
      try camera.lockForConfiguration()

      if let frameDuration = camera.activeDepthDataFormat?
        .videoSupportedFrameRateRanges.first?.minFrameDuration {
        camera.activeVideoMinFrameDuration = frameDuration
      }

      camera.unlockForConfiguration()
    } catch {
      fatalError(error.localizedDescription)
    }
  }
}

// MARK: - Capture Video Data Delegate Methods

extension DepthVideoViewController: AVCaptureVideoDataOutputSampleBufferDelegate {

  func captureOutput(_ output: AVCaptureOutput,
                     didOutput sampleBuffer: CMSampleBuffer,
                     from connection: AVCaptureConnection) {

    let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)
    let image = CIImage(cvPixelBuffer: pixelBuffer!)

    let previewImage: CIImage

    switch previewMode {
    case .original:
      previewImage = image
    case .depth:
      previewImage = depthMap ?? image
    case .mask:
      previewImage = mask ?? image
    case .filtered:
      if let mask = mask {
        switch filter {
        case .comic:
          previewImage = depthFilters.comic(image: image, mask: mask)
        case .greenScreen:
          if let background = background {
            previewImage = depthFilters.greenScreen(image: image,
                                                    background: background,
                                                    mask: mask)
          } else {
            previewImage = image
          }
        case .blur:
          previewImage = depthFilters.blur(image: image, mask: mask)
        }
      } else {
        previewImage = image
      }
    }

    let displayImage = UIImage(ciImage: previewImage)
    DispatchQueue.main.async { [weak self] in
      self?.previewView.image = displayImage
    }
  }
}

// MARK: - Capture Depth Data Delegate Methods

extension DepthVideoViewController: AVCaptureDepthDataOutputDelegate {

  func depthDataOutput(_ output: AVCaptureDepthDataOutput,
                       didOutput depthData: AVDepthData,
                       timestamp: CMTime,
                       connection: AVCaptureConnection) {

    if previewMode == .original {
      return
    }

    var convertedDepth: AVDepthData

    if depthData.depthDataType != kCVPixelFormatType_DisparityFloat32 {
      convertedDepth = depthData.converting(toDepthDataType: kCVPixelFormatType_DisparityFloat32)
    } else {
      convertedDepth = depthData
    }

    let pixelBuffer = convertedDepth.depthDataMap
    pixelBuffer.clamp()

    let depthMap = CIImage(cvPixelBuffer: pixelBuffer)

    if previewMode == .mask || previewMode == .filtered {
      switch filter {
      case .comic:
        mask = depthFilters.createHighPassMask(for: depthMap,
                                               withFocus: sliderValue,
                                               andScale: scale)
      case .greenScreen:
        mask = depthFilters.createHighPassMask(for: depthMap,
                                               withFocus: sliderValue,
                                               andScale: scale,
                                               isSharp: true)
      case .blur:
        mask = depthFilters.createBandPassMask(for: depthMap,
                                               withFocus: sliderValue,
                                               andScale: scale)
      }
    }

    DispatchQueue.main.async { [weak self] in
      self?.depthMap = depthMap
    }
  }
}

// MARK: - Slider Methods

extension DepthVideoViewController {

  @IBAction func sliderValueChanged(_ sender: UISlider) {
    sliderValue = CGFloat(depthSlider.value)
  }
}

// MARK: - Segmented Control Methods

extension DepthVideoViewController {

  @IBAction func previewModeChanged(_ sender: UISegmentedControl) {

    previewMode = PreviewMode(rawValue: previewModeControl.selectedSegmentIndex) ?? .original

    if previewMode == .mask || previewMode == .filtered {
      filterControlView.isHidden = false
      depthSlider.isHidden = false
    } else {
      filterControlView.isHidden = true
      depthSlider.isHidden = true
    }
  }

  @IBAction func filterTypeChanged(_ sender: UISegmentedControl) {
    filter = FilterType(rawValue: filterControl.selectedSegmentIndex) ?? .comic
  }
}

后记

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容