iOS滤镜系列-滤镜开发概览

filter-logo

概述

滤镜最先的出现应该是应用在相机镜头前实现天然光过滤和调色的镜片,然而在软件开发中更多的指的是软件滤镜,是对镜头滤镜的模拟实现。固然这种方式更加方便快捷,缺点天然就是没法还原拍摄时的真实场景,例如没法实现偏光镜和紫外线滤色镜的效果。今天简单介绍一下iOS滤镜开发中的正确姿式,让刚刚接触滤镜开发的朋友少走弯路。html

在iOS开发中常见的滤镜开发方式大概包括:CIFilter、GPUImage、OpenCV等。c++

CoreImage

core_image

CIFilter

CIFilter存在于CoreImage框架中,它基于OpenGL着色器来处理图像(最新的已经基于Metal实现),优势固然是快,由于它能够充分利用GPU加速来处理图像渲染,同时它自身支持滤镜链,多个滤镜同时使用时迅速高效。算法

CIFilter目前已经支持21个分类(以下代码)196种滤镜swift

public let kCICategoryDistortionEffect: String
public let kCICategoryGeometryAdjustment: String
public let kCICategoryCompositeOperation: String
public let kCICategoryHalftoneEffect: String
public let kCICategoryColorAdjustment: String
public let kCICategoryColorEffect: String
public let kCICategoryTransition: String
public let kCICategoryTileEffect: String
public let kCICategoryGenerator: String
@available(iOS 5.0, *)
public let kCICategoryReduction: String
public let kCICategoryGradient: String
public let kCICategoryStylize: String
public let kCICategorySharpen: String
public let kCICategoryBlur: String
public let kCICategoryVideo: String
public let kCICategoryStillImage: String
public let kCICategoryInterlaced: String
public let kCICategoryNonSquarePixels: String
public let kCICategoryHighDynamicRange: String
public let kCICategoryBuiltIn: String
@available(iOS 9.0, *)
public let kCICategoryFilterGenerator: String

使用open class func filterNames(inCategory category: String?) -> [String]能够查看每一个分类的滤镜名称。而每一个滤镜的属性设置经过CIFilter的attributes就能够查看。而应用一个CIFilter滤镜也仅仅须要:建立滤镜->设置属性(KVC)->读取输入图片(下面演示了高斯模糊滤镜的简单实现):缓存

guard let cgImage = UIImage(named:"CIFilter_Demo_Origin")?.cgImage else { return }
        let ciImage = CIImage(cgImage: cgImage)
        let filter = CIFilter(name: "CIGaussianBlur")
        filter?.setValue(ciImage, forKey: kCIInputImageKey)
        filter?.setValue(5.0, forKey: "inputRadius")
        
        if let outputImage = filter?.value(forKeyPath: kCIOutputImageKey) as? CIImage {
            let context = CIContext()
            if let cgImage = context.createCGImage(outputImage, from: outputImage.extent) {
                let image = UIImage(cgImage: cgImage)
                UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
            }
        }

原图app

CIFilter_Demo_Origin

应用高斯模糊框架

CIFilter_Demo_Gauss

滤镜链
所谓滤镜链就是将一个滤镜A的输出做为另外一个滤镜B的输入造成有向图,使用这种方式Core Image并不是一步步执行结果应用到B滤镜,而是将多个滤镜的着色器合并操做,从而提升性能。
例如在上面的高斯模糊滤镜基础上应用像素化滤镜:dom

guard let cgImage = UIImage(named:"CIFilter_Demo_Origin")?.cgImage else { return }
        let ciImage = CIImage(cgImage: cgImage)
        let blurFilter = CIFilter(name: "CIGaussianBlur")
        blurFilter?.setValue(ciImage, forKey: kCIInputImageKey)
        blurFilter?.setValue(5.0, forKey: "inputRadius")
        
        let pixelFilter = CIFilter(name: "CIPixellate", parameters: [kCIInputImageKey:blurFilter!.outputImage!])
        pixelFilter?.setDefaults()
        
        if let outputImage = pixelFilter?.outputImage {
            let context = CIContext()
            if let cgImage = context.createCGImage(outputImage, from: outputImage.extent) {
                let image = UIImage(cgImage: cgImage)
                UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
            }
        }

