Live Face Detection - захватывает только прямоугольную часть как изображение - PullRequest
0 голосов
/ 17 февраля 2020

Обнаружение и отслеживание лиц с камеры ie в режиме реального времени. Я мог бы получить это на основе источника: - https://developer.apple.com/documentation/vision/tracking_the_user_s_face_in_real_time. На следующем изображении показано, что прямоугольник будет помещен в лицо, enter image description here

. Как вы можете видеть красный прямоугольник angular часть экрана, мне нужно захватить только внутреннюю часть прямоугольник как изображение и сохранить не полный экран как изображение. Как я мог получить это?

Я пробовал использовать другой источник, который дает мне только полный экран в качестве изображения, но не прямоугольную angular часть.

Исходный код для распознавания лиц в реальном времени:

import UIKit
import AVKit
import Vision

class ViewController: UIViewController, AVCaptureVideoDataOutputSampleBufferDelegate {

// Main view for showing camera content.
@IBOutlet weak var previewView: UIView?

// AVCapture variables to hold sequence data
var session: AVCaptureSession?
var previewLayer: AVCaptureVideoPreviewLayer?

var videoDataOutput: AVCaptureVideoDataOutput?
var videoDataOutputQueue: DispatchQueue?

var captureDevice: AVCaptureDevice?
var captureDeviceResolution: CGSize = CGSize()

// Layer UI for drawing Vision results
var rootLayer: CALayer?
var detectionOverlayLayer: CALayer?
var detectedFaceRectangleShapeLayer: CAShapeLayer?
var detectedFaceLandmarksShapeLayer: CAShapeLayer?

// Vision requests
private var detectionRequests: [VNDetectFaceRectanglesRequest]?
private var trackingRequests: [VNTrackObjectRequest]?

lazy var sequenceRequestHandler = VNSequenceRequestHandler()

// MARK: UIViewController overrides

override func viewDidLoad() {
    super.viewDidLoad()

    self.session = self.setupAVCaptureSession()

    self.prepareVisionRequest()

    self.session?.startRunning()
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
}

// Ensure that the interface stays locked in Portrait.
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
    return .portrait
}

// Ensure that the interface stays locked in Portrait.
override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
    return .portrait
}

// MARK: AVCapture Setup

/// - Tag: CreateCaptureSession
fileprivate func setupAVCaptureSession() -> AVCaptureSession? {
    let captureSession = AVCaptureSession()
    do {
        let inputDevice = try self.configureFrontCamera(for: captureSession)
        self.configureVideoDataOutput(for: inputDevice.device, resolution: inputDevice.resolution, captureSession: captureSession)
        self.designatePreviewLayer(for: captureSession)
        return captureSession
    } catch let executionError as NSError {
        self.presentError(executionError)
    } catch {
        self.presentErrorAlert(message: "An unexpected failure has occured")
    }

    self.teardownAVCapture()

    return nil
}

/// - Tag: ConfigureDeviceResolution
fileprivate func highestResolution420Format(for device: AVCaptureDevice) -> (format: AVCaptureDevice.Format, resolution: CGSize)? {
    var highestResolutionFormat: AVCaptureDevice.Format? = nil
    var highestResolutionDimensions = CMVideoDimensions(width: 0, height: 0)

    for format in device.formats {
        let deviceFormat = format as AVCaptureDevice.Format

        let deviceFormatDescription = deviceFormat.formatDescription
        if CMFormatDescriptionGetMediaSubType(deviceFormatDescription) == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange {
            let candidateDimensions = CMVideoFormatDescriptionGetDimensions(deviceFormatDescription)
            if (highestResolutionFormat == nil) || (candidateDimensions.width > highestResolutionDimensions.width) {
                highestResolutionFormat = deviceFormat
                highestResolutionDimensions = candidateDimensions
            }
        }
    }

    if highestResolutionFormat != nil {
        let resolution = CGSize(width: CGFloat(highestResolutionDimensions.width), height: CGFloat(highestResolutionDimensions.height))
        return (highestResolutionFormat!, resolution)
    }

    return nil
}

