Пользовательская камера и кадрирование изображения в Swift

Я создал пользовательскую камеру и реализовал приведенный ниже код для обрезки снятого изображения, я показал направляющие в слое предварительного просмотра, поэтому я хочу обрезать изображение, которое появляется в этой области.

func imageByCropToRect(rect:CGRect, scale:Bool) -> UIImage {    
    var rect = rect
    var scaleFactor: CGFloat = 1.0
    if scale  {
        scaleFactor = self.scale
        rect.origin.x *= scaleFactor
        rect.origin.y *= scaleFactor
        rect.size.width *= scaleFactor
        rect.size.height *= scaleFactor
    }

    var image: UIImage? = nil;
    if rect.size.width > 0 && rect.size.height > 0 {
        let imageRef = self.cgImage!.cropping(to: rect)
        image = UIImage(cgImage: imageRef!, scale: scaleFactor, orientation: self.imageOrientation)
    }

    return image!
}

Этот код просто отлично работает, когда & дает точное обрезанное изображение, когда прокомментирована строка кода ниже, хотя я хочу, чтобы потоковая передача изображения была полноэкранной, поэтому я должен использовать приведенную ниже строку кода. Изображение как-то уменьшается.

(self.previewLayer as! AVCaptureVideoPreviewLayer).videoGravity = AVLayerVideoGravity.resizeAspectFill

Как решить эту проблему? Код обрезки неправильный?

Вот полный код класса

import UIKit
import AVFoundation

class CameraViewController: UIViewController {

    @IBOutlet weak var guideImageView: UIImageView!
    @IBOutlet weak var guidesView: UIView!
    @IBOutlet weak var cameraPreviewView: UIView!
    @IBOutlet weak var cameraButtonView: UIView!

    @IBOutlet weak var captureButton: UIButton!

    var captureSession = AVCaptureSession()
    var previewLayer: CALayer!
    var captureDevice: AVCaptureDevice!

    /// This will be true when the user clicks on the click photo button.
    var takePhoto = false

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        captureSession = AVCaptureSession()
        previewLayer = CALayer()
        takePhoto = false

