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莫屬。

相關文章
相關標籤/搜索