另外新的API(iOS 11)若是使用滤镜建议使用更加直观的表达以简化书写:let outputImage = ciImage.applyingFilter("CIGaussianBlur", parameters: [kCIInputRadiusKey:5.0]).applyingFilter("CIPixellate")
此外说到CoreImage的高斯模糊时直接使用是有一个问题的,那就是radius越大越会产生一个明显的空白边缘,固然这个问题是由于滤镜的卷积操做一般从中心点开始应用形成的,这样就会导致边缘上的像素值不能获得有效应用,相似于OpenCV会本身处理这个问题,可是Core Image并无处理这个边缘问题,一般的处理方法就是放大图片,而后剪切到原来的图片大小便可(其实就是在滤镜先后分别调用clampedToExtend()获取一个边缘扩展的图像,应用滤镜以后调用croped()获取一个裁剪边缘的图像便可)。机器学习

guard let cgImage = UIImage(named:"CIFilter_Demo_Origin")?.cgImage else { return }
        let ciImage = CIImage(cgImage: cgImage)
        let outputImage = ciImage.clampedToExtent().applyingFilter("CIGaussianBlur", parameters: [kCIInputRadiusKey:5.0]).cropped(to: ciImage.extent)
        let context = CIContext()
        if let cgImage = context.createCGImage(outputImage, from: ciImage.extent) {
            let image = UIImage(cgImage: cgImage)
            UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
        }

自定义算子

尽管Core Image提供了很多滤镜可使用,不过实际开发中还并不可以知足需求,好比说描绘边缘这个操做在Core Image中应该就没有提供直接的滤镜。而有很多滤镜是经过卷积操做完成的,只要提供一个算子就能够造成一个新的滤镜效果,事实上Core Image框架也提供了这个滤镜:CIConvolution3X3CIConvolution5X5。这两个滤镜支持开发者自定义算子实现一个滤镜操做,下面是使用CIConvolution3X3实现的sobel算子提取边缘的滤镜:异步

guard let cgImage = UIImage(named:"CIFilter_Demo_Origin")?.cgImage else { return }
        let ciImage = CIImage(cgImage: cgImage)
        let sobel:[CGFloat] = [-1,0,1,-2,0,2,-1,0,1]
        let weight = CIVector(values: sobel, count: 9)
        let outputImage = ciImage.applyingFilter("CIConvolution3X3", parameters: [kCIInputWeightsKey:weight,kCIInputBiasKey:0.5])
        
        let context = CIContext()
        if let cgImage = context.createCGImage(outputImage, from: ciImage.extent) {
            let image = UIImage(cgImage: cgImage)
            UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
        }

前面的图应用Sobel算子后的效果:

CIFilter_Demo_Sobel

能够看出来边缘已经被提取出来,其实不管是CIConvolution3X3仍是CIConvolution5X5都只是进行一个卷积操做,本质就是对应的像素分别乘以对应算子上的值最后相加等于产生一个新的值做为当前像素的值(这个值一般是待处理图像区块中心)以下图:

sobel_demo

除了上面的Sobel算子,常见的算子还有锐化算子{0,-1,0,-1,5,-1,0,-1,0}、浮雕算子{1,0,0,0,0,0,0,0,-1}、拉普拉斯算子(边缘检测){0,1,0,1,-4,1,0,1,0}等等。

自定义滤镜

若是仅仅是自定义算子恐怕还不能体现出CIFilter的强大之处,毕竟很多滤镜经过特定算子仍是没法知足的,CIFilter支持自定义片断着色器实现本身的滤镜效果。
自定义的 Filter 和系统内置的各类 CIFilter,使用起来方式是同样的。咱们惟一要作的,就是实现一个符合规范的 CIFilter 的子类。过程你们就是:编写 kernel->加载 kernel->设置参数。假设如今编写一个图片翻转的效果大概过程以下:

1.编写kernel脚本,保存为Flip.kernel

kernel vec2 mirrorX ( float imageWidth ) 
{
    vec2 currentVec = destCoord();
    return vec2 ( imageWidth - currentVec.x , currentVec.y ); 
}

2.加载kernel

class FlipFilterGenerator:NSObject, CIFilterConstructor {
    func filter(withName name: String) -> CIFilter? {
        if name == "\(FlipFilter.self)" {
            return FlipFilter()
        }
        return nil
    }
}
private let flipKernel:CIWarpKernel? = CIWarpKernel(source:try! String(contentsOf:Bundle.main.url(forResource: "Flip", withExtension: "cikernel")!))
class FlipFilter: CIFilter {
    
    
    
    override init() {
        super.init()
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    static func register() {
        CIFilter.registerName("\(FlipFilter.self)", constructor: FlipFilterGenerator(), classAttributes: [kCIAttributeFilterName:"\(FlipFilter.self)"])
    }
    
    override func setDefaults() {
        
    }
    
    @objc var inputImage: CIImage?
    
    override var outputImage: CIImage? {
        guard let width = self.inputImage?.extent.size.width else { return nil }
        let result = flipKernel?.apply(extent: inputImage!.extent, roiCallback: { (index, rect) -> CGRect in
            return rect
        }, image: self.inputImage!, arguments: [width])
        return result
    }
    
    override var name: String {
        get {
            return "\(FlipFilter.self)"
        }
        set {
            
        }
    }
    
}

使用CIFilter的source构造函数传入着色器代码,而后经过apply()方法传入参数便可执行着色。固然使用以前记得进行注册,这样在使用的时候就能够像使用内置滤镜同样使用了。

可是这里必须着重看一下apply()方法的几个参数
extent:要处理的输入图片的区域(称之为DOD ( domain of definition ) ),通常处理的都是原图,并不会改变图像尺寸因此上面传的是inputImage.extent
roiCallback:感兴趣的处理区域(ROI ( region of interest ),能够理解为当前处理区域对应的原图区域)处理完后的回调,回调参数index表明图片索引顺序,回调参数rect表明输出图片的区域DOD,可是须要注意在Core Image处理中这个回调会屡次调用。这个值一般只要不发生旋转就是当前图片的坐标(若是旋转90°,则返回为CGRect(x: rect.origin.y, y: rect.origin.x, width: rect.size.height, height: rect.size.width))
arguments:着色器函数中须要的参数,按顺序传入。

自定义滤镜调用:

FlipFilter.register()
        guard let cgImage = UIImage(named:"CIFilter_Demo_Origin")?.cgImage else { return }
        let ciImage = CIImage(cgImage: cgImage)
        let outputImage = ciImage.applyingFilter("FlipFilter")
        
