視頻處理相關(一) —— 視頻深度相關處理簡單示例(一)

版本記錄

版本號 時間
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閱讀 228,739評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,634評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,653評論 0 377
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,063評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,835評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,235評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,315評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,459評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,000評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,819評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,004評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,560評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,257評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,676評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,937評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,717評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,003評論 2 374

推薦閱讀更多精彩內容