fileprivate func configureFrontCamera(for captureSession: AVCaptureSession) throws -> (device: AVCaptureDevice, resolution: CGSize) {
    let deviceDiscoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: .video, position: .front)

    if let device = deviceDiscoverySession.devices.first {
        if let deviceInput = try? AVCaptureDeviceInput(device: device) {
            if captureSession.canAddInput(deviceInput) {
                captureSession.addInput(deviceInput)
            }

            if let highestResolution = self.highestResolution420Format(for: device) {
                try device.lockForConfiguration()
                device.activeFormat = highestResolution.format
                device.unlockForConfiguration()

                return (device, highestResolution.resolution)
            }
        }
    }

    throw NSError(domain: "ViewController", code: 1, userInfo: nil)
}

/// - Tag: CreateSerialDispatchQueue
fileprivate func configureVideoDataOutput(for inputDevice: AVCaptureDevice, resolution: CGSize, captureSession: AVCaptureSession) {

    let videoDataOutput = AVCaptureVideoDataOutput()
    videoDataOutput.alwaysDiscardsLateVideoFrames = true

    // Create a serial dispatch queue used for the sample buffer delegate as well as when a still image is captured.
    // A serial dispatch queue must be used to guarantee that video frames will be delivered in order.
    let videoDataOutputQueue = DispatchQueue(label: "com.example.apple-samplecode.VisionFaceTrack")
    videoDataOutput.setSampleBufferDelegate(self, queue: videoDataOutputQueue)

    if captureSession.canAddOutput(videoDataOutput) {
        captureSession.addOutput(videoDataOutput)
    }

    videoDataOutput.connection(with: .video)?.isEnabled = true

    if let captureConnection = videoDataOutput.connection(with: AVMediaType.video) {
        if captureConnection.isCameraIntrinsicMatrixDeliverySupported {
            captureConnection.isCameraIntrinsicMatrixDeliveryEnabled = true
        }
    }

    self.videoDataOutput = videoDataOutput
    self.videoDataOutputQueue = videoDataOutputQueue

    self.captureDevice = inputDevice
    self.captureDeviceResolution = resolution
}

/// - Tag: DesignatePreviewLayer
fileprivate func designatePreviewLayer(for captureSession: AVCaptureSession) {
    let videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
    self.previewLayer = videoPreviewLayer

    videoPreviewLayer.name = "CameraPreview"
    videoPreviewLayer.backgroundColor = UIColor.black.cgColor
    videoPreviewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill

    if let previewRootLayer = self.previewView?.layer {
        self.rootLayer = previewRootLayer

        previewRootLayer.masksToBounds = true
        videoPreviewLayer.frame = previewRootLayer.bounds
        previewRootLayer.addSublayer(videoPreviewLayer)
    }
}

// Removes infrastructure for AVCapture as part of cleanup.
fileprivate func teardownAVCapture() {
    self.videoDataOutput = nil
    self.videoDataOutputQueue = nil

    if let previewLayer = self.previewLayer {
        previewLayer.removeFromSuperlayer()
        self.previewLayer = nil
    }
}

// MARK: Helper Methods for Error Presentation

fileprivate func presentErrorAlert(withTitle title: String = "Unexpected Failure", message: String) {
    let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
    self.present(alertController, animated: true)
}

fileprivate func presentError(_ error: NSError) {
    self.presentErrorAlert(withTitle: "Failed with error \(error.code)", message: error.localizedDescription)
}

// MARK: Helper Methods for Handling Device Orientation & EXIF

fileprivate func radiansForDegrees(_ degrees: CGFloat) -> CGFloat {
    return CGFloat(Double(degrees) * Double.pi / 180.0)
}

func exifOrientationForDeviceOrientation(_ deviceOrientation: UIDeviceOrientation) -> CGImagePropertyOrientation {

    switch deviceOrientation {
    case .portraitUpsideDown:
        return .rightMirrored

    case .landscapeLeft:
        return .downMirrored

    case .landscapeRight:
        return .upMirrored

    default:
        return .leftMirrored
    }
}

func exifOrientationForCurrentDeviceOrientation() -> CGImagePropertyOrientation {
    return exifOrientationForDeviceOrientation(UIDevice.current.orientation)
}

// MARK: Performing Vision Requests

