web-dev-qa-db-ja.com

AVCaptureからディスプレイに表示される四角形に画像をトリミングする方法

私はそれを機能させることができないので、これは私を夢中にさせています。次のシナリオがあります。

AVCaptureSessionAVCaptureVideoPreviewLayerを使用して、独自のカメラインターフェイスを作成しています。インターフェイスには長方形が表示されます。以下は、画面全体を埋めるAVCaptureVideoPreviewLayerです。

キャプチャした画像をトリミングして、結果の画像がディスプレイの四角形に表示されるコンテンツを正確に表示するようにします。

私のセットアップは次のようになります:

_session = [[AVCaptureSession alloc] init];
AVCaptureSession *session = _session;
session.sessionPreset = AVCaptureSessionPresetPhoto;

AVCaptureDevice *camera = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
if (camera == nil) {
    [self showImagePicker];
    _isSetup = YES;
    return;
}
AVCaptureVideoPreviewLayer *captureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:session];
captureVideoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;

captureVideoPreviewLayer.frame = self.liveCapturePlaceholderView.bounds;
[self.liveCapturePlaceholderView.layer addSublayer:captureVideoPreviewLayer];

NSError *error;
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:camera error:&error];
if (error) {
    HGAlertViewWrapper *av = [[HGAlertViewWrapper alloc] initWithTitle:kFailedConnectingToCameraAlertViewTitle message:kFailedConnectingToCameraAlertViewMessage cancelButtonTitle:kFailedConnectingToCameraAlertViewCancelButtonTitle otherButtonTitles:@[kFailedConnectingToCameraAlertViewRetryButtonTitle]];
    [av showWithBlock:^(NSString *buttonTitle){
        if ([buttonTitle isEqualToString:kFailedConnectingToCameraAlertViewCancelButtonTitle]) {
            [self.delegate gloameCameraViewControllerDidCancel:self];
        }
        else {
            [self setupAVSession];
        }
    }];
}
[session addInput:input];

NSDictionary *options = @{ AVVideoCodecKey : AVVideoCodecJPEG };
_stillImageOutput = [[AVCaptureStillImageOutput alloc] init];
[_stillImageOutput setOutputSettings:options];

[session addOutput:_stillImageOutput];

[session startRunning];
_isSetup = YES;

私はこのように画像をキャプチャしています:

[_stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler: ^(CMSampleBufferRef imageSampleBuffer, NSError *error)
 {
     if (error) {
         MWLogDebug(@"Error capturing image from camera. %@, %@", error, [error userInfo]);
         _capturePreviewLayer.connection.enabled = YES;
     }
     else
     {
         NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];
         UIImage *image = [[UIImage alloc] initWithData:imageData];

         CGRect cropRect = [self createCropRectForImage:image];
         UIImage *croppedImage;// = [self cropImage:image toRect:cropRect];
         UIGraphicsBeginImageContext(cropRect.size);
         [image drawAtPoint:CGPointMake(-cropRect.Origin.x, -cropRect.Origin.y)];
         croppedImage = UIGraphicsGetImageFromCurrentImageContext();
         UIGraphicsEndImageContext();
         self.capturedImage = croppedImage;
         [_session stopRunning];             
     }
 }];

の中に createCropRectForImage:メソッド画像から切り出すために、さまざまな方法で四角形を計算してみましたが、今のところ成功していません。

- (CGRect)createCropRectForImage:(UIImage *)image
{
    CGPoint maskTopLeftCorner = CGPointMake(self.maskRectView.frame.Origin.x, self.maskRectView.frame.Origin.y);
    CGPoint maskBottomRightCorner = CGPointMake(self.maskRectView.frame.Origin.x + self.maskRectView.frame.size.width, self.maskRectView.frame.Origin.y + self.maskRectView.frame.size.height);

    CGPoint maskTopLeftCornerInLayerCoords = [_capturePreviewLayer convertPoint:maskTopLeftCorner fromLayer:self.maskRectView.layer.superlayer];
    CGPoint maskBottomRightCornerInLayerCoords = [_capturePreviewLayer convertPoint:maskBottomRightCorner fromLayer:self.maskRectView.layer.superlayer];
    CGPoint maskTopLeftCornerInDeviceCoords = [_capturePreviewLayer captureDevicePointOfInterestForPoint:maskTopLeftCornerInLayerCoords];
    CGPoint maskBottomRightCornerInDeviceCoords = [_capturePreviewLayer captureDevicePointOfInterestForPoint:maskBottomRightCornerInLayerCoords];

    float x = maskTopLeftCornerInDeviceCoords.x * image.size.width;
    float y = (1 - maskTopLeftCornerInDeviceCoords.y) * image.size.height;
    float width = fabsf(maskTopLeftCornerInDeviceCoords.x - maskBottomRightCornerInDeviceCoords.x) * image.size.width;
    float height = fabsf(maskTopLeftCornerInDeviceCoords.y - maskBottomRightCornerInDeviceCoords.y) * image.size.height;

    return CGRectMake(x, y, width, height);
}

それは私の現在のバージョンですが、プロポーションが正しくありません。誰か助けてください!