        requestAuthorization()
    }

    private func userinteractionToButton(_ interaction: Bool) {
        captureButton.isEnabled = interaction
    }

    /// This function will request authorization, If authorized then start the camera.
    private func requestAuthorization() {
        switch AVCaptureDevice.authorizationStatus(for: AVMediaType.video) {
        case .authorized:
            prepareCamera()

        case .denied, .restricted, .notDetermined:
            AVCaptureDevice.requestAccess(for: AVMediaType.video, completionHandler: { (granted) in
                if !Thread.isMainThread {
                    DispatchQueue.main.async {
                        if granted {
                            self.prepareCamera()
                        } else {
                            let alert = UIAlertController(title: "unable_to_access_the_Camera", message: "to_enable_access_go_to_setting_privacy_camera_and_turn_on_camera_access_for_this_app", preferredStyle: UIAlertControllerStyle.alert)
                            alert.addAction(UIAlertAction(title: "ok", style: .default, handler: {_ in
                                self.navigationController?.popToRootViewController(animated: true)
                            }))
                            self.present(alert, animated: true, completion: nil)
                        }
                    }
                } else {
                    if granted {
                        self.prepareCamera()
                    } else {
                        let alert = UIAlertController(title: "unable_to_access_the_Camera", message: "to_enable_access_go_to_setting_privacy_camera_and_turn_on_camera_access_for_this_app", preferredStyle: UIAlertControllerStyle.alert)
                        alert.addAction(UIAlertAction(title: "ok", style: .default, handler: {_ in
                            self.navigationController?.popToRootViewController(animated: true)
                        }))
                        self.present(alert, animated: true, completion: nil)
                    }
                }
            })
        }
    }

    /// Will see if the primary camera is avilable, If found will call method which will asign the available device to the AVCaptureDevice.
    private func prepareCamera() {
        // Resets the session.
        self.captureSession.sessionPreset = AVCaptureSession.Preset.photo

        if #available(iOS 10.0, *) {
            let availableDevices = AVCaptureDevice.DiscoverySession(deviceTypes: [AVCaptureDevice.DeviceType.builtInWideAngleCamera], mediaType: AVMediaType.video, position: .back).devices
            self.assignCamera(availableDevices)
        } else {
            // Fallback on earlier versions
            // development, need to test this on iOS 8
            if let availableDevices = AVCaptureDevice.default(for: AVMediaType.video) {
                self.assignCamera([availableDevices])
            } else {
                self.showAlert()
            }
        }
    }

    /// Assigns AVCaptureDevice to the respected the variable, will begin the session.
    ///
    /// - Parameter availableDevices: [AVCaptureDevice]
    private func assignCamera(_ availableDevices: [AVCaptureDevice]) {
        if availableDevices.first != nil {
            captureDevice = availableDevices.first
            beginSession()
        } else {
            self.showAlert()
        }
    }

    /// Configures the camera settings and begins the session, this function will be responsible for showing the image on the UI.
    private func beginSession() {
        do {
            let captureDeviceInput = try AVCaptureDeviceInput(device: captureDevice)
            captureSession.addInput(captureDeviceInput)
        } catch {
            print(error.localizedDescription)
        }

        let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
        self.previewLayer = previewLayer
        self.cameraPreviewView.layer.addSublayer(self.previewLayer)
        self.previewLayer.frame = self.view.layer.frame
        self.previewLayer.frame.origin.y = +self.cameraPreviewView.frame.origin.y
        (self.previewLayer as! AVCaptureVideoPreviewLayer).videoGravity = AVLayerVideoGravity.resizeAspectFill
        self.previewLayer.masksToBounds = true
        self.cameraPreviewView.clipsToBounds = true
        captureSession.startRunning()

        self.view.bringSubview(toFront: self.cameraPreviewView)
        self.view.bringSubview(toFront: self.cameraButtonView)
        self.view.bringSubview(toFront: self.guidesView)

        let dataOutput = AVCaptureVideoDataOutput()
        dataOutput.videoSettings = [((kCVPixelBufferPixelFormatTypeKey as NSString) as String):NSNumber(value:kCVPixelFormatType_32BGRA)]

        dataOutput.alwaysDiscardsLateVideoFrames = true

        if captureSession.canAddOutput(dataOutput) {
            captureSession.addOutput(dataOutput)
        }

        captureSession.commitConfiguration()

        let queue = DispatchQueue(label: "com.letsappit.camera")
        dataOutput.setSampleBufferDelegate(self, queue: queue)

        self.userinteractionToButton(true)
    }


    /// Get the UIImage from the given CMSampleBuffer.
    ///
    /// - Parameter buffer: CMSampleBuffer
    /// - Returns: UIImage?
    func getImageFromSampleBuffer(buffer:CMSampleBuffer, orientation: UIImageOrientation) -> UIImage? {
        if let pixelBuffer = CMSampleBufferGetImageBuffer(buffer) {
            let ciImage = CIImage(cvPixelBuffer: pixelBuffer)
            let context = CIContext()
            let imageRect = CGRect(x: 0, y: 0, width: CVPixelBufferGetWidth(pixelBuffer), height: CVPixelBufferGetHeight(pixelBuffer))

            if let image = context.createCGImage(ciImage, from: imageRect) {
                return UIImage(cgImage: image, scale: UIScreen.main.scale, orientation: orientation)

            }

        }
        return nil
    }

    /// This function will destroy the capture session.
    func stopCaptureSession() {
        self.captureSession.stopRunning()

        if let inputs = captureSession.inputs as? [AVCaptureDeviceInput] {
            for input in inputs {
                self.captureSession.removeInput(input)
            }
        }
    }

    func showAlert() {
        let alert = UIAlertController(title: "Unable to access the camera", message: "It appears that either your device doesn't have camera or its broken", preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "cancel", style: .cancel, handler: {_ in
            self.navigationController?.dismiss(animated: true, completion: nil)
        }))
        self.present(alert, animated: true, completion: nil)
    }

    @IBAction func didTapClick(_ sender: Any) {
        userinteractionToButton(false)
        takePhoto = true
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "showImage" {
            let vc = segue.destination as! ShowImageViewController
            vc.image = sender as! UIImage
        }
    }
}