/// - Tag: WriteCompletionHandler
fileprivate func prepareVisionRequest() {

    //self.trackingRequests = []
    var requests = [VNTrackObjectRequest]()

    let faceDetectionRequest = VNDetectFaceRectanglesRequest(completionHandler: { (request, error) in

        if error != nil {
            print("FaceDetection error: \(String(describing: error)).")
        }

        guard let faceDetectionRequest = request as? VNDetectFaceRectanglesRequest,
            let results = faceDetectionRequest.results as? [VNFaceObservation] else {
                return
        }
        DispatchQueue.main.async {
            // Add the observations to the tracking list
            for observation in results {
                let faceTrackingRequest = VNTrackObjectRequest(detectedObjectObservation: observation)
                requests.append(faceTrackingRequest)
            }
            self.trackingRequests = requests
        }
    })

    // Start with detection.  Find face, then track it.
    self.detectionRequests = [faceDetectionRequest]

    self.sequenceRequestHandler = VNSequenceRequestHandler()

    self.setupVisionDrawingLayers()
}

// MARK: Drawing Vision Observations

fileprivate func setupVisionDrawingLayers() {
    let captureDeviceResolution = self.captureDeviceResolution

    let captureDeviceBounds = CGRect(x: 0,
                                     y: 0,
                                     width: captureDeviceResolution.width,
                                     height: captureDeviceResolution.height)


    let captureDeviceBoundsCenterPoint = CGPoint(x: captureDeviceBounds.midX,
                                                 y: captureDeviceBounds.midY)

    let normalizedCenterPoint = CGPoint(x: 0.5, y: 0.5)

    guard let rootLayer = self.rootLayer else {
        self.presentErrorAlert(message: "view was not property initialized")
        return
    }

    let overlayLayer = CALayer()
    overlayLayer.name = "DetectionOverlay"
    overlayLayer.masksToBounds = true
    overlayLayer.anchorPoint = normalizedCenterPoint
    overlayLayer.bounds = captureDeviceBounds
    overlayLayer.position = CGPoint(x: rootLayer.bounds.midX, y: rootLayer.bounds.midY)

    let faceRectangleShapeLayer = CAShapeLayer()
    faceRectangleShapeLayer.name = "RectangleOutlineLayer"
    faceRectangleShapeLayer.bounds = captureDeviceBounds
    faceRectangleShapeLayer.anchorPoint = normalizedCenterPoint
    faceRectangleShapeLayer.position = captureDeviceBoundsCenterPoint
    faceRectangleShapeLayer.fillColor = nil
    faceRectangleShapeLayer.strokeColor = UIColor.green.withAlphaComponent(0.7).cgColor
    faceRectangleShapeLayer.lineWidth = 5
    faceRectangleShapeLayer.shadowOpacity = 0.7
    faceRectangleShapeLayer.shadowRadius = 5

    let faceLandmarksShapeLayer = CAShapeLayer()
    faceLandmarksShapeLayer.name = "FaceLandmarksLayer"
    faceLandmarksShapeLayer.bounds = captureDeviceBounds
    faceLandmarksShapeLayer.anchorPoint = normalizedCenterPoint
    faceLandmarksShapeLayer.position = captureDeviceBoundsCenterPoint
    faceLandmarksShapeLayer.fillColor = nil
    faceLandmarksShapeLayer.strokeColor = UIColor.yellow.withAlphaComponent(0.7).cgColor
    faceLandmarksShapeLayer.lineWidth = 3
    faceLandmarksShapeLayer.shadowOpacity = 0.7
    faceLandmarksShapeLayer.shadowRadius = 5

    overlayLayer.addSublayer(faceRectangleShapeLayer)
    faceRectangleShapeLayer.addSublayer(faceLandmarksShapeLayer)
    rootLayer.addSublayer(overlayLayer)

    self.detectionOverlayLayer = overlayLayer
    self.detectedFaceRectangleShapeLayer = faceRectangleShapeLayer
    self.detectedFaceLandmarksShapeLayer = faceLandmarksShapeLayer

    self.updateLayerGeometry()
}

