CoreImage浅谈与使用

2019-03-11  本文已影响0人  YesWeCan

本文主要介绍一下CoreImage的图像处理框架的应用以及我在使用过程中的坑点。本文提供一个简易的Demo,对于CoreImage不了解的,可以借助demo快速上手。CoreImage是前一段时间了解的,现在记录一下,供大家参考。

概览

本文主要从一下几个方面来介绍:
1.CoreImage概念
2.内建滤镜的使用
3.CPU/GPU的不同选择方案
4.人脸检测
5.自动增强滤镜
6.自定义滤镜
7.注意点

一、CoreImage的概念

Core Image一个图像处理和分析技术,同时也提供了对视频图像实时处理的技术。iOS5中新加入的一个框架,里面提供了强大高效的图像处理功能,用来对基于像素的图像进行操作与分析。还提供了很多强大的滤镜,可以实现你想要的效果,它的处理数据基于CoreGraphics,CoreVideo,和Image I/O框架,既可以使用GPU也可以使用CPU的渲染路径。
CoreImage封装了底层图形处理的实现细节,你不必关心OpenGL和OpenGL ES是如何利用GPU的,也不必知晓GCD是如何利用多核进行处理的。

image.png
其内置了很多强大的滤镜(Filter) (目前数量超过了190种), 这些Filter 提供了各种各样的效果, 并且还可以通过 滤镜链 将各种效果的 Filter叠加 起来形成强大的自定义效果。
一个 滤镜 是一个对象,有很多输入和输出,并执行一些变换。
一个 滤镜链 是一个链接在一起的滤镜网络,使得一个滤镜的输出可以是另一个滤镜的输入。 image.png

iOS8 之后更是支持自定义 CIFilter,可以定制满足业务需求的复杂效果。

官方解释: image.png
翻译:底层细节都帮你做好了,放心调用API就行了 。

二、内建滤镜的使用

首先介绍一下CoreImage中三个最重要的对象:

- (UIImage *)addEffect:(NSString *)filtername fromImage:(UIImage *)image{
    ///note 1
//        CIImage * image1 = [image CIImage];
//        NSLog(@"%@",image1);
    //因为: UIImage 对象可能不是基于 CIImage 创建的(由 imageWithCIImage: 生成的),这样就无法获取到 CIImage 对象
    //解决方法一:
//    NSString * path = [[NSBundle mainBundle] pathForResource:@"tu.jpg" ofType:nil];
//    UIImage * tempImage = [UIImage imageWithCIImage:[CIImage imageWithContentsOfURL:[NSURL fileURLWithPath:path]]];
//    CIImage * tempCIimg = [tempImage CIImage];
//    NSLog(@"%@",tempCIimg);
    
    //解决方法2
    CIImage * ciimage = [[CIImage alloc] initWithImage:image];
    CIFilter * filter = [CIFilter filterWithName:filtername];
    [filter setValue:ciimage forKey:kCIInputImageKey];
    // 已有的值不改变, 其他的设为默认值
    [filter setDefaults];
    //渲染并输出CIImage
    CIImage * outimage = [filter outputImage];
    
    //UIImage * newImage = [UIImage imageWithCIImage:outimage]; //每次创建都会开辟新的CIContext上下文,耗费空间

    // 获取绘制上下文
    CIContext * context = [CIContext contextWithOptions:nil];//(GPU上创建)
    //self.context; //
    //创建CGImage
    CGImageRef cgimage = [context createCGImage:outimage fromRect:[outimage extent]];
    UIImage * newImage = [UIImage imageWithCGImage:cgimage];
    CGImageRelease(cgimage);
    return newImage;
}

上方有一个注意点:UIImage 对象可能不是基于 CIImage 创建的(由 imageWithCIImage: 生成的),这样就无法获取到 CIImage 对象。正确写法如代码中的解决方法1 和 解决方法2.
该方法是传入两个参数(一张图片、滤镜的名称),具体滤镜有哪些可以看官方文档,也可以自己通过下面方法输出:

// 打印滤镜名称
// `kCICategoryBuiltIn`内置; `kCICategoryColorEffect`色彩
- (void)showFilters {
    NSArray *filterNames = [CIFilter filterNamesInCategory:kCICategoryColorEffect];
    for (NSString *filterName in filterNames) {
        NSLog(@"%@", filterName);
        // CIFilter *filter = [CIFilter filterWithName:filterName];
        // NSDictionary *attributes = filter.attributes;
        // NSLog(@"%@", attributes); // 查看属性
    }
}
例如一个CIMotionBlur滤镜可以做如下处理: image.png

所以一个滤镜的基本使用可以分为四步:

三、CPU/GPU的不同选择

CIContext上下文是绘制操作发生的地方,它决定了CoreImage是使用GPU还是CPU来渲染。

