Swift之Vision 圖像識別框架

Swift之Vision 圖像識別框架

  • 2017年蘋果大大又推出了新機型iPhone 8和iPhone 8Plus, 這還不是重點, 重點是那一款價值9000RMB的iPhone X, 雖然說網上吐槽聲從未中止過, 可是我以爲仍是不錯的哈!
  • 軟件方面, 蘋果大大也推出了iOS 11, 經本人iPhone 7手機親測, 耗電快外加通知欄改不完的bug
  • 固然了隨着iOS 11的推出, 也隨之推出了一些新的API,如:ARKitCore MLFileProviderIdentityLookupCore NFCVison 等。
  • 這裏咱們還要說的就是Apple 在 WWDC 2017 推出的圖像識別框架--Vison官方文檔
  • Demo地址

一. Vision應用場景

  • Face Detection and Recognition : 人臉檢測
    • 支持檢測笑臉、側臉、局部遮擋臉部、戴眼鏡和帽子等場景,能夠標記出人臉的矩形區域
    • 能夠標記出人臉和眼睛、眉毛、鼻子、嘴、牙齒的輪廓,以及人臉的中軸線
  • Image Alignment Analysis: 圖像對比分析
  • Barcode Detection: 二維碼/條形碼檢測
    • 用於查找和識別圖像中的條碼
    • 檢測條形碼信息
  • Text Detection: 文字檢測
    • 查找圖像中可見文本的區域
    • 檢測文本區域的信息
  • Object Detection and Tracking: 目標跟蹤
    • 臉部,矩形和通用模板

二. Vision支持的圖片類型

1. Objective-C中

  • CVPixelBufferRef
  • CGImageRef
  • CIImage
  • NSURL
  • NSData

2. Swift中

  • CVPixelBuffer
  • CGImage
  • CIImage
  • URL
  • Data

具體詳情可在Vision.frameworkVNImageRequestHandler.h文件中查看git

三. Vision之API介紹

  • 使用在vision的時候,咱們首先須要明確本身須要什麼效果,而後根據想要的效果來選擇不一樣的類
  • 給各類功能的 Request 提供給一個 RequestHandler
  • Handler 持有須要識別的圖片信息,並將處理結果分發給每一個 Requestcompletion Block
  • 能夠從 results 屬性中獲得 Observation 數組
  • observations數組中的內容根據不一樣的request請求返回了不一樣的observation
  • 每種ObservationboundingBoxlandmarks等屬性,存儲的是識別後物體的座標,點位等
  • 咱們拿到座標後,就能夠進行一些UI繪製。

1. RequestHandler處理請求對象

  • VNImageRequestHandler: 處理與單個圖像有關的一個或多個圖像分析請求的對象
    • 通常狀況下都是用該類處理識別請求
    • 初始化方法支持CVPixelBuffer, CGImage, CIImage, URL, Data
  • VNSequenceRequestHandler: 處理與多個圖像序列有關的圖像分析請求的對象
    • 目前我在處理物體跟蹤的時候使用該類
    • 初始化方法同上

2. VNRequest介紹

  • VNRequest: 圖像分析請求的抽象類, 繼承於NSObject
  • VNBaseImageRequest: 專一於圖像的特定部分的分析請求
  • 具體分析請求類以下:

VNImageBasedRequest.png

3. VNObservation檢測對象

  • VNObservation: 圖像分析結果的抽象類, 繼承與NSObject
  • 圖像檢測結果的相關處理類以下:

VNObservation.png

四. 實戰演練

1. 文本檢測

  • 方式一: 識別出具體的每個字體的位置信息
  • 方式二: 識別一行字體的位置信息
  • 如圖效果:

WechatIMG3.jpeg

WechatIMG5.jpeg

1.1 現將圖片轉成初始化VNImageRequestHandler對象時, 可接受的的CIImage

//1. 轉成ciimage
guard let ciImage = CIImage(image: image) else { return }
複製代碼

1.2 建立處理請求的handle

  • 參數一: 圖片類型
  • 參數二: 字典類型, 有默認值爲[:]
let requestHandle = VNImageRequestHandler(ciImage: ciImage, options: [:])
複製代碼

1.3 建立回調閉包

  • 兩個參數, 無返回值
  • VNRequest: 是全部請求Request的父類
public typealias VNRequestCompletionHandler = (VNRequest, Error?) -> Swift.Void

複製代碼
  • 具體代碼以下:
//4. 設置回調
let completionHandle: VNRequestCompletionHandler = { request, error in
    let observations = request.results
    //識別出來的對象數組 
}

