12

AVFoundation用来展示相机。

我想防止相机本身旋转,以便观看者只能以纵向看到相机,并且只能以纵向模式拍摄图像。

我定义Supported Interface Orientation为仅支持纵向,并且视图本身仅在纵向模式下显示,而不是相机 - 正在随设备方向旋转

如何强制AVFoundation显示相机并仅像 UIViewController 那样以纵向方式捕获图像?

我设置相机的代码:

AVCaptureVideoPreviewLayer* lay = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.sess];
UIView *view = [self videoPreviewView];
CALayer *viewLayer = [view layer];
[viewLayer setMasksToBounds:YES];
CGRect bounds = [view bounds];
[lay setFrame:bounds];
if ([lay respondsToSelector:@selector(connection)])
 {
   if ([lay.connection isVideoOrientationSupported])
   {
      [lay.connection setVideoOrientation:AVCaptureVideoOrientationPortrait];
    }
 }
 [lay setVideoGravity:AVLayerVideoGravityResizeAspectFill];
 [viewLayer insertSublayer:lay below:[[viewLayer sublayers] objectAtIndex:0]];
 self.previewLayer = lay;
4

3 回答 3

10

这是基于我对您的问题的理解的部分答案(与您的其他答案不同)。

您已将应用程序锁定为纵向。因此,无论手机的方向如何,状态栏始终位于手机的纵向顶部。这成功地锁定了您的界面,包括您的 AVCapture 界面。但是您还想锁定来自相机的原始图像馈送,以便图像水平始终与状态栏平行。

理想情况下,这需要连续进行- 如果您的相机处于 45 度角,则图像将反向旋转 45 度。否则,大多数情况下,图像将无法正确对齐(另一种方法是在您的 90 度方向开关更新之前它总是不对齐,这会将图像旋转 90 度)。

为此,您需要使用 Core Motion 和加速度计。您希望将手机 Y 轴的角度设为真正垂直并相应地旋转图像。有关几何细节,请参见此处:

iPhone 方向 - 我如何确定哪个方向是向上的?

使用 Core Motion,从 viewDidLoad 触发此方法

- (void)startAccelerometerUpdates {
    self.coreMotionManager = [[CMMotionManager alloc] init];
    if ([self.coreMotionManager isAccelerometerAvailable] == YES) {
        CGFloat updateInterval = 0.1;
            // Assign the update interval to the motion manager
        [self.coreMotionManager setAccelerometerUpdateInterval:updateInterval];
        [self.coreMotionManager startAccelerometerUpdatesToQueue:[NSOperationQueue mainQueue] 
           withHandler: ^(CMAccelerometerData *accelerometerData, NSError *error) {
             CGFloat angle =  -atan2( accelerometerData.acceleration.x, 
                                      accelerometerData.acceleration.y) 
                               + M_PI ;
             CATransform3D rotate = CATransform3DMakeRotation(angle, 0, 0, 1);
             self.previewLayer.transform = rotate;

         }];
    }
}

a 直立 b 45度 c 90度

电话持有 (a) 肖像;(b) 旋转约 30 度;(c) 风景

您可能会发现这有点跳动,并且设备移动和视图之间有一点延迟。您可以使用 updateInterval,并更深入地使用其他 Core Motion 技巧来抑制运动。(我没有处理手机完全倒置的情况,如果您将相机面朝下或面朝上,结果是不确定 的,已更新代码/使用atan2)。

现在方向相当正确,但您的图像不适合您的视图。您对此无能为力,因为原始相机馈送的格式由其传感器阵列的物理尺寸固定。解决方法是缩放图像,以便您在各个角度都有足够的多余图像数据,以便您可以裁剪图像以适合您想要的纵向格式。

