入门
在此过程中,您将学习如何使用Vision 框架:
- 创建请求以执行显着性分析。
- 使用返回的观察结果生成热图。
- 使用热图作为输入过滤视频流。
:由于 iOS 13 中使用的操作系统和 iOS API,您至少要使用 Xcode 1.0 或更高版本的设备。

点击屏幕将标签更改为热图。摄像头馈送中没有任何变化。
你会解决这个问题。首先,什么是显着性分析?
显着性分析
从本质上确定它是关于吸引眼球的重要内容。
您选择了照片中的这些信息,您就可以使用这些信息,或者提供它们自动显示区域的重要效果。
如果您在视频源上实时执行显着性分析,您还可以使用这些信息来帮助关注关键领域。
Apple 提供有不同的框架类型的显着性分析:类型标准的愿景和基于对象的。
以人为本,彰显了整个人的性取向。
卷起袖子,掰开你的指关节。是时候编码了。:]
基于关注的热图
视觉 API 都热图。有更多的视觉方法可以返回两个可视化的方法。请求返回的那些是粗略的分析性。另外,热图是用于显示比照片视频或视频网根据文档,您将获得64 x 6或68 x 68实时您的热图,您是否需要进行 API 调用。
注意:虽然文档说在调用 API 代码时需要64 x 64 堆栈的实时视觉图,但本中用于视频在源上执行请求的仍然会产生80 x 68的热图。
CVPixelBuffer
报告的68 x 68的丰富性和高度的函数。但是,其中包含的数据CVPixelBuffer
实际上是80 x 68。这可能是一个返回错误,这可能会改变。
如果教程您以前使用过 Vision 框架,请查看我们的使用适用于 iOS 的 Vision Framework 的一些人脸检测,了解有关 Vision 请求如何工作的信息。
在CameraViewController.swift中,将以下代码添加到captureOutput(_:didOutput:from:)
:
// 1
让 req = VNGenerateAttentionBasedSaliencyImageRequest (
完成处理程序:handleSaliency)
做{
// 2
尝试 sequenceHandler.perform(
[请求],
上:图像,
方向:.up)
} 抓住 {
// 3
打印(错误.localizedDescription)
}
使用此代码,您可以:
- 生成基于关注的显着性视觉请求。
- 使用在方法中创建的
VNSequenceRequestHandler
执行请求。CVImageBuffer
- 并打印错误,如果有的话。
那里!了解机器人iPhone的第一步!
你会注意到 Xcode 很不高兴,而且看起来不知道是什么handleSaliency
。即使他们在计算机视觉方面有长足的进步,Apple 也没有找到让 Xcode 为你编写代码的方法。
您需要handleSaliency
提出,接受一个完整的视觉请求,才能做一些有用的事情。
在同一个文件的方法反馈中,添加一个新的扩展交付来与 Vision 相关的:
扩展CameraViewController {
}
然后,在此扩展中,添加handleSaliency
提交给 Vision 的完成处理程序:
func handleSaliency(请求:VNRequest,错误:错误?) {
// 1
警卫让结果= request.results 为?[ VNSaliencyImageObservation
] ,
让结果=结果。
否则{返回}
// 2
守卫让 targetExtent = currentFrame ? .extent else {
返回
}
// 3
var ciImage = CIImage (cvImageBuffer: result.pixelBuffer)
// 4
让 heatmapExtent = ciImage.extent
让 scaleX = targetExtent.width / heatmapExtent.width
让 scaleY = targetExtent.height / heatmapExtent.height
// 5
ciImage = ciImage
.transformed(by: CGAffineTransform (scaleX: scaleX, y: scaleY))
// 6
showHeatMap(with: ciImage)
}
在这里,你:
- 确保结果是
VNSaliencyImageObservation
对象,并从请求返回的观察列表中提取第一个结果。 - 这从当前是当前帧的大小。
-
CIImage
从CVPixelBuffer
代表热图的中创建一个。 - 计算当前帧和热图之间的比例因子。
- 将热图缩放到当前帧的大小。
- 使用显示热图
showHeatMap
。
现在,在上面handleSaliency(request:error:)
,添加方便的帮助方法来显示热图:
func showHeatMap ( with heatMap : CIImage ) {
// 1
守卫让框架= currentFrame else {
返回
}
让黄色热图=热图
// 2
.applyingFilter( "CIColorMatrix" , 参数:
[“inputBVector”:CIVector(x:0,y:0,z:0,w:0),
“inputAVector” : CIVector (x: 0 , y: 0 , z: 0 , w: 0.7 )])
// 3
.composited(过:帧)
// 4
显示(框架:黄色热图)
}
在这种方法中,您:
- 打开
currentFrame
的任选。 - 将
CIColorMatrix
核心图像过滤器热图。您将部分分量归零,同时将显示的α分量的黄色分量以0.7的形式生成。 - 在帧的顶部添加原始黄色热图。
- 使用提供的辅助方法来显示生成的图像。
在您构建和运行应用程序之前,需要进行最后的程序更改。
返回captureOutput(_:didOutput:from:)
并替换以下代码行:
显示(帧:当前帧)
和:
如果模式== .original {
显示(帧:当前帧)
返回
}
代码可确保您仅在此模式下任何未显示过滤的帧。
好吧,是时候了制造并并运行程序,然后点击以将运行其喷涂热图模式!

