版本记录
版本号 | 时间 |
---|---|
V1.0 | 2018.10.15 星期一 |
前言
App中很多时候都需要进行图像处理,包括各种缩放、滤镜和压缩等处理,好的图像处理不仅可以提高App的性能,也会给用户带来耳目一新的感觉,这里重新开了一个专题,专门讲述对图像的各种处理。
开始
首先看一下写作环境。
Swift 4, iOS 11, Xcode 9
说实话。 作为人类,我们最终会创造出能够接管世界的机器人,对吧? 对我们最终的机器人大师来说,最重要的一件事就是良好的深度感知。 没有它,他们怎么会知道它是真的是一个人还是只是一个纸板剪影? 他们可以做到这一点的一种方法是使用深度图。
但是在机器人能够做到这一点之前,他们首先需要按照这种方式进行编程,这就是你进来的原因! 在本教程中,您将了解Apple为图像深度贴图(image depth maps)
提供的API。 你会:
- 了解iPhone如何生成深度信息。
- 从图像中读取深度数据。
- 将此深度数据与滤镜相结合,以创建整洁的效果。
在开始之前,您需要确保运行Xcode 9或更高版本。 另外,我强烈建议您直接在设备上运行本教程。 这意味着您需要运行iOS 11或更高版本的iPhone。 在撰写本文时,模拟器的速度非常慢。
打开入门项目。里面的图像包括与教程一起使用的深度信息。
如果您愿意并且拥有双摄像头iPhone,则可以使用自己的图像来使用本教程。 要拍摄包含深度数据的图片,iPhone需要运行iOS 11或更高版本。 并且不要忘记在Camera
应用程序中使用Portrait
模式。
您将在入门项目中看到三个警告。 不要担心它们,因为您将在本教程中修复它们。
Build并运行项目。 你应该看到这个:
点击图像循环到下一个。 如果添加自己的图片,则需要遵循命名约定test##.jpg
。 数字从00
开始并按顺序递增。
在本教程中,您将填写Depth
, Mask
, 和Filtered
部分的功能。
Reading Depth Data - 读取深度数据
深度数据最重要的类是AVDepthData
。
不同的图像格式稍微不同地存储深度数据。 在HEIC
中,它存储为元数据。 但在JPG
中,它被存储为JPG
中的第二个图像。
您通常使用AVDepthData
从图像中提取此辅助数据,这是第一步。 打开DepthReader.swift
并将以下方法添加到DepthReader
:
func depthDataMap() -> CVPixelBuffer? {
// 1
guard let fileURL = Bundle.main.url(forResource: name, withExtension: ext) as CFURL? else {
return nil
}
// 2
guard let source = CGImageSourceCreateWithURL(fileURL, nil) else {
return nil
}
// 3
guard let auxDataInfo = CGImageSourceCopyAuxiliaryDataInfoAtIndex(source, 0,
kCGImageAuxiliaryDataTypeDisparity) as? [AnyHashable : Any] else {
return nil
}
// 4
var depthData: AVDepthData
do {
// 5
depthData = try AVDepthData(fromDictionaryRepresentation: auxDataInfo)
} catch {
return nil
}
// 6
if depthData.depthDataType != kCVPixelFormatType_DisparityFloat32 {
depthData = depthData.converting(toDepthDataType: kCVPixelFormatType_DisparityFloat32)
}
// 7
return depthData.depthDataMap
}
好的,这是相当多的代码,下面细分讲解:
- 1) 首先,您获取图像文件的URL并安全地将其转换为
CFURL
。 - 2) 然后,您可以从此文件创建
CGImageSource
。 - 3) 从索引0处的图像源,您可以从其辅助数据中复制
disparity
数据(更多关于以后的含义,但您现在可以将其视为深度数据)。索引为0,因为图像源中只有一个图像。 iOS知道如何从JPG
和HEIC
文件中提取数据,但不幸的是,这在模拟器中不起作用。 - 4) 您为深度数据准备了属性。如前所述,您使用
AVDepthData
从图像中提取辅助数据。 - 5) 您可以从读入的辅助数据中创建
AVDepthData
实体。 - 6) 您可以确保深度数据是您需要的格式:32位浮点
disparity
信息。 - 7) 最后,返回此深度数据
map
。
现在,在运行此之前,您需要更新DepthImageViewController.swift
。
找到loadCurrent(image:withExtension :)
并将以下代码行添加到开头:
// 1
let depthReader = DepthReader(name: name, ext: ext)
// 2
let depthDataMap = depthReader.depthDataMap()
// 3
depthDataMap?.normalize()
// 4
let ciImage = CIImage(cvPixelBuffer: depthDataMap)
depthDataMapImage = UIImage(ciImage: ciImage)
使用此代码:
- 1) 您可以使用当前图像创建
DepthReader
实体。 - 2) 使用新的
depthDataMap
方法,将深度数据读入CVPixelBuffer
。 - 3) 然后使用提供的
CVPixelBuffer
扩展来标准化深度数据。 这可以确保所有像素都在0.0和1.0之间,其中0.0是最远的像素,1.0是最近的像素。 - 4) 然后,将深度数据转换为
CIImage
,然后转换为UIImage
,并将其保存到属性中。
如果您对
normalize
方法的工作方式感兴趣,请查看CVPixelBufferExtension.swift
。 它循环遍历2D数组中的每个值,并跟踪所看到的最小值和最大值。 然后它再次循环遍历所有值,并使用最小值和最大值来计算介于0.0和1.0之间的新值。
Build并运行项目,然后点击底部分段控件的Depth
分段。
真棒! 还记得你对深度数据进行标准化吗? 这是视觉表现。 像素越白,越近,像素越暗,距离越远。
做的很好!
How Does the iPhone Do This? - iPhone如何做到这一点?
简而言之,iPhone的双摄像头正在模仿立体视觉。
试试这个。 将食指紧紧贴在鼻子前方并指向上方。 闭上你的左眼。 不要移动手指或头部,同时打开左眼并闭上右眼。
现在快速来回关闭一只眼睛并打开另一只眼睛。 注意手指相对于背景中物体的相对位置。 看看你的手指如何与远处的物体相比左右跳跃?
物体离眼睛越近,其相对位置与背景相比的变化越大。 这听起来很熟悉吗? 这是一个视差效应!
iPhone的双摄像头就像它的眼睛一样,看着两张相互略微偏移的图像。 它对应两个图像中的特征并计算它们移动了多少像素。 像素的这种变化称为视差disparity。
1. Depth vs Disparity - 深度与视差
到目前为止,我们主要使用术语depth data
,但在您的代码中,您请求了kCGImageAuxiliaryDataTypeDisparity
数据,深度和差异基本上是成反比的。
物体距离越远,深度越大。 但是这些物体的像素之间的距离越来越接近零。 如果你打开开始项目,你可能已经注意到在选择Mask和Filter片段时屏幕底部有一个滑块可见。
您将使用此滑块以及深度数据为特定深度的图像制作蒙版。 然后你将使用这个蒙版过滤原始图像并创建一些其他的效果!
Creating a Mask - 创建一个蒙层
打开DepthImageFilters.swift
并找到createMask(for:withFocus:andScale :)
。 然后将以下代码添加到其顶部:
let s1 = MaskParams.slope
let s2 = -MaskParams.slope
let filterWidth = 2 / MaskParams.slope + MaskParams.width
let b1 = -s1 * (focus - filterWidth / 2)
let b2 = -s2 * (focus + filterWidth / 2)
这些常量将定义我们如何将深度数据转换为图像蒙版。
将深度数据图想象为以下函数:
深度图图像的像素值等于标准化视差。 请记住,像素值1.0是白色,视差值1.0是最接近相机的。 在比例的另一侧,像素值0.0是黑色,并且视差值0.0离相机最远。
当您从深度数据创建蒙版时,您将更改此函数以使其更有趣。
使用斜率4.0,宽度0.1和0.75作为焦点,createMask(for:withFocus:andScale :)
将在您完成后使用以下函数:
这意味着最白像素(值1.0)将是具有0.75±0.05
(焦点±宽度/ 2)的视差的像素。 然后,对于高于和低于该范围的视差值,像素将快速渐变为黑色。 斜率越大,它们褪色到黑色的速度越快。
在常量添加此行之后:
let mask0 = depthImage
.applyingFilter("CIColorMatrix", parameters: [
"inputRVector": CIVector(x: s1, y: 0, z: 0, w: 0),
"inputGVector": CIVector(x: 0, y: s1, z: 0, w: 0),
"inputBVector": CIVector(x: 0, y: 0, z: s1, w: 0),
"inputBiasVector": CIVector(x: b1, y: b1, z: b1, w: 0)])
.applyingFilter("CIColorClamp")
该滤波器将所有像素乘以斜率s1
。 由于蒙版是灰度的,因此您需要确保所有颜色通道具有相同的值。 使用CIColorClamp
将值钳位在0.0和1.0之间后,此过滤器将应用以下函数:
s1
越大,线的斜率越陡。 常数b1
向左或向右移动线。
要处理遮罩函数的另一面,请添加以下内容:
let mask1 = depthImage
.applyingFilter("CIColorMatrix", parameters: [
"inputRVector": CIVector(x: s2, y: 0, z: 0, w: 0),
"inputGVector": CIVector(x: 0, y: s2, z: 0, w: 0),
"inputBVector": CIVector(x: 0, y: 0, z: s2, w: 0),
"inputBiasVector": CIVector(x: b2, y: b2, z: b2, w: 0)])
.applyingFilter("CIColorClamp")
由于斜率s2
为负,因此滤波器应用以下函数:
现在,把两个masks
放在一起:
let combinedMask = mask0.applyingFilter("CIDarkenBlendMode", parameters: ["inputBackgroundImage" : mask1])
let mask = combinedMask.applyingFilter("CIBicubicScaleTransform", parameters: ["inputScale": scale])
您可以使用CIDarkenBlendMode
过滤器组合蒙版,该过滤器选择输入蒙版的两个值中的较低者。
然后缩放蒙版以匹配图像大小。
最后,用以下代码替换返回行:
return mask
Build并运行您的项目。 点击Mask
分段部分并使用滑块进行播放。
警告:如果您在模拟器中运行,这将是无法忍受的缓慢。 如果您希望看到此改进,请在bugreport.apple.com上复制此open radar。
你应该看到这样的东西:
1. Your First Depth-Inspired Filter - 你的第一个深度激发过滤器
接下来,您将创建一个有点模仿聚光灯的过滤器。 “聚光灯”将照射在选定深度的物体上,并从那里淡化为黑色。
而且因为你已经在深度数据和创建mask中进行了艰苦的阅读,所以它将变得非常简单。
打开DepthImageFilters.swift
并添加以下内容:
func spotlightHighlight(image: CIImage, mask: CIImage, orientation: UIImageOrientation = .up) -> UIImage? {
// 1
let output = image.applyingFilter("CIBlendWithMask", parameters: ["inputMaskImage": mask])
// 2
guard let cgImage = context.createCGImage(output, from: output.extent) else {
return nil
}
// 3
return UIImage(cgImage: cgImage, scale: 1.0, orientation: orientation)
}
以下是您在这三行中所做的工作:
- 1) 您使用了
CIBlendWithMask
过滤器并传入了您在上一节中创建的mask。 滤波器实质上将像素的alpha值设置为对应的mask像素值。 因此,当mask像素值是1.0时,图像像素是完全不透明的,并且当mask像素值是0.0时,图像像素是完全透明的。 由于UIImageView
背后的UIView
有黑色,因此黑色就是您从图像后面看到的。 - 2) 使用
CIContext
创建CGImage
。 - 3) 然后创建一个
UIImage
并将其返回。
要查看此过滤器的运行情况,首先需要DepthImageViewController
在适当时调用此方法。
打开DepthImageViewController.swift
并转到updateImageView
。 在主switch语句的.filtered
的case中,您将找到selectedFilter
的嵌套switch语句。
将.spotlight
案例的代码替换为:
finalImage = depthFilters?.spotlightHighlight(image: filterImage, mask: mask, orientation: orientation)
Build并运行您的项目! 点按Filtered
的部分,并确保在顶部选择Spotlight
。 滑动滑块。 你应该看到这样的东西:
恭喜! 您已经编写了第一个深度激发的图像滤镜。
2. Color Highlight Filter - 颜色突出显示过滤器
打开DepthImageFilters.swift
,然后在刚刚编写的spotlightHighlight(image:mask:orientation :)
下面添加以下新方法:
func colorHighlight(image: CIImage, mask: CIImage, orientation: UIImageOrientation = .up) -> UIImage? {
let greyscale = image.applyingFilter("CIPhotoEffectMono")
let output = image.applyingFilter("CIBlendWithMask", parameters: ["inputBackgroundImage" : greyscale,
"inputMaskImage": mask])
guard let cgImage = context.createCGImage(output, from: output.extent) else {
return nil
}
return UIImage(cgImage: cgImage, scale: 1.0, orientation: orientation)
}
这应该看起来很熟悉。 它几乎与您刚写的spotlightHighlight(image:mask:orientation :)
过滤器完全相同。 一个区别是,这次您将背景图像设置为原始图像的灰度版本。
此滤镜将根据滑块位置在焦点处显示全色,并从那里淡入灰色。
打开DepthImageViewController.swift
并在selectedFilter
的相同switch
语句中,将.color
大小写的代码替换为:
finalImage = depthFilters?.colorHighlight(image: filterImage, mask: mask, orientation: orientation)
这将调用您的新过滤器方法并显示结果。
Build并运行以查看魔法:
当你拍照只是为了稍后发现相机聚焦在错误的物体上时,你不讨厌它吗? 如果你能在事后改变焦点怎么样?
这正是您接下来要写的深度激发过滤器!
3. Change the Focal Length - 改变焦距
在DepthImageFilters.swift
中的colorHightlight(image:mask:orientation :)
方法下,添加:
func blur(image: CIImage, mask: CIImage, orientation: UIImageOrientation = .up) -> UIImage? {
// 1
let invertedMask = mask.applyingFilter("CIColorInvert")
// 2
let output = image.applyingFilter("CIMaskedVariableBlur", parameters: ["inputMask" : invertedMask,
"inputRadius": 15.0])
// 3
guard let cgImage = context.createCGImage(output, from: output.extent) else {
return nil
}
// 4
return UIImage(cgImage: cgImage, scale: 1.0, orientation: orientation)
}
这个过滤器与其他两个过滤器略有不同。
- 1) 首先,您反转mask。
- 2) 然后应用
CIMaskedVariableBlur
过滤器,这是iOS 11中的新过滤器。此过滤器将使用等于inputRadius * mask pixel value
的半径进行模糊。 因此,当掩码像素值为1.0时,模糊处于最大值,这就是您需要首先反转掩码的原因。 - 3) 再次,您使用
CIContext
生成CGImage
... - 4) ...并使用它来创建
UIImage
并将其返回。
注意:如果遇到性能问题,可以尝试减少
inputRadius
。 高斯模糊计算成本高,模糊半径越大,需要进行的计算越多。
在运行之前,需要再次更新selectedFilter switch
语句。 要使用闪亮的新方法,请将.blur
情况下的代码更改为:
finalImage = depthFilters?.blur(image: filterImage, mask: mask, orientation: orientation)
Build并运行
很完美,对吧!
More About AVDepthData - 有关AVDepthData
的更多信息
你还记得你如何在createMask(for:withFocus:andScale:)
中缩放mask吗?原因是iPhone捕获的深度数据的分辨率低于传感器分辨率。与相机可拍摄的1200万像素相比,它更接近0.5百万像素。
另一个重要的事情是数据可以过滤或未过滤。未过滤的数据可能具有由NaN
表示的空洞(非数字 - 浮点数据类型中的可能值)。如果手机无法关联两个像素,或者某些东西只遮挡其中一个摄像头,则会导致这些NaN值出现disparity
。
值为NaN的像素将显示为黑色。由于乘以NaN始终为NaN,因此这些黑色像素将传播到最终图像。它们看起来就像是图像中的洞。
由于这可能很难处理,Apple会在可用时为您提供过滤数据,以填补这些空白并平滑数据。
如果您不确定,则应始终检查isDepthDataFiltered
属性,以确定您是否正在处理已过滤或未过滤的数据。
还有更多的Core Image过滤器可用。 点击here查看完整列表。 当与深度数据结合时,许多这些滤镜可以创建有趣的效果。
源码
1. Swift
首先看一下文档结构
看一下sb中的内容
下面就是看代码了
1. ControlEnums.swift
enum ImageMode: Int {
case original = 0
case depth = 1
case mask = 2
case filtered = 3
}
enum FilterType: Int {
case spotlight = 0
case color = 1
case blur = 2
}
2. DepthImageFilters.swift
import UIKit
enum MaskParams {
static let slope: CGFloat = 4.0
static let width: CGFloat = 0.1
}
class DepthImageFilters {
var context: CIContext
init(context: CIContext) {
self.context = context
}
init() {
context = CIContext()
}
func createMask(for depthImage: CIImage, withFocus focus: CGFloat, andScale scale: CGFloat) -> CIImage {
let s1 = MaskParams.slope
let s2 = -MaskParams.slope
let filterWidth = 2 / MaskParams.slope + MaskParams.width
let b1 = -s1 * (focus - filterWidth / 2)
let b2 = -s2 * (focus + filterWidth / 2)
let mask0 = depthImage
.applyingFilter("CIColorMatrix", parameters: [
"inputRVector": CIVector(x: s1, y: 0, z: 0, w: 0),
"inputGVector": CIVector(x: 0, y: s1, z: 0, w: 0),
"inputBVector": CIVector(x: 0, y: 0, z: s1, w: 0),
"inputBiasVector": CIVector(x: b1, y: b1, z: b1, w: 0)])
.applyingFilter("CIColorClamp")
let mask1 = depthImage
.applyingFilter("CIColorMatrix", parameters: [
"inputRVector": CIVector(x: s2, y: 0, z: 0, w: 0),
"inputGVector": CIVector(x: 0, y: s2, z: 0, w: 0),
"inputBVector": CIVector(x: 0, y: 0, z: s2, w: 0),
"inputBiasVector": CIVector(x: b2, y: b2, z: b2, w: 0)])
.applyingFilter("CIColorClamp")
let combinedMask = mask0.applyingFilter("CIDarkenBlendMode", parameters: ["inputBackgroundImage" : mask1])
let mask = combinedMask.applyingFilter("CIBicubicScaleTransform", parameters: ["inputScale": scale])
return mask
}
func spotlightHighlight(image: CIImage, mask: CIImage, orientation: UIImageOrientation = .up) -> UIImage? {
let output = image.applyingFilter("CIBlendWithMask", parameters: ["inputMaskImage": mask])
guard let cgImage = context.createCGImage(output, from: output.extent) else {
return nil
}
return UIImage(cgImage: cgImage, scale: 1.0, orientation: orientation)
}
func colorHighlight(image: CIImage, mask: CIImage, orientation: UIImageOrientation = .up) -> UIImage? {
let greyscale = image.applyingFilter("CIPhotoEffectMono")
let output = image.applyingFilter("CIBlendWithMask", parameters: ["inputBackgroundImage" : greyscale,
"inputMaskImage": mask])
guard let cgImage = context.createCGImage(output, from: output.extent) else {
return nil
}
return UIImage(cgImage: cgImage, scale: 1.0, orientation: orientation)
}
func blur(image: CIImage, mask: CIImage, orientation: UIImageOrientation = .up) -> UIImage? {
let invertedMask = mask.applyingFilter("CIColorInvert")
let output = image.applyingFilter("CIMaskedVariableBlur", parameters: ["inputMask" : invertedMask,
"inputRadius": 15.0])
guard let cgImage = context.createCGImage(output, from: output.extent) else {
return nil
}
return UIImage(cgImage: cgImage, scale: 1.0, orientation: orientation)
}
}
3. DepthImageViewController.swift
import AVFoundation
import UIKit
class DepthImageViewController: UIViewController {
@IBOutlet weak var imageView: UIImageView!
@IBOutlet weak var imageModeControl: UISegmentedControl!
@IBOutlet weak var filterControl: UISegmentedControl!
@IBOutlet weak var depthSlider: UISlider!
@IBOutlet weak var filterControlView: UIView!
var origImage: UIImage?
var depthDataMapImage: UIImage?
var filterImage: CIImage?
var bundledJPGs = [String]()
var current = 0
let context = CIContext()
var depthFilters: DepthImageFilters?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
depthFilters = DepthImageFilters(context: context)
// Figure out which images are bundled in the app
bundledJPGs = getAvailableImages()
// Load current image
loadCurrent(image: bundledJPGs[current], withExtension: "jpg")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
// MARK: Depth Data Methods
extension DepthImageViewController {
}
// MARK: Helper Methods
extension DepthImageViewController {
func getAvailableImages() -> [String] {
var availableImages = [String]()
var base = "test"
var name = "\(base)00"
var num = 0 {
didSet {
name = "\(base)\(String(format: "%02d", num))"
}
}
while Bundle.main.url(forResource: name, withExtension: "jpg") != nil {
availableImages.append(name)
num += 1
}
return availableImages
}
func loadCurrent(image name: String, withExtension ext: String) {
let depthReader = DepthReader(name: name, ext: ext)
// Read the depth data from the image
let depthDataMap = depthReader.depthDataMap()
// Normalize the depth data to be between 0.0 -> 1.0
depthDataMap?.normalize()
let ciImage = CIImage(cvPixelBuffer: depthDataMap)
depthDataMapImage = UIImage(ciImage: ciImage)
// Create the original unmodified image
origImage = UIImage(named: "\(name).\(ext)")
filterImage = CIImage(image: origImage)
// Set the segmented control to point to the original image
imageModeControl.selectedSegmentIndex = ImageMode.original.rawValue
// Update the image view
updateImageView()
}
func updateImageView() {
depthSlider.isHidden = true
filterControlView.isHidden = true
imageView.image = nil
let selectedImageMode = ImageMode(rawValue: imageModeControl.selectedSegmentIndex) ?? .original
switch selectedImageMode {
case .original:
// Original
imageView.image = origImage
case .depth:
// Depth
#if IOS_SIMULATOR
guard let orientation = origImage?.imageOrientation,
let ciImage = depthDataMapImage?.ciImage,
let cgImage = context.createCGImage(ciImage, from: ciImage.extent) else {
return
}
imageView.image = UIImage(cgImage: cgImage, scale: 1.0, orientation: orientation)
#else
imageView.image = depthDataMapImage
#endif
case .mask:
// Mask
depthSlider.isHidden = false
guard let depthImage = depthDataMapImage?.ciImage else {
return
}
let maxToDim = max((origImage?.size.width ?? 1.0), (origImage?.size.height ?? 1.0))
let maxFromDim = max((depthDataMapImage?.size.width ?? 1.0), (depthDataMapImage?.size.height ?? 1.0))
let scale = maxToDim / maxFromDim
guard let mask = depthFilters?.createMask(for: depthImage, withFocus: CGFloat(depthSlider.value), andScale: scale) else {
return
}
guard let cgImage = context.createCGImage(mask, from: mask.extent),
let origImage = origImage else {
return
}
imageView.image = UIImage(cgImage: cgImage, scale: 1.0, orientation: origImage.imageOrientation)
case .filtered:
// Filtered
depthSlider.isHidden = false
filterControlView.isHidden = false
guard let depthImage = depthDataMapImage?.ciImage else {
return
}
let maxToDim = max((origImage?.size.width ?? 1.0), (origImage?.size.height ?? 1.0))
let maxFromDim = max((depthDataMapImage?.size.width ?? 1.0), (depthDataMapImage?.size.height ?? 1.0))
let scale = maxToDim / maxFromDim
guard let mask = depthFilters?.createMask(for: depthImage, withFocus: CGFloat(depthSlider.value), andScale: scale),
let filterImage = filterImage,
let orientation = origImage?.imageOrientation else {
return
}
let finalImage: UIImage?
let selectedFilter = FilterType(rawValue: filterControl.selectedSegmentIndex) ?? .spotlight
switch selectedFilter {
case .spotlight:
finalImage = depthFilters?.spotlightHighlight(image: filterImage, mask: mask, orientation: orientation)
case .color:
finalImage = depthFilters?.colorHighlight(image: filterImage, mask: mask, orientation: orientation)
case .blur:
finalImage = depthFilters?.blur(image: filterImage, mask: mask, orientation: orientation)
}
imageView.image = finalImage
}
}
}
// MARK: Slider Methods
extension DepthImageViewController {
@IBAction func sliderValueChanged(_ sender: UISlider) {
updateImageView()
}
}
// MARK: Segmented Control Methods
extension DepthImageViewController {
@IBAction func segementedControlValueChanged(_ sender: UISegmentedControl) {
updateImageView()
}
@IBAction func filterTypeChanged(_ sender: UISegmentedControl) {
updateImageView()
}
}
// MARK: Gesture Recognizor Methods
extension DepthImageViewController {
@IBAction func imageTapped(_ sender: UITapGestureRecognizer) {
current = (current + 1) % bundledJPGs.count
loadCurrent(image: bundledJPGs[current], withExtension: "jpg")
}
}
4. DepthReader.swift
#if !IOS_SIMULATOR
import AVFoundation
struct DepthReader {
var name: String
var ext: String
func depthDataMap() -> CVPixelBuffer? {
// Create a CFURL for the image in the Bundle
guard let fileURL = Bundle.main.url(forResource: name, withExtension: ext) as CFURL? else {
return nil
}
// Create a CGImageSource
guard let source = CGImageSourceCreateWithURL(fileURL, nil) else {
return nil
}
guard let auxDataInfo = CGImageSourceCopyAuxiliaryDataInfoAtIndex(source, 0, kCGImageAuxiliaryDataTypeDisparity) as? [AnyHashable : Any] else {
return nil
}
// This is the star of the show!
var depthData: AVDepthData
do {
// Get the depth data from the auxiliary data info
depthData = try AVDepthData(fromDictionaryRepresentation: auxDataInfo)
} catch {
return nil
}
// Make sure the depth data is the type we want
if depthData.depthDataType != kCVPixelFormatType_DisparityFloat32 {
depthData = depthData.converting(toDepthDataType: kCVPixelFormatType_DisparityFloat32)
}
return depthData.depthDataMap
}
}
#endif
5. DepthReaderSimulatorHack.swift
#if IOS_SIMULATOR
import AVFoundation
import UIKit
struct DepthReader {
var name: String
var ext: String
func depthDataMap() -> CVPixelBuffer? {
// Create a CFURL for the image in the Bundle
guard let fileURL = Bundle.main.url(forResource: name, withExtension: ext) as CFURL? else {
return nil
}
// Create a CGImageSource
guard let source = CGImageSourceCreateWithURL(fileURL, nil) else {
return nil
}
guard let cgImage = CGImageSourceCreateImageAtIndex(source, 1, nil) else {
return nil
}
let depthDataMap = cgImage.pixelBuffer()?.convertToDisparity32()
depthDataMap?.normalize()
return depthDataMap
}
}
#endif
6. CGImageExtension.swift
import CoreVideo
import CoreGraphics
extension CGImage {
func pixelBuffer() -> CVPixelBuffer? {
var pxbuffer: CVPixelBuffer?
guard let dataProvider = dataProvider else {
return nil
}
let dataFromImageDataProvider = CFDataCreateMutableCopy(kCFAllocatorDefault, 0, dataProvider.data)
CVPixelBufferCreateWithBytes(
kCFAllocatorDefault,
width,
height,
kCVPixelFormatType_32ARGB,
CFDataGetMutableBytePtr(dataFromImageDataProvider),
bytesPerRow,
nil,
nil,
nil,
&pxbuffer
)
return pxbuffer
}
}
7. CIImageExtension.swift
import UIKit
extension CIImage {
convenience init?(image: UIImage?) {
guard let image = image else {
return nil
}
self.init(image: image)
}
convenience init?(cvPixelBuffer: CVPixelBuffer?) {
guard let cvPixelBuffer = cvPixelBuffer else {
return nil
}
self.init(cvPixelBuffer: cvPixelBuffer)
}
convenience init?(cgImage: CGImage?) {
guard let cgImage = cgImage else {
return nil
}
self.init(cgImage: cgImage)
}
}
8. CVPixelBufferExtension.swift
import AVFoundation
import UIKit
extension CVPixelBuffer {
func normalize() {
let width = CVPixelBufferGetWidth(self)
let height = CVPixelBufferGetHeight(self)
CVPixelBufferLockBaseAddress(self, CVPixelBufferLockFlags(rawValue: 0))
let floatBuffer = unsafeBitCast(CVPixelBufferGetBaseAddress(self), to: UnsafeMutablePointer<Float>.self)
var minPixel: Float = 1.0
var maxPixel: Float = 0.0
for y in 0 ..< height {
for x in 0 ..< width {
let pixel = floatBuffer[y * width + x]
minPixel = min(pixel, minPixel)
maxPixel = max(pixel, maxPixel)
}
}
let range = maxPixel - minPixel
for y in 0 ..< height {
for x in 0 ..< width {
let pixel = floatBuffer[y * width + x]
floatBuffer[y * width + x] = (pixel - minPixel) / range
}
}
CVPixelBufferUnlockBaseAddress(self, CVPixelBufferLockFlags(rawValue: 0))
}
func printDebugInfo() {
let width = CVPixelBufferGetWidth(self)
let height = CVPixelBufferGetHeight(self)
let bytesPerRow = CVPixelBufferGetBytesPerRow(self)
let totalBytes = CVPixelBufferGetDataSize(self)
print("Depth Map Info: \(width)x\(height)")
print(" Bytes per Row: \(bytesPerRow)")
print(" Total Bytes: \(totalBytes)")
}
func convertToDisparity32() -> CVPixelBuffer? {
let width = CVPixelBufferGetWidth(self)
let height = CVPixelBufferGetHeight(self)
var dispartyPixelBuffer: CVPixelBuffer?
let _ = CVPixelBufferCreate(nil, width, height, kCVPixelFormatType_DisparityFloat32, nil, &dispartyPixelBuffer)
guard let outputPixelBuffer = dispartyPixelBuffer else {
return nil
}
CVPixelBufferLockBaseAddress(outputPixelBuffer, CVPixelBufferLockFlags(rawValue: 0))
CVPixelBufferLockBaseAddress(self, CVPixelBufferLockFlags(rawValue: 1))
let outputBuffer = unsafeBitCast(CVPixelBufferGetBaseAddress(outputPixelBuffer), to: UnsafeMutablePointer<Float>.self)
let inputBuffer = unsafeBitCast(CVPixelBufferGetBaseAddress(self), to: UnsafeMutablePointer<UInt8>.self)
for y in 0 ..< height {
for x in 0 ..< width {
let pixel = inputBuffer[y * width + x]
outputBuffer[y * width + x] = (Float(pixel) / Float(UInt8.max))
}
}
CVPixelBufferUnlockBaseAddress(self, CVPixelBufferLockFlags(rawValue: 1))
CVPixelBufferUnlockBaseAddress(outputPixelBuffer, CVPixelBufferLockFlags(rawValue: 0))
return dispartyPixelBuffer
}
}
9. UIImageExtension.swift
import UIKit
extension UIImage {
convenience init?(ciImage: CIImage?) {
guard let ciImage = ciImage else {
return nil
}
self.init(ciImage: ciImage)
}
}
后记
本篇主要讲述了图像深度相关处理简单示例,感兴趣给个赞或者关注~~~