extension CameraViewController: AVCaptureVideoDataOutputSampleBufferDelegate {
    func captureOutput(_ captureOutput: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {

        if connection.isVideoOrientationSupported {
            connection.videoOrientation = .portrait
        }

        if takePhoto {
            takePhoto = false

            // Rotation should be unlocked to work.
            var orientation = UIImageOrientation.up
            switch UIDevice.current.orientation {
            case .landscapeLeft:
                orientation = .left

            case .landscapeRight:
                orientation = .right

            case .portraitUpsideDown:
                orientation = .down

            default:
                orientation = .up
            }

            if let image = self.getImageFromSampleBuffer(buffer: sampleBuffer, orientation: orientation) {
                DispatchQueue.main.async {
                    let newImage = image.imageByCropToRect(rect: self.guideImageView.frame, scale: true)
                    self.stopCaptureSession()
                    self.previewLayer.removeFromSuperlayer()
                    self.performSegue(withIdentifier: "showImage", sender: newImage)
                }
            }
        }
    }
}

Вот изображение иерархии представлений введите здесь описание изображения


person Anirudha Mahale    schedule 15.03.2018    source источник
comment
Помните об этом в своем проекте: openradar.me/36292067   -  person Scriptable    schedule 15.03.2018
comment
@Scriptable, пожалуйста, взгляните на мой код, я не использую UIImagePickerControllerEditedImage.   -  person Anirudha Mahale    schedule 15.03.2018
comment
лучше всего объяснить как можно больше здесь, мне не нужно щелкать в другом месте, чтобы понять вашу проблему   -  person Scriptable    schedule 15.03.2018
comment
@Scriptable Я создал приложение камеры, используя AVCaptureSession, CALayer, AVCaptureDevice, а затем транслировал видео на слой предварительного просмотра, после нажатия кнопки я беру изображение из CMSampleBuffer.   -  person Anirudha Mahale    schedule 15.03.2018
comment
Если у вас есть свободное время, вы можете пройти проект. Заранее спасибо.   -  person Anirudha Mahale    schedule 15.03.2018
comment
Вы просите людей здесь о помощи, поэтому вам нужно сделать так, чтобы людям было как можно проще помочь вам. Опубликуйте столько подробностей, сколько необходимо для ответа на вопрос (и не более того). Не заставляйте нас делать больше работы, чем нужно. Ссылка на код вне вопроса не работает, потому что этот код может измениться в любое время.   -  person Chris Garrett    schedule 16.03.2018
comment
@ChrisGarrett Я сделал то, что ты посоветовал.   -  person Anirudha Mahale    schedule 17.03.2018
comment
Хорошо, это лучше, но нам все еще нужно немного больше информации. Откуда вы вызываете функцию обрезки? Похоже, вы пытаетесь сделать это для каждого кадра предварительного просмотра, и в этом случае это будет серьезным узким местом в производительности.   -  person Chris Garrett    schedule 18.03.2018
comment
@ChrisGarrett здесь я добавил полный код класса и иерархию представлений.   -  person Anirudha Mahale    schedule 18.03.2018


Ответы (2)


Не понятно где проблема. Я бы использовал отладчик или некоторые операторы печати, чтобы выяснить, связана ли проблема с изображением или представлением, отображающим изображение. Распечатайте размер обрезанного изображения, чтобы убедиться, что он правильный.

Затем распечатайте размер представления изображения в ShowImageViewController в viewDidAppear, чтобы убедиться, что он правильный.

person Chris Garrett    schedule 19.03.2018
comment
Хорошо, я попробую использовать отладчик и посмотреть там иерархию представлений. - person Anirudha Mahale; 20.03.2018

Для исправления уменьшения масштаба обрезанного изображения вы должны изменить функцию кадрирования на это, используя ориентацию изображения.

func croppedInRect(rect: CGRect) -> UIImage? {
    func rad(_ degree: Double) -> CGFloat {
        return CGFloat(degree / 180.0 * .pi)
    }

    var rectTransform: CGAffineTransform
    switch imageOrientation {
    case .left:
        rectTransform = CGAffineTransform(rotationAngle: rad(90)).translatedBy(x: 0, y: -self.size.height)
    case .right:
        rectTransform = CGAffineTransform(rotationAngle: rad(-90)).translatedBy(x: -self.size.width, y: 0)
    case .down:
        rectTransform = CGAffineTransform(rotationAngle: rad(-180)).translatedBy(x: -self.size.width, y: -self.size.height)
    default:
        rectTransform = .identity
    }
    rectTransform = rectTransform.scaledBy(x: self.scale, y: self.scale)

    var cgImage = self.cgImage

    if cgImage == nil{

        let ciContext = CIContext()
        if let ciImage = self.ciImage{
            cgImage = ciContext.createCGImage(ciImage, from: ciImage.extent)
        }
    }

    if let imageRef = cgImage?.cropping(to: rect.applying(rectTransform)){
        let result = UIImage(cgImage: imageRef, scale: self.scale, orientation: self.imageOrientation)
        return result
    }


    return nil

}
person Taranjeet Kaur    schedule 17.07.2019