複製代碼

1.4 建立識別請求

  • 兩種初始化方式
//無參數
public convenience init()
    
//閉包參數
public init(completionHandler: Vision.VNRequestCompletionHandler? = nil)

複製代碼
  • 這裏使用帶閉包的初始化方式
let baseRequest = VNDetectTextRectanglesRequest(completionHandler: completionHandle)
複製代碼
  • 屬性設置(是否識別具體的每個文字)
// 設置識別具體文字
baseRequest.setValue(true, forKey: "reportCharacterBoxes") 
複製代碼
  • 不設置該屬性, 識別出來的是一行文字

1.5 發送請求

open func perform(_ requests: [VNRequest]) throws
複製代碼
  • 該方法會拋出一個異常錯誤
  • 在接二連三(攝像頭掃描)發送請求過程當中, 必須在子線程執行該方法, 不然會形成線程堵塞
//6. 發送請求
DispatchQueue.global().async {
    do{
        try requestHandle.perform([baseRequest])
    }catch{
        print("Throws:\(error)")
    }
}

複製代碼

1.6 處理識別的Observations對象

  • 識別出來的results[Any]?類型
  • 根據boundingBox屬性能夠獲取到對應的文本區域的尺寸
  • 須要注意的是:
    • boundingBox獲得的是相對iamge的比例尺寸, 都是小於1的
    • Y軸座標於UIView座標系是相反的
//1. 獲取識別到的VNTextObservation
guard let boxArr = observations as? [VNTextObservation] else { return }
        
//2. 建立rect數組
var bigRects = [CGRect](), smallRects = [CGRect]()
        
//3. 遍歷識別結果
for boxObj in boxArr {
    // 3.1尺寸轉換
    //獲取一行文本的區域位置
    bigRects.append(convertRect(boxObj.boundingBox, image))
    
    //2. 獲取
    guard let rectangleArr = boxObj.characterBoxes else { continue }
    for rectangle in rectangleArr{
        //3. 獲得每個字體的的尺寸
        let boundBox = rectangle.boundingBox
        smallRects.append(convertRect(boundBox, image))
    }
}

複製代碼

座標轉換github

/// image座標轉換
fileprivate func convertRect(_ rectangleRect: CGRect, _ image: UIImage) -> CGRect {
//此處是將Image的實際尺寸轉化成imageView的尺寸
    let imageSize = image.scaleImage()
    let w = rectangleRect.width * imageSize.width
    let h = rectangleRect.height * imageSize.height
    let x = rectangleRect.minX * imageSize.width
    //該Y座標與UIView的Y座標是相反的
    let y = (1 - rectangleRect.minY) * imageSize.height - h
    return CGRect(x: x, y: y, width: w, height: h)
}

複製代碼

2. 矩形識別和靜態人臉識別

  • 識別圖像中的矩形

1511935758595.jpg

  • 靜態人臉識別

1511936019734.jpg

  • 主要核心代碼
//1. 轉成ciimage
guard let ciImage = CIImage(image: image) else { return }
        
//2. 建立處理request
let requestHandle = VNImageRequestHandler(ciImage: ciImage, options: [:])
        
//3. 建立baseRequest
//大多數識別請求request都繼承自VNImageBasedRequest
var baseRequest = VNImageBasedRequest()
        
//4. 設置回調
let completionHandle: VNRequestCompletionHandler = { request, error in
    let observations = request.results
    self.handleImageObservable(type: type, image: image, observations, completeBack)
}
        
//5. 建立識別請求
switch type {
case .rectangle:
    baseRequest = VNDetectRectanglesRequest(completionHandler: completionHandle)
case .staticFace:
    baseRequest = VNDetectFaceRectanglesRequest(completionHandler: completionHandle)
default:
    break
}

複製代碼
  • 處理識別的observation
/// 矩形檢測
    fileprivate func rectangleDectect(_ observations: [Any]?, image: UIImage, _ complecHandle: JunDetectHandle){
        //1. 獲取識別到的VNRectangleObservation
        guard let boxArr = observations as? [VNRectangleObservation] else { return }
        //2. 建立rect數組
        var bigRects = [CGRect]()
        //3. 遍歷識別結果
        for boxObj in boxArr {
            // 3.1
            bigRects.append(convertRect(boxObj.boundingBox, image))
        }
        //4. 回調結果
        complecHandle(bigRects, [])
    }

複製代碼
  • 靜態人臉識別須要將observation轉成VNFaceObservation
