2011-10-21 22 views
7

Tôi đang sử dụng các lớp AVFoundation để triển khai máy ảnh tùy chỉnh trong ứng dụng của mình. Tôi chỉ đang chụp ảnh tĩnh chứ không phải video. Tôi có tất cả mọi thứ làm việc nhưng tôi bị mắc kẹt bởi một cái gì đó. Tôi xem xét hướng thiết bị khi chụp ảnh tĩnh và đặt videoOrientation của kết nối video một cách thích hợp. Đoạn mã:Tại sao chế độ phong cảnh AVCaptureVideoOrientation dẫn đến hình ảnh tĩnh lộn ngược?

// set the videoOrientation based on the device orientation to 
    // ensure the pic is right side up for all orientations 
    AVCaptureVideoOrientation videoOrientation; 
    switch ([UIDevice currentDevice].orientation) { 
     case UIDeviceOrientationLandscapeLeft: 
      // Not clear why but the landscape orientations are reversed 
      // if I use AVCaptureVideoOrientationLandscapeLeft here the pic ends up upside down 
      videoOrientation = AVCaptureVideoOrientationLandscapeRight; 
      break; 
     case UIDeviceOrientationLandscapeRight: 
      // Not clear why but the landscape orientations are reversed 
      // if I use AVCaptureVideoOrientationLandscapeRight here the pic ends up upside down 
      videoOrientation = AVCaptureVideoOrientationLandscapeLeft; 
      break; 
     case UIDeviceOrientationPortraitUpsideDown: 
      videoOrientation = AVCaptureVideoOrientationPortraitUpsideDown; 
      break; 
     default: 
      videoOrientation = AVCaptureVideoOrientationPortrait; 
      break; 
    } 

    videoConnection.videoOrientation = videoOrientation; 

Lưu ý nhận xét của tôi trong trường hợp ngang. Tôi phải đảo ngược bản đồ định hướng hoặc hình ảnh kết quả bị lộn ngược. Tôi chụp và lưu hình ảnh với mã sau:

[self.stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection 
    completionHandler:^(CMSampleBufferRef imageSampleBuffer, NSError *error) 
    { 
     NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer]; 
     self.stillImage = [UIImage imageWithData:imageData]; 
     // notify observers (image gets saved to the camera roll)               
     [[NSNotificationCenter defaultCenter] postNotificationName:CaptureSessionManagerDidCaptureStillImageNotification object:self]; 
     self.stillImage = nil; 
}]; 

Không có xử lý hoặc thao tác hình ảnh khác.

Ứng dụng của tôi hoạt động với mã ở trên. Tôi chỉ cố gắng hiểu tại sao hằng số định hướng phải được đảo ngược để định hướng phong cảnh. Cảm ơn!

+0

Không sử dụng định hướng UIDevice khi bạn thực sự muốn sử dụng Định hướng UII. – Till

+0

Bạn không thể sử dụng định hướng 'UIInterface' trong trường hợp này. Hướng thiết bị tại thời điểm chụp ảnh luôn chính xác. định hướng hình ảnh của 'stillImage' sau khi dữ liệu đó được chụp là không chính xác. Tôi đã có thể giải quyết vấn đề bằng cách sử dụng UIDeviceOrientation và dịch sang UIImageOrientation thích hợp khi quay. Vào thời điểm đó, không ai đang nhét vào đây nên tôi không đăng mã. Nếu tôi có thời gian tôi sẽ thêm mã. – XJones

+0

@XJones, vui lòng cập nhật câu trả lời của bạn với giải pháp chính xác. – Hemang

Trả lời

20

Heh, có vẻ như không ai cảm thấy như đang tút vào cái này. Hóa ra câu trả lời rất đơn giản. Ảnh chụp thông qua phương thức stillImageOutput captureStillImageAsynchronouslyFromConnection:... luôn kết thúc với các thuộc tính sau:

  • UIImage định hướng = luôn UIImageOrientationRight bất kể hướng thiết bị
  • kích thước
  • UIImage = W x H (ví dụ như bức chân dung chiều cao chiều rộng x chân dung, phụ thuộc vào máy ảnh của bạn độ phân giải)
  • CGImage size = phụ thuộc vào hướng thiết bị (ví dụ như bức chân dung hoặc phong cảnh)

Vì vậy, các giải pháp để xoay hình ảnh lên là sử dụng hướng thiết bị kết hợp với kích thước CGImage để áp dụng một biến đổi affine thích hợp. Như tôi đang trả lời câu hỏi của riêng tôi, tôi không phải là giải pháp trong mã nhưng tôi đã kết thúc viết một thói quen gọi là:

