web-dev-qa-db-fra.com

Détection de visage avec caméra

Comment puis-je faire la détection de visages en temps réel comme le fait "Camera"?

enter image description here

J'ai remarqué que AVCaptureStillImageOutput est déconseillé après 10.0, donc j'utilise AVCapturePhotoOutput à la place . Cependant, j'ai trouvé que l'image que j'avais enregistrée pour la détection faciale n'était pas si satisfaisante? Des idées?


[~ # ~] mise à jour [~ # ~]

Après avoir donné un essai de @Shravya Boggarapu mentionné. Actuellement, j'utilise AVCaptureMetadataOutput pour détecter le visage sans CIFaceDetector. Cela fonctionne comme prévu. Cependant, lorsque j'essaie de dessiner des limites du visage, cela semble mal placé. 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)
            }
        }
    }

}
}

Original

voir dans 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!)
        }
}

findFace fonctionne avec une image normale. Cependant, l'image que je capture via l'appareil photo ne fonctionnera pas ou ne reconnaîtra parfois qu'un seul visage.

Image normale

enter image description here

Capture d'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)

}
39
Willjay

Il existe deux façons de détecter les visages: l’une est CIFaceDetector et l’autre AVCaptureMetadataOutput

Selon vos besoins, choisissez ce qui vous convient.

CIFaceDetector a plus de fonctionnalités - Par exemple: vous donne l'emplacement des yeux et de la bouche, un détecteur de sourire, etc.

D'un autre côté, AVCaptureMetadataOutput est calculé sur les images et les visages détectés sont suivis et il n'y a pas besoin de code supplémentaire à ajouter par nous. Je trouve qu'en raison du suivi des visages sont détectés de manière plus fiable dans ce processus. L'inconvénient est que vous détecterez simplement des visages, aucune position des yeux/de la bouche. Un autre avantage de cette méthode est que les problèmes d'orientation sont moindres car vous pouvez videoOrientation chaque fois que l'orientation du périphérique est modifiée et que l'orientation des faces sera relative à cette orientation.

Dans mon cas, mon application utilise YUV420 comme format requis, donc l'utilisation de CIDetector (qui fonctionne avec RGB) en temps réel n'était pas viable. L'utilisation d'AVCaptureMetadataOutput a permis d'économiser beaucoup d'efforts et de réaliser de manière plus fiable grâce au suivi continu.

Une fois que j'ai eu le cadre de délimitation pour les visages, j'ai codé des fonctionnalités supplémentaires, telles que la détection de la peau et l'ai appliqué sur l'image fixe.

Remarque: lorsque vous capturez une image fixe, les informations de la zone de visage sont ajoutées avec les métadonnées, donc aucun problème de synchronisation.

Vous pouvez également utiliser une combinaison des deux pour obtenir de meilleurs résultats.

Explorez et évaluez les avantages et les inconvénients selon votre application.

MISE À JOUR

Le rectangle du visage est l'image d'origine. Donc pour l'écran, ça peut être différent. Utilisez le suivant:

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 */
}
12
Shravya Boggarapu

Pour effectuer la détection des visages sur iOS, il existe soit CIDetector (Apple) ou Mobile Vision (Google) API.

IMO, Google Mobile Vision offre de meilleures performances.

Si vous êtes intéressé, voici le projet avec lequel vous pouvez jouer. (IOS 10.2, Swift 3)


Après la WWDC 2017, Apple introduit CoreML dans iOS 11. Le cadre Vision rend le détection de visage plus précise :)

J'ai fait un Projet de démonstration . contenant Vision v.s. CIDetector . En outre, il contient la détection des repères de visage en temps réel.

6
Willjay

Un peu en retard, mais ici c'est la solution au problème des coordonnées. Il existe une méthode que vous pouvez appeler sur la couche d'aperçu pour transformer l'objet de métadonnées en votre système de coordonnées: transformedMetadataObject (pour: metadataObject).

guard let transformedObject = previewLayer.transformedMetadataObject(for: metadataObject) else {
     continue
}
let bounds = transformedObject.bounds
showBounds(at: bounds)

Source: https://developer.Apple.com/documentation/avfoundation/avcapturevideopreviewlayer/1623501-transformedmetadataobjectformeta

Par ailleurs, dans le cas où vous utilisez (ou mettez à niveau votre projet vers) Swift 4, la méthode déléguée de AVCaptureMetadataOutputsObject a changé en:

func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection)

Sincères amitiés

2
Elena
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)
      }
    }
  }
}

Assurez-vous de supprimer les vues créées par didOutputMetadataObjects.

Garder une trace des identifiants faciaux actifs est la meilleure façon de le faire ^

De plus, lorsque vous essayez de trouver l'emplacement des visages pour votre couche d'aperçu, il est beaucoup plus facile d'utiliser les données faciales et de les transformer. De plus, je pense que CIDetector est indésirable, la sortie de métadonnées utilisera des éléments matériels pour la détection des visages, ce qui le rend très rapide.

2
jnblanchard
  1. Créer CaptureSession
  2. Pour AVCaptureVideoDataOutput, créez les paramètres suivants

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

3.Lorsque vous recevez CMSampleBuffer, créez une 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);
    }
1
duzvik

En regardant votre code, j'ai détecté 2 choses qui pourraient conduire à une détection de visage mauvaise/mauvaise.

  1. L'un d'eux est le détecteur de visage propose des options où vous filtrez les résultats par [CIDetectorSmile: true, CIDetectorEyeBlink: true]. Essayez de le mettre à zéro: faceDetector?.features(in: faceImage, options: nil)
  2. Une autre supposition que j'ai est le résultat l'orientation de l'image . J'ai remarqué que vous utilisez la méthode AVCapturePhotoOutput.jpegPhotoDataRepresentation 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 Left/LandscapeLeft , Je pense. Donc, en gros, vous pouvez dire au détecteur de visage de garder cela à l'esprit en utilisant la touche CIDetectorImageOrientation .

CIDetectorImageOrientation : la valeur de cette clé est un entier NSNumber de 1..8 tel que celui trouvé dans kCGImagePropertyOrientation . Si elle est présente, la détection sera effectuée sur la base de cette orientation mais les coordonnées dans les entités renvoyées seront toujours basées sur celles de l'image.

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

0
ricardopereira