入门
本教程中,您将学习如何使用Vision 框架:
- 创建请求以执行肖像检测。
- 以不同的方式。
- 头像以创造艺术效果。
获取底部的项目文件启动项目包括一些扩展、文件和 UI。
01.png如果您现在并运行,您将看到屏幕点击和功能设置图标的说明。
您可能会注意到现在点击屏幕不会做任何事情,但在您进入本教程的视觉部分之前,您需要在屏幕上显示图像。
显示图像
在浏览启动项目时,您可能已经注意到ImageView
连接image
到了ContentViewModel
。您打开ContentViewModel.swift,您的属性会看到这image
是一个已发布的,但没有任何分配内容。
你需要做的第一个就是改变它!
首先在ContentViewModel.swift中直接定义的三个已发布属性之后添加以下代码:
在里面(){
让 uiImage = UIImage (named: "sample" )
让 cgImage = uiImage ?.cgImage
自我.image = cgImage
}
从资产代码加载目录名为this.pngCGImage
的图像并获得已经给出的一个,然后将其分配image
发布的属性。
通过这个小作品,继续并重新运行应用程序,您将在上面看到下面的图像:
02.png现在,当您点击上面的屏幕时,它应该在您最初看到的空白屏幕的图像和之间切换。
空白屏幕最终将包含您使用 Vision 框架检测到的剪影。
视觉 API 管道
在开始编写一些代码时,你很容易在这个场景中发现视觉 API 管道会很有帮助。如果你知道它是如何工作的,你就可以很容易地在未来的项目中包含任何视觉;漂亮。
Vision API管道由三部分组成:
- 第一个是请求,它是
VNRequest
所有分析请求的子类。随后的请求传递给处理程序。 - 处理程序可以是一种
VNImageRequestHandler
或一种类型VNSequenceRequestHandler
。 - 最后,作为原始请求对象的属性返回的结果是子类。
VNObservation
通常,很容易判断出结果类型,例如与您的请求,因为它们的名称相似,如果您的请求是一个VNDetectFaceRectanglesRequest
,那么返回的结果类型是一个VNFaceObservation
。
对于这个项目,它的请求将VNDetectContoursRequest
作为结果VNContoursObservation
。
当处理图像时,图像是序列中的帧,而不是您将使用的图像VNImageRequestHandler
。在VNSequenceRequestHandler
处理序列中,您将使用来自请求项目中的请求时使用的相关图像,例如,您将使用该图像来处理图像。
现在你已经掌握了背景理论,是时候将其付诸实践了!
影像检测
为了使项目井井有条,请创作项目导航器中的轮廓艺术组,然后选择新组。将新组命名为视觉。
点击新的Vision组并选择New File…。选择
Swift File并将其命名为ContourDetector。
用下面的代码替换文件的内容:
进口愿景
类检测器{
静态让共享= ContourDetector ()
所有权(){}
}
一个新的实例设置您的只是将ContourDetector
设置为单例模式。确保中运行不是绝对必要的,但它只有ContourDetector
在应用程序中的实例。
执行视觉请求
现在是时候让检测器类做点什么了。
将以下属性添加到ContourDetector类:
{ Convar请求:VNDetecttoursRequest =
让 req = VNDetectContoursRequest ()
返回请求
}()
VNDetectContoursRequest
Singleton结构还确保只有一个 Vision 请求,可以在应用程序的整个生命周期中重复使用。
现在添加以下方法:
函数执行(:VNRequest,
在图像上:CGImage)抛出 -> VNRequest {
// 1
让 requestHandler = VNImageRequestHandler (cgImage: image, options: [:])
// 2
试用 requestHandler.perform([request])
// 3
回复请求
}
你在这里:方法简单但功能强大。
- 创建请求的处理程序并将其提交给提供
CGImage
。 - 使用处理程序执行请求。
- 返回请求,现在已经附加了结果。
为了使用请求的结果,您需要进行一些处理。在下面的上一个方法中,添加以下方法来处理返回的请求:
func postProcess ( request : VNRequest ) -> [ Contour ] {
// 1守卫
让结果= request.results 为?[ VNContoursObservation ] 其他{
返回 []
}
// 2
让 vnContours = results.flatMap { 姿态在
( 0 ..< contour.contourCount).compactMap {试试? } 轮廓.contour(at: $0 ) }
}
// 3
返回 vnContours.map { Contour (vnContour: $0 ) }
}
在这种方法中,您:
- 检查结果是一个
VNContoursObservation
对象。 - 将每一个结果转换为的商品
VNContour
。-
flatMap
将导致结果以各种方式计算。 -
contour
在使用中简单的值以确保仅保留非零。compactMap
- 检索
contour(at:)
索引指定处的可爱对象。
-
- 将目录映射
VNContour
到自定义Contour
目录中。
注意从的原因很容易发生一些转换VNContour
SwiftUI的Contour
代码。Contour
因此Identifiable
,相应的。查看ContoursView 。查看ContoursView。查看实际情况。
在检测器中处理图像
现在您只需要将这几种方法用于绑定到某个地方。
func 过程(图像:CGImage?)抛出-> [轮廓] {
守卫让图像=图像否则{
返回 []
}
let contourRequest = try perform(request: request, on: image)
返回 postProcess(请求:contourRequest)
}
在这里,您正在检查是否有图像,然后使用perform(request:on:)
来创建请求,最后使用postProcess(request:)
。这将您的图像视图模型将调用以使用轮廓的方法,这正是您接下来要做的。
打开ContentViewModel.swift并在下面的方法中添加到类的方法:
func asyncUpdateContours () async -> [轮廓] {
让检测器= ContourDetector .shared
返回(试用?检测器过程。(图像:self .image))?[]
}
在此代码中,您将通过一个异步方法来检测。但为什么是异步的?虽然检测到通常比较快,但您仍然不想在创建 API 调用时调用结果 UI。电视剧返回的方法是空空的。另外,透出警报,在这里添加了更多的逻辑,您将在这部剧中给您带来一个后果。]
方法,你还需要从某个地方调用这个方法。updateContours
函数更新轮廓(){
// 1
守卫!计算其他{返回}
计算=真
// 2
任务{
// 3让视线=等待
asyncUpdateContours ()
// 4
DispatchQueue .main.async {
self .contours =风景
自我计算=假
}
}
}
使用此代码,您可以:
- 如果已经在您计算头像,什么也不做。否则设置一个标志性指示以我们正在计算的通知。然后,用户界面将能够用户,因此他们也不要做原型。
- 创建一个大概需要这个运行的工具。对于工作来说是的。
- 启动检测方法并等待其结果。
- 由于都是已经发布的,因此只能在主线程上分配它们
calculating
。contours``calculating
此更新方法需要从以下地方调用,并且底部init
任何地方都好init
!
更新完美()
现在正在构建和运行您的应用程序的时间。显示应用程序并看到后,单击程序以使用默认设置将其检测到的图像。
04.pngVNContoursObservation 和 VNContour
在多个本文中,一个看起来从结果列表中VNDetectContoursObservation
返回VNContoursObservation
相反,您不会看到的所有观点(在上一个屏幕截图中共有 43 个)都由您完成VNContoursObservation
。
注意:您编写的代码处理VNContoursObservation
结果,以防 Apple 决定更改其工作方式。
单独的画面由一个描述VNContour
并按层次组织。一个VNContour
可以有子视图要访问它们,您可以选择:选择:
-
childContours
属性,它是索引VNContour
的索引。 - 结合使用
childContourCount
属性和childContour(at: Int)
方法来循环访问对象。
任何人VNContour
都可以有任何孩子VNContour
,如果您需要保留一个级别的信息,则必须让他们访问。
如果您VNContoursObservation
提供了一种数据属性和访问所有所有微笑的方法,VNContoursObservation
就好像是平面结构一样。contourCount``contour(at: Int)
如果您的头像结构对您很重要,您需要访问topLevelContours
属性,它是一个VNContours
。从那里,您可以访问各个头像的子轮廓。
您编写的代码来计算笑脸和可爱子,会发现示例图像有 4 个要看起来像和下 39 个,如果有几个简单的设置 33 个。
VNDetectContoursRequest 设置
,您已经创建了一个VNDetectContoursRequest
没有对您启用的设置进行试验的。目前,您可以更改四个属性以实现不同的结果 -
- 对比该算法在执行前使能的暗暗调整图像调整组合的方法。调整范围调整范围会改变并减轻图像亮部它们的差异。以这个大的。以这个大的。从00到30的默认值。为 2.0 ,对这个值的值就比较合适了,确实更容易检测到一些图像。
-
contrastPivot:算法如何如何的哪一个部分应该是被暗与亮?任何该将设定的值都将变暗,而确定此值的任何值都将变亮。您还可以将属性设置为“让Vision自动设置”的任何值。
NSNumber``nil
-
detectDarkOnLight布尔属性是对模特检测算法的提示,意味着
true
它应该在浅色背景上寻找这个场景。 -
最大图像尺寸:由于您可以将任何尺寸的图像提交给请求处理程序,因此允许图像属性设置使用的最大尺寸。如果您的图像的尺寸大于此值,API 会缩放图像,中值中这个处理
maximumImageDimension
的默认属性是512。 要更改它的检测能力?允许您根据需要改进此属性。
改变信号
现在您了解代码的设置,您将编写一些默认的来更改两种协议设置detectsDarkOnLight
和可用的值。在本属性中,您将不会理会maximumImageDimension
,只使用它们的值。
打开ContourDetector.swift将以下方法添加到底部ContourDetector
:
函数集(对比枢轴:CGFloat?) {
request.contrastPivot = contrastPivot.map {
NSNumber(价值:$0)
}
}
函数集(调整:CGFloat) {
request.stAdjustment =调整(对比度调整)
}
这些方法分别更改contrastPivot
和contrastAdjustment
,VNDetectContoursRequest
并使用一些额外的逻辑来允许您设置contrastPivot
为nil
。
您会记得它request
是一个lazy var
,这意味着如果在您调用这些方法时它还没有被实例化,那么它就是现在。
,打开ContentViewModel 。并找到输出asyncUpdateContours
。更新方法,迅速看起来像这样:
func asyncUpdateContours () async -> [轮廓] {
让检测器= ContourDetector .shared
// 新逻辑
检测器.set(手机轴5) : 0.
检测器.set(调整调整:2.0)
返回(试用?检测器过程。(图像:self .image))?[]
}
这是新行硬编码contrastPivot
和的两个值contrastAdjustment
。
并运行应用程序并为设置这些不同的值(需要更改这些值,然后为您制造不同的值并运行)。以下是制造这些值的一些截图:
05.png 06.png 07.png 08.png,,得到了一些有趣的结果。但是,有点令人烦恼的是,没有神奇的设置图像从它们中获取所有结果,然后你现在组合成一个结果。
……有一个解决方案但是。
在项目中,可能点击了角的设置图标。如果您探索下它,请点击它,然后点击它,然后您会看到用于显示和主画面时您的最合适的选择。
您将使用对这些图像中的所有对象,并将它们设置为更多设置创建它们。
请注意:每个设置的范围,它运行的视觉请求都有很多。这可能是一个缓慢的过程,您使用非常有帮助,否则不建议在旧设备上使用较新的 iPhone、iPad 。和基于M1的Mac上运行良好。
如果您还没有打开ContentViewModel.swift,请继续打开它。删除所有内容asyncUpdateContours
并替换以下代码:
// 1个
微笑:[微笑] = []
// 2
让pivotStride =步幅(
来自:UserDefaults .standard.minPivot,
至:UserDefaults .standard.maxPivot,
由:0.1 )
让步幅=步幅(
来自:UserDefaults .standard.minAdjust,
至:UserDefaults .standard.maxAdjust,
由:0.2 )
// 3
让检测器= ContourDetector .shared
// 4
对于 pivotStride {
调整调整步幅{
// 5
检测器.set(对比枢轴:枢轴)
检测器.set(调整:调整)
// 6
让 newContours = ( try ? detector.process(image: self .image)) ?? []
// 7
风景.append(contentsOf:newContours)
}
}
// 8
返回 满意
在这个新版本中asyncUpdateContours
,您:
- 创建一个
Contour
很酷的名字。 - 设置
contourPivot
和contourAdjustment
值循环遍历的步幅。 - 获取对
ContourDetector
单例的引用。 - 请注意,这是一个将每个循环的每个环节,通过每个步骤
contourPivot
与与价值。请注意contourAdjustment
。 -
VNDetectContoursRequest
使用您创建的访问器方法更改设置。 - 通过视觉图像检测器API运行。
- 将结果附加到
Contour
列表中并... - 返回这个
Contour
列表。
呸那是很多或,更改其会是值得的。继续制造并运行应用程序设置中的选项。通过关闭菜单列表后,外部开始点击重新计算设置外观。
下面的屏幕截图中使用的范围是:
- 双轴轴:0.2 -0.7
- 亮度:0.5 - 3.0
细化微笑
这是一个很酷的但是,你可以研究出更好的效果!
你可能会注意到一些现在看起来很厚,而却很薄。
如果您可以检测到重复的轮廓,您可以删除它们,这应该会看起来更细。
确定它们两个不同但是否相同的一种方法是查看它们有多少重叠。并不是完全是 100% 准确的要简单,它是一个相对较快的您的近似值。确定重叠,可以计算它的相似框的交集。
Intersection over union 或 IoU 是两个地方的交集形状除以他们的并集形状。
10.png当 IoU 为 1.0 时,则任何地方。如果 IoU为 0.0,则两个边框之间没有重叠。
你可以将它的类似价值来看起来像“附近”的地方。
返回ContentViewModel.swiftasyncUpdateContours
,在语句之前添加以下代码:return
// 1
低轮廓.count < 9000 {
// 2
让 iouThreshold = UserDefaults .standard.iouThresh
// 3
肤色位置= 0
而 pos <微笑.count {
// 4
微笑=肖像 [pos]
// 5肖像
=肖像[ 微笑... pos] +肖像 [(pos + 10 ) ... ]。筛选 {
contour.intersectionOverUnion(with: $0 ) < iouThreshold
}
// 6 个
位置+= 1
}
}
使用此代码,您可以:
- 只是当我看着这整整9,000时运行。
- 可以在设置屏幕中的设置中更改。
- 您在此循环使用图标。
while
- 指标精确度以获取精确度。
- 如果只保留您的当前头像头像,则如果她的头像像我一样。
- 增加索引位置。
注意:可能有一种更有效的操作来完成这个,但这是解释这个概念最简单的方法。
继续构建并运行应用程序。
11.png注意有几分厚现在明显变薄了!
凝视
您可以另外使用一种技巧为您的头像艺术天赋。您可以添加头像。
VNC有一个名字叫成员的方法polygonApproximation(epsilon:)
,它就是在上面的。这个方法的目的是返回一个相似点的相似的相似值。
epsilon 的选择将最终返回蓝色的高度。
打开 ContourDetector.swift。在的顶部ContourDetector
,添加以下属性:
属性 epsilon 数字:0 = 0.0
在的底部ContourDetector
,添加以下方法:
函数集(epsilon:CGFloat
)il on =数(epsilon)
}
仍然在同一个类中,找到方法底部的语句postProcess(request:)
并将其替换为以下代码:return
让Contours {
试用?$0 .polygonApproximation(epsilon: self .epsilon)
}
返回的Contours { Contour (v) 的Contour: $0 }
epsilon
代码在返回之前,以当前的价值为目标。
在试用此新功能之前,需要将 epsilon 设置连接到ContourDetector
。您只需从设置您的屏幕写入UserDefaults
的中位即可。
打开ContentViewModel.swift并asyncUpdateContours
再次找到。然后,在定义特征的行列下detector
,添加以下行:
检测器.set(epsilon:UserDefaults .standard.epsilon)
这将确定检测器在epsilon
需要更新时显示的最新值。
一次,继续制造并运行!
12.png此示例将值 0.01 用于终止 Epsilon设置。
结论
您可以使用教程中的所有代码部分下载最终项目。
这里也推荐一些面试相关的内容,祝各位网友都能拿到满意offer!
GCD面试要点
block面试要点
Runtime面试要点
RunLoop面试要点
内存管理面试要点
MVC、MVVM面试要点
网络性能优化面试要点
网络编程面试要点
KVC&KVO面试要点
数据存储面试要点
混编技术面试要点
设计模式面试要点
UI面试要点
网友评论