- (UIImage *)imageRotatedUpForDeviceOrientation:(UIDeviceOrientation)deviceOrientation 

trong một thể loại UIImage chứa cải tiến xử lý hình ảnh khác nhau.

EDIT - Thực hiện Ví dụ

Tôi đã nhận được một số yêu cầu đối với mã chức năng về vấn đề này. Tôi đã trích xuất triển khai có liên quan từ một ứng dụng đang hoạt động.

// this method is implemented in your capture session manager (wherever AVCaptureSession is used) 
// capture a still image and save the device orientation 
- (void)captureStillImage 
{ 
    UIDeviceOrientation currentDeviceOrientation = UIDevice.currentDevice.orientation; 
     [self.stillImageOutput 
     captureStillImageAsynchronouslyFromConnection:self.videoConnection 
     completionHandler:^(CMSampleBufferRef imageSampleBuffer, NSError *error) { 
      NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer]; 
      if (imageData) { 
       UIImage *image = [UIImage imageWithData:imageData]; 
       NSDictionary *captureInfo = { 
        @"image" : image, 
        @"deviceOrientation" : @(currentDeviceOrientation) 
       }; 
       // TODO: send image & orientation to delegate or post notification to observers 
      } 
      else { 
       // TODO: handle image capture error 
      } 
    }]; 
} 

// this method rotates the UIImage captured by the capture session manager based on the 
// device orientation when the image was captured 
- (UIImage *)imageRotatedUpFromCaptureInfo:(NSDictionary *)captureInfo 
{ 
    UIImage *image = [captureInfo objectForKey:@"image"]; 
    UIDeviceOrientation deviceOrientation = [[captureInfo objectForKey:@"deviceOrientation"] integerValue]; 
    UIImageOrientation rotationOrientation = [self rotationNeededForImageCapturedWithDeviceOrientation:deviceOrientation]; 
    // TODO: scale the image if desired 
    CGSize newSize = image.size; 
    return [imageScaledToSize:newSize andRotatedByOrientation:rotationOrientation]; 
} 

// return a scaled and rotated an image 
- (UIImage *)imageScaledToSize:(CGSize)newSize andRotatedByOrientation:(UIImageOrientation)orientation 
{ 
    CGImageRef imageRef = self.CGImage;  
    CGRect imageRect = CGRectMake(0.0, 0.0, newSize.width, newSize.height); 
    CGRect contextRect = imageRect; 
    CGAffineTransform transform = CGAffineTransformIdentity; 

    switch (orientation) 
    { 
     case UIImageOrientationDown: { // rotate 180 deg 
      transform = CGAffineTransformTranslate(transform, imageRect.size.width, imageRect.size.height); 
      transform = CGAffineTransformRotate(transform, M_PI); 
     } break; 

     case UIImageOrientationLeft: { // rotate 90 deg left 
      contextRect = CGRectTranspose(contextRect); 
      transform = CGAffineTransformTranslate(transform, imageRect.size.height, 0.0); 
      transform = CGAffineTransformRotate(transform, M_PI/2.0); 
     } break; 

     case UIImageOrientationRight: { // rotate 90 deg right 
      contextRect = CGRectTranspose(contextRect); 
      transform = CGAffineTransformTranslate(transform, 0.0, imageRect.size.width); 
      transform = CGAffineTransformRotate(transform, 3.0 * M_PI/2.0); 
     } break; 

     case UIImageOrientationUp: // no rotation 
     default: 
      break; 
    } 

    CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef); 
    CGColorSpaceRef colorSpaceRef = CGImageGetColorSpace(imageRef); 

    // madify bitmapInfo to work with PNG if necessary 
    if (bitmapInfo == kCGImageAlphaNone) { 
     bitmapInfo = kCGImageAlphaNoneSkipLast; 
    } 
    else if (bitmapInfo == kCGImageAlphaLast) { 
     bitmapInfo = kCGImageAlphaPremultipliedLast; 
    } 

    // Build a context that's the same dimensions as the new size 
    CGContextRef context = CGBitmapContextCreate(NULL, 
               contextRect.size.width, 
               contextRect.size.height, 
               CGImageGetBitsPerComponent(imageRef), 
               0, 
               colorSpaceRef, 
               bitmapInfo); 


    CGContextConcatCTM(context, transform); 
    CGContextDrawImage(context, imageRect, imageRef); 

    // Get the rotated image from the context and a UIImage 
    CGImageRef rotatedImageRef = CGBitmapContextCreateImage(context); 
    UIImage *rotatedImage = [UIImage imageWithCGImage:rotatedImageRef]; 

    // Clean up 
    CGImageRelease(rotatedImageRef); 
    CGContextRelease(context); 

    return rotatedImage; 
} 