        let context = CIContext()
        if let cgImage = context.createCGImage(outputImage, from: ciImage.extent) {
            let image = UIImage(cgImage: cgImage)
            UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
        }

下面是上图使用翻转滤镜后的效果:

CIFilter_Demo_Flip

其实准确的来讲实现一个自定义滤镜就是实现一个自定义的CIKernel类,固然这个类自己包括两个子类CIColorKernelCIWarpKernel,前者用于图像颜色转化滤镜,然后者用于形变滤镜,如前面的翻转很明显不是一个颜色值的修改就能解决的,必须依赖于形变操做因此继承自CIWarpKernel要简单些。固然若是你的滤镜综合了两者的特色那么直接选择使用CIKernel是正确的。至于着色器代码编写使用的是Core Image Kernel Language (CIKL),它是OpenGL Shading Language (GLSL) 的子集。CIKL 集成了 GLSL 绝大部分的参数类型和内置函数,另外它还添加了一些适应 Core Image 的参数相似和函数。另外编写CIKL须要注意坐标系,它的坐标系从左下角开始而不是UIKit的左上角。

因为篇幅缘由关于编写CIKL的具体细节这里再也不赘述,感兴趣能够参考Writing KernelsCore Image Kernel Language Reference,而编写CIKL的工具天然推荐官方的Quartz Composer

从前面的演示也能够看到图片在UIImage、CGImage和CIImage之间不停的转化,那么三者之间有什么区别呢?
UIImage存在于UIKit中,CGImage存在于Core Graphics中,CIImage存在于Core Image中。前者负责展现和管理图片数据,例如可使用UIImageView展现、或者绘制到UIView、layer上等,主要在CPU上操做;CGImage表示图像的像素矩阵,每一个点都对应了图片的像素信息,主要运行在GPU上;而CIImage包含了建立图片的必要数据,自身并不会渲染成图片,表明了图像的数据或者操做图像的流程(如滤镜),主要运行在GPU上。换句话说对于CIImage的操做并不会进行大量的图片运算,只有要输出图片时才须要转化成图片数据(推荐这一步尽可能放到异步线程中操做)。
注意:获取一个图片的CIImage类型时请使用CIImage()构造方法建立,请勿直接访问uiImage.ciImage,由于若是一个UIImage不是从CIImage建立是没法获取ciImage的(uiImage.cgImage相似,上面之因此能够直接使用UIImage.cgImage属性是由于它并不是从ciImage建立)。反之,若是从ciImage建立UIImage就不推荐使用UIImage的构造方法了,由于这种方式会丢失信息,例如使用UIViewImage显示时会丢失contentMode设置,若是使用上面的代码保存会出现保存失败的状况,推荐的方式则是使用UIContext先生成CGImage,而后从CGImage建立UIImage(总结起来就是UIImage到CGImage明确的状况下能够直接访问cgImage属性,可是cgImage为空则访问ciImage属性再从ciImage建立cgImage,从CGImage转化为UIImage使用构造函数;UIImage到CIImage推荐使用构造函数,也可使用CGImage从中间过渡,而从CIImage转化为UIImage只能经过CGImage过渡再用构造函数建立)。

Metal Shader

CIKL-MetalShader

若是你编写过CIKL你会发现这种开发方式很古老,Quartz Composer尽管做为目前开发CIKL最合适的工具但在Xcode7以后几乎没有更新过,尽管有语法高亮可是没有错误调试,更不用说运行时出错的问题(尽管可使用+(id)kernelsWithString:(id)arg1 messageLog:(id)arg2这个私有方法打印kernel中的错误,可是调试依然很麻烦),自身以字符串传入CIKernel类的方式让它自然失去了语法检查。更重要的是这种方式最终要将CIKL片断变成CIKernel必须通过CIKL->GLSL->CIKernel->IL->GPU识别码->Render到GPU,若是遇到滤镜链还必须在中间连接Kernel,而这些操做所有在运行时进行。因此首次使用会比较慢(后面使用会缓存),而2017年Metal支持CIKernel则将Kernel的编译提早到了App编译阶段,从而支持了语法检查,大大提升了开发效率和运行效率。

例如前面的滤镜链中使用了一个马赛克风格的滤镜,这里不妨先看一下使用CIKL编写这个滤镜(注意这是一个CIWrapKernel,返回值是变化后的坐标位置):

kernel vec2 pixellateKernel(float radius)
{
    vec2 positionOfDestPixel, centerPoint;
    positionOfDestPixel = destCoord();
    centerPoint.x = positionOfDestPixel.x - mod(positionOfDestPixel.x, radius * 2.0) + radius;
    centerPoint.y = positionOfDestPixel.y - mod(positionOfDestPixel.y, radius * 2.0) + radius;

    return centerPoint;
}

这个CIKL用Metal Shader书写以下:

extern "C" {
    namespace coreimage {
        
        float2 pixellateMetal(float radius,destination dest) {
            float2 positionOfDestPixel, centerPoint;
            positionOfDestPixel = dest.coord();
            centerPoint.x = positionOfDestPixel.x - fmod(positionOfDestPixel.x, radius * 2.0) + radius;
            centerPoint.y = positionOfDestPixel.y - fmod(positionOfDestPixel.y, radius * 2.0) + radius;
            
            return centerPoint;
        }
        
    }
}

固然对应的自定义CIFilter须要作少量调整:

class PixellateFilterGenerator:NSObject, CIFilterConstructor {
    func filter(withName name: String) -> CIFilter? {
        if name == "\(PixellateFilter.self)" {
            return PixellateFilter()
        }
        return nil
    }
}

private var pixellateKernel:CIWarpKernel? = {
    guard let url = Bundle.main.url(forResource: "default", withExtension: "metallib") else { return nil }
    guard let data = try? Data(contentsOf: url) else { return nil }
    let kernel = try? CIWarpKernel(functionName: "pixellateMetal", fromMetalLibraryData: data)
    return kernel
}()
class PixellateFilter: CIFilter {
    
