2016-12-28 3 views
35

Comment puis-je effectuer une détection de visage en temps réel comme le fait la caméra?Détection de visage avec caméra

enter image description here

je remarquai que AVCaptureStillImageOutput est dépréciée après 10,0, donc j'utiliser AVCapturePhotoOutput à la place. Cependant, j'ai trouvé que l'image que j'ai enregistrée pour la détection faciale n'est pas si satisfaite? Des idées?


MISE À JOUR

Après avoir essayer de @Shravya Boggarapu mentionné. Actuellement, j'utilise AVCaptureMetadataOutput pour détecter le visage sans CIFaceDetector. Cela fonctionne comme prévu. Cependant, quand j'essaye de dessiner des limites du visage, il semble être dérangé. Une idée?

enter image description here

let metaDataOutput = AVCaptureMetadataOutput() 

captureSession.sessionPreset = AVCaptureSessionPresetPhoto 
    let backCamera = AVCaptureDevice.defaultDevice(withDeviceType: .builtInWideAngleCamera, mediaType: AVMediaTypeVideo, position: .back) 
    do { 
     let input = try AVCaptureDeviceInput(device: backCamera) 

     if (captureSession.canAddInput(input)) { 
      captureSession.addInput(input) 

      // MetadataOutput instead 
      if(captureSession.canAddOutput(metaDataOutput)) { 
       captureSession.addOutput(metaDataOutput) 

       metaDataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main) 
       metaDataOutput.metadataObjectTypes = [AVMetadataObjectTypeFace] 

       previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) 
       previewLayer?.frame = cameraView.bounds 
       previewLayer?.videoGravity = AVLayerVideoGravityResizeAspectFill 

       cameraView.layer.addSublayer(previewLayer!) 
       captureSession.startRunning() 
      } 

     } 

    } catch { 
     print(error.localizedDescription) 
    } 

et

extension CameraViewController: AVCaptureMetadataOutputObjectsDelegate { 
func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!) { 
    if findFaceControl { 
     findFaceControl = false 
     for metadataObject in metadataObjects { 
      if (metadataObject as AnyObject).type == AVMetadataObjectTypeFace { 
       print("") 
       print(metadataObject) 
       let bounds = (metadataObject as! AVMetadataFaceObject).bounds 
       print("origin x: \(bounds.origin.x)") 
       print("origin y: \(bounds.origin.y)") 
       print("size width: \(bounds.size.width)") 
       print("size height: \(bounds.size.height)") 
       print("cameraView width: \(self.cameraView.frame.width)") 
       print("cameraView height: \(self.cameraView.frame.height)") 
       var face = CGRect() 
       face.origin.x = bounds.origin.x * self.cameraView.frame.width 
       face.origin.y = bounds.origin.y * self.cameraView.frame.height 
       face.size.width = bounds.size.width * self.cameraView.frame.width 
       face.size.height = bounds.size.height * self.cameraView.frame.height 
       print(face) 

       showBounds(at: face) 
      } 
     } 
    } 

} 
} 

originale

see in Github

var captureSession = AVCaptureSession() 
var photoOutput = AVCapturePhotoOutput() 
var previewLayer: AVCaptureVideoPreviewLayer?  

override func viewWillAppear(_ animated: Bool) { 
    super.viewWillAppear(true) 

    captureSession.sessionPreset = AVCaptureSessionPresetHigh 

    let backCamera = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo) 
    do { 
     let input = try AVCaptureDeviceInput(device: backCamera) 

     if (captureSession.canAddInput(input)) { 
      captureSession.addInput(input) 

      if(captureSession.canAddOutput(photoOutput)){ 
       captureSession.addOutput(photoOutput) 
       captureSession.startRunning() 

       previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) 
       previewLayer?.videoGravity = AVLayerVideoGravityResizeAspectFill 
       previewLayer?.frame = cameraView.bounds 

       cameraView.layer.addSublayer(previewLayer!) 
      } 
     } 

    } catch { 
     print(error.localizedDescription) 
    } 

} 

func captureImage() { 
    let settings = AVCapturePhotoSettings() 
    let previewPixelType = settings.availablePreviewPhotoPixelFormatTypes.first! 
    let previewFormat = [kCVPixelBufferPixelFormatTypeKey as String: previewPixelType 
         ] 
    settings.previewPhotoFormat = previewFormat 
    photoOutput.capturePhoto(with: settings, delegate: self) 

} 



