使用相机进行人脸检测

Posted

技术标签:

【中文标题】使用相机进行人脸检测【英文标题】:Face Detection with Camera 【发布时间】:2017-05-12 07:16:08 【问题描述】:

如何像“相机”一样实时进行人脸检测?

我注意到 AVCaptureStillImageOutput 在 10.0 之后已被弃用,所以我使用 AVCapturePhotoOutput 代替。但是,我发现我保存的用于面部检测的图像不是很满意?有任何想法吗?


更新

在尝试了提到的@Shravya Boggarapu 之后。目前,我使用AVCaptureMetadataOutput 来检测没有CIFaceDetector 的人脸。它按预期工作。但是,当我尝试绘制脸部边界时,它似乎定位错误。任何想法?

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)
    

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)
            
        
    




原创

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

findFace 适用于普通图像。但是,我通过相机拍摄的图像无法正常工作,或者有时只能识别一张脸。

普通图像

捕获图像

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)


【问题讨论】:

你应该使用CIDetector来检测人脸。 这是一个链接,其中包含一个使用来自实时视频源的 Core Image 人脸检测的示例。它来自 ios 5 天,所以它显然已经过时并且在 Objective-C 中,但如果你以前使用过 CI,你可能会翻译它。 icapps.com/face-detection-with-core-image-on-live-video。抱歉,点击回车没有意识到它等同于编辑。这是帮助使用 Swift 2 并将 CI 过滤器应用于相机源的第二个链接:flexmonkey.blogspot.com/2015/07/… 使用来自here的这个例子。此示例对矩形/正方形和二维码进行实时检测,但您可以轻松地对其进行调整以检测人脸。您也可以使用此示例更改叠加层和各种其他内容,它非常可定制。希望这会有所帮助:D 您通过使用options: [CIDetectorSmile: true, CIDetectorEyeBlink: true] 过滤结果来强迫您微笑和眨眼。那是你要的吗?这可能会导致检测人脸时结果不佳。 我已经设置了您提到的options: nil,但它仍然无法按预期工作 【参考方案1】:

检测人脸的方法有两种:CIFaceDetector 和 AVCaptureMetadataOutput。根据您的要求,选择与您相关的内容。

CIFaceDetector 有更多功能,它可以为您提供眼睛和嘴巴的位置、微笑检测器等。

另一方面,AVCaptureMetadataOutput 是在帧上计算的,检测到的人脸会被跟踪,我们不需要添加额外的代码。我发现,因为跟踪。在此过程中更可靠地检测到人脸。这样做的缺点是您只会检测面部,而不是眼睛或嘴巴的位置。 这种方法的另一个优点是方向问题更小,因为您可以在设备方向发生变化时使用videoOrientation,并且面的方向将相对于该方向。

就我而言,我的应用程序使用 YUV420 作为所需格式,因此实时使用 CIDetector(与 RGB 配合使用)是不可行的。由于连续跟踪,使用 AVCaptureMetadataOutput 节省了大量工作并更可靠地执行。

一旦我有了面部的边界框,我就编写了额外的功能,例如皮肤检测并将其应用到静止图像上。

注意:当您捕获静止图像时,面部框信息会与元数据一起添加,因此不会出现同步问题。

您也可以将两者结合使用以获得更好的结果。

根据您的应用探索和评估优缺点。


面部矩形是图像原点。因此,对于屏幕,它可能会有所不同。 使用:

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 */

【讨论】:

我将metadataObjectTypes 设置为[AVMetadataObjectTypeFace]。此外,didOutputMetadataObjects 将在找到人脸后调用。但是,如何在屏幕上绘制一个矩形? 在iOS7-day-by-day中,它使用AVCaptureMetadataOutput 检测人脸,然后使用CIFaceDetector AVCaptureStillImageOutput 我的问题是,即使我通过AVCaptureMetadataOutput 检测到人脸,当我用AVCapturePhotoOutput 捕获它时,我想用矩形使用CIFaceDetector 绘制人脸位置,CIFaceDetector 确实做到了没有按预期工作。 使用AVCaptureMetadataOutputObjectsDelegate协议中的delegate函数获取连续输出。使用它来连续显示面孔 当您最终捕获图像时,打印出 CMSampleBuffer 中的元数据(使用 CMSampleBufferGetSampleAttachmentsArray)。我的工作区使用较旧的 API,但我相信元数据不会因为版本的变化而改变。无论如何,我会尝试更新它以使用 iOS 10 API,然后让你知道【参考方案2】:

