GPUImage 实现自定义相机

96
沙琪玛dd
0.1 2016.07.20 19:49* 字数 501

前言

  • 这是基于第三方库GPUImage实现的自定义相机,主要是功能方面的实现,在UI上暂时并没有做很好的完善。主要包括功能有实时滤镜,准确聚焦,调整焦距,调整曝光,闪光灯设置,翻转前后相机以及拍照后的滤镜调整等

引入GPUImage库

  • 主要有工程以依赖的形式加入GPUImage或者用Pod安装GPUImage的方法,网上类似教程很多,这里就不赘述了

制作自定义相机

新建一个CameraViewController,作为实时相机界面的Controller,并且做以下预处理及声明变量

#define HEIGHT 667.0
#define WIDTH 375.0
static int CameraFilterCount = 9;//滤镜的数量

///滤镜View的显示状态
typedef NS_ENUM(NSInteger, FilterViewState) {
    
    FilterViewHidden,/**<隐藏*/
    
    FilterViewUsing /**<显示*/
};

///闪光灯状态
typedef NS_ENUM(NSInteger, CameraManagerFlashMode) {
    
    CameraManagerFlashModeAuto, /**<自动*/
    
    CameraManagerFlashModeOff, /**<关闭*/
    
    CameraManagerFlashModeOn /**<打开*/
};

@interface CameraViewController() <UIGestureRecognizerDelegate>
{
    CALayer *_focusLayer; //聚焦层
}

///新建相机cameraManager
@property (nonatomic,strong) GPUImageStillCamera *cameraManager;

/**
*根据storyboard上将界面分为三个View,预览View,底部View以及整体的cameraView,也可以用代码实现
*/
@property (strong, nonatomic) IBOutlet UIView *cameraView;
@property (weak, nonatomic) IBOutlet UIView *preview;
@property (weak, nonatomic) IBOutlet UIView *bottomView;

@property CameraFilterView *cameraFilterView;//自定义滤镜视图

@property (nonatomic , assign) CameraManagerFlashMode flashMode;
@property (nonatomic , assign) FilterViewState filterViewState;

@property (nonatomic , weak) IBOutlet UIButton *flashButton;//闪光灯按钮

@property (nonatomic , assign) CGFloat beginGestureScale;//开始的缩放比例
@property (nonatomic , assign) CGFloat effectiveScale;//最后的缩放比例

@property GPUImageOutput *filter;//滤镜
@property GPUImageView *filterView;//实时滤镜预览视图
@property AVCaptureStillImageOutput *photoOutput;//用于保存原图

@property CheckViewController *checkVC;//拍照之后跳转的ViewController
@end

控制器视图方法

- (void)viewDidLoad{
//隐藏导航栏
    [self.navigationController setNavigationBarHidden:YES];
//从SB初始化CheckController,后续解释FilterCode
    _checkVC = [[self storyboard] instantiateViewControllerWithIdentifier:@"Check"];
    [_checkVC setFilterCode:0];

//初始化相机,默认为前置相机
    _cameraManager = [[MyCamera alloc] initWithSessionPreset:AVCaptureSessionPresetPhoto cameraPosition:AVCaptureDevicePositionFront];
    _cameraManager.outputImageOrientation = UIInterfaceOrientationPortrait;//设置照片的方向为设备的定向
    _cameraManager.horizontallyMirrorFrontFacingCamera = YES;//设置是否为镜像
    _cameraManager.horizontallyMirrorRearFacingCamera = NO;

    _filter = [[GPUImageFilter alloc] init];//初始化滤镜,默认初始化为原图GPUImageFilter

    [self setfocusImage:[UIImage imageNamed:@"touch_focus_x"]];//初始化聚焦图片
/**
*设置cameraManager的输出对象为filter,然后将preview强制转换为filterView添加到filter的输出对象中,这样在filterView中显示的就是相机捕捉到的并且经过filter滤镜处理的实时图像了
*/
    [self.cameraManager addTarget:_filter];
    _filterView = (GPUImageView *)self.preview;
    [_filter addTarget:_filterView];

//初始化闪光灯模式为Auto
    [self setFlashMode:CameraManagerFlashModeAuto];
//默认滤镜视图为隐藏,就是点击滤镜的按钮才会出现让你选择滤镜的那个小视图
    [self setFilterViewState:FilterViewHidden];
//初始化开始及结束的缩放比例都为1.0
    [self setBeginGestureScale:1.0f];
    [self setEffectiveScale:1.0f];
 //启动相机捕获
    [self.cameraManager startCameraCapture];}