改进热图
你拥有的热图非常酷,但它存在两个问题:
- 如果这个算法的结果不是很有信心,最亮的地方可能会非常暗淡。
- 它看起来化。
好消息是这两个问题都可以解决的。
规范化热图
您将通过规范化热图来解决第一个问题。
在CVPixelBufferExtension.swift中,将以下规范化方法添加到现有扩展中:CVPixelBuffer
函数规范化(){
// 1
让 bytesPerRow = CVPixelBufferGetBytesPerRow ( self )
让 totalBytes = CVPixelBufferGetDataSize ( self )
让宽度= bytesPerRow / MemoryLayout < Float > .size
让高度= totalBytes / bytesPerRow
// 2
CVPixelBufferLockBaseAddress ( self , CVPixelBufferLockFlags (rawValue: 0 ))
// 3
让 floatBuffer = unsafeBitCast (
CVPixelBufferGetBaseAddress ( self ),
到:UnsafeMutablePointer < Float >。自我)
// 4
var minPixel: 浮点数= var maxPixel0
: 浮点数1= 0.0
// 5
对于我在0 ..<丰富度*高度 {让=
floatBuffer [i]
minPixel = min(,minPixel)
最大,即(即,最大)
}
// 6
让范围= maxPixel - minPixel
// 7
对于我在0 ..<丰富度*高度 {让=
floatBuffer [i]
floatBuffer[i] = (pixel - minPixel) / range
}
// 8
CVPixelBufferUnlockBaseAddress ( self , CVPixelBufferLockFlags (rawValue: 0 ))
}
呸那是很多代码。你在这里!
- 您可以使用并因为您完成这一点
CVPixelBuffer
。但是,要遍历实际数据,实际上最好使用每行字节数和总数据大小,以便您知道您在分配的内存范围内进行操作。CVPixelBufferGetWidth``CVPixelBufferGetHeight
- 这是在确定数据使用之前的基础地址。
-
CVPixelBuffer
转换为您的指针的基地址Float
,因为知道数据是数据。 - 一些初始变量以跟踪找到的和最大的值。
-
CVPixelBuffer
数据在中间是线性映射CVPixelBuffer
的,因此可以直接在中间的执行中执行和执行。 - 计算结果的范围。
- 再次循环数值为它们之间的0.0 1.0 。
- 具体的地址。
在您试用之前,您需要normalize
从您的愿景请求管道中调用。
在您声明和初始化的行开始时,迅速添加以下行:handleSaliency(request:error:)``ciImage
结果.pixelBuffer.normalize()
随着日期的normalize
更新CVPixelBuffer
,请确保在result.pixelBuffer
其他地方使用之前调用它。
再次构建并应用程序以查看更多的运行热图。

还不错吧?
模糊热图
现在,是时候解决第二个视频问题了:因为它的呈现化是因为热图为80 x 68,您将并且将其放大到源的标准。
要解决这个问题,请在放大后对热图应用高斯模糊。打开CameraViewController.swift再次找到handleSaliency(request:error:)
。然后替换以下行:
ciImage = ciImage
.transformed(by: CGAffineTransform (scaleX: scaleX, y: scaleY))
和:
ciImage = ciImage
.transformed(by: CGAffineTransform (scaleX: scaleX, y: scaleY))
.applyingGaussianBlur(sigma: 20.0 )
.cropped(到:targetExtent)
由于模糊会导致图像在每个侧边都模糊增长,因此将其直接放大到原厂范围内。
再次制作并运行,看看你的新的和改进的热图!

