美文网首页
CoreImage浅谈与使用

CoreImage浅谈与使用

作者: YesWeCan | 来源:发表于2019-03-11 16:29 被阅读0次

    本文主要介绍一下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中三个最重要的对象:

    • CIImage
      保存图像数据的类,是一个不可变对象,它表示一个图像数据。CIImage对象,你可以通过UIImage,图像文件或者像素数据来创建,也可以从一个CIFilter对象的输出来获取。
      下面有一段官方解释:

      image.png
    • CIFilter
      表示应用的滤镜,这是框架对图片属性进行细节处理的类。它对所有的像素进行操作,用一些键-值设置来决定具体操作的程度。

      image.png
      每个源图像的像素由CISampler对象提取(简单地取样器sampler)。
      顾名思义,采样器sampler检索图像的样本,并将其提供给内核。过滤器创建者为每个源图像提供一个采样器。过滤器客户端不需要知道有关采样器的任何信息。
      过滤器创建者在内核中定义每块像素图像处理计算,Core Image确定是否使用GPU或CPU执行计算。 Core Image根据设备功能使用Metal,OpenGL或OpenGL ES实现图像的处理。
    • CIContex
      表示上下文,也是实现对图像处理的具体对象。可以基于CPU或者GPU ,用于绘制渲染,可以从其中取得图片的信息。
      代码展示:

    - (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

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

    • Create a CIImage :
    • Create a CIContext
    • Create a CIFilter
    • Get the filter output
      创建过滤器时,您可以在其上配置许多依赖于您正在使用的过滤器的属性。过滤器为您提供输出图像作为CIImage ,您可以使用CIContext将其转换为UIImage。

    三、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实现。

    七、注意点

    • 不要每次渲染都去创建一个CIConcext,上下文中保存了大量的状态信息,重用会更加高效
    • 当使用GPU的上下文时,应当避免使用CoreAnimation。如果希望同时使用它们,则应该使用CPU上下文。 涉及GPU的处理应该放到主线程来完成。
    • 避免CPU和GPU之间进行没必要的纹理切换
    • 保证图像不要超过CPU和GPU的限制。

    参考文献:

    1 官方教材

    2 iOS原生系统架构

    3 CoreImage基础

    4 CoreImage进阶系列

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

    相关文章

      网友评论

          本文标题:CoreImage浅谈与使用

          本文链接:https://www.haomeiwen.com/subject/fhlvpqtx.html