UI方法

  • 这里先给出CameraFilterView的实现方法,其实就是一个简单的一行的CollectionView,博主比较懒,引用了ShawnDu大哥的代码,在此表示感谢🙏
///.h文件
#import <UIKit/UIKit.h>

@protocol CameraFilterViewDelegate;

@interface CameraFilterView : UICollectionView <UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout>

@property (strong, nonatomic) NSMutableArray *picArray;//图片数组
@property (strong, nonatomic) id <CameraFilterViewDelegate> cameraFilterDelegate;
@end

@protocol CameraFilterViewDelegate <NSObject>

- (void)switchCameraFilter:(NSInteger)index;//滤镜选择方法

@end
\\\.m文件
#import "CameraFilterView.h"
static const float CELL_HEIGHT = 84.0f;
static const float CELL_WIDTH = 56.0f;
@implementation CameraFilterView

#pragma mark 初始化方法

- (id)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout{
    self = [super initWithFrame:frame collectionViewLayout:layout];
    if (self) {
        self.delegate = self;
        self.dataSource = self;
    }
    return self;
}

#pragma mark collection 方法
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
    return 1;
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
    return [_picArray count];
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
    static NSString *identifier = @"cameraFilterCellID";
    [collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:identifier];
    UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
    UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, CELL_WIDTH, CELL_HEIGHT)];
    imageView.image = [_picArray objectAtIndex:indexPath.row];
    [cell addSubview:imageView];
    cell.backgroundColor = [UIColor whiteColor];
    
    return cell;
}

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath{
    return CGSizeMake(CELL_WIDTH, CELL_HEIGHT);
}

- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
    [_cameraFilterDelegate switchCameraFilter:indexPath.row];
}

-(BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
    return YES;
}
@end
  • 关于Collection控件不懂的同学可以先复习一下CollectionView或者用其他的方法实现自定义控件
//添加滤镜视图到主视图上
- (void)addCameraFilterView {
    UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
    layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
    _cameraFilterView = [[CameraFilterView alloc] initWithFrame:CGRectMake(0, HEIGHT - _bottomView.frame.size.height - (WIDTH-4)/5 - 4, WIDTH, (WIDTH-4)/5) collectionViewLayout:layout];
    NSMutableArray *filterNameArray = [[NSMutableArray alloc] initWithCapacity:CameraFilterCount];
    for (NSInteger index = 0; index < CameraFilterCount; index++) {
        UIImage *image = [UIImage imageNamed:@"girl"];
        [filterNameArray addObject:image];
    }
    _cameraFilterView.cameraFilterDelegate = self;
    _cameraFilterView.picArray = filterNameArray;
    [self.view addSubview:_cameraFilterView];
}

//使用滤镜
- (IBAction)useFilter:(id)sender {
    if (self.filterViewState == FilterViewHidden) {
        [self addCameraFilterView];
        [self setFilterViewState:FilterViewUsing];
    }
    else {
        [_cameraFilterView removeFromSuperview];
        [self setFilterViewState:FilterViewHidden];
    }
}

//设置聚焦图片
- (void)setfocusImage:(UIImage *)focusImage {
    if (!focusImage) return;
    
    if (!_focusLayer) {
    //增加tap手势,用于聚焦及曝光
        UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(focus:)];
        [self.preview addGestureRecognizer:tap];
     //增加pinch手势,用于调整焦距   
        UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(focusDisdance:)];
        [self.preview addGestureRecognizer:pinch];
        pinch.delegate = self;
    }

    UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, focusImage.size.width, focusImage.size.height)];
    imageView.image = focusImage;
    CALayer *layer = imageView.layer;
    layer.hidden = YES;
    [self.preview.layer addSublayer:layer];
    _focusLayer = layer;

}

//点击闪光灯按钮
- (IBAction)changeFlash:(id)sender {
    [self changeFlashMode:_flashButton];
}

滤镜选择方法的实现