// return the UIImageOrientation needed for an image captured with a specific deviceOrientation 
- (UIImageOrientation)rotationNeededForImageCapturedWithDeviceOrientation:(UIDeviceOrientation)deviceOrientation 
{ 
    UIImageOrientation rotationOrientation; 
    switch (deviceOrientation) { 
     case UIDeviceOrientationPortraitUpsideDown: { 
      rotationOrientation = UIImageOrientationLeft; 
     } break; 

     case UIDeviceOrientationLandscapeRight: { 
      rotationOrientation = UIImageOrientationDown; 
     } break; 

     case UIDeviceOrientationLandscapeLeft: { 
      rotationOrientation = UIImageOrientationUp; 
     } break; 

     case UIDeviceOrientationPortrait: 
     default: { 
      rotationOrientation = UIImageOrientationRight; 
     } break; 
    } 
    return rotationOrientation; 
} 
+7

Câu trả lời hay nhưng một vài dòng thực hiện sẽ tiết kiệm cho tôi một số cách gõ! 8 -) – kalperin

+3

@XJones Tôi không thể hiểu được dòng mã dưới đây, bạn có thể giải thích điều này cho tôi không? contextRect = CGRectTranspose (contextRect); –

+1

CGRectTranspose WTF – jjxtra

6

Đối với lý do tại sao bạn cần AVCaptureVideoOrientationLandscape Ngay khi định hướng của thiết bị là UIDeviceOrientationLandscape Left, đó là bởi vì, đối với một số lý do, UIDeviceOrientationLandscape Left == UIInterfaceOrientationLandscape Ngay và AVCaptureVideoOrientations đang theo dõi các UIInterfaceOrientation convention.

Ngoài ra, UIDeviceOrientation bao gồm các tùy chọn khác như UIDeviceOrientationFaceUp, UIDeviceOrientationFaceDown và UIDeviceOrientationUnknown. Nếu bạn có giao diện xoay để khớp với hướng của thiết bị, bạn có thể thử nhận được UIDeviceOrientation từ [UIApplication sharedApplication] .statusBarOrientation thay thế.

+3

Có thể là Phong cảnh trái/phải là do sử dụng camera trước và sau? – VaporwareWolf

+0

Không, các giá trị bằng nhau cho cả máy ảnh trước và sau. –

0

Thực ra, không có lỗi trong quá trình triển khai của bạn và bạn có thể loại bỏ toàn bộ trường hợp chuyển đổi.

Tại sao trường hợp chuyển đổi có thể được loại bỏ:

Các giá trị corressponding của thiết bị và video hướng là số lượng bằng nhau, tức là chừng nào hướng thiết bị không phải là không rõ, đối mặt lên hoặc úp mặt xuống, bạn có thể đánh đồng UIDeviceOrientation và AVCaptureVideoOrientation

Giới thiệu về danh pháp khó hiểu:

Device hướng LandscapeLeft => để p trên điện thoại của bạn ở bên trái.

Vì vậy, hãy tưởng tượng vị trí điểm bắt đầu của video là ..... đúng vậy, góc trên cùng bên phải => Phong cảnh bên phải.

Tương tự như ở hướng ngang khác, hướng video nằm ở phía trên cùng của điện thoại. Đây không phải là khó hiểu trong trường hợp của bức chân dung và lộn ngược

Proof

Khi bạn chụp một hình ảnh tĩnh, kiểm tra giá trị định hướng EXIF. Đối với cảnh quan bên trái, định hướng EXIF ​​là 3 (180 độ đảo ngược) Đối với cảnh quan đúng, định hướng EXIF ​​là 1 (không xoay)

Khi bạn đang hiển thị, định hướng exif của bạn được chuyển thành tương ứng UIImageOrientation, do đó nên không đảo ngược.

Trong trường hợp camera trước, tôi đã nhận thấy rằng giá trị EXIF, chỉ cần làm theo các giá trị định hướng video (hiệu ứng gương không được quan tâm). Chỉ sử dụng giá trị 1, 3, 6, 8 của EXIF.

Ngoài ra, gương luôn là dọc theo đường từ đỉnh của thiết bị xuống dưới Vì vậy, cảnh quan sẽ có vẻ lộn ngược trong khi chân dung và lộn ngược chân dung chỉ được nhân đôi