iOS视觉框架教程:轮廓检测

3,197 阅读9分钟

入门

本教程中,您将学习如何使用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管道由三部分组成:

  1. 第一个是请求,它是VNRequest所有分析请求的子类。随后的请求传递给处理程序
  2. 处理程序可以是一种VNImageRequestHandler或一种类型VNSequenceRequestHandler
  3. 最后,作为原始请求对象的属性返回的结果是子类。VNObservation

03.png

通常,很容易判断出结果类型,例如与您的请求,因为它们的名称相似,如果您的请求是一个VNDetectFaceRectanglesRequest,那么返回的结果类型是一个VNFaceObservation

对于这个项目,它的请求将VNDetectContoursRequest作为结果VNContoursObservation

当处理图像时,图像是序列中的帧,而不是您将使用的图像VNImageRequestHandler。在VNSequenceRequestHandler处理序列中,您将使用来自请求项目中的请求时使用的相关图像,例如,您将使用该图像来处理图像。

现在你已经掌握了背景理论,是时候将其付诸实践了!

影像检测

为了使项目井井有条,请创作项目导航器中的轮廓艺术组,然后选择新组。将新组命名为视觉

点击新的Vision组并选择New File…。选择 Swift File并将其命名为ContourDetector

用下面的代码替换文件的内容:

进口愿景

类检测器{
  静态让共享=  ContourDetector ()
  
  所有权(){}
}

一个新的实例设置您的只是将ContourDetector设置为单例模式。确保中运行不是绝对必要的,但它只有ContourDetector在应用程序中的实例。

执行视觉请求

现在是时候让检测器类做点什么了。

将以下属性添加到ContourDetector类:

{ Convar请求:VNDetecttoursRequest = 
  让 req =  VNDetectContoursRequest ()
  返回请求
}()

VNDetectContoursRequestSingleton结构还确保只有一个 Vision 请求,可以在应用程序的整个生命周期中重复使用。

现在添加以下方法:

函数执行(:VNRequest,
                     在图像上:CGImage)抛出 -> VNRequest {
   // 1requestHandler =  VNImageRequestHandler (cgImage: image, options: [:])
  
  // 2
  试用 requestHandler.perform([request])
  
  // 3
  回复请求
}

你在这里:方法简单但功能强大。

  1. 创建请求的处理程序并将其提交给提供CGImage
  2. 使用处理程序执行请求。
  3. 返回请求,现在已经附加了结果。

为了使用请求的结果,您需要进行一些处理。在下面的上一个方法中,添加以下方法来处理返回的请求:

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 ) }
}

在这种方法中,您:

  1. 检查结果是一个VNContoursObservation对象。
  2. 将每一个结果转换为的商品VNContour
    • flatMap将导致结果以各种方式计算。
    • contour在使用中简单的值以确保仅保留非零。compactMap
    • 检索contour(at:)索引指定处的可爱对象。
  3. 将目录映射VNContour到自定义Contour目录中。

注意从的原因很容易发生一些转换VNContourSwiftUI的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 =风景
      自我计算=假
    }
  }
}

使用此代码,您可以:

  1. 如果已经在您计算头像,什么也不做。否则设置一个标志性指示以我们正在计算的通知。然后,用户界面将能够用户,因此他们也不要做原型。
  2. 创建一个大概需要这个运行的工具。对于工作来说是的。
  3. 启动检测方法并等待其结果。
  4. 由于都是已经发布的,因此只能在主线程上分配它们calculatingcontours``calculating

此更新方法需要从以下地方调用,并且底部init任何地方都好init

更新完美()

现在正在构建和运行您的应用程序的时间。显示应用程序并看到后,单击程序以使用默认设置将其检测到的图像。

04.png

VNContoursObservation 和 VNContour

在多个本文中,一个看起来从结果列表中VNDetectContoursObservation返回VNContoursObservation相反,您不会看到的所有观点(在上一个屏幕截图中共有 43 个)都由您完成VNContoursObservation

注意:您编写的代码处理VNContoursObservation结果,以防 Apple 决定更改其工作方式。

单独的画面由一个描述VNContour并按层次组织。一个VNContour可以有子视图要访问它们,您可以选择:选择:

  1. childContours属性,它是索引VNContour的索引。
  2. 结合使用childContourCount属性和childContour(at: Int)方法来循环访问对象。

任何人VNContour都可以有任何孩子VNContour,如果您需要保留一个级别的信息,则必须让他们访问。

如果您VNContoursObservation提供了一种数据属性和访问所有所有微笑的方法,VNContoursObservation就好像是平面结构一样。contourCount``contour(at: Int)

如果您的头像结构对您很重要,您需要访问topLevelContours属性,它是一个VNContours。从那里,您可以访问各个头像的子轮廓。

您编写的代码来计算笑脸和可爱子,会发现示例图像有 4 个要看起来像和下 39 个,如果有几个简单的设置 33 个。