image.png image.png
上图分别给出了CPU和GPU的创建方式,其中contextWithOptions创建GPU方式的上下文没有实时性,虽然渲染是在GPU上执行,但是其输出的image是不能显示的,只有当其被复制回CPU存储器上时,才会被转成一个可被显示的image类型,比如UIImage。该方式处理流程如下图:
image.png
对照上图,当使用 Core Image 在 GPU 上渲染图片的时候,先是把图像传递到 GPU 上,然后执行滤镜相关操作。但是当需要生成 CGImage 对象的时候,图像又被复制回 CPU 上。最后要在视图上显示的时候,又返回 GPU 进行渲染。这样在 GPU 和 CPU 之前来回切换,会造成很严重的性能损耗。
如果需要很高的实时性,则需要基于EAGLContext创建上下文,该种方式也是GPU上处理的,代码如下: image.png 处理流程如下图: image.png 这种方式创建的上下文是利用实时渲染的特效,而不是每次操作都产生一个 UIImage,然后再设置到视图上。
核心实现代码:
 //获取openGLES渲染环境
        EAGLContext * context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
        //初始化GLKview 并制定openGLES渲染环境 + 绑定
        _showView = [[GLKView alloc] initWithFrame:frame context:context];
        //A default implementation for views that draw their content using OpenGL ES.
        /*
         Binds the context and drawable. This needs to be called when the currently bound framebuffer
         has been changed during the draw method.
         */
        [_showView bindDrawable];
        
        //添加进图层
        [self addSubview:_showView];
        
        //创建上下文CIContext - GPU方式 :但是必须在主线程
        _context = [CIContext contextWithEAGLContext:context options:@{kCIContextWorkingColorSpace:[NSNull null]}];

然后再使用context进行绘制:

[_context drawImage:ciimage inRect:CGRectMake(0, 0, viewSize.width*scale, viewSize.height*scale) fromRect:[ciimage extent]];

具体详细实现过程可以看:demo中的GLESvcGLESView实现。
两种方式的对比
GPU方式:处理速度更快,因为利用了 GPU 硬件的并行优势。可以使用 OpenGLES 或者 Metal 来渲染图像,这种方式CPU完全没有负担,但是 GPU 受限于硬件纹理尺寸,当 App 切换到后台状态时 GPU 处理会被打断。
CPU方式:会采用GCD对图像进行渲染处理,这保证了CPU方式比较可靠,并且更容易使用,可以在后台实现渲染过程。

四、人脸检测

CIDetecror是Core Image框架中提供的一个识别类,包括对人脸、形状、条码、文本的识别。
人脸识别功能不单单可以对人脸进行获取,还可以获取眼睛和嘴等面部特征信息。但是CIDetector不包括面纹编码提取, 只能判断是不是人脸,而不能判断这张人脸是谁的。
实现代码:

-(void)detector:(CIImage *)image{
    //CIContext * context = [CIContext contextWithOptions:nil];
    NSDictionary * param = [NSDictionary dictionaryWithObject:CIDetectorAccuracyLow forKey:CIDetectorAccuracy];
    CIDetector * faceDetector = [CIDetector detectorOfType:CIDetectorTypeFace context:_context options:param];
    NSArray * detectResult = [faceDetector featuresInImage:image];
    printf("count: %lu \n",(unsigned long)detectResult.count);
    if (detectResult.count == 0) {
        self.resultView.hidden = YES;
        return;
    }
    self.resultView.hidden = NO;
    for (CIFaceFeature * feature in detectResult) {
        [UIView animateWithDuration:5/60.0f animations:^{
            self.face.frame = CGRectMake(feature.bounds.origin.x/scaleValue, feature.bounds.origin.y/scaleValue, feature.bounds.size.width/scaleValue, feature.bounds.size.height/scaleValue);
            if (feature.hasLeftEyePosition) {
                self.leftEye.center = CGPointMake(feature.leftEyePosition.x/scaleValue, feature.leftEyePosition.y/scaleValue);
            }
            if (feature.hasRightEyePosition) {
                self.rightEye.center = CGPointMake(feature.rightEyePosition.x/scaleValue, feature.rightEyePosition.y/scaleValue);
            }
            if (feature.hasMouthPosition) {
                self.mouth.center = CGPointMake(feature.mouthPosition.x/scaleValue, feature.mouthPosition.y/scaleValue);
            }
            ///note: UI坐标系 和 CoreImage坐标系不一样:左下角为原点
        }];
        //_resultView.transform = CGAffineTransformMakeScale(1, -1);
    }
}

此处有一个注意点,UI坐标系 和 CoreImage坐标系不一样,CoreImage坐标系左下角为原点,UI坐标系左上角为圆点。

五、自动增强滤镜

CoreImage的自动增强特征分析了图像的直方图,人脸区域内容和元数据属性。接下来它将返回一个CIFilter对象的数组,每个CIFilter的输入参数已经被设置好了,这些设置能够自动去改善被分析的图像。这种也是常见的自动美颜方式。下表列出了CoreImage用作自动图像增强的滤镜。这些滤镜将会解决在照片中被发现的那些常见问题。 image.png

实现代码:

