กล้องแบบกำหนดเองและครอบตัดรูปภาพใน 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