it-swarm.com.de

Gesichtserkennung mit Kamera

Wie kann ich die Gesichtserkennung in Echtzeit genau wie bei "Kamera" durchführen?

enter image description here

Ich habe festgestellt, dass AVCaptureStillImageOutput nach 10.0 veraltet ist, und verwende stattdessen AVCapturePhotoOutput . Ich habe jedoch festgestellt, dass das Bild, das ich für die Gesichtserkennung gespeichert habe, nicht so zufrieden ist. Irgendwelche Ideen?


[~ # ~] Update [~ # ~]

Nach einem Versuch von @Shravya Boggarapu erwähnt. Derzeit benutze ich AVCaptureMetadataOutput, um das Gesicht ohne CIFaceDetector zu erkennen. Es funktioniert wie erwartet. Wenn ich jedoch versuche, Grenzen des Gesichts zu ziehen, scheint es falsch positioniert zu sein. Irgendeine Idee?

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

und

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

siehe 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!)
        }
}

Das findFace funktioniert mit normalen Bildern. Das mit der Kamera aufgenommene Bild funktioniert jedoch nicht oder erkennt manchmal nur ein Gesicht.

Normales Bild

enter image description here

Bild aufnehmen

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

Es gibt zwei Möglichkeiten, Gesichter zu erkennen: Eine ist CIFaceDetector und die andere ist AVCaptureMetadataOutput

Wählen Sie je nach Ihren Anforderungen, was für Sie relevant ist.

CIFaceDetector verfügt über weitere Funktionen - z. B .: Gibt Ihnen die Position von Augen und Mund, den Lächeln - Detektor usw

Auf der anderen Seite wird AVCaptureMetadataOutput für die Frames berechnet und die erkannten Gesichter werden verfolgt. Es ist kein zusätzlicher Code erforderlich, der von uns hinzugefügt werden muss. Ich finde, dass durch die Spurführung Gesichter bei diesem Vorgang zuverlässiger erkannt werden. Der Nachteil dabei ist, dass Sie einfach Gesichter erkennen, keine Position von Augen/Mund. Ein weiterer Vorteil dieser Methode besteht darin, dass die Orientierungsprobleme geringer sind als bei VideoOrientation, wenn die Geräteorientierung geändert wird und die Ausrichtung der Gesichter relativ zu dieser Ausrichtung ist

In meinem Fall verwendet meine Anwendung YUV420 als erforderliches Format, sodass die Verwendung von CIDetector (das mit RGB funktioniert) in Echtzeit nicht möglich war. Die Verwendung von AVCaptureMetadataOutput sparte viel Mühe und wurde durchgeführt zuverlässiger aufgrund der kontinuierlichen Verfolgung.

Sobald ich den Begrenzungsrahmen für die Gesichter hatte, codierte ich zusätzliche Funktionen wie die Hauterkennung und wendete sie auf Standbilder an.

Hinweis: Wenn Sie ein Standbild aufnehmen, werden die Gesichtsfeldinformationen zusammen mit den Metadaten hinzugefügt, sodass keine Synchronisierungsprobleme auftreten.

Sie können auch eine Kombination aus beiden verwenden, um bessere Ergebnisse zu erzielen.

Erforschen und bewerten Sie die Vor- und Nachteile gemäß Ihrer Anwendung.

UPDATE

Das Gesichtsrechteck ist in Bildursprung. Für den Bildschirm kann es also anders sein. Verwenden Sie Folgendes:

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

Für die Gesichtserkennung unter iOS gibt es entweder CIDetector (Apple) oder Mobile Vision (Google) API.

IMO bietet Google Mobile Vision eine bessere Leistung.

Wenn Sie interessiert sind, hier ist das Projekt, mit dem Sie spielen können. (iOS 10.2, Swift 3)


Nach dem WWDC 2017 führt Apple CoreML in iOS 11 ein. Das Vision Framework macht die Gesichtserkennung genauer :)

Ich habe ein Demo-Projekt gemacht . mit Vision v.s. CIDetector . Außerdem enthält es die Erkennung von Gesichtsmarkierungen in Echtzeit.

6
Willjay

Ein bisschen spät, aber hier ist es die Lösung für das Koordinatenproblem. Es gibt eine Methode, die Sie auf der Vorschauebene aufrufen können, um das Metadatenobjekt in Ihr Koordinatensystem umzuwandeln: transformedMetadataObject (für: metadataObject).

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

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

Übrigens, falls Sie Ihr Projekt verwenden (oder aktualisieren auf) Swift 4, die Delegate-Methode von AVCaptureMetadataOutputsObject hat sich geändert in:

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

Mit freundlichen Grüßen

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

Stellen Sie sicher, dass Sie die von didOutputMetadataObjects erstellten Ansichten entfernen.

Das Verfolgen der aktiven Gesichtserkennung ist der beste Weg, dies zu tun

Auch wenn Sie versuchen, die Position von Gesichtern für Ihre Vorschauebene zu ermitteln, ist es viel einfacher, Gesichtsdaten zu verwenden und zu transformieren. Ich denke auch, dass CIDetector Junk ist, Metadatenausgabe wird Hardware-Zeug für die Gesichtserkennung verwenden, was es wirklich schnell macht.

2
jnblanchard
  1. Erstelle CaptureSession
  2. Erstellen Sie für AVCaptureVideoDataOutput die folgenden Einstellungen

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

3.Wenn Sie CMSampleBuffer erhalten, erstellen Sie ein 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

Beim Betrachten Ihres Codes habe ich zwei Dinge entdeckt, die zu einer falschen/schlechten Gesichtserkennung führen könnten.

  1. Eine davon ist die Gesichtserkennungsfunktion, bei der Sie die Ergebnisse nach [CIDetectorSmile: true, CIDetectorEyeBlink: true] filtern. Versuchen Sie es auf Null zu setzen: faceDetector?.features(in: faceImage, options: nil)
  2. Eine weitere Vermutung, die ich habe, ist das Ergebnis der Bildorientierung . Ich habe festgestellt, dass Sie die Methode AVCapturePhotoOutput.jpegPhotoDataRepresentation Verwenden, um das Quellbild für die Erkennung zu generieren, und dass das System dieses Bild standardmäßig mit einer bestimmten Ausrichtung vom Typ Left/LandscapeLeft generiert. , Ich glaube. Im Grunde können Sie den Gesichtsdetektor anweisen, dies zu berücksichtigen, indem Sie die Taste CIDetectorImageOrientation verwenden.

CIDetectorImageOrientation : Der Wert für diesen Schlüssel ist eine Ganzzahl NSNumber von 1..8, wie sie in kCGImagePropertyOrientation . Falls vorhanden, erfolgt die Erkennung anhand dieser Ausrichtung, die Koordinaten in den zurückgegebenen Features basieren jedoch weiterhin auf denen des Bildes.

Versuchen Sie, es wie faceDetector?.features(in: faceImage, options: [CIDetectorImageOrientation: 8 /*Left, bottom*/]) einzustellen.

0
ricardopereira