将 AVCaptureVideoPreviewLayer 输出裁剪为正方形

Cropping AVCaptureVideoPreviewLayer output to a square

在抓取可视屏幕的裁剪 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]];

}

查看代码,我觉得您正在将图像绘制成正方形。这是在不考虑宽高比的情况下缩小图像的部分。

相反,做这样的事情:

- (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]];
}

即使预览图层是正方形,请记住生成的静止图像会保持其原始大小。

据我所知,问题出在这里:

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

您已经将上下文与第一行对齐。您仍然需要以原始格式绘制图像,它会被该上下文裁剪。在第二行,您强制将原始图像绘制成正方形,从而使其看起来 "squeezed".

您应该找到合适的图像高度,在适合您的 "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];
}

应该可以了。

由于相机拍摄的图像不是正方形,纵横比失真。将其绘制成正方形时,将以扭曲的纵横比绘制。一种可能的解决方案是将图像绘制到保持纵横比的正方形中,例如:

- (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);

}

新图像的宽度为 宽度。对于该宽度,保持纵横比的高度为 aspect_h。然后,图像沿 y 轴移动,使其垂直居中。

请注意,如果您的应用要在非纵向方向上运行,则需要做更多的工作。

另一个观察结果:如果使用 image.size.width 作为新图像上下文的宽度,您可以获得更好的分辨率图像。

希望对您有所帮助!

您试过设置videoGravity吗?

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

我的 previewLayer 是方形的,这对我没有任何作用:

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

成功了!!!这里是 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
        }
    }
}