要在 iOS 上执行人脸检测,有 CIDetector (Apple) 或Mobile Vision (Google) API。

IMO,Google Mobile Vision 提供更好的性能。

如果你有兴趣,here is the project you can play with. (iOS 10.2, Swift 3)


在 WWDC 2017 之后,Apple 在 iOS 11 中引入了CoreML。 Vision 框架使人脸检测更加准确:)

我创建了一个Demo Project。包含 Vision 与CI检测器。此外,它还包含实时人脸地标检测。

【讨论】:

谢谢魏!我分叉并更新了您的项目以检测面部标志:github.com/wanderingstan/AppleFaceDetection 新视觉框架不提供微笑和睁眼检测【参考方案3】:

有点晚了,但这是坐标问题的解决方案。您可以在预览层上调用一种方法将元数据对象转换为您的坐标系:transformedMetadataObject(for: metadataObject)。

guard let transformedObject = previewLayer.transformedMetadataObject(for: metadataObject) else 
     continue

let bounds = transformedObject.bounds
showBounds(at: bounds)

来源:https://developer.apple.com/documentation/avfoundation/avcapturevideopreviewlayer/1623501-transformedmetadataobjectformeta

顺便说一句,如果你正在使用(或将你的项目升级到)Swift 4,AVCaptureMetadataOutputsObject 的委托方法已更改为:

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

亲切的问候

【讨论】:

【参考方案4】:
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)
      
    
  

请务必删除由 didOutputMetadataObjects 创建的视图。

跟踪活动的面部 ID 是执行此操作的最佳方法 ^

此外,当您尝试为预览图层查找人脸位置时,使用人脸数据和转换会更容易。另外我认为 CIDetector 是垃圾,元数据输出将使用硬件东西进行人脸检测,使其非常快。

【讨论】:

【参考方案5】:
    创建CaptureSession

    为 AVCaptureVideoDataOutput 创建以下设置

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

3.收到CMSampleBuffer后,创建图片

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

【讨论】:

【参考方案6】:

通过查看您的代码,我发现了 2 个可能导致错误/不良人脸检测的事情。

    其中之一是面部检测器功能选项,您可以在其中通过 [CIDetectorSmile: true, CIDetectorEyeBlink: true] 过滤结果。尝试将其设置为零:faceDetector?.features(in: faceImage, options: nil) 我的另一个猜测是结果图像方向。我注意到您使用AVCapturePhotoOutput.jpegPhotoDataRepresentation 方法生成用于检测的源图像,系统默认生成具有特定方向的图像,类型为Left/LandscapeLeft,我认为。因此,基本上您可以使用 CIDetectorImageOrientation 键告诉面部检测器记住这一点。

CIDetectorImageOrientation:此键的值是一个整数 NSNumber,来自 1..8,例如在 kCGImagePropertyOrientation 中找到的值。如果存在,将根据该方向进行检测,但返回特征中的坐标仍将基于图像的坐标。

尝试将其设置为 faceDetector?.features(in: faceImage, options: [CIDetectorImageOrientation: 8 /*Left, bottom*/])

【讨论】:

我不认为[CIDetectorSmile: true, CIDetectorEyeBlink: true] 是一个过滤器。它告诉检测器花费更多时间,以便能够返回指定的信息。所以它实际上扩展了结果

以上是关于使用相机进行人脸检测的主要内容,如果未能解决你的问题,请参考以下文章

使用Android相机进行人脸检测。?

如何从 cwac-cam2 获取相机实例进行人脸检测?

使用opencv和覆盆子相机模块进行人脸检测的最佳算法是啥

如何使用opencv从相机的所有位置检测带有android的人脸?

在实时人脸检测 Android 中的 Live CameraPreview 上围绕人脸绘制矩形

如何在 Windows 10 相机应用程序中执行实时人脸检测?