iOS下使用OpenCV来控制相机

本文翻译自iOS  Application  Development  with  OpenCV 3

iOS SDK和OpenCV提供了几个用于摄像头控制的编程接口。 在iOS SDK中,AVFoundation是用于视听(AV)内容的所有录制和回放的通用框架。 AVFoundation提供对iOS相机参数的完全访问,包括图像格式,焦距,曝光,闪光,帧速率和数字变焦(裁剪因子)。 但是,AVFoundation无法解决任何GUI问题。 应用程序开发人员可以创建自定义相机GUI,使用提供GUI的更高级别框架,或自动化相机,使其在没有GUI输入的情况下运行。 AVFoundation足够灵活,可以支持任何这些设计,但由于AVFoundation很复杂,这种灵活性是有代价的。

iOS SDK在UIImagePickerController类中实现了标准的相机GUI,该类在AVFoundation上构建。 该GUI使用户能够配置相机并捕获照片或视频。 应用程序开发人员可以在捕获后处理照片或视频,但可以选择自定义控件和视频预览有限。

OpenCV提供了一个CvVideoCamera类,它实现了高级摄像机控制功能和预览GUI,但支持高度自定义。 CvVideoCamera在AVFoundation之上构建,并提供对某些底层类的访问。 因此,应用程序开发人员可以选择使用高级CvVideoCamera功能和较低级别AVFoundation功能的组合。 应用程序开发人员实现了大部分GUI,可能会禁用视频预览或指定其中CvVideoCamera将呈现它的父视图。 此外,应用程序可以在捕获时处理每个视频帧,并且如果应用程序就地编辑捕获的帧,则CvVideoCamera将在预览中显示结果。

子类化CvVideoCamera

CvVideoCamera是一个Objective-C类,Objective-C允许我们覆盖子类中的任何实例方法或属性。 而且,由于OpenCV是开源的,我们可以研究CvVideoCamera的整个实现。 因此,我们有能力和知识来创建一个子类,通过修改重新实现CvVideoCamera的各个部分。 这是一种调整或修补开源类实现的便捷方式,无需修改和重建库的源代码。

我们将创建一个名为VideoCamera的子类。 添加一个名为VideoCamera.h的新头文件。 在这里,我们将声明子类的公共接口,包括一个新的属性和方法,如下面的代码所示:

#import

  @interface VideoCamera : CvVideoCamera

  @property BOOL letterboxPreview;

  - (void)setPointOfInterestInParentViewSpace:(CGPoint)point;

  @end

setPointOfInterestInParentViewSpace:方法将为相机的自动对焦和自动曝光算法设置一个感兴趣的点。

现在,让我们创建类的实现文件VideoCamera.m。 我们将添加一个带有属性customPreviewLayer的私有接口,如以下代码所示:

#import "VideoCamera.h"

  @interface VideoCamera ()

  @property (nonatomic, retain) CALayer *customPreviewLayer;

@end

我们将实现customPreviewLayer,以便它访问一个变量_customPreviewLayer,它在父类的私有接口中定义。此变量是视频预览图层,我们将在VideoCamera中自定义其位置和大小。 以下是开始实现VideoCamera并设置属性和变量之间关系的代码:

@implementation VideoCamera

  @synthesize customPreviewLayer = _customPreviewLayer;

接下来,让我们考虑设置PointOfInterestInParentViewSpace:方法.这个方法实现将涉及几个案例,但概念相当简单。作为参数,我们接受预览的父视图的坐标系中的一个点。如果我们假设调用者是视图控制器,那么这是一个方便的坐标系。 AVFoundation允许我们指定焦点和曝光的兴趣点,但它使用比例横向坐标系。这意味着左上角为(0.0,0.0),右下角为(1.0,1.0),轴方向基于横向右方向,与设备的实际方向无关。横向右方向意味着设备的主页按钮位于用户的右侧。因此,正X指向主页按钮,正Y指向远离音量按钮。这是我们方法的实现,它检查相机的自动曝光和自动对焦功能,执行坐标转换,验证坐标,并通过AVFoundation功能设置感兴趣的点:

- (void)setPointOfInterestInParentViewSpace:(CGPoint)parentViewPoint {

    if (!self.running) {

        return;

    }

    NSArray *captureDevices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];

    AVCaptureDevice *captureDevice;

    for (captureDevice in captureDevices) {

        if (captureDevice.position == self.defaultAVCaptureDevicePosition) {

            break;

        }

    }

    BOOL canSetFocus = [captureDevice isFocusModeSupported:AVCaptureFocusModeAutoFocus] && captureDevice.isFocusPointOfInterestSupported;

    BOOL canSetExposure = [captureDevice isExposureModeSupported:AVCaptureExposureModeAutoExpose] && captureDevice.isExposurePointOfInterestSupported;

    if (!canSetFocus && !canSetExposure) {


        return;

    }

    if (![captureDevice lockForConfiguration:nil]) {

        return;

    }

    CGFloat offsetX = 0.5 * (self.parentView.bounds.size.width - self.customPreviewLayer.bounds.size.width);

    CGFloat offsetY = 0.5 * (self.parentView.bounds.size.height - self.customPreviewLayer.bounds.size.height);

    CGFloat focusX =  (parentViewPoint.x - offsetX) / self.customPreviewLayer.bounds.size.width;

    CGFloat focusY =  (parentViewPoint.y - offsetY) / self.customPreviewLayer.bounds.size.height;

    if (focusX < 0.0 || focusX > 1.0 || focusY < 0.0 || focusY > 1.0) {

        return;

    }

    switch (self.defaultAVCaptureVideoOrientation) {

        case AVCaptureVideoOrientationPortraitUpsideDown:{

            CGFloat oldFocusX = focusX;

            focusX = 1.0 - focusY;

            focusY = oldFocusX;

            break;

        }

        case AVCaptureVideoOrientationLandscapeLeft:{

            focusX = 1.0 - focusX;

            focusY = 1.0 - focusY;

            break;

        }

        case AVCaptureVideoOrientationLandscapeRight:{

            break;

        }

        default:{

            CGFloat oldFocuX = focusX;

            focusX = focusY;

            focusY = 1.0 - oldFocuX;

            break;

        }

    }

    if (self.defaultAVCaptureDevicePosition == AVCaptureDevicePositionFront) {

        focusX = 1.0 - focusX;

    }

    CGPoint focusPoint = CGPointMake(focusX, focusY);

    if (canSetFocus) {

        captureDevice.focusMode = AVCaptureFocusModeAutoFocus;

        captureDevice.focusPointOfInterest = focusPoint;

    }

    if (canSetExposure) {

        captureDevice.exposureMode = AVCaptureExposureModeAutoExpose;

        captureDevice.exposurePointOfInterest = focusPoint;

    }

    [captureDevice unlockForConfiguration];

}

此时,我们已经实现了一个类,该类能够配置摄像机并捕获帧。但是,我们仍然需要实现另一个类来选择一个配置并接收帧。

在视图控制器中使用CvVideoCamera子类

以下是viewDidLoad的实现:

- (void)viewDidLoad {

    [super viewDidLoad];

    UIImage *originalStillImage = [UIImage imageNamed:@"Fleur.jpg"];

    UIImageToMat(originalStillImage, originalStillMat);

    self.videoCamera = [[VideoCamera alloc] initWithParentView:self.imageView];

    self.videoCamera.delegate = self;

    self.videoCamera.defaultAVCaptureSessionPreset = AVCaptureSessionPresetHigh;

    self.videoCamera.defaultFPS = 30;

    self.videoCamera.letterboxPreview = YES;

}

我们还将覆盖另一个名为viewDidLayoutSubviews的UIViewController方法。