fileprivate func updateLayerGeometry() {
    guard let overlayLayer = self.detectionOverlayLayer,
        let rootLayer = self.rootLayer,
        let previewLayer = self.previewLayer
        else {
        return
    }

    CATransaction.setValue(NSNumber(value: true), forKey: kCATransactionDisableActions)

    let videoPreviewRect = previewLayer.layerRectConverted(fromMetadataOutputRect: CGRect(x: 0, y: 0, width: 1, height: 1))

    var rotation: CGFloat
    var scaleX: CGFloat
    var scaleY: CGFloat

    // Rotate the layer into screen orientation.
    switch UIDevice.current.orientation {
    case .portraitUpsideDown:
        rotation = 180
        scaleX = videoPreviewRect.width / captureDeviceResolution.width
        scaleY = videoPreviewRect.height / captureDeviceResolution.height

    case .landscapeLeft:
        rotation = 90
        scaleX = videoPreviewRect.height / captureDeviceResolution.width
        scaleY = scaleX

    case .landscapeRight:
        rotation = -90
        scaleX = videoPreviewRect.height / captureDeviceResolution.width
        scaleY = scaleX

    default:
        rotation = 0
        scaleX = videoPreviewRect.width / captureDeviceResolution.width
        scaleY = videoPreviewRect.height / captureDeviceResolution.height
    }

    // Scale and mirror the image to ensure upright presentation.
    let affineTransform = CGAffineTransform(rotationAngle: radiansForDegrees(rotation))
        .scaledBy(x: scaleX, y: -scaleY)
    overlayLayer.setAffineTransform(affineTransform)

    // Cover entire screen UI.
    let rootLayerBounds = rootLayer.bounds
    overlayLayer.position = CGPoint(x: rootLayerBounds.midX, y: rootLayerBounds.midY)
}

fileprivate func addPoints(in landmarkRegion: VNFaceLandmarkRegion2D, to path: CGMutablePath, applying affineTransform: CGAffineTransform, closingWhenComplete closePath: Bool) {
    let pointCount = landmarkRegion.pointCount
    if pointCount > 1 {
        let points: [CGPoint] = landmarkRegion.normalizedPoints
        path.move(to: points[0], transform: affineTransform)
        path.addLines(between: points, transform: affineTransform)
        if closePath {
            path.addLine(to: points[0], transform: affineTransform)
            path.closeSubpath()
        }
    }
}

fileprivate func addIndicators(to faceRectanglePath: CGMutablePath, faceLandmarksPath: CGMutablePath, for faceObservation: VNFaceObservation) {
    let displaySize = self.captureDeviceResolution

    let faceBounds = VNImageRectForNormalizedRect(faceObservation.boundingBox, Int(displaySize.width), Int(displaySize.height))
    faceRectanglePath.addRect(faceBounds)

    if let landmarks = faceObservation.landmarks {
        // Landmarks are relative to -- and normalized within --- face bounds
        let affineTransform = CGAffineTransform(translationX: faceBounds.origin.x, y: faceBounds.origin.y)
            .scaledBy(x: faceBounds.size.width, y: faceBounds.size.height)

        // Treat eyebrows and lines as open-ended regions when drawing paths.
        let openLandmarkRegions: [VNFaceLandmarkRegion2D?] = [
            landmarks.leftEyebrow,
            landmarks.rightEyebrow,
            landmarks.faceContour,
            landmarks.noseCrest,
            landmarks.medianLine
        ]
        for openLandmarkRegion in openLandmarkRegions where openLandmarkRegion != nil {
            self.addPoints(in: openLandmarkRegion!, to: faceLandmarksPath, applying: affineTransform, closingWhenComplete: false)
        }

        // Draw eyes, lips, and nose as closed regions.
        let closedLandmarkRegions: [VNFaceLandmarkRegion2D?] = [
            landmarks.leftEye,
            landmarks.rightEye,
            landmarks.outerLips,
            landmarks.innerLips,
            landmarks.nose
        ]
        for closedLandmarkRegion in closedLandmarkRegions where closedLandmarkRegion != nil {
            self.addPoints(in: closedLandmarkRegion!, to: faceLandmarksPath, applying: affineTransform, closingWhenComplete: true)
        }
    }
}

/// - Tag: DrawPaths
fileprivate func drawFaceObservations(_ faceObservations: [VNFaceObservation]) {
    guard let faceRectangleShapeLayer = self.detectedFaceRectangleShapeLayer,
        let faceLandmarksShapeLayer = self.detectedFaceLandmarksShapeLayer
        else {
        return
    }

    CATransaction.begin()

    CATransaction.setValue(NSNumber(value: true), forKey: kCATransactionDisableActions)

    let faceRectanglePath = CGMutablePath()
    let faceLandmarksPath = CGMutablePath()

    for faceObservation in faceObservations {
        self.addIndicators(to: faceRectanglePath,
                           faceLandmarksPath: faceLandmarksPath,
                           for: faceObservation)
    }

    faceRectangleShapeLayer.path = faceRectanglePath
    faceLandmarksShapeLayer.path = faceLandmarksPath

    self.updateLayerGeometry()

    CATransaction.commit()
}

