美文网首页
iOS14 Vision

iOS14 Vision

作者: iOS虞 | 来源:发表于2021-06-25 12:21 被阅读0次

    在iOS11的时候苹果引入了Vision框架,可以人脸监测识别、图片分析、条形码识别、文本识别。是人工智能(Core ML)的一部分,今天写的是iOS14新增加的识别手指,直接上代码

    1. CameraView.swift
    import UIKit
    import AVFoundation
    import Vision
    
    enum AppError: Error {
        
        case captureSessionSetup(reason: String)
        case visionError(error: Error)
        case otherError(error: Error)
        
        static func display(_ error: Error, inViewController viewController: UIViewController) {
            
            if let appError = error as? AppError {
                
                appError.displayInViewController(viewController)
                
            } else {
                
                AppError.otherError(error: error).displayInViewController(viewController)
            }
        }
        
        
        func displayInViewController(_ viewController: UIViewController) {
            
            let title: String?
            let message: String?
            
            switch self {
            case .captureSessionSetup(reason: let reason):
                title = "AVSession Setup Error"
                message = reason
            case .visionError(error: let error):
                title = "Vision Error"
                message = error.localizedDescription
            case .otherError(error: let error):
                title = "Error"
                message = error.localizedDescription
                
            }
            print("错误:\(title!)  \(message!)")
        }
    
    }
    
    class CameraView: UIView {
        private var cameraSession: AVCaptureSession?
        private var videoDataOutputQueue = DispatchQueue(label: "CamerDataOutput", qos: .userInteractive)
        private var handPoseRequest = VNDetectHumanHandPoseRequest()
        private var lastObservationTimestamp = Date()
        private var gestureProcessor = HandGestureProcessor()
        private var pointsPath = UIBezierPath()
        private var overlayLayer = CAShapeLayer()
        private var evidenceBuffer = [HandGestureProcessor.PointsPair]()
      
        var previewLayer: AVCaptureVideoPreviewLayer {
            
            return self.layer as! AVCaptureVideoPreviewLayer
        }
        
        override class var layerClass: AnyClass {
            
            return AVCaptureVideoPreviewLayer.self
        }
        
        override func layoutSublayers(of layer: CALayer) {
            super.layoutSublayers(of: layer)
            if layer == previewLayer {
                overlayLayer.frame = layer.bounds
            }
        }
        override init(frame: CGRect) {
            super.init(frame: frame)
            handPoseRequest.maximumHandCount = 1
            gestureProcessor.didChangeStateClosure = {[weak self] state in
                
                self?.handleGestureStateChange(state: state)
            }
            setupOverlay()
            
            do {
                
                if cameraSession == nil {
                    previewLayer.videoGravity = .resizeAspectFill
                    try setupAVSession()
                    previewLayer.session = cameraSession
                }
                cameraSession?.startRunning()
                
            } catch {
                
                AppError.display(error, inViewController: UIApplication.shared.windows.first!.rootViewController!)
            }
            
        }
        
        private func setupOverlay() {
            
            previewLayer.addSublayer(overlayLayer)
        }
        
        fileprivate func setupAVSession() throws {
            
            guard let videoDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) else {
                throw AppError.captureSessionSetup(reason: "没有发现摄像头")
            }
        
            guard let deviceInput = try? AVCaptureDeviceInput(device: videoDevice) else {
                
                throw AppError.captureSessionSetup(reason: "不能创建摄像输入端")
            }
            
            let session = AVCaptureSession()
            session.beginConfiguration()
            session.sessionPreset = AVCaptureSession.Preset.high
            
            guard session.canAddInput(deviceInput) else {
                
                throw AppError.captureSessionSetup(reason: "不能添加输入的Session")
            }
            
            session.addInput(deviceInput)
            
            let dataOutput = AVCaptureVideoDataOutput()
            if session.canAddOutput(dataOutput) {
            
                session.addOutput(dataOutput)
                dataOutput.alwaysDiscardsLateVideoFrames = true
                dataOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String : kCVPixelFormatType_420YpCbCr8BiPlanarFullRange]
                dataOutput.setSampleBufferDelegate(self, queue: videoDataOutputQueue)
            } else {
                throw AppError.captureSessionSetup(reason: "不能添加输出的Session")
            }
            
            session.commitConfiguration()
            cameraSession = session
        }
        
        
        private func handleGestureStateChange(state: HandGestureProcessor.State) {
            
            let pointsPair = gestureProcessor.lastProcessedPointsPair
            var tipsColor: UIColor
            switch state {
            case .possiblePinch, .possibleApart:
                evidenceBuffer.append(pointsPair)
                tipsColor = .orange
                
            case .pinched:
                evidenceBuffer.removeAll()
                tipsColor = .green
                
            case .apart, .unknown:
                evidenceBuffer.removeAll()
                tipsColor = .red
            }
            
            showPoints([pointsPair.thumbTip, pointsPair.indexTip, pointsPair.middleTip, pointsPair.ringTip, pointsPair.littleTip], color: tipsColor)
        }
        
        func processPoints(thumbTip: CGPoint?, indexTip: CGPoint?, middleTip: CGPoint?, ringTip: CGPoint?, littleTip: CGPoint?) {
            
            guard let thumbPoint = thumbTip, let indexPoint = indexTip, let middlePoint = middleTip, let ringPoint = ringTip, let littlePoint = littleTip else {
                
                // 如果没有超过2s复位手势处理器
                if Date().timeIntervalSince(lastObservationTimestamp) > 2 {
                    
                    gestureProcessor.reset()
                }
                showPoints([], color: .clear)
                return
            }
            
            let thumbPointConverted = previewLayer.layerPointConverted(fromCaptureDevicePoint: thumbPoint)
            let indexPointConverted = previewLayer.layerPointConverted(fromCaptureDevicePoint: indexPoint)
            let middlePointConverted = previewLayer.layerPointConverted(fromCaptureDevicePoint: middlePoint)
            let ringPointConverted = previewLayer.layerPointConverted(fromCaptureDevicePoint: ringPoint)
            let littlePointConverted = previewLayer.layerPointConverted(fromCaptureDevicePoint: littlePoint)
            gestureProcessor.processPointsPair((thumbPointConverted, indexPointConverted, middlePointConverted, ringPointConverted, littlePointConverted))
        }
        
        
        func showPoints(_ points: [CGPoint], color: UIColor) {
           
            self.subviews.forEach { v in
                v.removeFromSuperview()
            }
            
            let titles = ["大拇指", "食指", "中指", "无名指", "小手指"]
            for (index,point) in points.enumerated() {
                let label = UILabel(frame: CGRect(x: point.x, y: point.y, width: 70, height: 20))
                label.text = titles[index]
                label.font = UIFont.systemFont(ofSize: 12)
                label.textAlignment = .center
                label.textColor = color
                self.addSubview(label)
            }
        }
        
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        
        /*
        // Only override draw() if you perform custom drawing.
        // An empty implementation adversely affects performance during animation.
        override func draw(_ rect: CGRect) {
            // Drawing code
        }
        */
    
    }
    
    extension CameraView: AVCaptureVideoDataOutputSampleBufferDelegate {
        
        
        func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
            
            var thumbTip: CGPoint?      //大拇指
            var indexTip: CGPoint?      //食指
            var middleTip: CGPoint?     //中指
            var ringTip: CGPoint?       //无名指
            var littleTip: CGPoint?     //小手指
            
            defer {
                DispatchQueue.main.sync {
                    self.processPoints(thumbTip: thumbTip, indexTip: indexTip, middleTip: middleTip, ringTip: ringTip, littleTip: littleTip)
                }
            }
            
            let handler = VNImageRequestHandler(cmSampleBuffer: sampleBuffer, orientation: .up, options: [ : ])
            
            do {
                try handler.perform([handPoseRequest])
                guard let observation = handPoseRequest.results?.first else {
                    return
                }
                // 获取大拇指和食指的点
                let thumbPoints = try observation.recognizedPoints(.thumb)
                let indexFingerPoints = try observation.recognizedPoints(.indexFinger)
                let middlePoints = try observation.recognizedPoints(.middleFinger)
                let ringPoints = try observation.recognizedPoints(.ringFinger)
                let littlePoints = try observation.recognizedPoints(.littleFinger)
                
                guard let thumbTipPoint = thumbPoints[.thumbTip], let indexTipPoint = indexFingerPoints[.indexTip], let midleTipPoint = middlePoints[.middleTip], let ringTipPoint = ringPoints[.ringTip], let littleTipPoint = littlePoints[.littleTip] else {
                    return
                }
                
                // 忽略掉的点
                guard thumbTipPoint.confidence > 0.3 && indexTipPoint.confidence > 0.3 && midleTipPoint.confidence > 0.3 && ringTipPoint.confidence > 0.3 && littleTipPoint.confidence > 0.3 else {
                    return
                }
                
                // 视觉坐标转成AVFoundation坐标
                thumbTip = CGPoint(x: thumbTipPoint.location.x, y: 1 - thumbTipPoint.location.y)
                indexTip = CGPoint(x: indexTipPoint.location.x, y: 1 - indexTipPoint.location.y)
                middleTip = CGPoint(x: midleTipPoint.location.x, y: 1 - midleTipPoint.location.y)
                ringTip = CGPoint(x: ringTipPoint.location.x, y: 1 - ringTipPoint.location.y)
                littleTip = CGPoint(x: littleTipPoint.location.x, y: 1 - littleTipPoint.location.y)
                
            } catch {
                
                cameraSession?.stopRunning()
                
                let error = AppError.visionError(error: error)
                
                DispatchQueue.main.async {
                    
                    error.displayInViewController(UIApplication.shared.windows.first!.rootViewController!)
                }
            }
        }
    }
    
    
    1. HandGestureProcessor.swift
    import CoreGraphics
    
    class HandGestureProcessor {
        
        enum State {
            
            case possiblePinch
            case pinched
            case possibleApart
            case apart
            case unknown
        }
        
        typealias PointsPair = (thumbTip: CGPoint, indexTip: CGPoint, middleTip: CGPoint, ringTip: CGPoint, littleTip: CGPoint)
        
        private var state = State.unknown {
            
            didSet {
                
                didChangeStateClosure?(state)
            }
        }
        private var pinchEvidenceCounter = 0
        private var apartEvidenceCounter = 0
        private var pinchMaxDistance: CGFloat
        private var evidenceCounterStateTrigger: Int
        
        var didChangeStateClosure: ((State) -> Void)?
        private (set) var lastProcessedPointsPair = PointsPair(.zero, .zero, .zero, .zero, .zero)
        
        init(pinchMaxDistance: CGFloat = 40, evidenceCounterStateTrigger: Int = 3) {
            
            self.pinchMaxDistance = pinchMaxDistance
            self.evidenceCounterStateTrigger = evidenceCounterStateTrigger
        }
        
        
        func reset() {
            
            state = .unknown
            pinchEvidenceCounter = 0
            apartEvidenceCounter = 0
        }
        
        func processPointsPair(_ pointsPair: PointsPair) {
            
            lastProcessedPointsPair = pointsPair
            let distance = pointsPair.indexTip.distance(from: pointsPair.thumbTip)
            if distance < pinchMaxDistance {
                
                pinchEvidenceCounter += 1
                apartEvidenceCounter = 0
                state = (pinchEvidenceCounter >= evidenceCounterStateTrigger) ? .pinched : .possiblePinch
            } else {
                
                apartEvidenceCounter += 1
                pinchEvidenceCounter = 0
                state = (apartEvidenceCounter >= evidenceCounterStateTrigger) ? .apart : .possibleApart
            }
        }
    }
    
    extension CGPoint {
        
        static func midPoint(p1: CGPoint, p2: CGPoint) -> CGPoint {
            
            return CGPoint(x: (p1.x + p2.x) / 2, y: (p1.y + p2.y) / 2)
        }
        
        
        func distance(from point: CGPoint) -> CGFloat {
            
            return hypot(point.x - self.x, point.y - self.y)
        }
    }
    
    1. 只是简单的实现了识别每根手指的位置,如果感兴趣的可以自己来实现判断手指的动作来控制视图的效果

    2. 最简单的效果


      IMG_0022.PNG

    相关文章

      网友评论

          本文标题:iOS14 Vision

          本文链接:https://www.haomeiwen.com/subject/zpanyltx.html