裁剪AVCaptureVideoPreviewLayer输出为正方形

24

当我尝试截取可见屏幕的裁剪 UIImage 时,我的 AVCaptureVideoPreviewLayer 方法出了问题。目前它能工作,但不能输出我所需的正确裁剪。

我正在尝试输出一个正方形,但(看起来)似乎给出了完整高度并压缩图像。

之前的图像显示了实时屏幕,而之后的图像显示了按下捕获按钮后的图像。您可以看到它已垂直变化以适应正方形,但高度未被垂直裁剪。

输入图像描述

捕获图像代码

[stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler: ^(CMSampleBufferRef imageSampleBuffer, NSError *error) {

    if (imageSampleBuffer != NULL) {

        NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];
        [self processImage:[UIImage imageWithData:imageData]];

    }
}];

剪裁代码

- (void) processImage:(UIImage *)image { //process captured image, crop, resize and rotate

        haveImage = YES;

    CGRect deviceScreen = _previewLayer.bounds;
    CGFloat width = deviceScreen.size.width;
    CGFloat height = deviceScreen.size.height;

    NSLog(@"WIDTH %f", width); // Outputing 320
    NSLog(@"HEIGHT %f", height); // Outputting 320

    UIGraphicsBeginImageContext(CGSizeMake(width, width));
    [image drawInRect: CGRectMake(0, 0, width, width)];

    UIImage *smallImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    CGRect cropRect = CGRectMake(0, 0, width, width);

    CGImageRef imageRef = CGImageCreateWithImageInRect([smallImage CGImage], cropRect);

    CGImageRelease(imageRef);

    [captureImageGrab setImage:[UIImage imageWithCGImage:imageRef]];

}

你能否在processImage中记录图像大小?还有captureImageGrab是什么?它是一个UIImageView吗? - Yuchen
5个回答

18

你尝试过设置videoGravity吗?

previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
previewLayer?.videoGravity = AVLayerVideoGravityResizeAspectFill

9
即使预览层是正方形的,也要记住生成的静态图像保持其原始大小。
从我看来,问题在这里:
UIGraphicsBeginImageContext(CGSizeMake(width, width));
[image drawInRect: CGRectMake(0, 0, width, width)];

你已经通过第一行使你的上下文成为一个正方形了。但你仍需要按原始格式绘制图片,否则它会被该上下文剪裁。在第二行中,你强制将原始图片绘制成正方形,从而使其看起来“挤压”了。
你应该找到合适的图片高度来保持原始比例,同时适应你的“宽度”。接下来,你需要在正方形上下文中以正确的尺寸(保持原始比例)绘制该图片。如果你想裁剪中心,请改变绘图的Y位置。
类似于这样的代码:
- (void) processImage:(UIImage *)image {
    UIGraphicsBeginImageContext(CGSizeMake(width, width));
    CGFloat imageHeight = floorf(width / image.width * image.height);
    CGFloat offsetY = floorf((imageHeight - width) / 2.0f);
    [image drawInRect: CGRectMake(0, -offsetY, width, imageHeight)];
    UIImage *smallImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    [captureImageGrab setImage:smallImage];
}

那应该可以了。

@memyselfandmyiphone,你有时间尝试一下吗?或者你已经解决了你的问题? - Rufel

1

从代码来看,我认为你正在将图像绘制成一个正方形。这是缩小图像而不考虑宽高比的部分。

相反,可以尝试以下方法:

