Comment puis-je faire la détection de visages en temps réel comme le fait "Camera"?
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?
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
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
Capture d'image
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)
}
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 */
}
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.
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)
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
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.
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);
}
En regardant votre code, j'ai détecté 2 choses qui pourraient conduire à une détection de visage mauvaise/mauvaise.
[CIDetectorSmile: true, CIDetectorEyeBlink: true]
. Essayez de le mettre à zéro: faceDetector?.features(in: faceImage, options: nil)
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é danskCGImagePropertyOrientation
. 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*/])
.