基于对象的热图
现在是您关注对象的热图方面的专家,是时候尝试基于对象的热图了。
框架将实验使热图符合对象的形状。
此外,让您可以轻松地让您在基于对象和对象之间的切换性方式之间进行性代码切换。
再次打开CameraViewController.swift。转到并captureOutput(_:didOutput:from:)
找到您创建VNGenerateAttentionBasedSaliencyImageRequest
。
行替换为以下代码
// 1
让请求:VNImageBasedRequest
// 2
var selectedSegmentIndex = 0
// 3
DispatchQueue .main.sync {
selectedSegmentIndex = saliencyControl.selectedSegmentIndex
}
// 4
切换段索引{
案例0:
请求=
VNGenerateAttentionBasedSaliencyImageRequest(completionHandler:handleSaliency)
情况1:
请求=
VNGenerateObjectnessBasedSaliencyImageRequest(completionHandler:handleSaliency)
默认:
错误(“未处理的段索引!”)
}
通过此代码更改,您可以:
- 声明一个声明
VNImageBasedRequest
。一次。 - 初始化一个变量存储从接收器中的索引
UISegmentedControl
。在之外,您必须进行的初始化,否则,您将在一个错误的位置上仍然声明。 - 在后台处理线程定义的属性主界面,在后台调用。
selectedSegmentIndex``UISegmentedControl
- 可以选择的细分创建一个
VNGenerateAttentionBasedSaliencyImageRequest
或一个。VNGenerateObjectnessBasedSaliencyImageRequest
在制造和运行之前,请UISegmentedControl
在合适的时间启用。
handleTap(_:)
在方法的顶部找到并添加以下行:
saliencyControl.isHidden = false
然后,在这种.heatMap
情况下,添加以下行:
saliencyControl.isHidden = true
完整的方法如下所示:
@IBAction func handleTap ( _ sender : UITapGestureRecognizer ) {
saliencyControl.isHidden = false
切换模式 {
case .original:
模式= .heatMap
案例.heatMap:
模式= .original
saliencyControl.isHidden = true
}
modeLabel.text = mode.rawValue
}
您正在使默认状态saliencyControl
可见,除非您要转换到该.original
状态,此时您想要隐藏它。
构建并运行。切换到热图模式。您应该在屏幕底部看到一个分段控件,它允许您在基于注意力的显着性和基于对象的显着性之间进行切换。

使用显着性热图聚焦效果
显着性分析的一种用途是根据热图创建效果以应用于图像或视频源。您将创建一个突出显着区域并使其他所有区域变暗的图像。
仍然在CameraViewController.swift中,就在下面showHeatMap(with:)
,添加以下方法:
func showFlashlight ( with heatMap : CIImage ) {
// 1
guard let frame = currentFrame else {
return
}
// 2
让掩码= heatMap
.applyingFilter( "CIColorMatrix" , 参数:
[ “inputAVector”:CIVector(x:0,y:0,z:0,w:2)])
// 3
让聚光灯=框架
.applyingFilter( "CIBlendWithMask" , 参数: [ "inputMaskImage" : mask])
// 4
显示(框架:聚光灯)
}
在这种方法中,您:
- 展开当前帧,即
CIImage
. - 使用核心图像过滤器将热图的 Alpha 通道乘以 2,从而在热图中产生更亮且稍大的加热区域。
- 应用另一个核心图像过滤器来屏蔽热图为黑色的帧中的任何像素。
- 显示过滤后的图像。
此方法将成为您的特效主力。要启用它,请ViewMode
在文件顶部的枚举中添加一个新案例:
案例手电筒= “聚光灯”
Xcode 现在应该抱怨了。将switch
语句替换handleTap
为以下内容:
切换模式 {
case .original:
模式= .heatMap
案例.heatMap:
模式= .flashlight
案例.flashlight:
模式= .original
saliencyControl.isHidden = true
}
这将添加新.flashlight
案例并将其添加为 之后的新模式.heatMap
。
最后,在 的底部handleSaliency(request:error:)
,将对 的调用替换为showHeatMap(with:)
以下代码:
切换模式 {
case .heatMap:
showHeatMap(with: ciImage)
案例.手电筒:
showFlashlight(with: ciImage)
默认:
中断
}
在这里,您可以根据应用所处的模式选择合适的显示方式。
构建并运行您的应用程序,并使用基于注意力和基于对象的显着性检查聚光灯效果!


结论
恭喜!你学到了很多东西,完成了大量的编码并用它创造了一个很酷的效果。现在怎么办?
您可以使用教程中的所有代码部分下载最终项目。
这里也推荐一些面试相关的内容,祝各位网友都能拿到满意offer!
GCD面试要点
block面试要点
Runtime面试要点
RunLoop面试要点
内存管理面试要点
MVC、MVVM面试要点
网络性能优化面试要点
网络编程面试要点
KVC&KVO面试要点
数据存储面试要点
混编技术面试要点
设计模式面试要点
UI面试要点
网友评论