- (void) processImage:(UIImage *)image { //process captured image, crop, resize and rotate

    haveImage = YES;

    CGRect deviceScreen = _previewLayer.bounds;
    CGFloat width = deviceScreen.size.width;
    CGFloat height = deviceScreen.size.height;

    NSLog(@"WIDTH %f", width); // Outputing 320
    NSLog(@"HEIGHT %f", height); // Outputting 320

    CGRect cropRect = CGRectZero;
    if (image.size.width > image.size.height) {
        cropRect.origin.x = (image.size.width-image.size.height)/2;
        cropRect.size = CGSizeMake(image.size.height, image.size.height);
    } else {
        cropRect.origin.y = (image.size.height-image.size.width)/2;
        cropRect.size = CGSizeMake(image.size.width, image.size.width);
    }
    CGImageRef croppedImage = CGImageCreateWithImageInRect(image.CGImage, cropRect);

    UIGraphicsBeginImageContext(CGSizeMake(width, width));
    [[UIImage imageWithCGImage:croppedImage] drawInRect:CGRectMake(0, 0, width, width)];
    UIImage *smallImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    CGImageRelease(croppedImage);

    [captureImageGrab setImage:[UIImage imageWithCGImage:smallImage.CGImage scale:image.scale orientation:image.imageOrientation]];
}

@memyselfandmyiphone,是的,确实可以。请查看更新后的代码(我替换了CGContextDrawImage调用)。 - Jernej Strasner
仍在翻转中。看起来它仍在被压缩。 :/ - memyselfandmyiphone
@memyselfandmyiphone 我又编辑了代码,请检查最后一行。那应该可以解决方向的问题。 - Jernej Strasner
那样固定了方向,但仍会导致图像在垂直方向上压缩。 - memyselfandmyiphone
有什么想法,可能是什么问题? - memyselfandmyiphone

1
因为相机捕捉到的图像不是正方形,所以宽高比被扭曲了。将其绘制成正方形时,它会带有扭曲的宽高比。一种可能的解决方案是保留宽高比将图像绘制成正方形,例如:
- (void) processImage:(UIImage *)image { //process captured image, crop, resize and rotate

    haveImage = YES;

    CGRect deviceScreen = _previewLayer.bounds;
    CGFloat width = deviceScreen.size.width;
    CGFloat height = deviceScreen.size.height;

    NSLog(@"WIDTH %f", width); // Outputing 320
    NSLog(@"HEIGHT %f", height); // Outputting 320

    UIGraphicsBeginImageContext(CGSizeMake(width, width));

    CGFloat aspect_h = image.size.height * width / image.size.width;

    [image drawInRect: CGRectMake(0, -(aspect_h - width)/2.0, width, aspect_h)];

    UIImage *smallImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    CGRect cropRect = CGRectMake(0, 0, width, width);

    CGImageRef imageRef = CGImageCreateWithImageInRect([smallImage CGImage], cropRect);

    CGImageRelease(imageRef);

}

新图片的宽度将为width。该宽度下保持纵横比的高度为aspect_h。然后,图像沿y轴移动以使其垂直居中。
请注意,如果您的应用程序在非纵向方向上运行,则需要进行更多的工作。
另一个观察结果:如果您使用image.size.width作为新图像上下文的宽度,则可以获得更好的分辨率图像。
希望这可以帮到您!

1

我的预览层是正方形,但这对我没有任何帮助:

previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
previewLayer?.videoGravity = AVLayerVideoGravityResizeAspectFill

Rufel的答案就是有效的!这是Swift 5的代码:

func processImageIntoSquare(image: UIImage) -> UIImage? {
    
    guard let previewLayer = previewLayer else { return nil }
    
    let width = previewLayer.bounds.size.width
    
    let size = CGSize(width: width, height: width)
    UIGraphicsBeginImageContext(size)
    
    let imageHeight = floor(width / image.size.width * image.size.height)
    let offSetY = floor((imageHeight - width) / 2.0)
    
    let rect = CGRect(x: 0, y: -offSetY, width: width, height: imageHeight)
    image.draw(in: rect)
    
    let smallImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    
    return smallImage
}

这是我使用它的方式:

MyCameraController: AVCapturePhotoCaptureDelegate {
    
    func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {

        if let error = error { return }

        guard let imageData = photo.fileDataRepresentation(), let imageFromCamera = UIImage(data: imageData) else { return }

        guard let squareImage = processImageIntoSquare(image: imageFromCamera) else { return }

        DispatchQueue.main.async { [weak self] in

            self?.myImageView.image = squareImage
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接