【问题标题】:Cropping a captured image exactly to how it looks in AVCaptureVideoPreviewLayer将捕获的图像完全裁剪为 AVCaptureVideoPreviewLayer 中的外观
【发布时间】:2014-03-22 08:48:28
【问题描述】:

我有一个使用 AV Foundation 的照片应用程序。我已经使用占据屏幕上半部分的 AVCaptureVideoPreviewLayer 设置了一个预览层。因此,当用户尝试拍照时,他们所能看到的只是屏幕的上半部分。

这很好用,但是当用户实际拍摄照片并且我尝试将照片设置为图层的内容时,图像会失真。我做了研究并意识到我需要裁剪图像。

我想要做的就是裁剪完整的捕获图像,这样剩下的就是用户最初可以在屏幕上半部分看到的内容。

我已经能够做到这一点,但我是通过手动输入 CGRect 值来做到这一点的,但它看起来仍然不完美。必须有一种更简单的方法来做到这一点。

在过去 2 天里,我确实浏览了所有关于堆栈溢出的关于裁剪图像的帖子,但没有任何效果。

必须有一种方法以编程方式裁剪捕获的图像,以便最终图像与最初在预览层中看到的完全一致。

这是我的 viewDidLoad 实现:

- (void)viewDidLoad
{
    [super viewDidLoad];

    AVCaptureSession *session =[[AVCaptureSession alloc]init];
    [session setSessionPreset:AVCaptureSessionPresetPhoto];

    AVCaptureDevice *inputDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];

    NSError *error = [[NSError alloc]init];
    AVCaptureDeviceInput *deviceInput = [AVCaptureDeviceInput deviceInputWithDevice:inputDevice error:&error];

    if([session canAddInput:deviceInput])
        [session addInput:deviceInput];

    CALayer *rootLayer = [[self view]layer];
    [rootLayer setMasksToBounds:YES];

    _previewLayer = [[AVCaptureVideoPreviewLayer alloc]initWithSession:session];
    [_previewLayer setFrame:CGRectMake(0, 0, rootLayer.bounds.size.width, rootLayer.bounds.size.height/2)];
    [_previewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];

    [rootLayer insertSublayer:_previewLayer atIndex:0];

    _stillImageOutput = [[AVCaptureStillImageOutput alloc] init];
    [session addOutput:_stillImageOutput];

    [session startRunning];
    }

这是当用户按下按钮拍摄照片时运行的代码:

-(IBAction)stillImageCapture {
    AVCaptureConnection *videoConnection = nil;
    for (AVCaptureConnection *connection in _stillImageOutput.connections){
        for (AVCaptureInputPort *port in [connection inputPorts]){
            if ([[port mediaType] isEqual:AVMediaTypeVideo]){
                videoConnection = connection;
                break;
            }
        }
        if (videoConnection) {
            break;
        }
    }

    NSLog(@"about to request a capture from: %@", _stillImageOutput);

    [_stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
        if(imageDataSampleBuffer) {
            NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];

            UIImage *image = [[UIImage alloc]initWithData:imageData];
            CALayer *subLayer = [CALayer layer];
            subLayer.frame = _previewLayer.frame;
            image = [self rotate:image andOrientation:image.imageOrientation];

            //Below is the crop that is sort of working for me, but as you can see I am manually entering in values and just guessing and it still does not look perfect.
            CGRect cropRect = CGRectMake(0, 650, 3000, 2000);
            CGImageRef imageRef = CGImageCreateWithImageInRect([image CGImage], cropRect);

            subLayer.contents = (id)[UIImage imageWithCGImage:imageRef].CGImage;
            subLayer.frame = _previewLayer.frame;

            [_previewLayer addSublayer:subLayer];
        }
    }];
}