guard let boxArr = observations as? [VNFaceObservation] else { return }
複製代碼

3. 條碼識別

1511936988374.jpg

  • 這裏請求的步驟與矩形識別相同, 這裏再也不贅述
  • 須要注意的是,在初始化request的時候須要設一個置可識別的條碼類型參數
  • 這裏先看一下VNDetectBarcodesRequest的兩個參數
//支持的可識別的條碼類型(須要直接用class調用)
open class var supportedSymbologies: [VNBarcodeSymbology] { get }

//設置可識別的條碼類型
open var symbologies: [VNBarcodeSymbology]
複製代碼
  • 此處設置可識別到的條碼類型爲, 該請求支持是別的全部類型, 以下
  • 注意supportedSymbologies參數的調用方法
let request = VNDetectBarcodesRequest(completionHandler: completionHandle)
request.symbologies = VNDetectBarcodesRequest.supportedSymbologies
複製代碼
  • 條碼識別不但能識別條碼的位置信息, 還能夠識別出條碼的相關信息, 這裏以二維碼爲例
  • 這裏須要將識別的observations轉成[VNBarcodeObservation]
  • VNBarcodeObservation有三個屬性
//條碼類型: qr, code128....等等
open var symbology: VNBarcodeSymbology { get }

//條碼的相關信息
open var barcodeDescriptor: CIBarcodeDescriptor? { get }

//若是是二維碼, 則是二維碼的網址連接 
open var payloadStringValue: String? { get }
複製代碼
  • 如上述圖片識別出來的payloadStringValue參數則是小編的簡書地址
  • 下面是以上述圖片的二維碼爲例處理的CIBarcodeDescriptor對象
  • 有興趣的能夠仔細研究研究
/// 二維碼信息處理
    fileprivate func qrCodeHandle(barCode: CIBarcodeDescriptor?){
        //1. 轉成對應的條碼對象
        guard let code = barCode as? CIQRCodeDescriptor else { return }
        
        //2. 解讀條碼信息
        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 ?? "")
    }

複製代碼

4. 人臉特徵識別

  • 可識別出人臉的輪廓, 眼睛, 鼻子, 嘴巴等具體位置

1511944652200.jpg

  • VNFaceLandmarks2D介紹
/// 臉部輪廓
    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?
複製代碼
//某一部位全部的像素點
@nonobjc public var normalizedPoints: [CGPoint] { get }

//某一部位的全部像素點的個數
open var pointCount: Int { get }
複製代碼
  • 將全部的像素點座標轉換成image對應的尺寸座標
  • 使用圖像上下文, 對應部位畫線
  • 在UIView中重寫func draw(_ rect: CGRect)方法
//5.1 獲取當前上下文
let content = UIGraphicsGetCurrentContext()
                
//5.2 設置填充顏色(setStroke設置描邊顏色)
UIColor.green.set()
                
//5.3 設置寬度
content?.setLineWidth(2)
                
//5.4. 設置線的類型(鏈接處)
content?.setLineJoin(.round)
content?.setLineCap(.round)
                
//5.5. 設置抗鋸齒效果
content?.setShouldAntialias(true)
content?.setAllowsAntialiasing(true)
                
//5.6 開始繪製
content?.addLines(between: pointArr)
content?.drawPath(using: .stroke)
                
//5.7 結束繪製
content?.strokePath()
複製代碼

5. 動態人臉識別和實時動態添加

因爲真機很差錄製gif圖(嘗試了一下, 效果不是很好, 放棄了), 想看效果的朋友下載源碼真機運行吧數組

  • 這裏提供一張可供掃描的圖片bash

  • request的初始化這裏就不作介紹了, 說一下handle的初始化方法閉包

    • CVPixelBuffer: 掃描實時輸出的對象
//1. 建立處理請求
let faceHandle = VNImageRequestHandler(cvPixelBuffer: pixelBuffer, options: [:])

複製代碼
  • 主要強調一點, 相機掃描, 獲取實時圖像的過程, 必須在子線程執行, 否在會堵塞線程, 整個app失去響應, 親自踩過的坑
DispatchQueue.global().async {
    do{
        try faceHandle.perform([baseRequest])
    }catch{
        print("Throws:\(error)")
    }
}
複製代碼