- (void)viewDidLayoutSubviews {

    [super viewDidLayoutSubviews];

    switch ([UIDevice currentDevice].orientation) {

      case UIDeviceOrientationPortraitUpsideDown:

        self.videoCamera.defaultAVCaptureVideoOrientation =

          AVCaptureVideoOrientationPortraitUpsideDown;

        break;

      case UIDeviceOrientationLandscapeLeft:

        self.videoCamera.defaultAVCaptureVideoOrientation =

          AVCaptureVideoOrientationLandscapeLeft;

        break;

      case UIDeviceOrientationLandscapeRight:

        self.videoCamera.defaultAVCaptureVideoOrientation =

          AVCaptureVideoOrientationLandscapeRight;

        break;

      default:

        self.videoCamera.defaultAVCaptureVideoOrientation =

          AVCaptureVideoOrientationPortrait;

break;

}

    [self refresh];

}

请注意,我们在重新配置相机后调用辅助方法refresh。 它将确保重新启动摄像机或重新处理静态图像以反映最新配置。

当用户点击预览的父视图时,我们将找到坐标点击并将它们传递给我们之前在VideoCamera中实现的setPointOfInterestInParentViewSpace:方法。 以下是点击事件的相关回调:

- (void)onTapToSetPointOfInterest:(UITapGestureRecognizer *)tapGesture {

    if (tapGesture.state == UIGestureRecognizerStateEnded) {

        if (self.videoCamera.running) {

            CGPoint tapPoint = [tapGesture locationInView:self.imageView];

            [self.videoCamera setPointOfInterestInParentViewSpace:tapPoint];

        }

    }

}

切换灰度图和彩色图

- (IBAction)onColorModeSelected:

      (UISegmentedControl *)segmentedControl {

    switch (segmentedControl.selectedSegmentIndex) {

      case 0:

        self.videoCamera.grayscaleMode = NO;

        break;

      default:

        self.videoCamera.grayscaleMode = YES;

break;

}

    [self refresh];

  }

切换摄像头

- (void)onSwitchCameraButtonPressed {

    if (self.videoCamera.running) {

        switch (self.videoCamera.defaultAVCaptureDevicePosition) {

            case AVCaptureDevicePositionFront:

                self.videoCamera.defaultAVCaptureDevicePosition = AVCaptureDevicePositionBack;

                [self refresh];

                break;

            default:

                [self.videoCamera stop];

                [self refresh];

                break;

        }

    }else {

        self.imageView.image = nil;

        self.videoCamera.defaultAVCaptureDevicePosition = AVCaptureDevicePositionFront;

        [self.videoCamera start];

    }

}

- (void)refresh {

    if (self.videoCamera.running) {

        self.imageView.image = nil;

        [self.videoCamera stop];

        [self.videoCamera start];

    } else {

        UIImage *image;

        if (self.videoCamera.grayscaleMode) {

            cv::cvtColor(originalStillMat, updatedStillMatGray, cv::COLOR_RGB2GRAY);

            [self processImage:updatedStillMatGray];

            image = MatToUIImage(updatedStillMatGray);

        } else {

            cv::cvtColor(originalStillMat, updatedStillMatRGBA, cv::COLOR_RGBA2BGRA);

            [self processImage:updatedStillMatRGBA];

            cv::cvtColor(updatedStillMatRGBA, updatedStillMatRGBA, cv::COLOR_BGRA2RGBA);

            image = MatToUIImage(updatedStillMatRGBA);

        }

        self.imageView.image = image;

    }

}

- (void)processImage:(cv::Mat &)mat {

    if (self.videoCamera.running) {

        switch (self.videoCamera.defaultAVCaptureVideoOrientation) {

            case AVCaptureVideoOrientationLandscapeLeft:

            case AVCaptureVideoOrientationLandscapeRight:

                cv::flip(mat, mat, -1);

                break;

            default:

                break;

        }

    }

    [self processImageHelper:mat];

    if (self.saveNextFrame) {

        UIImage *image;

        if (self.videoCamera.grayscaleMode) {

            mat.copyTo(updatedVideoMatGray);

            image = MatToUIImage(updatedVideoMatGray);

        } else {

            cv::cvtColor(mat, updatedVideoMatRGBA, cv::COLOR_BGRA2RGBA);

            image = MatToUIImage(updatedVideoMatRGBA);

        }

        [self saveImage:image];

        self.saveNextFrame = NO;

    }

}

你可能感兴趣的:(iOS下使用OpenCV来控制相机)