在界面生成器中:

  • 将您的 previewLayer 的视图设置为以它的超级视图为中心的正方形,宽度和高度等于可见图像区域的对角线(sqrt (width 2 +height 2 )

在代码中:

- (void)resizeCameraView
{
    CGSize size = self. videoPreviewView.bounds.size;
    CGFloat diagonal = sqrt(pow(size.width,2)+pow(size.height,2));
    diagonal = 2*ceil(diagonal/2);  //rounding
    self.videoPreviewView.bounds = (CGRect){0,0,diagonal,diagonal};
}

如果您在代码中执行此操作,resizeCameraView那么如果您从viewDidLoad. 确保这self.videoPreviewView是您对正确视图的 IBOutlet 引用。

现在,当您拍照时,您将从相机阵列中捕获整个“原始”图像数据,这些数据将以横向格式显示。它将与显示旋转的方向标志一起保存。但您可能想要的是保存屏幕上看到的照片。这意味着您必须在保存照片之前旋转和裁剪照片以匹配您的屏幕视图,并删除它的方向元数据。那是你要解决的问题(“部分答案”的另一部分):我怀疑你可能会认为整个方法不能让你得到你想要的(我认为你真正想要的是一个摄像头传感器- 逆着设备的旋转旋转以保持地平线稳定)。

更新
更改为从而不是startAccelerometerUpdates获取角度,更平滑并考虑所有方向而无需摆弄atan2acos

更新 2
从您的评论来看,您的旋转预览层似乎卡住了?我无法复制您的错误,它必须在您的代码或设置中的其他位置。

为了让您可以使用干净的代码进行检查,我已将我的解决方案添加到Apple 的 AVCam 项目中,因此您可以对其进行检查。这是做什么:

  • 将 Core Motion 框架添加到 AVCam。

在 AVCamViewController.m

  • #import <CoreMotion/CoreMotion.h>
  • 添加我的startAccelerometerUpdates方法
  • 添加我的resizeCameraView方法(将这两个方法都放在类文件的顶部附近,否则您可能会感到困惑,该文件中有多个@implementations)
  • 添加行:[self resizeCameraView];to viewDidLoad(可以是方法的第一行)
  • 将属性添加
    @property (strong, nonatomic) CMMotionManager* coreMotionManager
    到@interface(它不需要是属性,但我的方法假定它存在,所以如果你不添加它,你将不得不修改我的方法)。

startAccelerometerUpdates更改此行:

    self.previewLayer.transform = rotate;  

到:

    self.captureVideoPreviewLayer.transform = rotate;

此外,在 中的 Objects 列表中AVCamViewController.xib,将 videoPreview 视图移到 ToolBar 上方(否则,当您放大它时,您会覆盖控件)

请务必禁用旋转 - 对于 iOS<6.0,这已经是正确的,但对于 6.0+,您需要在目标摘要中仅选择支持方向的纵向。

我认为这是我对 AVCam 所做更改的完整列表,并且旋转/方向都运行良好。我建议你尝试做同样的事情。如果你能让它顺利工作,你就知道代码中的某个地方还有其他故障。如果你仍然发现你的旋转棒,我很想知道更多关于你的硬件和软件环境的信息,比如你正在测试哪些设备。

我正在 XCode 4.6/OSX10.8.2 上编译,并测试:

- iPhone4S  /  iOS5.1  
- iPhone3G  /  iOS6.1  
- iPad mini /  iOS6.1 

所有结果都是平滑和准确的。

于 2013-02-12T03:39:14.560 回答
2

我想您需要使用此方法来限制相机旋转。

AVCaptureConnection *videoConnection = [CameraVC connectionWithMediaType:AVMediaTypeVideo fromConnections:[imageCaptureOutput connections]];
if ([videoConnection isVideoOrientationSupported])
{
    [videoConnection setVideoOrientation:[UIDevice currentDevice].orientation];
}

假设您的预览层被定义为属性,可以使用

[self.previewLayer setOrientation:[[UIDevice currentDevice] orientation]];

在您的情况下,您可以将 [[UIDevice currentDevice]orientation] 替换为 UIInterfaceOrientationPortrait

已编辑

实际需要时尝试添加预览层。例子

preview = [[self videoPreviewWithFrame:CGRectMake(0, 0, 320, 480)] retain];
[self.view addSubview:preview];

videoPreviewWithFrame 函数。

- (UIView *) videoPreviewWithFrame:(CGRect) frame 
{
  AVCaptureVideoPreviewLayer *tempPreviewLayer = [[AVCaptureVideoPreviewLayer alloc]initWithSession:[self captureSession]];
  [tempPreviewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
  tempPreviewLayer.frame = frame;

  UIView* tempView = [[UIView alloc] init];
  [tempView.layer addSublayer:tempPreviewLayer];
  tempView.frame = frame;

  [tempPreviewLayer autorelease];
  [tempView autorelease];
  return tempView;
}
于 2013-02-10T05:29:35.847 回答
2

假设您的 previewlayer 被添加到 viewcontroller 视图中。这样做viewDidLoad

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationChanged:) name:UIDeviceOrientationDidChangeNotification object:nil];

并将选择器定义为:

- (void)orientationChanged:(NSNotification*)notification {
    UIInterfaceOrientation interfaceOrientation = [UIApplication sharedApplication].statusBarOrientation;
    if ([self.previewlayer respondsToSelector:@selector(orientation)]) {
        //for iOS5
        if (interfaceOrientation != UIInterfaceOrientationPortrait) {
            self.previewlayer.orientation = (AVCaptureVideoOrientation)UIInterfaceOrientationPortrait;
        }
    } else {
        //for iOS6
        if (interfaceOrientation != UIInterfaceOrientationPortrait) {
            self.previewlayer.connection.videoOrientation = (AVCaptureVideoOrientation)UIInterfaceOrientationPortrait;
        }
    }
}

注意:放入tempPreviewLayer属性self.previewlayer

当设备方向改变时,这将强制预览层到纵向位置。

编辑

您也可以在 viewController 的“shouldAutoRotate”方法中添加它

    - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
    {
        // Return YES for supported orientations
        if ([self.previewlayer respondsToSelector:@selector(orientation)]) {
            if (interfaceOrientation != UIInterfaceOrientationPortrait) {
                self.previewlayer.orientation = (AVCaptureVideoOrientation)UIInterfaceOrientationPortrait;
            }
        } else {
            if (interfaceOrientation != UIInterfaceOrientationPortrait) {
                self.previewlayer.connection.videoOrientation = (AVCaptureVideoOrientation)UIInterfaceOrientationPortrait;
            }
        }

        return (interfaceOrientation == UIInterfaceOrientationPortrait);
    }

对于 ios6 骑这两个并检查。

-(NSUInteger)supportedInterfaceOrientations {
    //UIInterfaceOrientation interfaceOrientation = [UIApplication sharedApplication].statusBarOrientation;
    //return (
    //interfaceOrientation == UIInterfaceOrientationLandscapeLeft |
    //interfaceOrientation == UIInterfaceOrientationLandscapeRight);
    return UIInterfaceOrientationMaskLandscape;//(UIInterfaceOrientationLandscapeLeft | UIInterfaceOrientationLandscapeRight);
}

- (BOOL)shouldAutorotate {
    return YES;
}

return这两种方法之前附加代码..并在我给出的通知中,看看它是否在您旋转设备时被调用。

于 2013-02-11T10:10:00.757 回答