Vision应用场景
Face Detection and Recognition
: 人脸检测
- 支持检测笑脸、侧脸、局部遮挡脸部、戴眼镜和帽子等场景,可以标记出人脸的矩形区域
- 可以标记出人脸和眼睛、眉毛、鼻子、嘴、牙齿的轮廓,以及人脸的中轴线
Image Alignment Analysis
: 图像对比分析
Barcode Detection
: 二维码/条形码检测
Text Detection
: 文字检测
Object Detection and Tracking
: 目标跟踪
Vision支持的图片类型
Objective-C中
CVPixelBufferRef
CGImageRef
CIImage
NSURL
NSData
Swift中
CVPixelBuffer
CGImage
CIImage
URL
Data
具体详情可在Vision.framework
的VNImageRequestHandler.h
文件中查看
Vision之API介绍
- 使用在
vision
的时候,我们首先需要明确自己需要什么效果,然后根据想要的效果来选择不同的类
- 给各种功能的
Request
提供给一个 RequestHandler
Handler
持有需要识别的图片信息,并将处理结果分发给每个 Request
的 completion Block
中
- 可以从
results
属性中得到 Observation
数组
observations
数组中的内容根据不同的request请求返回了不同的observation
- 每种
Observation
有boundingBox
,landmarks
等属性,存储的是识别后物体的坐标,点位等
- 我们拿到坐标后,就可以进行一些UI绘制。
RequestHandler
处理请求对象
VNImageRequestHandler
: 处理与单个图像有关的一个或多个图像分析请求的对象
- 一般情况下都是用该类处理识别请求
- 初始化方法支持
CVPixelBuffer
, CGImage
, CIImage
, URL
, Data
VNSequenceRequestHandler
: 处理与多个图像序列有关的图像分析请求的对象
- 目前我在处理物体跟踪的时候使用该类
- 初始化方法同上
VNRequest介绍
VNRequest
: 图像分析请求的抽象类, 继承于NSObject
VNBaseImageRequest
: 专注于图像的特定部分的分析请求
- 具体分析请求类如下:
VNObservation
检测对象
VNObservation
: 图像分析结果的抽象类, 继承与NSObject
- 图像检测结果的相关处理类如下:
实战演练
文本检测
- 方式一: 识别出具体的每一个字体的位置信息
- 方式二: 识别一行字体的位置信息
- 如图效果:
现将图片转成初始化VNImageRequestHandler
对象时, 可接受的的CIImage
1 2
| guard let ciImage = CIImage(image: image) else { return }
|
创建处理请求的handle
- 参数一: 图片类型
- 参数二: 字典类型, 有默认值为[:]
1
| let requestHandle = VNImageRequestHandler(ciImage: ciImage, options: [:])
|
创建回调闭包
- 两个参数, 无返回值
VNRequest
: 是所有请求Request的父类
1 2
| public typealias VNRequestCompletionHandler = (VNRequest, Error?) -> Swift.Void
|
1 2 3 4 5 6
| let completionHandle: VNRequestCompletionHandler = { request, error in let observations = request.results }
|
创建识别请求
1 2 3 4 5 6
| public convenience init()
public init(completionHandler: Vision.VNRequestCompletionHandler? = nil)
|
1
| let baseRequest = VNDetectTextRectanglesRequest(completionHandler: completionHandle)
|
1 2
| baseRequest.setValue(true, forKey: "reportCharacterBoxes")
|
发送请求
1
| open func perform(_ requests: [VNRequest]) throws
|
- 该方法会抛出一个异常错误
- 在连续不断(摄像头扫描)发送请求过程中, 必须在子线程执行该方法, 否则会造成线程堵塞
1 2 3 4 5 6 7 8 9
| DispatchQueue.global().async { do{ try requestHandle.perform([baseRequest]) }catch{ print("Throws:\(error)") } }
|
处理识别的Observations
对象
- 识别出来的
results
是[Any]?
类型
- 根据
boundingBox
属性可以获取到对应的文本区域的尺寸
- 需要注意的是:
boundingBox
得到的是相对iamge的比例尺寸, 都是小于1的
- Y轴坐标于UIView坐标系是相反的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| guard let boxArr = observations as? [VNTextObservation] else { return }
var bigRects = [CGRect](), smallRects = [CGRect]()
for boxObj in boxArr { bigRects.append(convertRect(boxObj.boundingBox, image)) guard let rectangleArr = boxObj.characterBoxes else { continue } for rectangle in rectangleArr{ let boundBox = rectangle.boundingBox smallRects.append(convertRect(boundBox, image)) } }
|
坐标转换
1 2 3 4 5 6 7 8 9 10 11 12
| fileprivate func convertRect(_ rectangleRect: CGRect, _ image: UIImage) -> CGRect {
let imageSize = image.scaleImage() let w = rectangleRect.width * imageSize.width let h = rectangleRect.height * imageSize.height let x = rectangleRect.minX * imageSize.width let y = (1 - rectangleRect.minY) * imageSize.height - h return CGRect(x: x, y: y, width: w, height: h) }
|
矩形识别和静态人脸识别
识别图像中的矩形
-
静态人脸识别
-
主要核心代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| guard let ciImage = CIImage(image: image) else { return }
let requestHandle = VNImageRequestHandler(ciImage: ciImage, options: [:])
var baseRequest = VNImageBasedRequest()
let completionHandle: VNRequestCompletionHandler = { request, error in let observations = request.results self.handleImageObservable(type: type, image: image, observations, completeBack) }
switch type { case .rectangle: baseRequest = VNDetectRectanglesRequest(completionHandler: completionHandle) case .staticFace: baseRequest = VNDetectFaceRectanglesRequest(completionHandler: completionHandle) default: break }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| fileprivate func rectangleDectect(_ observations: [Any]?, image: UIImage, _ complecHandle: JunDetectHandle){ guard let boxArr = observations as? [VNRectangleObservation] else { return } var bigRects = [CGRect]() for boxObj in boxArr { bigRects.append(convertRect(boxObj.boundingBox, image)) } complecHandle(bigRects, []) }
|
- 静态人脸识别需要将
observation
转成VNFaceObservation
1
| guard let boxArr = observations as? [VNFaceObservation] else { return }
|
条码识别
- 这里请求的步骤与矩形识别相同, 这里不再赘述
- 需要注意的是,在初始化request的时候需要设一个置可识别的条码类型参数
- 这里先看一下
VNDetectBarcodesRequest
的两个参数
1 2 3 4 5
| open class var supportedSymbologies: [VNBarcodeSymbology] { get }
open var symbologies: [VNBarcodeSymbology]
|
- 此处设置可识别到的条码类型为, 该请求支持是别的所有类型, 如下
- 注意
supportedSymbologies
参数的调用方法
1 2
| let request = VNDetectBarcodesRequest(completionHandler: completionHandle) request.symbologies = VNDetectBarcodesRequest.supportedSymbologies
|
- 条码识别不但能识别条码的位置信息, 还可以识别出条码的相关信息, 这里以二维码为例
- 这里需要将识别的
observations
转成[VNBarcodeObservation]
VNBarcodeObservation
有三个属性
1 2 3 4 5 6 7 8
| open var symbology: VNBarcodeSymbology { get }
open var barcodeDescriptor: CIBarcodeDescriptor? { get }
open var payloadStringValue: String? { get }
|
- 如上述图片识别出来的
payloadStringValue
参数则是小编的简书地址
- 下面是以上述图片的二维码为例处理的
CIBarcodeDescriptor
对象
- 有兴趣的可以仔细研究研究
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| fileprivate func qrCodeHandle(barCode: CIBarcodeDescriptor?){ guard let code = barCode as? CIQRCodeDescriptor else { return } let level = code.errorCorrectionLevel.hashValue let version = code.symbolVersion let mask = code.maskPattern let data = code.errorCorrectedPayload let dataStr = String(data: data, encoding: .utf8) print("这是二维码信息--", level, "---", version, "----", mask, "---", dataStr ?? "") }
|
人脸特征识别
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| var faceContour: VNFaceLandmarkRegion2D?
var leftEye: VNFaceLandmarkRegion2D? var rightEye: VNFaceLandmarkRegion2D?
var leftEyebrow: VNFaceLandmarkRegion2D? var rightEyebrow: VNFaceLandmarkRegion2D?
var leftPupil: VNFaceLandmarkRegion2D? var rightPupil: VNFaceLandmarkRegion2D?
var nose: VNFaceLandmarkRegion2D? var noseCrest: VNFaceLandmarkRegion2D? var medianLine: VNFaceLandmarkRegion2D?
var outerLips: VNFaceLandmarkRegion2D? var innerLips: VNFaceLandmarkRegion2D?
|
1 2 3 4 5
| //某一部位所有的像素点 @nonobjc public var normalizedPoints: [CGPoint] { get }
//某一部位的所有像素点的个数 open var pointCount: Int { get }
|
- 将所有的像素点坐标转换成image对应的尺寸坐标
- 使用图像上下文, 对应部位画线
- 在UIView中重写
func draw(_ rect: CGRect)
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| let content = UIGraphicsGetCurrentContext()
UIColor.green.set()
content?.setLineWidth(2)
content?.setLineJoin(.round) content?.setLineCap(.round)
content?.setShouldAntialias(true) content?.setAllowsAntialiasing(true)
content?.addLines(between: pointArr) content?.drawPath(using: .stroke)
content?.strokePath()
|
动态人脸识别和实时动态添加
由于真机不好录制gif图(尝试了一下, 效果不是很好, 放弃了), 想看效果的朋友下载源码真机运行吧
1 2 3
| let faceHandle = VNImageRequestHandler(cvPixelBuffer: pixelBuffer, options: [:])
|
- 主要强调一点, 相机扫描, 获取实时图像的过程, 必须在子线程执行, 否在会堵塞线程, 整个app失去响应, 亲自踩过的坑
1 2 3 4 5 6 7
| DispatchQueue.global().async { do{ try faceHandle.perform([baseRequest]) }catch{ print("Throws:\(error)") } }
|
扫描结果处理
- 动态人脸识别和静态人脸识别不同的地方就是, 动态实时刷新, 更新UI, 所以处理结果的方法相同
- 动态添加: 这里处理方式是添加一个眼镜效果
- 这里需要获取到两只眼睛的位置和宽度
- 先获取到左右眼的所有的像素点和像素点的个数
- 遍历所有的像素点, 转换成合适的坐标
- 将左右眼的所有的point, 分别获取X和Y坐标放到不同的数组
- 将数组有小到大排序, 得到X的最大和最小的差值, Y的最大和最小的差值
- 具体代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| fileprivate func getEyePoint(faceModel: FaceFeatureModel, position: AVCaptureDevice.Position) -> CGRect{ guard let leftEye = faceModel.leftEye else { return CGRect.zero } guard let rightEye = faceModel.rightEye else { return CGRect.zero }
let leftPoint = conventPoint(landmark: leftEye, faceRect: faceModel.faceObservation.boundingBox, position: position) let rightPoint = conventPoint(landmark: rightEye, faceRect: faceModel.faceObservation.boundingBox, position: position)
let pointXs = (leftPoint.0 + rightPoint.0).sorted() let pointYs = (leftPoint.1 + rightPoint.1).sorted() let image = UIImage(named: "eyes")! let imageWidth = (pointXs.last ?? 0.0) - (pointXs.first ?? 0) + 40 let imageHeight = image.size.height / image.size.width * imageWidth return CGRect(x: (pointXs.first ?? 0) - 20, y: (pointYs.first ?? 0) - 5, width: imageWidth, height: imageHeight) }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| fileprivate func conventPoint(landmark: VNFaceLandmarkRegion2D, faceRect: CGRect, position: AVCaptureDevice.Position) -> ([CGFloat], [CGFloat]){ var XArray = [CGFloat](), YArray = [CGFloat]() let viewRect = previewLayer.frame for i in 0..<landmark.pointCount { let point = landmark.normalizedPoints[i] let rectWidth = viewRect.width * faceRect.width let rectHeight = viewRect.height * faceRect.height let rectY = viewRect.height - (point.y * rectHeight + faceRect.minY * viewRect.height) var rectX = point.x * rectWidth + faceRect.minX * viewRect.width if position == .front{ rectX = viewRect.width + (point.x - 1) * rectWidth } XArray.append(rectX) YArray.append(rectY) } return (XArray, YArray) }
|
物体跟踪
- 简介
- 我们在屏幕上点击某物体, 然后Vision就会根据点击的物体, 实时跟踪该物体
- 当你移动手机或者物体时, 识别的对象和红框的位置是统一的
- 这里我们出的的对象是
VNDetectedObjectObservation
- 定义一个观察属性
1
| fileprivate var lastObservation: VNDetectedObjectObservation?
|
1 2
| let sequenceHandle = VNSequenceRequestHandler()
|
1 2 3 4 5
| let trackRequest = VNTrackObjectRequest(detectedObjectObservation: lastObservation, completionHandler: completionHandle)
trackRequest.trackingLevel = .accurate
|
- 当用户点击屏幕时,我们想要找出用户点击的位置,
- 根据点击的位置, 获取到一个新的物体对象
1 2 3 4 5 6
| let convertRect = visionTool.convertRect(viewRect: redView.frame, layerRect: previewLayer.frame)
let newObservation = VNDetectedObjectObservation(boundingBox: convertRect) lastObservation = newObservation
|
- 获取到扫描的结果, 如果是一个
VNDetectedObjectObservation
对象, 重新赋值
1 2 3 4 5
| //1. 获取一个实际的结果 guard let newObservation = observations?.first as? VNDetectedObjectObservation else { return } //2. 重新赋值 self.lastObservation = newObservation
|
- 根据获取到的新值, 获取物体的坐标位置
- 转换坐标, 改变红色框的位置
1 2 3 4
| let newRect = newObservation.boundingBox let convertRect = visionTool.convertRect(newRect, self.previewLayer.frame) self.redView.frame = convertRect
|
以上就是iOS 11的新框架Vision在Swift中的所有使用的情况
- 文中所列的内容可能有点空洞, 也稍微有点乱
- 小编也是刚接触Vision, 文中如有解释不全, 或者错误的地方, 还请不吝赐教
- 注意:
- 这里只是列出了主要的核心代码,具体的代码逻辑请参考demo
- 文中相关介绍有的地方如果有不是很详细或者有更好建议的,欢迎联系小编
- 如果方便的话, 还望star一下