func capture(_ captureOutput: AVCapturePhotoOutput, didFinishProcessingPhotoSampleBuffer photoSampleBuffer: CMSampleBuffer?, previewPhotoSampleBuffer: CMSampleBuffer?, resolvedSettings: AVCaptureResolvedPhotoSettings, bracketSettings: AVCaptureBracketedStillImageSettings?, error: Error?) { 
    if let error = error { 
     print(error.localizedDescription) 
    } 
    // Not include previewPhotoSampleBuffer 
    if let sampleBuffer = photoSampleBuffer, 
     let dataImage = AVCapturePhotoOutput.jpegPhotoDataRepresentation(forJPEGSampleBuffer: sampleBuffer, previewPhotoSampleBuffer: nil) { 
      self.imageView.image = UIImage(data: dataImage) 
      self.imageView.isHidden = false 
      self.previewLayer?.isHidden = true 
      self.findFace(img: self.imageView.image!) 
     } 
} 

Le findFace fonctionne avec l'image normale. Cependant, l'image que je capture par caméra ne fonctionnera pas ou reconnaîtra parfois seulement un visage.

image normale

enter image description here

capture l'image

enter image description here

func findFace(img: UIImage) { 
    guard let faceImage = CIImage(image: img) else { return } 
    let accuracy = [CIDetectorAccuracy: CIDetectorAccuracyHigh] 
    let faceDetector = CIDetector(ofType: CIDetectorTypeFace, context: nil, options: accuracy) 


    // For converting the Core Image Coordinates to UIView Coordinates 
    let detectedImageSize = faceImage.extent.size 
    var transform = CGAffineTransform(scaleX: 1, y: -1) 
    transform = transform.translatedBy(x: 0, y: -detectedImageSize.height) 


    if let faces = faceDetector?.features(in: faceImage, options: [CIDetectorSmile: true, CIDetectorEyeBlink: true]) { 
     for face in faces as! [CIFaceFeature] { 

      // Apply the transform to convert the coordinates 
      var faceViewBounds = face.bounds.applying(transform) 
      // Calculate the actual position and size of the rectangle in the image view 
      let viewSize = imageView.bounds.size 
      let scale = min(viewSize.width/detectedImageSize.width, 
          viewSize.height/detectedImageSize.height) 
      let offsetX = (viewSize.width - detectedImageSize.width * scale)/2 
      let offsetY = (viewSize.height - detectedImageSize.height * scale)/2 

      faceViewBounds = faceViewBounds.applying(CGAffineTransform(scaleX: scale, y: scale)) 
      print("faceBounds = \(faceViewBounds)") 
      faceViewBounds.origin.x += offsetX 
      faceViewBounds.origin.y += offsetY 

      showBounds(at: faceViewBounds) 
     } 

     if faces.count != 0 { 
      print("Number of faces: \(faces.count)") 
     } else { 
      print("No faces ") 
     } 
    } 


} 

func showBounds(at bounds: CGRect) { 
    let indicator = UIView(frame: bounds) 
    indicator.frame = bounds 
    indicator.layer.borderWidth = 3 
    indicator.layer.borderColor = UIColor.red.cgColor 
    indicator.backgroundColor = .clear 

    self.imageView.addSubview(indicator) 
    faceBoxes.append(indicator) 

} 
+0

vous devriez utiliser 'CIDetector' pour détecter le visage. – aircraft

+0

Voici un lien qui présente un exemple d'utilisation de la détection de visage Core Image à partir d'un flux vidéo en direct. C'est iOS 5 jours, donc c'est évidemment daté et en Objective-C, mais si vous avez déjà travaillé avec CI, vous pouvez probablement le traduire. http://www.icapps.com/face-detection-with-core-image-on-live-video/. Désolé, appuyez sur retour sans réaliser que cela équivaut à une modification. Voici un deuxième lien pour vous aider à utiliser Swift 2 et appliquer des filtres CI à un flux de caméra: http://flexmonkey.blogspot.com/2015/07/applying-cifilters-to-live-camera-feed.html?q=camera – dfd

+0

