web-dev-qa-db-ja.com

カメラによる顔検出

「カメラ」のようにリアルタイムで顔検出を行うにはどうすればよいですか?

enter image description here

AVCaptureStillImageOutputは10.0以降で非推奨になったため、代わりにAVCapturePhotoOutputを使用します。しかし、顔検出のために保存した画像はそれほど満足できないことがわかりましたか?何か案は?


[〜#〜] update [〜#〜]

@Shravya Boggarapuの試みを述べた後。現在、AVCaptureMetadataOutputを使用せずにCIFaceDetectorのない顔を検出しています。期待どおりに動作します。ただし、顔の境界線を描画しようとすると、見当違いのように見えます。何か案が?

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

そして

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

}
}

オリジナル

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は通常の画像で動作します。ただし、カメラでキャプチャした画像は機能しないか、1つの顔しか認識されないことがあります。

通常画像

enter image description here

画像のキャプチャ

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

顔を検出する方法は2つあります。1つはCIFaceDetectorで、もう1つはAVCaptureMetadataOutputです。

要件に応じて、自分に関連するものを選択します。

CIFaceDetectorには、より多くの機能があります-例:目と口の位置、笑顔検出器などを提供します

一方、AVCaptureMetadataOutputはフレーム上で計算され、検出された顔が追跡され、追加のコードを追加する必要はありません。このプロセスでは、追跡顔により確実に検出されることがわかりました。これの短所は、顔を検出するだけで、目/口の位置は検出しないことです。この方法のもう1つの利点は、デバイスの向きが変更され、顔の向きがその向きに関連する場合は常にvideoOrientationが可能なため、向きの問題が少ないことです。

私の場合、アプリケーションはYUV420を必要な形式として使用しているため、リアルタイムでCIDetector(RGBで動作する)を使用することは実行不可能でした。 AVCaptureMetadataOutputを使用すると、継続的な追跡により、多くの労力を節約し、実行できましたより確実に

顔の境界ボックスを作成したら、肌検出などの追加機能をコーディングし、静止画像に適用しました。

注:静止画像をキャプチャすると、顔ボックス情報がメタデータとともに追加されるため、同期の問題はありません。

また、2つの組み合わせを使用して、より良い結果を得ることができます。

アプリケーションごとに長所と短所を調べて評価してください。

[〜#〜] update [〜#〜]

顔の長方形は画像の原点です。そのため、画面については異なる場合があります。以下を使用してください。

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

IOSで顔検出を実行するには、 CIDetector (Apple)または Mobile Vision (Google)APIがあります。

IMO、Google Mobile Visionはパフォーマンスを向上させます。

興味のある方は、 これはあなたが遊ぶことができるプロジェクトです。(iOS 10.2、Swift 3)


WWDC 2017以降、AppleはiOS 11で CoreML を導入します。Visionフレームワークはより正確な顔検出:)

デモプロジェクト を作成しました。含むVision v.s. CIDetector。また、リアルタイムで顔のランドマークを検出します。

6
Willjay

少し遅れましたが、ここでは座標の問題の解決策です。プレビューレイヤーで呼び出してメタデータオブジェクトを座標系に変換できるメソッドがあります: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)

敬具

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

DidOutputMetadataObjectsによって作成されたビューを必ず削除してください。

これを行うには、アクティブな顔のIDを追跡するのが最善の方法です^

また、プレビューレイヤーの顔の位置を見つけようとしている場合、顔データを使用して変換する方がはるかに簡単です。また、CIDetectorはジャンクだと思います。メタデータ出力は顔検出にハードウェアを使用するため、非常に高速になります。

2
jnblanchard
  1. 作成CaptureSession
  2. AVCaptureVideoDataOutputの場合、次の設定を作成します

    output.videoSettings = [AnyHashableとしてのkCVPixelBufferPixelFormatTypeKey: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);
    }
1
duzvik

あなたのコードを見ることで、間違った/悪い顔検出につながる可能性のある2つのことを検出しました。

  1. それらの1つは、_[CIDetectorSmile: true, CIDetectorEyeBlink: true]_で結果をフィルタリングする顔検出機能オプションです。 nilに設定してみてください:faceDetector?.features(in: faceImage, options: nil)
  2. 私が持っている別の推測は、結果画像の向きです。 _AVCapturePhotoOutput.jpegPhotoDataRepresentation_メソッドを使用して検出とシステムのソースイメージを生成していることに気付きました。デフォルトでは、Left/LandscapeLeft型の特定の向きでそのイメージを生成します。したがって、基本的には CIDetectorImageOrientation キーを使用して、顔検出器にそのことを念頭に置くことができます。

CIDetectorImageOrientation:このキーの値は、 NSNumber にあるような1..8からの整数kCGImagePropertyOrientationです。存在する場合、検出はその方向に基づいて行われますが、返されるフィーチャの座標は引き続き画像の座標に基づきます。

faceDetector?.features(in: faceImage, options: [CIDetectorImageOrientation: 8 /*Left, bottom*/])のように設定してみてください。

0
ricardopereira