私はこの方法を使って画像をトリミングすることも試みました:

- (UIImage*)cropImage:(UIImage*)originalImage toRect:(CGRect)rect{

    CGImageRef imageRef = CGImageCreateWithImageInRect([originalImage CGImage], rect);

    CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
    CGColorSpaceRef colorSpaceInfo = CGImageGetColorSpace(imageRef);
    CGContextRef bitmap = CGBitmapContextCreate(NULL, rect.size.width, rect.size.height, CGImageGetBitsPerComponent(imageRef), CGImageGetBytesPerRow(imageRef), colorSpaceInfo, bitmapInfo);

    if (originalImage.imageOrientation == UIImageOrientationLeft) {
        CGContextRotateCTM (bitmap, radians(90));
        CGContextTranslateCTM (bitmap, 0, -rect.size.height);

    } else if (originalImage.imageOrientation == UIImageOrientationRight) {
        CGContextRotateCTM (bitmap, radians(-90));
        CGContextTranslateCTM (bitmap, -rect.size.width, 0);

    } else if (originalImage.imageOrientation == UIImageOrientationUp) {
        // NOTHING
    } else if (originalImage.imageOrientation == UIImageOrientationDown) {
        CGContextTranslateCTM (bitmap, rect.size.width, rect.size.height);
        CGContextRotateCTM (bitmap, radians(-180.));
    }

    CGContextDrawImage(bitmap, CGRectMake(0, 0, rect.size.width, rect.size.height), imageRef);
    CGImageRef ref = CGBitmapContextCreateImage(bitmap);

    UIImage *resultImage=[UIImage imageWithCGImage:ref];
    CGImageRelease(imageRef);
    CGContextRelease(bitmap);
    CGImageRelease(ref);

    return resultImage;
}

誰かがこれを機能させるための方法の「正しい組み合わせ」を持っていますか? :)

40
Tobi

metadataOutputRectOfInterestForRect関数を使用してこの問題を解決しました。

それはあらゆる方向で動作します。

[_stillImageOutput captureStillImageAsynchronouslyFromConnection:stillImageConnection
                                               completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error)
 {
     if (error)
     {
         [_delegate cameraView:self error:@"Take picture failed"];
     }
     else
     {

         NSData *jpegData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
         UIImage *takenImage = [UIImage imageWithData:jpegData];

         CGRect outputRect = [_previewLayer metadataOutputRectOfInterestForRect:_previewLayer.bounds];
         CGImageRef takenCGImage = takenImage.CGImage;
         size_t width = CGImageGetWidth(takenCGImage);
         size_t height = CGImageGetHeight(takenCGImage);
         CGRect cropRect = CGRectMake(outputRect.Origin.x * width, outputRect.Origin.y * height, outputRect.size.width * width, outputRect.size.height * height);

         CGImageRef cropCGImage = CGImageCreateWithImageInRect(takenCGImage, cropRect);
         takenImage = [UIImage imageWithCGImage:cropCGImage scale:1 orientation:takenImage.imageOrientation];
         CGImageRelease(cropCGImage);

     }
 }
 ];

TakeImageは、依然としてimageOrientationに依存する画像です。さらに画像処理を行うために方向情報を削除できます。

UIGraphicsBeginImageContext(takenImage.size);
[takenImage drawAtPoint:CGPointZero];
takenImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
43
UnknownStack

Swift 3:

private func cropToPreviewLayer(originalImage: UIImage) -> UIImage {
    let outputRect = previewLayer.metadataOutputRectOfInterest(for: previewLayer.bounds)
    var cgImage = originalImage.cgImage!
    let width = CGFloat(cgImage.width)
    let height = CGFloat(cgImage.height)
    let cropRect = CGRect(x: outputRect.Origin.x * width, y: outputRect.Origin.y * height, width: outputRect.size.width * width, height: outputRect.size.height * height)

    cgImage = cgImage.cropping(to: cropRect)!
    let croppedUIImage = UIImage(cgImage: cgImage, scale: 1.0, orientation: originalImage.imageOrientation)

    return croppedUIImage
}
47

Swift 4

never強制的にアンラップしてクラッシュを回避したいので、オプションでguardsを使用しています。

private func cropToPreviewLayer(originalImage: UIImage) -> UIImage? {
    guard let cgImage = originalImage.cgImage else { return nil }

    let outputRect = previewLayer.metadataOutputRectConverted(fromLayerRect: previewLayer.bounds)

    let width = CGFloat(cgImage.width)
    let height = CGFloat(cgImage.height)
    let cropRect = CGRect(x: outputRect.Origin.x * width, y: outputRect.Origin.y * height, width: outputRect.size.width * width, height: outputRect.size.height * height)

    if let croppedCGImage = cgImage.cropping(to: cropRect) {
        return UIImage(cgImage: croppedCGImage, scale: 1.0, orientation: originalImage.imageOrientation)
    }

    return nil
}
4
Alex Wall

AVMakeRectWithAspectRatioInsideRectこのAPIはAVFoundationからのものであり、指定されたトリミングサイズで画像のトリミング領域を返します。

0
rui