掃描結果處理

  • 動態人臉識別和靜態人臉識別不一樣的地方就是, 動態實時刷新, 更新UI, 因此處理結果的方法相同
  • 動態添加: 這裏處理方式是添加一個眼鏡效果
  • 這裏須要獲取到兩隻眼睛的位置和寬度
    • 先獲取到左右眼的全部的像素點和像素點的個數
    • 遍歷全部的像素點, 轉換成合適的座標
    • 將左右眼的全部的point, 分別獲取X和Y座標放到不一樣的數組
    • 將數組有小到大排序, 獲得X的最大和最小的差值, Y的最大和最小的差值
    • 具體代碼以下
/// H偶去轉換後的尺寸座標
    fileprivate func getEyePoint(faceModel: FaceFeatureModel, position: AVCaptureDevice.Position) -> CGRect{
        //1. 獲取左右眼
        guard let leftEye = faceModel.leftEye else { return CGRect.zero }
        guard let rightEye = faceModel.rightEye else { return CGRect.zero }

        //2. 位置數組
        let leftPoint = conventPoint(landmark: leftEye, faceRect: faceModel.faceObservation.boundingBox, position: position)
        let rightPoint = conventPoint(landmark: rightEye, faceRect: faceModel.faceObservation.boundingBox, position: position)

        //3. 排序
        let pointXs = (leftPoint.0 + rightPoint.0).sorted()
        let pointYs = (leftPoint.1 + rightPoint.1).sorted()
        
        //4. 添加眼睛
        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)
    }

複製代碼
  • 每一隻眼睛的座標處理
/// 座標轉換
    fileprivate func conventPoint(landmark: VNFaceLandmarkRegion2D, faceRect: CGRect, position: AVCaptureDevice.Position) -> ([CGFloat], [CGFloat]){
        //1. 定義
        var XArray = [CGFloat](), YArray = [CGFloat]()
        let viewRect = previewLayer.frame
        
        //2. 遍歷
        for i in 0..<landmark.pointCount {
            //2.1 獲取當前位置並轉化到合適尺寸
            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)
    }

複製代碼
  • 最後獲取到該CGRect, 添加眼鏡效果便可

6. 物體跟蹤

  • 簡介
    • 咱們在屏幕上點擊某物體, 而後Vision就會根據點擊的物體, 實時跟蹤該物體
    • 當你移動手機或者物體時, 識別的對象和紅框的位置是統一的
  • 這裏咱們出的的對象是VNDetectedObjectObservation
  • 定義一個觀察屬性
fileprivate var lastObservation: VNDetectedObjectObservation?
複製代碼
  • 建立一個處理多個圖像序列的請求
//處理與多個圖像序列的請求handle
let sequenceHandle = VNSequenceRequestHandler()
複製代碼
  • 建立跟蹤識別請求
//4. 建立跟蹤識別請求
let trackRequest = VNTrackObjectRequest(detectedObjectObservation: lastObservation, completionHandler: completionHandle)
//將精度設置爲高
trackRequest.trackingLevel = .accurate

複製代碼
  • 當用戶點擊屏幕時,咱們想要找出用戶點擊的位置,
  • 根據點擊的位置, 獲取到一個新的物體對象
//2. 轉換座標
let convertRect = visionTool.convertRect(viewRect: redView.frame, layerRect: previewLayer.frame)
        
//3. 根據點擊的位置獲取新的對象
let newObservation = VNDetectedObjectObservation(boundingBox: convertRect)
lastObservation = newObservation
複製代碼
  • 獲取到掃描的結果, 若是是一個VNDetectedObjectObservation對象, 從新賦值
//1. 獲取一個實際的結果
guard let newObservation = observations?.first as? VNDetectedObjectObservation else { return }
            
//2. 從新賦值
self.lastObservation = newObservation
複製代碼
  • 根據獲取到的新值, 獲取物體的座標位置
  • 轉換座標, 改變紅色框的位置
//4. 座標轉換
let newRect = newObservation.boundingBox
let convertRect = visionTool.convertRect(newRect, self.previewLayer.frame)
self.redView.frame = convertRect
複製代碼

以上就是iOS 11的新框架Vision在Swift中的全部使用的狀況app

  • 文中所列的內容可能有點空洞, 也稍微有點亂
  • 小編也是剛接觸Vision, 文中若有解釋不全, 或者錯誤的地方, 還請不吝賜教

GitHub--Demo地址

  • 注意:
  • 這裏只是列出了主要的核心代碼,具體的代碼邏輯請參考demo
  • 文中相關介紹有的地方若是有不是很詳細或者有更好建議的,歡迎聯繫小編
  • 若是方便的話, 還望star一下

其餘相關文章

相關文章
相關標籤/搜索