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