Utilisez cet exemple de [ici] (https://github.com/shinobicontrols/iOS8-day-by-day/blob/master/13-coreimage-detectors/13-coreimage-detectors.md). Cet exemple a une détection en direct pour les rectangles/carrés et les codes qr, mais vous pouvez facilement modifier cela pour détecter les visages. Vous pouvez utiliser cet exemple pour changer les superpositions et toutes sortes d'autres choses, c'est très personnalisable. Espérons que cela aide: D – Munib

Répondre

9

Il y a 2 façons d'aller sur la détection de visages: l'un est CIFaceDetector et l'autre est AVCaptureMetadataOutput

En fonction de vos besoins, choisissez ce qui est pertinent pour vous.

CIFaceDetector a plus Caractéristiques- Par exemple: Vous donne lieu des yeux et de la bouche, détecteur de sourire, etc

D'autre part, AVCaptureMetadataOutput est calculée sur les cadres et les visages détectés sont contrôlés et il n'y a pas de code supplémentaire besoin d'être ajouté par nous. Je trouve qu'en raison des visages de suivi sont détectés de manière plus fiable dans ce processus. Le con de ceci est que vous détecterez simplement des visages, aucune position des yeux/bouche. Un autre avantage de cette méthode est que les problèmes d'orientation sont moindres comme vous pouvez videoOrientation chaque fois que l'orientation du périphérique est modifiée et que l'orientation des faces est relative à cette orientation

Dans mon cas, mon application utilise YUV420 comme format requis donc utiliser CIDetector (qui fonctionne avec RGB) en temps réel n'était pas viable. L'utilisation de AVCaptureMetadataOutput a permis d'économiser beaucoup d'efforts et d'effectuer de manière plus fiable grâce au suivi continu. Une fois que j'ai eu la boîte de délimitation pour les visages, j'ai codé des fonctionnalités supplémentaires, telles que la détection de peau et l'a appliqué sur l'image fixe. Remarque: Lorsque vous capturez une image fixe, les informations de la boîte de visages sont ajoutées avec les métadonnées, donc pas de problèmes de synchronisation.

Vous pouvez également utiliser une combinaison des deux pour obtenir de meilleurs résultats. Examinez et évaluez les avantages et les inconvénients selon votre demande.

MISE À JOUR

rectangle visage est l'origine de l'image WRT. Donc pour l'écran, ça peut être différent. Utilisez les éléments suivants:

for (AVMetadataFaceObject *faceFeatures in metadataObjects) { 
    CGRect face = faceFeatures.bounds; 
    CGRect facePreviewBounds = CGRectMake(face.origin.y * previewLayerRect.size.width, 
           face.origin.x * previewLayerRect.size.height, 
           face.size.width * previewLayerRect.size.height, 
           face.size.height * previewLayerRect.size.width); 

    /* Draw rectangle facePreviewBounds on screen */ 
} 
+1

J'ai mis le 'metadataObjectTypes' à' [AVMetadataObjectTypeFace] '. De plus, 'didOutputMetadataObjects' sera appelé après les faces trouvées. Cependant, comment puis-je dessiner un rectangle sur l'écran? – WeiJay

+0

Dans [iOS7-day-by-day] (https://github.com/shinobicontrols/iOS7-day-by-day/blob/master/18-coreimage-features/SimleyFace/SimleyFace/SCViewController.m), il détecte les visages en utilisant 'AVCaptureMetadataOutput', et utilise 'CIFaceDetector' après avec 'AVCaptureStillImageOutput' – WeiJay

+0

Ma question est que même si je détecte les visages par 'AVCaptureMetadataOutput', quand je le capture avec 'AVCapturePhotoOutput', alors je veux dessiner l'emplacement du visage avec le rectangle utiliser 'CIFaceDetector', le CIFaceDetector n'a pas fonctionné comme prévu. – WeiJay

0

En regardant votre code, je détecté 2 choses qui pourraient conduire à une mauvaise/pauvre visage détection.

  1. L'un d'eux est les caractéristiques du détecteur de visage options où vous filtrez les résultats par [CIDetectorSmile: true, CIDetectorEyeBlink: true]. Essayez de le définir à zéro: faceDetector?.features(in: faceImage, options: nil)
  2. Une autre supposition que j'ai est le résultat orientation de l'image. J'ai remarqué que vous utilisez AVCapturePhotoOutput.jpegPhotoDataRepresentation méthode pour générer l'image source pour la détection et le système, par défaut, il génère cette image avec une orientation spécifique, de type /LandscapeLeft, je pense. Donc, fondamentalement, vous pouvez dire au détecteur de visage d'avoir cela à l'esprit en utilisant la clé CIDetectorImageOrientation.

CIDetectorImageOrientation: la valeur de cette clé est un nombre entier de NSNumber 1..8 telle que celle trouvée dans kCGImagePropertyOrientation. S'il est présent, la détection sera effectuée en fonction de cette orientation, mais les coordonnées des entités renvoyées seront toujours basées sur celles de l'image.

Essayez de définir comme faceDetector?.features(in: faceImage, options: [CIDetectorImageOrientation: 8 /*Left, bottom*/]).

+0

Je ne pense pas que '[CIDetectorSmile: true, CIDetectorEyeBlink: true]' est un filtre. Il indique au détecteur de passer plus de temps afin qu'il soit capable de renvoyer des informations spécifiées. Donc, il étend réellement les résultats –

0
  1. Créer CaptureSession
  2. Pour AVCaptureVideoDataOutput créer des paramètres suivants

    output.videoSettings = [kCVPixelBufferPixelFormatTypeKey comme AnyHashable: Int (kCMPixelFormat_32BGRA)]

3.Lorsque vous recevez CMSampleBuffer , créer l'image

DispatchQueue.main.async { 
    let sampleImg = self.imageFromSampleBuffer(sampleBuffer: sampleBuffer) 
    self.imageView.image = sampleImg 
} 
func imageFromSampleBuffer(sampleBuffer : CMSampleBuffer) -> UIImage 
    { 
     // Get a CMSampleBuffer's Core Video image buffer for the media data 
     let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); 
     // Lock the base address of the pixel buffer 
     CVPixelBufferLockBaseAddress(imageBuffer!, CVPixelBufferLockFlags.readOnly); 


     // Get the number of bytes per row for the pixel buffer 
     let baseAddress = CVPixelBufferGetBaseAddress(imageBuffer!); 

     // Get the number of bytes per row for the pixel buffer 
     let bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer!); 
     // Get the pixel buffer width and height 
     let width = CVPixelBufferGetWidth(imageBuffer!); 
     let height = CVPixelBufferGetHeight(imageBuffer!); 

     // Create a device-dependent RGB color space 
     let colorSpace = CGColorSpaceCreateDeviceRGB(); 

     // Create a bitmap graphics context with the sample buffer data 
     var bitmapInfo: UInt32 = CGBitmapInfo.byteOrder32Little.rawValue 
     bitmapInfo |= CGImageAlphaInfo.premultipliedFirst.rawValue & CGBitmapInfo.alphaInfoMask.rawValue 
     //let bitmapInfo: UInt32 = CGBitmapInfo.alphaInfoMask.rawValue 
     let context = CGContext.init(data: baseAddress, width: width, height: height, bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo) 
     // Create a Quartz image from the pixel data in the bitmap graphics context 
     let quartzImage = context?.makeImage(); 
     // Unlock the pixel buffer 
     CVPixelBufferUnlockBaseAddress(imageBuffer!, CVPixelBufferLockFlags.readOnly); 

     // Create an image object from the Quartz image 
     let image = UIImage.init(cgImage: quartzImage!); 

     return (image); 
    } 
