Обнаружение лиц с помощью камеры
Как я могу распознавать лицо в реальном времени так же, как "Камера"?
![введите описание изображения здесь]()
Я заметил, что AVCaptureStillImageOutput устарел после 10.0, поэтому я использую
AVCapturePhotoOutput. Однако я обнаружил, что изображение, которое я сохранил для обнаружения лица, не так удовлетворено? Есть идеи?
UPDATE
После того, как попробовал @Шрива Боггарапу, упомянул. В настоящее время я использую 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)
}
}
}
}
}
Оригинал
см. в 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)
}
Ответы
Ответ 1
Есть два способа поиска грани: один - CIFaceDetector, а другой - AVCaptureMetadataOutput
В зависимости от ваших требований выберите то, что вам подходит.
CIFaceDetector имеет больше возможностей - например: дает вам местоположение глаз и рта, детектор улыбки и т.д.
С другой стороны, AVCaptureMetadataOutput вычисляется по кадрам, и обнаруженные лица отслеживаются, и нет дополнительного кода, который должен быть добавлен нами. Я нахожу, что из-за отслеживания лица обнаружены более надежно в этом процессе. Кон этим является то, что вы просто обнаружите лица, нет положения глаз/рта.
Другим преимуществом этого метода является то, что проблемы ориентации меньше, так как вы можете видеоориентировать всякий раз, когда изменяется ориентация устройства и ориентация граней будет относительно этой ориентации.
В моем случае мое приложение использует YUV420 как необходимый формат, поэтому использование CIDetector (который работает с RGB) в режиме реального времени не является жизнеспособным. Использование AVCaptureMetadataOutput сэкономило массу усилий и выполнило более надежно из-за непрерывного отслеживания.
Как только у меня была ограничивающая рамка для лиц, я закодировал дополнительные функции, такие как обнаружение скинов и наложил их на неподвижное изображение.
Примечание. Когда вы снимаете неподвижное изображение, информация о лицевом поле добавляется вместе с метаданными, поэтому нет проблем с синхронизацией.
Вы также можете использовать комбинацию из двух, чтобы получить лучшие результаты.
Изучите и оцените плюсы и минусы в соответствии с вашим приложением.
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 */
}
Ответ 2
Чтобы выполнить обнаружение лиц на iOS, есть CIDetector (Apple)
или Mobile Vision (Google) API.
IMO, Google Mobile Vision обеспечивает лучшую производительность.
Если вам интересно, вот проект, с которым вы можете играть. (iOS 10.2, Swift 3)
После WWDC 2017 Apple представляет CoreML в iOS 11.
Структура Видение делает определение лица более точным:)
Я создал демонстрационный проект . содержащий Видение vs. CIDetector. Кроме того, он содержит обнаружение ориентиров лица в режиме реального времени.
Ответ 3
Посмотрев на свой код, я обнаружил 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*/])
.
Ответ 4
3. Когда вы получаете CMSampleBuffer, создайте изображение
DispatchQueue.main.async {
let sampleImg = self.imageFromSampleBuffer(sampleBuffer: sampleBuffer)
self.imageView.image = sampleImg
}
func imageFromSampleBuffer(sampleBuffer : CMSampleBuffer) -> UIImage
{
// Get a CMSampleBuffer 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);
}
Ответ 5
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.
Отслеживание активных лицевых идентификаторов - лучший способ сделать это ^
Также, когда вы пытаетесь найти расположение лиц для вашего слоя предварительного просмотра, гораздо проще использовать лицевые данные и преобразовать. Также я думаю, что CIDetector - это неактивный, metadataoutput будет использовать аппаратные средства для обнаружения лиц, что делает его очень быстрым.