Я новичок в разработке iOS, и моя текущая миссия состоит в том, чтобы реализовать функцию распознавания лиц с помощью библиотеки Mobile Vision от Google (не могу использовать распознавание лиц Apple из-за минимума iOS версия = 10). Я следую за официальный пример из Google Все работает хорошо, за исключением того, что, даже если я запускаю пример, прямоугольник лица (и ориентиры) не расположен в правильном месте (они всегда отражаются и смещаются), похоже, проблема в том, что в снятом с камеры изображении, потому что на снимках c в данном случае распознавание лица работает хорошо. Я гуглил подобные проблемы, и никто из них не помог мне. Что я делаю не так? Любая помощь приветствуется:)
Примечание: я использую только фронтальную камеру в портретном режиме
Исходный код:
import UIKit
import AVFoundation
import GoogleMobileVision
class FaceDetectorViewController: BaseViewController , AVCaptureVideoDataOutputSampleBufferDelegate {
@IBOutlet weak var placeholder: UIView!
@IBOutlet weak var overlay: UIView!
private var session = AVCaptureSession()
private var videoDataOutput = AVCaptureVideoDataOutput ()
private var previewLayer = AVCaptureVideoPreviewLayer()
private let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front)
private var detector :GMVDetector? = nil
private var videoDataOutputQueue = DispatchQueue(label: "face_queue")
override func setup() {
super.setup()
session.sessionPreset = AVCaptureSession.Preset.high
setupCamera()
setupVideoProcessing()
setupCameraPreview()
self.detector = GMVDetector(ofType: GMVDetectorTypeFace, options:
[
GMVDetectorFaceMinSize: 0.3,
GMVDetectorFaceTrackingEnabled: true,
GMVDetectorFaceLandmarkType: GMVDetectorFaceLandmark.all.rawValue
])
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
session.startRunning()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidAppear(animated)
session.stopRunning()
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
if UIDevice.current.orientation.isLandscape {
previewLayer.connection?.videoOrientation = AVCaptureVideoOrientation.portrait
} else {
previewLayer.connection?.videoOrientation = AVCaptureVideoOrientation.portrait
}
}
override func willAnimateRotation(to toInterfaceOrientation: UIInterfaceOrientation, duration: TimeInterval) {
previewLayer.connection?.videoOrientation = .portrait
}
func scaledRect(_ rect: CGRect, xScale xscale: CGFloat, yScale yscale: CGFloat, offset: CGPoint) -> CGRect {
var resultRect = CGRect(
x: rect.origin.x * xscale,
y: rect.origin.y * yscale,
width: rect.size.width * xscale,
height: rect.size.height * yscale)
resultRect = resultRect.offsetBy(dx: offset.x, dy: offset.y)
return resultRect
}
func scaledPoint(_ point: CGPoint, xScale xscale: CGFloat, yScale yscale: CGFloat, offset: CGPoint) -> CGPoint {
let resultPoint = CGPoint(x: point.x * xscale + offset.x, y: point.y * yscale + offset.y)
return resultPoint
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.previewLayer.frame = self.view.layer.bounds;
self.previewLayer.position = CGPoint(
x : self.previewLayer.frame.midX,
y : self.previewLayer.frame.midY);
}
private func cleanupVideoProcessing() {
session.removeOutput(videoDataOutput)
}
private func setupVideoProcessing() {
videoDataOutput = AVCaptureVideoDataOutput()
videoDataOutput.alwaysDiscardsLateVideoFrames = true
videoDataOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as AnyHashable as! String: NSNumber(value: kCVPixelFormatType_32BGRA)]
videoDataOutput.setSampleBufferDelegate(self, queue: videoDataOutputQueue ) //
if(!session.canAddOutput(videoDataOutput)){
cleanupVideoProcessing()
return
}
session.addOutput(videoDataOutput)
session.commitConfiguration()
session.startRunning()
}
private func setupCameraPreview() {
previewLayer = AVCaptureVideoPreviewLayer(session: session)
previewLayer.backgroundColor = UIColor.white.cgColor
previewLayer.videoGravity = AVLayerVideoGravity.resizeAspect
placeholder.layer.masksToBounds = true
previewLayer.frame = placeholder.layer.bounds
placeholder?.layer.addSublayer(previewLayer)
previewLayer.connection?.videoOrientation = AVCaptureVideoOrientation.portrait
}
private func setupCamera() {
session.beginConfiguration()
let oldInputs = session.inputs
for old in oldInputs{
session.removeInput(old)
}
guard let input = getInput() else {
for old in oldInputs{
session.addInput(old)
}
session.commitConfiguration()
return
}
session.addInput(input)
session.commitConfiguration()
}
private func getInput() -> AVCaptureDeviceInput? {
guard let device = device else {
return nil
}
guard let input = try? AVCaptureDeviceInput.init(device: device) else {
return nil
}
if session.canAddInput(input) {
return input
}
return nil
}
func convert(cmage:CIImage) -> UIImage
{
let context:CIContext = CIContext.init(options: nil)
let cgImage:CGImage = context.createCGImage(cmage, from: cmage.extent)!
let image:UIImage = UIImage.init(cgImage: cgImage)
return image
}
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
let imageBuffer: CVPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)!
let ciimage : CIImage = CIImage(cvPixelBuffer: imageBuffer)
let image : UIImage = self.convert(cmage: ciimage)
let imageOrientation = GMVUtility.imageOrientation(from: UIDevice.current.orientation, with: AVCaptureDevice.Position.front, defaultDeviceOrientation: .portrait)
let options = [
GMVDetectorImageOrientation: imageOrientation
]
let faces = detector?.features(in: image, options: options)
guard let fdesc = CMSampleBufferGetFormatDescription(sampleBuffer) else {
return
}
let clap = CMVideoFormatDescriptionGetCleanAperture(fdesc, originIsAtTopLeft: false)
let parentFrameSize = previewLayer.frame.size
let cameraRatio: CGFloat = clap.size.height / clap.size.width
let viewRatio: CGFloat = parentFrameSize.width / parentFrameSize.height
var xScale: CGFloat = 1
var yScale: CGFloat = 1
var videoBox = CGRect.zero
if viewRatio > cameraRatio {
videoBox.size.width = parentFrameSize.height * clap.size.width / clap.size.height
videoBox.size.height = parentFrameSize.height
videoBox.origin.x = (parentFrameSize.width - videoBox.size.width) / 2
videoBox.origin.y = (videoBox.size.height - parentFrameSize.height) / 2
xScale = videoBox.size.width / clap.size.width
yScale = videoBox.size.height / clap.size.height
} else {
videoBox.size.width = parentFrameSize.width
videoBox.size.height = clap.size.width * (parentFrameSize.width / clap.size.height)
videoBox.origin.x = (videoBox.size.width - parentFrameSize.width) / 2
videoBox.origin.y = (parentFrameSize.height - videoBox.size.height) / 2
xScale = videoBox.size.width / clap.size.height
yScale = videoBox.size.height / clap.size.width
}
var transform = CGAffineTransform(scaleX: parentFrameSize.width/clap.width, y: -parentFrameSize.height/clap.height);
transform = transform.translatedBy(x: 0, y: -clap.height);
DispatchQueue.main.async {
for featureView in self.overlay.subviews {
featureView.removeFromSuperview()
}
//
guard let f = faces else {
return
}
for face in f {
let faceRect = self.scaledRect(face.bounds, xScale: xScale, yScale: yScale, offset: videoBox.origin)
DrawingUtility.addRectangle(faceRect, to: self.overlay, with: UIColor.red)
}
}
}
}
// MARK: Helper, xib initialization
extension FaceDetectorViewController {
static func xibInit() -> FaceDetectorViewController {
let vc = FaceDetectorViewController(nibName: "FaceDetectorViewController", bundle: nil)
vc.modalPresentationStyle = .overFullScreen
vc.modalTransitionStyle = .coverVertical
return vc
}
}