5

Pour effectuer la détection de visage sur iOS, il y a soit API CIDetector (Apple) ou Mobile Vision (Google).

IMO, Google Mobile Vision offre de meilleures performances.

Si vous êtes intéressé, here is the project you can play with. (iOS 10.2, Swift 3)


Après la WWDC 2017, Apple lance CoreML dans iOS 11. Le Vision cadre rend la détection des visages plus précis :)

J'ai fait un Demo Project. contenant Vision v.s. CIDetector. En outre, il contient la détection des points de repère de face en temps réel.

+0

Merci Wei! J'ai fourchu et mis à jour votre projet pour détecter également les points de repère de visage: https://github.com/wanderingstan/AppleFaceDetection –

+0

Le cadre de New Vision ne fournit pas la détection de sourire et d'yeux ouverts –

0
extension CameraViewController: AVCaptureMetadataOutputObjectsDelegate { 
    func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!) { 
    if findFaceControl { 
     findFaceControl = false 
     let faces = metadata.flatMap { $0 as? AVMetadataFaceObject } .flatMap { (face) -> CGRect in 
        guard let localizedFace = 
     previewLayer?.transformedMetadataObject(for: face) else { return nil } 
        return localizedFace.bounds } 
     for face in faces { 
     let temp = UIView(frame: face) 
     temp.layer.borderColor = UIColor.white 
     temp.layer.borderWidth = 2.0 
     view.addSubview(view: temp) 
     } 
    } 
    } 
} 

Veillez à supprimer les vues créées par didOutputMetadataObjects.

Garder la trace des ids visage actifs est la meilleure façon de le faire^

Aussi quand vous essayez de trouver l'emplacement des visages pour votre couche de prévisualisation, il est beaucoup plus facile d'utiliser les données du visage et transformer . Aussi, je pense que CIDetector est indésirable, metadataoutput utilisera des trucs matériels pour la détection de visages, ce qui rendra le jeu très rapide.