///自动图像增强
-(UIImage *)autoAdjust:(CIImage *)image{
    id orientationProperty = [[image properties] valueForKey:(__bridge id)kCGImagePropertyOrientation];
    NSDictionary *options = nil;
    if (orientationProperty) {
        options = @{CIDetectorImageOrientation : orientationProperty};
        //用于设置识别方向,值是一个从1 ~ 8的整型的NSNumber。如果值存在,检测将会基于这个方向进行,但返回的特征仍然是基于这些图像的。
    }
    NSArray *adjustments = [image autoAdjustmentFiltersWithOptions:options];
    for (CIFilter *filter in adjustments) {
        [filter setValue:image forKey:kCIInputImageKey];
        image = filter.outputImage;
    }
    CIContext * context = [CIContext contextWithOptions:nil];//(GPU上创建) //self.context;
    //创建CGImage
    CGImageRef cgimage = [context createCGImage:image fromRect:[image extent]];
    UIImage * newImage = [UIImage imageWithCGImage:cgimage];
    return newImage;
}

六、自定义滤镜

什么时候需要自定义滤镜?
1 对于一种表达效果,我们使用了多种滤镜,并且后续还会继续使用这种效果。
2 对于一些高级的、apple并没有提供的一些效果,需要对算法进行封装的。
封装方法:

1可以基于已存在的滤镜来子类化一个CIFilter,还可以描述具有多个滤镜链的配方
2用属性来声明滤镜的输入参数,属性名必须以input为前缀,例如:inputImage
3可用重写setDefaults方法来设置默认参数。 在iOS中,CIFilter被创建后会自动调用该方法
4需要重写outputImage方法
例如我这边有一个对视频每一帧添加水印的方法:

-(void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection{
    CVPixelBufferRef pixelBuffer = (CVPixelBufferRef)CMSampleBufferGetImageBuffer(sampleBuffer);
    CIImage *ciimage = [CIImage imageWithCVPixelBuffer:pixelBuffer];
    if (_witchBtn.isOn) {
        //[self.filter setValue:ciimage forKey:kCIInputImageKey];
        //ciimage = [_filter outputImage];
//        CLColorInvertFilter * customeFilter = [[CLColorInvertFilter alloc] init];
//        customeFilter.inputImage = ciimage;
//        ciimage = [customeFilter outputImage];
        //自定义添加水印
        _customerFilter = [[HZChromaKeyFilter alloc] initWithInputImage:[UIImage imageNamed:@"tu.jpg"] backgroundImage:ciimage];
        ciimage = _customerFilter.outputImage;
    }
    [self.gpuView drawCIImage:ciimage];
    
}

而具体的HZChromaKeyFilter类的实现:

@interface HZChromaKeyFilter : CIFilter

-(instancetype)initWithInputImage:(UIImage *)image
                  backgroundImage:(CIImage *)bgImage;

@property (nonatomic,readwrite,strong) UIImage *inputFilterImage;
@property (nonatomic,readwrite,strong) CIImage *backgroundImage;
@end
@implementation HZChromaKeyFilter
-(instancetype)initWithInputImage:(UIImage *)image backgroundImage:(CIImage *)bgImage{
    self=[super init];
    
    if (!self) {
        return nil;
    }
    

    self.inputFilterImage=image;
    self.backgroundImage=bgImage;
    
    return self;
    
}
static int angle = 0;

-(CIImage *)outputImage{
    
    CIImage *myImage = [[CIImage alloc] initWithImage:self.inputFilterImage];
    //位移
    CIImage * tempImage = myImage;//[scaleFilter outputImage];
    CGSize extsz1 = self.backgroundImage.extent.size;
    CGSize extsz2 = tempImage.extent.size;
    CGAffineTransform transform = CGAffineTransformMakeTranslation(extsz1.width-extsz2.width -100, extsz2.height+100);
    transform = CGAffineTransformRotate(transform, M_PI*2*(angle/360.0));
    angle ++;
    if (angle == 360) {
        angle = 0;
    }
    CIFilter * transformFilter = [CIFilter filterWithName:@"CIAffineTransform"];
    [transformFilter setValue:tempImage forKey:@"inputImage"];
    [transformFilter setValue:[NSValue valueWithCGAffineTransform:transform] forKey:@"inputTransform"];
    
    CIImage *backgroundCIImage = self.backgroundImage; //[[CIImage alloc] initWithImage:self.backgroundImage];
    CIImage *resulImage = [[CIFilter filterWithName:@"CISourceOverCompositing"  keysAndValues:kCIInputImageKey,transformFilter.outputImage,
                            kCIInputBackgroundImageKey,backgroundCIImage,nil]
                           valueForKey:kCIOutputImageKey];

    return resulImage;
}

具体可以参考demo中的HZChromaKeyFilter.h实现。

七、注意点

参考文献:

1 官方教材

2 iOS原生系统架构

3 CoreImage基础

4 CoreImage进阶系列

5 图形图像处理:位图图像原图修改

上一篇下一篇

猜你喜欢

热点阅读