    override init() {
        super.init()
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    static func register() {
        CIFilter.registerName("\(PixellateFilter.self)", constructor: PixellateFilterGenerator(), classAttributes: [kCIAttributeFilterName:"\(PixellateFilter.self)"])
    }
    
    override func setDefaults() {
        
    }
    
    @objc var inputImage: CIImage?
    
    @objc var radius:CGFloat = 5.0
    
    override var outputImage: CIImage? {
        let result = pixellateKernel?.apply(extent: inputImage!.extent, roiCallback: { (index, rect) -> CGRect in
            return rect
        }, image: self.inputImage!, arguments: [radius])
        return result
    }
    
    override var name: String {
        get {
            return "\(PixellateFilter.self)"
        }
        set {
            
        }
    }
    
    override var attributes: [String : Any] {
        get {
            return [
                "radius":[
                    kCIAttributeMin:1,
                    kCIAttributeDefault:5.0,
                    kCIAttributeType:kCIAttributeTypeScalar
                ]
            ]
        }
    }
}

若是说只是像前面同样简单的使用这个滤镜恐怕还没法体现Metal Shader的高性能,不妨把上面应用自定义滤镜后直接保存相册的操做改为一个滑动条在UIImageView直接预览:

class ViewController: UIViewController {

    var filter:CIFilter?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.addSubview(self.imageView)
        self.view.addSubview(sliderBar)
        
        PixellateFilter.register()
        filter = CIFilter(name: "PixellateFilter")

        guard let cgImage = UIImage(named: "CIFilter_Demo_Origin")?.cgImage else { return }
        let ciImage = CIImage(cgImage: cgImage)
        filter?.setValue(ciImage, forKey: kCIInputImageKey)
    }
    
    @objc func sliderValueChange(_ sender:UISlider) {
        filter?.setValue(sender.value, forKey: "radius")
        if let outputImage = filter?.outputImage {
            self.imageView.image = UIImage(ciImage: outputImage)
        }
    }
    
    private lazy var imageView:UIImageView = {
        let temp = UIImageView(frame: CGRect(x: 0.0, y: 0.0, width: Constants.screenSize.width, height: Constants.screenSize.height-60))
        temp.contentMode = .scaleAspectFill
        temp.image = UIImage(named: "CIFilter_Demo_Origin")
        return temp
    }()
    