【问题讨论】:

    标签: ios objective-c avfoundation calayer avcapturesession


    【解决方案1】:

    看看AVCaptureVideoPreviewLayers

    -(CGRect)metadataOutputRectOfInterestForRect:(CGRect)layerRect
    

    此方法让您可以轻松地将图层的可见 CGRect 转换为实际的相机输出。

    一个警告:物理相机不是“顶面朝上”安装的,而是顺时针旋转 90 度。 (因此,如果您将 iPhone - Home 按钮放在右侧,相机实际上是正面朝上的)。

    记住这一点,您必须转换上述方法提供的 CGRect,才能将图像裁剪为屏幕上的内容。

    例子:

    CGRect visibleLayerFrame = THE ACTUAL VISIBLE AREA IN THE LAYER FRAME
    CGRect metaRect = [self.previewView.layer metadataOutputRectOfInterestForRect:visibleLayerFrame];
    
    
    CGSize originalSize = [originalImage size];
    
    if (UIInterfaceOrientationIsPortrait(_snapInterfaceOrientation)) {
        // For portrait images, swap the size of the image, because
        // here the output image is actually rotated relative to what you see on screen.
    
        CGFloat temp = originalSize.width;
        originalSize.width = originalSize.height;
        originalSize.height = temp;
    }
    
    
    // metaRect is fractional, that's why we multiply here
    
    CGRect cropRect;
    
    cropRect.origin.x = metaRect.origin.x * originalSize.width;
    cropRect.origin.y = metaRect.origin.y * originalSize.height;
    cropRect.size.width = metaRect.size.width * originalSize.width;
    cropRect.size.height = metaRect.size.height * originalSize.height;
    
    cropRect = CGRectIntegral(cropRect);
    

    这可能有点令人困惑,但让我真正理解的是:

    按住您的设备“Home Button right” -> 您会看到 x 轴实际上位于 iPhone 的“高度”上,而 y 轴位于 iPhone 的“宽度”上。这就是为什么对于肖像图像,您必须交换大小;)

    【讨论】:

    • @user3117509 这个答案值得加分,请采纳!
    • 和 @Cabus 你不旋转图像。我用下面的代码UIImage *croppedImage = [UIImage imageWithCGImage:imageRef]; UIGraphicsBeginImageContext(croppedImage.size); [[UIImage imageWithCGImage:[croppedImage CGImage] scale:1.0 orientation:UIImageOrientationRight] drawInRect:CGRectMake(0,0,croppedImage.size.height ,croppedImage.size.width)]; UIImage *rotatedCroppedImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext();
    【解决方案2】:

    @Cabus 有一个可行的解决方案,您应该对他的答案投赞成票。但是,我在 Swift 中做了我自己的版本:

    // The image returned in initialImageData will be larger than what
    //  is shown in the AVCaptureVideoPreviewLayer, so we need to crop it.
    let image : UIImage = UIImage(data: initialImageData)!
    
    let originalSize : CGSize
    let visibleLayerFrame = self.previewView!.bounds // THE ACTUAL VISIBLE AREA IN THE LAYER FRAME
    
    // Calculate the fractional size that is shown in the preview
    let metaRect : CGRect = (self.videoPreviewLayer?.metadataOutputRectOfInterestForRect(visibleLayerFrame))!
    if (image.imageOrientation == UIImageOrientation.Left || image.imageOrientation == UIImageOrientation.Right) {
        // For these images (which are portrait), swap the size of the
        // image, because here the output image is actually rotated
        // relative to what you see on screen.
        originalSize = CGSize(width: image.size.height, height: image.size.width)
    }
    else {
        originalSize = image.size
    }
    
    // metaRect is fractional, that's why we multiply here.
    let cropRect : CGRect = CGRectIntegral(
            CGRect( x: metaRect.origin.x * originalSize.width,
                    y: metaRect.origin.y * originalSize.height,
                    width: metaRect.size.width * originalSize.width,
                    height: metaRect.size.height * originalSize.height))
    
    let finalImage : UIImage = 
        UIImage(CGImage: CGImageCreateWithImageInRect(image.CGImage, cropRect)!, 
            scale:1, 
            orientation: image.imageOrientation )
    

    【讨论】:

      【解决方案3】:

      这是@Erik Allen 在 Swift 3 中的回答:

      let originalSize: CGSize
      let visibleLayerFrame = self?.photoView.bounds
      
      // Calculate the fractional size that is shown in the preview
      let metaRect = (self?.videoPreviewLayer?.metadataOutputRectOfInterest(for: visibleLayerFrame ?? CGRect.zero)) ?? CGRect.zero
      
      if (image.imageOrientation == UIImageOrientation.left || image.imageOrientation == UIImageOrientation.right) {
          // For these images (which are portrait), swap the size of the
          // image, because here the output image is actually rotated
          // relative to what you see on screen.
          originalSize = CGSize(width: image.size.height, height: image.size.width)
      } else {
          originalSize = image.size
      }
      
      let cropRect: CGRect = CGRect(x: metaRect.origin.x * originalSize.width, y: metaRect.origin.y * originalSize.height, width: metaRect.size.width * originalSize.width, height: metaRect.size.height * originalSize.height).integral
      
      if let finalCgImage = image.cgImage?.cropping(to: cropRect) {
          let finalImage = UIImage(cgImage: finalCgImage, scale: 1.0, orientation: image.imageOrientation)
      
          // User your image...
      }
      

      【讨论】:

        【解决方案4】:

        图像尺寸与您的屏幕尺寸有不同的单位,因此您不能使用视图的尺寸来裁剪图像。

        例如:捕获的图像为 1080 * 1920(像素),但您的全屏为 440 * 800

        并且您正在使用 videoGravity = .resizeAspectFill,因此您的预览将按视图的宽度或高度进行缩放。所以你必须计算哪个方向适合“捕获的图像”,你需要计算另一个。

        例如:如果“捕获的图像”适合高度 => 计算宽度,反之亦然。

        就我而言:我有一个全屏摄像头,但在捕获图像时我仍然需要裁剪。 我的解决方案:

        func cropImage(image: UIImage) -> UIImage {
            DispatchQueue.main.sync {
                let originalSize: CGSize = image.size
                var cropFrame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height)
                if originalSize.width / originalSize.height >= view.frame.width / view.frame.height {
                    // base on originalSize.height
                    let cropWidth = originalSize.height * view.frame.width / view.frame.height
                    cropFrame = CGRect(x: 0, y: 0, width: cropWidth, height: originalSize.height)
                } else {
                    // base on originalSize.width
                    let cropHeight = originalSize.width * view.frame.height / view.frame.width
                    cropFrame = CGRect(x: 0, y: 0, width: originalSize.width, height: cropHeight)
                }
                if let finalCgImage = image.cgImage?.cropping(to: cropFrame) {
                    let finalImage = UIImage(cgImage: finalCgImage, scale: 1.0, orientation: image.imageOrientation)
                    return finalImage
                }
                return image
            }
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-08-15
          • 2013-08-14
          • 2013-05-26
          相关资源
          最近更新 更多