VNDetectContoursRequest 设置

,您已经创建了一个VNDetectContoursRequest没有对您启用的设置进行试验的。目前,您可以更改四个属性以实现不同的结果 -

  1. 对比该算法在执行前使能的暗暗调整图像调整组合的方法。调整范围调整范围会改变并减轻图像亮部它们的差异。以这个大的。以这个大的。从00到30的默认值。为 2.0 ,对这个值的值就比较合适了,确实更容易检测到一些图像。
  2. contrastPivot:算法如何如何的哪一个部分应该是被暗与亮?任何该将设定的值都将变暗,而确定此值的任何值都将变亮。您还可以将属性设置为“让Vision自动设置”的任何值。NSNumber``nil
  3. detectDarkOnLight布尔属性是对模特检测算法的提示,意味着true它应该在浅色背景上寻找这个场景。
  4. 最大图像尺寸:由于您可以将任何尺寸的图像提交给请求处理程序,因此允许图像属性设置使用的最大尺寸。如果您的图像的尺寸大于此值,API 会缩放图像,中值中这个处理maximumImageDimension的默认属性是512。 要更改它的检测能力?允许您根据需要改进此属性。

改变信号

现在您了解代码的设置,您将编写一些默认的来更改两种协议设置detectsDarkOnLight和可用的值。在本属性中,您将不会理会maximumImageDimension,只使用它们的值。

打开ContourDetector.swift将以下方法添加到底部ContourDetector

函数集(对比枢轴:CGFloat?) {
  request.contrastPivot = contrastPivot.map {
     NSNumber(价值:$0)
  }
}

函数集(调整:CGFloat) {
  request.stAdjustment =调整(对比度调整)
}

这些方法分别更改contrastPivotcontrastAdjustmentVNDetectContoursRequest并使用一些额外的逻辑来允许您设置contrastPivotnil

您会记得它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,您:

  1. 创建一个Contour很酷的名字。
  2. 设置contourPivotcontourAdjustment值循环遍历的步幅。
  3. 获取对ContourDetector单例的引用。
  4. 请注意,这是一个将每个循环的每个环节,通过每个步骤contourPivot与与价值。请注意contourAdjustment
  5. VNDetectContoursRequest使用您创建的访问器方法更改设置。
  6. 通过视觉图像检测器API运行。
  7. 将结果附加到Contour列表中并...
  8. 返回这个Contour列表。

呸那是很多或,更改其会是值得的。继续制造并运行应用程序设置中的选项。通过关闭菜单列表后,外部开始点击重新计算设置外观。

下面的屏幕截图中使用的范围是:

  • 双轴轴:0.2 -0.7
  • 亮度:0.5 - 3.0

09.png

细化微笑

这是一个很酷的但是,你可以研究出更好的效果!

你可能会注意到一些现在看起来很厚,而却很薄。

如果您可以检测到重复的轮廓,您可以删除它们,这应该会看起来更细。

确定它们两个不同但是否相同的一种方法是查看它们有多少重叠。并不是完全是 100% 准确的要简单,它是一个相对较快的您的近似值。确定重叠,可以计算它的相似框的交集。

Intersection over union 或 IoU 是两个地方的交集形状除以他们的并集形状。

10.png

当 IoU 为 1.0 时,则任何地方。如果 IoU为 0.0,则两个边框之间没有重叠。

你可以将它的类似价值来看起来像“附近”的地方。

返回ContentViewModel.swiftasyncUpdateContours,在语句之前添加以下代码:return

// 1
低轮廓.count <  9000 {
   // 2iouThreshold =  UserDefaults .standard.iouThresh
  
  // 3
  肤色位置=  0pos <微笑.count {
     // 4
    微笑=肖像 [pos]
     // 5肖像
    =肖像[ 微笑... pos] +肖像 [(pos + 10 ) ... ]。筛选 {
      contour.intersectionOverUnion(with: $0 ) < iouThreshold
    }
    // 6 个
    位置+=  1
  }
}

使用此代码,您可以:

  1. 只是当我看着这整整9,000时运行。
  2. 可以在设置屏幕中的设置中更改。
  3. 您在此循环使用图标。while
  4. 指标精确度以获取精确度。
  5. 如果只保留您的当前头像头像,则如果她的头像像我一样。
  6. 增加索引位置。

注意:可能有一种更有效的操作来完成这个,但这是解释这个概念最简单的方法。

继续构建并运行应用程序。

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.swiftasyncUpdateContours再次找到。然后,在定义特征的行列下detector,添加以下行:

检测器.set(epsilon:UserDefaults .standard.epsilon)

这将确定检测器在epsilon需要更新时显示的最新值。

一次,继续制造并运行

12.png

此示例将值 0.01 用于终止 Epsilon设置。

结论

您可以使用教程中的所有代码部分下载最终项目。

这里也推荐一些面试相关的内容!