    private lazy var sliderBar:UISlider = {
        let temp = UISlider(frame: CGRect(x: 0.0, y: Constants.screenSize.height-50, width: Constants.screenSize.width, height: 30))
        temp.minimumValue = 1
        temp.maximumValue = 20
        temp.addTarget(self, action: #selector(sliderValueChange(_:)), for: UIControl.Event.valueChanged)
        return temp
    }()
}

运行效果:

Metal-Shader-Demo

能够看到,拖动滑动条能够实时预览滤镜效果而没有丝毫卡顿,前面也提到CIImage自己并不包含图像数据,当UIImageView显示时会在GPU上执行Core Image操做,释放了CPU的压力(这也是UIImageView针对Core Image优化的结果)。

不管是经过CIKL仍是经过Metal自定义CIFilter都不是万能的,这是因为kernel自己的限制所形成的。kernel的原理简单理解就是遍历一个图片的全部像素点,而后经过kernel处理后返回新的像素点做为新的图片的像素点。而相似于绘制直方图、动漫风格等操做依赖于整个图片的分布或者依赖于机器学习的操做则很难使用kernel完成,固然这能够借助于后面的OpenCV轻松作到。

GPUImage

GPUImage能够说是iOS滤镜开发中多数app的首选,缘由在于它不只高效(从名字就能够看出它运行在GPU上),并且简单(下面三行代码就实现了上面的高斯模糊效果),固然还有它强大的工具属性。它不只支持实时滤镜预览,还支持视频实时滤镜等。

下面是使用高斯模糊的演示:

GPUImageGaussianBlurFilter * blurFilter = [[GPUImageGaussianBlurFilter alloc] init];
 blurFilter.blurRadiusInPixels = 2.0;
 UIImage * image = [UIImage imageNamed:@"CIFilter_Demo_Origin"];
 UIImage *blurredImage = [blurFilter imageByFilteringImage:image];

滤镜后的效果:

GPUImage_GaussianBlur_Demo

不过能够对比以前的效果,发现GPUImage对于高斯模糊的处理包括了边缘的处理,并不须要针对边缘进行从新裁剪。

固然若是不支持自定义那么GPUImage也谈不上强大,GPUImage 自定义滤镜须要使用 OpenGL 着色语言( GLSL )编写 Fragment Shader(片断着色器),这些其实和自定义Core Image是相似的。

下面演示了使用GPUImage自定义实现一个图片暗角滤镜:

#import <GPUImage/GPUImage.h>

@interface VignetteFilter : GPUImageFilter
    
    @property (nonatomic,assign) CGPoint center;
    @property (nonatomic,assign) CGFloat radius;
    @property (nonatomic,assign) CGFloat alpha;
    
@end
@implementation VignetteFilter {
    GLint centerXUniform,centerYUniform,alphaUniform,radiusUniform;
}

    - (instancetype)init
    {
        self = [super initWithFragmentShaderFromFile:@"VignetteFilter"];
        if (!self) {
            return nil;
        }
        
        centerXUniform = [filterProgram uniformIndex:@"centerX"];
        centerYUniform = [filterProgram uniformIndex:@"centerY"];
        alphaUniform = [filterProgram uniformIndex:@"alpha"];
        radiusUniform = [filterProgram uniformIndex:@"radius"];
        
        self.alpha = 0.5;
        self.radius = 100;
        return self;
    }

    - (void)setCenter:(CGPoint)center {
        [self setFloat:center.x forUniform:centerXUniform program:filterProgram];
        [self setFloat:center.y forUniform:centerYUniform program:filterProgram];
    }
    
    - (void)setAlpha:(CGFloat)alpha {
        [self setFloat:alpha forUniform:alphaUniform program:filterProgram];
    }
    