/**
*以下的滤镜是GPUImage自带的滤镜,后面有几个效果不是很明显,我只是拿来做测试用。
*GPUImage自带的滤镜很多,大家可以都试试看,当然也可以自己写滤镜,这就需要后续深入学习了
*对checkViewController中filtercode属性的说明:filtercode是在选择滤镜的时候设置,
*在拍照的时候传值给CheckViewController,实际上拍照传递给CheckVC的是原图加上当前设置的filtercode,
*所以在后续CheckVC中改变滤镜的时候是基于原图进行改变。
*/
- (void)switchCameraFilter:(NSInteger)index {
    [self.cameraManager removeAllTargets];
    switch (index) {
        case 0:
            _filter = [[GPUImageFilter alloc] init];//原图
            [_checkVC setFilterCode:0];
            break;
        case 1:
            _filter = [[GPUImageHueFilter alloc] init];//绿巨人
            [_checkVC setFilterCode:1];
            break;
        case 2:
            _filter = [[GPUImageColorInvertFilter alloc] init];//负片
            [_checkVC setFilterCode:2];
            break;
        case 3:
            _filter = [[GPUImageSepiaFilter alloc] init];//老照片
            [_checkVC setFilterCode:3];
            break;
        case 4: {
            _filter = [[GPUImageGaussianBlurPositionFilter alloc] init];
            [(GPUImageGaussianBlurPositionFilter*)_filter setBlurRadius:40.0/320.0];
            [_checkVC setFilterCode:4];
        }
            break;
        case 5:
            _filter = [[GPUImageSketchFilter alloc] init];//素描
            [_checkVC setFilterCode:5];
            break;
        case 6:
            _filter = [[GPUImageVignetteFilter alloc] init];//黑晕
            [_checkVC setFilterCode:6];
            break;
        case 7:
            _filter = [[GPUImageGrayscaleFilter alloc] init];//灰度
            [_checkVC setFilterCode:7];
            break;
        case 8:
            _filter = [[GPUImageToonFilter alloc] init];//卡通效果 黑色粗线描边
            [_checkVC setFilterCode:8];
        default:
            _filter = [[GPUImageFilter alloc] init];
            [_checkVC setFilterCode:9];
            break;
    }
    
    [self.cameraManager addTarget:_filter];
    [_filter addTarget:_filterView];
}

拍照方法的实现

- (IBAction)takePhoto:(id)sender {
    _photoOutput = [_cameraManager getPhotoOutput];//得到源数据输出流
    NSDictionary * outputSettings = [[NSDictionary alloc] initWithObjectsAndKeys:AVVideoCodecJPEG,AVVideoCodecKey, nil];
    //这是输出流的设置参数AVVideoCodecJPEG参数表示以JPEG的图片格式输出图片
    [_photoOutput setOutputSettings:outputSettings];
    AVCaptureConnection *captureConnection=[_photoOutput connectionWithMediaType:AVMediaTypeVideo];
    //根据连接取得设备输出的数据
    [_photoOutput captureStillImageAsynchronouslyFromConnection:captureConnection completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
        if (imageDataSampleBuffer) {
            NSData *imageData=[AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
            UIImage *image=[UIImage imageWithData:imageData];
//将输出流数据转换成NSData,然后通过NSData将数据转换为UIImage传递给checkVC
            _checkVC.image = image;
            [self.navigationController pushViewController:_checkVC animated:YES];
        }  
    }];
}

转置摄像头

#pragma mark 转置摄像头

- (IBAction)turn:(id)sender {
    [self.cameraManager stopCameraCapture];
    if (_cameraManager.cameraPosition == AVCaptureDevicePositionFront) {
        [_cameraManager initWithSessionPreset:AVCaptureSessionPresetPhoto cameraPosition:AVCaptureDevicePositionBack];
    }
    else if(_cameraManager.cameraPosition == AVCaptureDevicePositionBack)
    {
        [_cameraManager initWithSessionPreset:AVCaptureSessionPresetPhoto cameraPosition:AVCaptureDevicePositionFront];
    }
    _cameraManager.outputImageOrientation = UIInterfaceOrientationPortrait;
    _cameraManager.horizontallyMirrorFrontFacingCamera = YES;
    _cameraManager.horizontallyMirrorRearFacingCamera = NO;
    
    [self.cameraManager addTarget:_filter];
    _filterView = (GPUImageView *)self.preview;
    [_filter addTarget:_filterView];
    
    [self setBeginGestureScale:1.0f];//在转换摄像头的时候把摄像头的焦距调回1.0
    [self setEffectiveScale:1.0f];
    
    [self.cameraManager startCameraCapture];
}

调整焦距