// MARK: AVCaptureVideoDataOutputSampleBufferDelegate
/// - Tag: PerformRequests
// Handle delegate method callback on receiving a sample buffer.
public func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {

    var requestHandlerOptions: [VNImageOption: AnyObject] = [:]

    let cameraIntrinsicData = CMGetAttachment(sampleBuffer, key: kCMSampleBufferAttachmentKey_CameraIntrinsicMatrix, attachmentModeOut: nil)
    if cameraIntrinsicData != nil {
        requestHandlerOptions[VNImageOption.cameraIntrinsics] = cameraIntrinsicData
    }

    guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
        print("Failed to obtain a CVPixelBuffer for the current output frame.")
        return
    }

    let exifOrientation = self.exifOrientationForCurrentDeviceOrientation()

    guard let requests = self.trackingRequests, !requests.isEmpty else {
        // No tracking object detected, so perform initial detection
        let imageRequestHandler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer,
                                                        orientation: exifOrientation,
                                                        options: requestHandlerOptions)

        do {
            guard let detectRequests = self.detectionRequests else {
                return
            }
            try imageRequestHandler.perform(detectRequests)
        } catch let error as NSError {
            NSLog("Failed to perform FaceRectangleRequest: %@", error)
        }
        return
    }

    do {
        try self.sequenceRequestHandler.perform(requests,
                                                 on: pixelBuffer,
                                                 orientation: exifOrientation)
    } catch let error as NSError {
        NSLog("Failed to perform SequenceRequest: %@", error)
    }

    // Setup the next round of tracking.
    var newTrackingRequests = [VNTrackObjectRequest]()
    for trackingRequest in requests {

        guard let results = trackingRequest.results else {
            return
        }

        guard let observation = results[0] as? VNDetectedObjectObservation else {
            return
        }

        if !trackingRequest.isLastFrame {
            if observation.confidence > 0.3 {
                trackingRequest.inputObservation = observation
            } else {
                trackingRequest.isLastFrame = true
            }
            newTrackingRequests.append(trackingRequest)
        }
    }
    self.trackingRequests = newTrackingRequests

    if newTrackingRequests.isEmpty {
        // Nothing to track, so abort.
        return
    }

    // Perform face landmark tracking on detected faces.
    var faceLandmarkRequests = [VNDetectFaceLandmarksRequest]()

    // Perform landmark detection on tracked faces.
    for trackingRequest in newTrackingRequests {

        let faceLandmarksRequest = VNDetectFaceLandmarksRequest(completionHandler: { (request, error) in

            if error != nil {
                print("FaceLandmarks error: \(String(describing: error)).")
            }

            guard let landmarksRequest = request as? VNDetectFaceLandmarksRequest,
                let results = landmarksRequest.results as? [VNFaceObservation] else {
                    return
            }

            // Perform all UI updates (drawing) on the main queue, not the background queue on which this handler is being called.
            DispatchQueue.main.async {
                self.drawFaceObservations(results)
            }
        })

        guard let trackingResults = trackingRequest.results else {
            return
        }

        guard let observation = trackingResults[0] as? VNDetectedObjectObservation else {
            return
        }
        let faceObservation = VNFaceObservation(boundingBox: observation.boundingBox)
        faceLandmarksRequest.inputFaceObservations = [faceObservation]

        // Continue to track detected facial landmarks.
        faceLandmarkRequests.append(faceLandmarksRequest)

          let imageRequestHandler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer,
                                                        orientation: exifOrientation,
                                                        options: requestHandlerOptions)

          do {
              try imageRequestHandler.perform(faceLandmarkRequests)
          } catch let error as NSError {
              NSLog("Failed to perform FaceLandmarkRequest: %@", error)
         }
      }
  }

}

1 Ответ

0 голосов
/ 24 февраля 2020

Если вы хотите сохранить только эту прямоугольную angular часть изображения, зная значение прямоугольной angular кадра (CGRect), вы можете обрезать исходное изображение, передав требуемое значение кадра (CGRect).

let rectFrame = CGRect()
let image = UIImage()
image.cgImage?.cropping(to: rectFrame)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...