    - (void)setRadius:(CGFloat)radius {
        [self setFloat:radius forUniform:radiusUniform program:filterProgram];
    }

@end

片断着色器代码:

uniform highp float alpha;
uniform lowp float radius;
uniform lowp float centerX;
uniform lowp float centerY;
varying highp vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
void main()
{
    highp vec2 centerPoint = vec2(centerX, centerY);
    lowp vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);
    highp float distance = distance(gl_FragCoord.xy, centerPoint);
    highp float darken = 1.0 - (distance / (radius*0.5) * alpha);
    gl_FragColor = vec4(textureColor.rgb*darken,textureColor.a);
}

滤镜后的图片

GPUImage_Vignette_Demo

和Core Image不一样的是GPUImage使用的并不是CIKL而是GLSL(两者均是类C语言)来编写滤镜,优势天然是了解片断着色器就能够无过渡编写滤镜着色代码,无需转化,同时它也是跨平台的。缺点就是iOS 12以后Core Image使用Metal引擎逐渐摒弃了OpenGL,效率则更高(固然GPUImage3已经支持Metal Shader,这样两者就逐渐没有了区别)。

OpenCV

既然前面提到了OpenGL,那么就离不开另一个库OpenCV,前者主要用于显示,后者用于运算处理,固然OpenCV默认编译是不支持的GPU加速的,不过胜在它的算法强大,算法速度很快,并且使人兴奋的是3.0之后使用CUDA是能够支持使用GPU运算的。

使用OpenCV实现滤镜更像是使用vImage(存在于Accelerate.framework),不只能够像上面同样直接基于像素进行处理,还能使用它提供的不少强大算法,同时考虑到自定义算子OpenCV甚至直接暴漏了Filter2D让咱们能够直接像编写上面的着色器那样方便的进行卷积操做。

下面使用OpenCV实现一个羽化操做:

#include <math.h>
#include <opencv/cv.h>
#include <opencv/highgui.h>
#define MAXSIZE (32768)
using namespace cv;
using namespace std;



float mSize = 0.5;

int main()
{
    Mat src = imread("/Users/Kenshin/Downloads/CIFilter_Demo_Origin.jpg",1);
    imshow("src",src);
    int width=src.cols;
    int heigh=src.rows;
    int centerX=width>>1;
    int centerY=heigh>>1;
    
    int maxV=centerX*centerX+centerY*centerY;
    int minV=(int)(maxV*(1-mSize));
    int diff= maxV -minV;
    float ratio = width >heigh ? (float)heigh/(float)width : (float)width/(float)heigh;
    
    Mat img;
    src.copyTo(img);
    
    Scalar avg=mean(src);
    Mat dst(img.size(),CV_8UC3);
    Mat mask1u[3];
    float tmp,r;
    for (int y=0;y<heigh;y++)
    {
        uchar* imgP=img.ptr<uchar>(y);
        uchar* dstP=dst.ptr<uchar>(y);
        for (int x=0;x<width;x++)
        {
            int b=imgP[3*x];
            int g=imgP[3*x+1];
            int r=imgP[3*x+2];
            
            float dx=centerX-x;
            float dy=centerY-y;
            
            if(width > heigh)
                dx= (dx*ratio);
            else
                dy = (dy*ratio);
            
            int dstSq = dx*dx + dy*dy;
            
            float v = ((float) dstSq / diff)*255;
            
            r = (int)(r +v);
            g = (int)(g +v);
            b = (int)(b +v);
            r = (r>255 ? 255 : (r<0? 0 : r));
            g = (g>255 ? 255 : (g<0? 0 : g));
            b = (b>255 ? 255 : (b<0? 0 : b));
            
            dstP[3*x] = (uchar)b;
            dstP[3*x+1] = (uchar)g;
            dstP[3*x+2] = (uchar)r;
        }
    }
    imshow("blur",dst);
    
    waitKey();
    imwrite("/Users/Kenshin/Downloads/blur.jpg",dst);
}

没错,这是一段c++代码,但在OC中能够很方便的使用,只要实现一个Wrapper类,将.m改成.mm就能够直接调用c++代码。

下面是羽化后的效果:

CIFilter_Demo_EdgeBlur

总结

从上面能够看到其实开发滤镜选择不少,普通的滤镜使用GPUImage这种基于OpenGL的滤镜效率比较高、可移植性强,缺点固然就是GLSL调试比较难,遇到错误须要反复试验。若是你的App仅仅考虑iOS 11以上的运行环境,天然首推Metal Shading Language,调试方便又高效,尽管GPUImage3已经支持了Metal Shader可是当前还不完善,不少GPUImage有的功能还在待开发阶段当前不建议使用。而OpenCV天然是一把倚天剑,强大的算法,自然的可移植性,可是因为过于强大,不是相似于人脸识别这种复杂的非着色滤镜不推荐使用,固然换句话说一旦遇到机器学习相关(例如CARTOONGAN),高级特效通常非OpenCV莫属。

相关文章
相关标签/搜索