//调整焦距方法
-(void)focusDisdance:(UIPinchGestureRecognizer*)pinch {
     self.effectiveScale = self.beginGestureScale * pinch.scale;
    if (self.effectiveScale < 1.0f) {
        self.effectiveScale = 1.0f;
    }
    CGFloat maxScaleAndCropFactor = 3.0f;//设置最大放大倍数为3倍
    if (self.effectiveScale > maxScaleAndCropFactor)
        self.effectiveScale = maxScaleAndCropFactor;
    [CATransaction begin];
    [CATransaction setAnimationDuration:.025];
    NSError *error;
    if([self.cameraManager.inputCamera lockForConfiguration:&error]){
        [self.cameraManager.inputCamera setVideoZoomFactor:self.effectiveScale];
        [self.cameraManager.inputCamera unlockForConfiguration];
    }
    else {
        NSLog(@"ERROR = %@", error);
    }
    
    [CATransaction commit];
}

//手势代理方法
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
    if ( [gestureRecognizer isKindOfClass:[UIPinchGestureRecognizer class]] ) {
        self.beginGestureScale = self.effectiveScale;
    }
    return YES;
}

对焦

//对焦方法
- (void)focus:(UITapGestureRecognizer *)tap {
    self.preview.userInteractionEnabled = NO;
    CGPoint touchPoint = [tap locationInView:tap.view];
   // CGContextRef *touchContext = UIGraphicsGetCurrentContext();
    [self layerAnimationWithPoint:touchPoint];
/**
*下面是照相机焦点坐标轴和屏幕坐标轴的映射问题,这个坑困惑了我好久,花了各种方案来解决这个问题,以下是最简单的解决方案也是最准确的坐标转换方式
*/
    if(_cameraManager.cameraPosition == AVCaptureDevicePositionBack){
        touchPoint = CGPointMake( touchPoint.y /tap.view.bounds.size.height ,1-touchPoint.x/tap.view.bounds.size.width);
    }
    else
        touchPoint = CGPointMake(touchPoint.y /tap.view.bounds.size.height ,touchPoint.x/tap.view.bounds.size.width);
/*以下是相机的聚焦和曝光设置,前置不支持聚焦但是可以曝光处理,后置相机两者都支持,下面的方法是通过点击一个点同时设置聚焦和曝光,当然根据需要也可以分开进行处理
*/
    if([self.cameraManager.inputCamera isExposurePointOfInterestSupported] && [self.cameraManager.inputCamera isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure])
    {
        NSError *error;
        if ([self.cameraManager.inputCamera lockForConfiguration:&error]) {
        [self.cameraManager.inputCamera setExposurePointOfInterest:touchPoint];
        [self.cameraManager.inputCamera setExposureMode:AVCaptureExposureModeContinuousAutoExposure];
             if ([self.cameraManager.inputCamera isFocusPointOfInterestSupported] && [self.cameraManager.inputCamera isFocusModeSupported:AVCaptureFocusModeAutoFocus]) {
                 [self.cameraManager.inputCamera setFocusPointOfInterest:touchPoint];
                 [self.cameraManager.inputCamera setFocusMode:AVCaptureFocusModeAutoFocus];
             }
        [self.cameraManager.inputCamera unlockForConfiguration];
        } else {
            NSLog(@"ERROR = %@", error);
        }
    }
}

//对焦动画
- (void)layerAnimationWithPoint:(CGPoint)point {
    if (_focusLayer) {
///聚焦点聚焦动画设置
        CALayer *focusLayer = _focusLayer;
        focusLayer.hidden = NO;
        [CATransaction begin];
        [CATransaction setDisableActions:YES];
        [focusLayer setPosition:point];
        focusLayer.transform = CATransform3DMakeScale(2.0f,2.0f,1.0f);
        [CATransaction commit];
        
        CABasicAnimation *animation = [ CABasicAnimation animationWithKeyPath: @"transform" ];
        animation.toValue = [ NSValue valueWithCATransform3D: CATransform3DMakeScale(1.0f,1.0f,1.0f)];
        animation.delegate = self;
        animation.duration = 0.3f;
        animation.repeatCount = 1;
        animation.removedOnCompletion = NO;
        animation.fillMode = kCAFillModeForwards;
        [focusLayer addAnimation: animation forKey:@"animation"];
    }
}

//动画的delegate方法
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
    //    1秒钟延时
    [self performSelector:@selector(focusLayerNormal) withObject:self afterDelay:0.5f];
}

//focusLayer回到初始化状态
- (void)focusLayerNormal {
    self.preview.userInteractionEnabled = YES;
    _focusLayer.hidden = YES;
}

闪光灯设置

//设置闪光灯模式

- (void)setFlashMode:(CameraManagerFlashMode)flashMode {
    _flashMode = flashMode;
    
    switch (flashMode) {
        case CameraManagerFlashModeAuto: {
            [self.cameraManager.inputCamera lockForConfiguration:nil];
            if ([self.cameraManager.inputCamera isFlashModeSupported:AVCaptureFlashModeAuto]) {
                 [self.cameraManager.inputCamera setFlashMode:AVCaptureFlashModeAuto];
            }
            [self.cameraManager.inputCamera unlockForConfiguration];
        }
            break;
        case CameraManagerFlashModeOff: {
            [self.cameraManager.inputCamera lockForConfiguration:nil];
            [self.cameraManager.inputCamera setFlashMode:AVCaptureFlashModeOff];
            [self.cameraManager.inputCamera unlockForConfiguration];
        }
            
            break;
        case CameraManagerFlashModeOn: {
            [self.cameraManager.inputCamera lockForConfiguration:nil];
            [self.cameraManager.inputCamera setFlashMode:AVCaptureFlashModeOn];
            [self.cameraManager.inputCamera unlockForConfiguration];
        }
            break;
            
        default:
            break;
    }
}


#pragma mark 改变闪光灯状态
- (void)changeFlashMode:(UIButton *)button {
    switch (self.flashMode) {
        case CameraManagerFlashModeAuto:
            self.flashMode = CameraManagerFlashModeOn;
            [button setImage:[UIImage imageNamed:@"flashing_on"] forState:UIControlStateNormal];
            break;
        case CameraManagerFlashModeOff:
            self.flashMode = CameraManagerFlashModeAuto;
            [button setImage:[UIImage imageNamed:@"flashing_auto"] forState:UIControlStateNormal];
            break;
        case CameraManagerFlashModeOn:
            self.flashMode = CameraManagerFlashModeOff;
            [button setImage:[UIImage imageNamed:@"flashing_off"] forState:UIControlStateNormal];
            break;
            
        default:
            break;
    }
}

对拍照之后的照片进行滤镜处理

  • 这个其实很简单,处理方案和之前的实时滤镜差不多,也是通过FilterController来实现滤镜的选择,下面就给出选择滤镜之后进行处理的函数
- (void)switchCameraFilter:(NSInteger)index {
    UIImage *inputImage = self.image;
    switch (index) {
        case 0:
            _filter = [[GPUImageFilter alloc] init];//原图
            break;
        case 1:
            _filter = [[GPUImageHueFilter alloc] init];//绿巨人
            break;
        case 2:
            _filter = [[GPUImageColorInvertFilter alloc] init];//负片
            break;
        case 3:
            _filter = [[GPUImageSepiaFilter alloc] init];//老照片
            break;
        case 4: {
            _filter = [[GPUImageGaussianBlurPositionFilter alloc] init];
            [(GPUImageGaussianBlurPositionFilter*)_filter setBlurRadius:40.0/320.0];
        }
            break;
        case 5:
            _filter = [[GPUImageMedianFilter alloc] init];
            break;
        case 6:
            _filter = [[GPUImageVignetteFilter alloc] init];//黑晕
            break;
        case 7:
            _filter = [[GPUImageKuwaharaRadius3Filter alloc] init];
            break;
        case 8:
            _filter = [[GPUImageBilateralFilter alloc] init];
        default:
            _filter = [[GPUImageFilter alloc] init];
            break;
    }
    
    //到这里为止和实时滤镜的处理都一样,不一样的就只有下面几句
    [_filter forceProcessingAtSize:inputImage.size];
    [_filter useNextFrameForImageCapture];
    
    GPUImagePicture *stillImageSource = [[GPUImagePicture alloc] initWithImage:inputImage smoothlyScaleOutput:YES];
    
    [stillImageSource addTarget:_filter];
    
    [stillImageSource processImage];
    
    _currentImage = [_filter imageFromCurrentFramebuffer];
//这里得到的currentImage就是添加了滤镜之后的照片,可以放在imageView中直接显示,也可以稍加处理保存到相册
    [self.imageView setImage:_currentImage];
}

总结

  • 以上是最近在做自定义相机时做的一点总结,之前在做的时候也查阅了很多文章,感觉相对比较分散,所以这里就稍微做一下整理,希望大家在做类似需求的开发时能少碰到点坑。当然我做的也不怎么完善,风格也比较杂乱,后续会对此做些改进。
  • 有什么不对的地方欢迎大家批评指正,共同学习
日记本
Web note ad 1