GPUImage2(一)集成與使用

關(guān)鍵詞


濾鏡 GPUImage 顏色 Filter colorDistance 相機(jī) 識別 框架 紋理 渲染

本文所有示例代碼或Demo可以在此獲鹊擞取:https://github.com/WillieWangWei/SampleCode_GPUImage2_Usage

如果本文對你有所幫助片择,請給個Star??

相關(guān)文章
GPUImage2(二)濾鏡大全:圖像生成
GPUImage2(三)濾鏡大全:色彩調(diào)校
GPUImage2(四)濾鏡大全:圖像處理
GPUImage2(五)濾鏡大全:混合模式
GPUImage2(六)濾鏡大全:視覺特效

概述


GPUImage是一個基于OpenGL ES 2.0的開源的圖像處理庫献烦,作者是Brad Larson卖丸。GPUImageOpenGL ES封裝為簡潔的Objective-CSwift接口识椰,可以用來給圖像列牺、實時相機(jī)視頻、電影等添加濾鏡恃轩。對于諸如處理圖像或?qū)崨r視頻幀的大規(guī)模并行操作,GPU相對于CPU具有一些顯著的性能優(yōu)點黎做。在iPhone 4上叉跛,簡單的圖像濾鏡在GPU上的執(zhí)行速度比等效的基于CPU的濾鏡快100多倍。

目前它有兩個版本:

  1. GPUImage蒸殿。開發(fā)者使用最多的版本筷厘,它于2012年最早推出,使用Objective-C編寫宏所,支持macOSiOS酥艳。
  2. GPUImage2。同一作者在2016年推出的版本楣铁,使用Swift編寫玖雁,是GPUImage框架的第二代,支持macOS盖腕、iOSSwift代碼的Linux或未來平臺赫冬。

本文以Swift版的GPUImage2為主題,從以下幾個方面進(jìn)行講解:

  • 在項目中集成
  • 特性
  • 示例代碼
  • 注意問題

在項目中集成


  1. 下載壓縮包文件溃列,下載地址劲厌。
  2. 解壓后目錄如下:
    文件目錄

    framework下的GPUImage-iOS.xcodeproj項目和Source文件夾復(fù)制到你的項目中。
  3. 在你的項目的Build Phases欄听隐,Target Dependency中添加GPUImage依賴补鼻。
    Target Dependency

    在下面的Link Binary With Libraries中添加GPUImage
    Link Binary With Libraries

    點擊左上角的+,選擇New Copy Files Phase风范,在新建的Copy Files中將Destination選為Frameworks咨跌,并在欄目中添加GPUImage.framework
    Copy Files

    確認(rèn)現(xiàn)在你的項目文件夾中存在GPUImage-iOS.xcodeprojSource硼婿,像是這樣:
    編譯條件

    4.如果前幾步?jīng)]有問題锌半,現(xiàn)在Build。稍等會提示成功寇漫,但出現(xiàn)了一些警告:
    過期警告

    這是因為使用了過期的函數(shù)刊殉,但暫時不會造成功能上的問題。如果你覺得不爽州胳,可以參考如何忽略警告记焊。

特性


GPUImage2可以進(jìn)行多種模式的圖像處理,其邏輯類似于流水線的概念栓撞。流水線上有若干個工位(Filter)遍膜,每個工位接收來自上一個工位的產(chǎn)品(Data),完成此工序的加工(Processing)后交給下一個工位(Target)處理腐缤。產(chǎn)品從開始端(Input)經(jīng)過整條流水線加工捌归,到達(dá)結(jié)束端(Output)變?yōu)槌善贰?/p>

處理流程

雖然功能和GPUImage相似,但GPUImage2使用了大量Swift語言的特性岭粤,在命名規(guī)則惜索、代碼風(fēng)格上都產(chǎn)生了很大的差別,比如:

-->運(yùn)算符

-->GPUImage2定義的一個中綴運(yùn)算符剃浇,它將兩個對象像鏈條一樣串聯(lián)起來巾兆,用起來像是這樣:

camera --> basicOperation --> renderView

左邊的參數(shù)遵循ImageSource協(xié)議,作為數(shù)據(jù)的輸入虎囚,右邊的參數(shù)遵循ImageConsumer協(xié)議角塑,作為數(shù)據(jù)的輸出。這里的basicOperationBasicOperation的一個實例淘讥,其父類ImageProcessingOperation同時遵循ImageSourceImageConsumer協(xié)議圃伶,所以它可以放在-->的左邊或右邊。
-->的運(yùn)算是左結(jié)合的蒲列,類似于GPUImage中的addTarget方法窒朋,但是-->有一個返回值,就是右邊的參數(shù)蝗岖。在上面的示例中侥猩,先計算了前半部camera --> basicOperation,然后右邊的參數(shù)basicOperation作為返回值又參與了后半部basicOperation --> renderView的計算抵赢。
-->體現(xiàn)了鏈?zhǔn)骄幊痰乃枷肫劾停尨a更加優(yōu)雅唧取,在GPUImage2有著大量運(yùn)用,這得益于Swift強(qiáng)大的語法划提,關(guān)于Swift中的高級運(yùn)算符枫弟,請看這里

示例代碼


GPUImage2主要提供了這些功能:

  • 處理靜態(tài)圖片
  • 操作組
  • 實時視頻濾鏡
  • 從視頻中捕獲圖片
  • 編寫自定義的圖像處理操作
  • 從靜態(tài)圖片中捕獲并添加濾鏡(即將實現(xiàn))
  • 添加濾鏡并轉(zhuǎn)碼視頻(即將實現(xiàn))
準(zhǔn)備

導(dǎo)入頭文件

import GPUImage
import AVFoundation

聲明變量

var camera: Camera!
var basicOperation: BasicOperation!
var renderView: RenderView!

lazy var imageView: UIImageView = {
   
    let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
    imageView.image = UIImage(contentsOfFile: Bundle.main.path(forResource: "Yui", ofType: "jpg")!)
    imageView.contentMode = .scaleAspectFit
    
    return imageView
}()

初始化

override func viewDidLoad() {
    super.viewDidLoad()
    
    view.addSubview(imageView)
}
處理靜態(tài)圖片

創(chuàng)建濾鏡實例

// 創(chuàng)建一個BrightnessAdjustment顏色處理濾鏡
let brightnessAdjustment = BrightnessAdjustment()
brightnessAdjustment.brightness = 0.2

// 創(chuàng)建一個ExposureAdjustment顏色處理濾鏡
let exposureAdjustment = ExposureAdjustment()
exposureAdjustment.exposure = 0.5

使用GPUImageUIImage提供的擴(kuò)展方法進(jìn)行便利濾鏡處理

// 1.使用GPUImage對UIImage的擴(kuò)展方法進(jìn)行濾鏡處理
var filteredImage: UIImage

// 1.1單一濾鏡
filteredImage = imageView.image!.filterWithOperation(brightnessAdjustment)

// 1.2多個濾鏡疊加
filteredImage = imageView.image!.filterWithPipeline { (input, output) in
    input --> brightnessAdjustment --> exposureAdjustment --> output
}

// 不建議的
imageView.image = filteredImage

注意:如果要將圖片顯示在屏幕上或者進(jìn)行多次濾鏡處理時腔剂,以上方法會讓Core Graphics產(chǎn)生更多開銷媒区,建議使用處理鏈驼仪,最后指向RenderView來顯示掸犬,如下:

// 2.使用管道處理

// 創(chuàng)建圖片輸入
let pictureInput = PictureInput(image: imageView.image!)
// 創(chuàng)建圖片輸出
let pictureOutput = PictureOutput()
// 給閉包賦值
pictureOutput.imageAvailableCallback = { image in
    // 這里的image是處理完的數(shù)據(jù),UIImage類型
}
// 綁定處理鏈
pictureInput --> brightnessAdjustment --> exposureAdjustment --> pictureOutput
// 開始處理 synchronously: true 同步執(zhí)行 false 異步執(zhí)行绪爸,處理完畢后會調(diào)用imageAvailableCallback這個閉包
pictureInput.processImage(synchronously: true)
操作組

你可以將若干個BasicOperation的實例包裝成一個OperationGroup操作組湾碎,通過給閉包賦值來定義組內(nèi)濾鏡的處理流程,外部可以將OperationGroup的實例作為一個獨立單位參與其他濾鏡處理奠货。

// MARK: - 操作組
func operationGroup() {
    
    // 創(chuàng)建一個BrightnessAdjustment顏色處理濾鏡
    let brightnessAdjustment = BrightnessAdjustment()
    brightnessAdjustment.brightness = 0.2
    
    // 創(chuàng)建一個ExposureAdjustment顏色處理濾鏡
    let exposureAdjustment = ExposureAdjustment()
    exposureAdjustment.exposure = 0.5
    
    // 創(chuàng)建一個操作組
    let operationGroup = OperationGroup()
    
    // 給閉包賦值介褥,綁定處理鏈
    operationGroup.configureGroup{input, output in
        input --> brightnessAdjustment --> exposureAdjustment --> output
    }

    // 進(jìn)行濾鏡處理
    imageView.image = imageView.image!.filterWithOperation(operationGroup)
}
實時視頻濾鏡

從相機(jī)中獲取圖像數(shù)據(jù),經(jīng)過濾鏡處理后實時的顯示在屏幕上递惋。

// MARK: - 實時視頻濾鏡
func CameraFiltering() {
    
    // Camera的構(gòu)造函數(shù)是可拋出錯誤的
    do {
        // 創(chuàng)建一個Camera的實例柔滔,Camera遵循ImageSource協(xié)議,用來從相機(jī)捕獲數(shù)據(jù)
        
        /// Camera的指定構(gòu)造器
        ///
        /// - Parameters:
        ///   - sessionPreset: 捕獲視頻的分辨率
        ///   - cameraDevice: 相機(jī)設(shè)備萍虽,默認(rèn)為nil
        ///   - location: 前置相機(jī)還是后置相機(jī)睛廊,默認(rèn)為.backFacing
        ///   - captureAsYUV: 是否采集為YUV顏色編碼,默認(rèn)為true
        /// - Throws: AVCaptureDeviceInput構(gòu)造錯誤
        camera = try Camera(sessionPreset: AVCaptureSessionPreset1280x720,
                            cameraDevice: nil,
                            location: .backFacing,
                            captureAsYUV: true)
        
        // Camera的指定構(gòu)造器是有默認(rèn)參數(shù)的杉编,可以只傳入sessionPreset參數(shù)
        // camera = try Camera(sessionPreset: AVCaptureSessionPreset1280x720)
        
    } catch {
        
        print(error)
        return
    }
    
    // 創(chuàng)建一個Luminance顏色處理濾鏡
    basicOperation = Luminance()
    
    // 創(chuàng)建一個RenderView的實例并添加到view上超全,用來顯示最終處理出的內(nèi)容
    renderView = RenderView(frame: view.bounds)
    view.addSubview(renderView)
    
    // 綁定處理鏈
    camera --> basicOperation --> renderView
    
    // 開始捕捉數(shù)據(jù)
    camera.startCapture()
    
    // 結(jié)束捕捉數(shù)據(jù)
    // camera.stopCapture()
}
從視頻中捕獲圖片

從視頻中獲取某一幀的圖片,可以以任一濾鏡節(jié)點作為數(shù)據(jù)源邓馒。

// MARK: - 從實時視頻中截圖圖片
func captureImageFromVideo() {
    
    // 啟動實時視頻濾鏡
    self.cameraFiltering()
    
    // 設(shè)置保存路徑
    guard let outputPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first else { return }
    
    let originalPath = outputPath + "/originalImage.png"
    print("path: \(originalPath)")
    let originalURL = URL(fileURLWithPath: originalPath)
    
    let filteredPath = outputPath + "/filteredImage.png"
    print("path: \(filteredPath)")
    let filteredlURL = URL(fileURLWithPath: filteredPath)
    
    // 延遲1s執(zhí)行嘶朱,防止截到黑屏
    DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .seconds(1)) {
        
        // 保存相機(jī)捕捉到的圖片
        self.camera.saveNextFrameToURL(originalURL, format: .png)
        
        // 保存濾鏡后的圖片
        self.basicOperation.saveNextFrameToURL(filteredlURL, format: .png)
        
        // 如果需要處理回調(diào),有下面兩種寫法
        
        let dataOutput = PictureOutput()
        dataOutput.encodedImageFormat = .png
        dataOutput.encodedImageAvailableCallback = {imageData in
            // 這里的imageData是截取到的數(shù)據(jù)光酣,Data類型
        }
        self.camera --> dataOutput
        
        let imageOutput = PictureOutput()
        imageOutput.encodedImageFormat = .png
        imageOutput.imageAvailableCallback = {image in
            // 這里的image是截取到的數(shù)據(jù)疏遏,UIImage類型
        }
        self.camera --> imageOutput
    }
}
編寫自定義的圖像處理操作

自定義濾鏡需要使用OpenGL著色語言(GLSL)編寫Fragment Shader(片段著色器),調(diào)用BasicOperation的構(gòu)造器讀取寫好的文件救军,可以創(chuàng)建自定義濾鏡财异。

// MARK: - 編寫自定義的圖像處理操作
func customFilter() {
    
    // 獲取文件路徑
    let url = URL(fileURLWithPath: Bundle.main.path(forResource: "Custom", ofType: "fsh")!)
    
    var customFilter: BasicOperation
    
    do {
        // 從文件中創(chuàng)建自定義濾鏡
        customFilter = try BasicOperation(fragmentShaderFile: url)
    } catch {
        
        print(error)
        return
    }
    
    // 進(jìn)行濾鏡處理
    imageView.image = imageView.image!.filterWithOperation(customFilter)
}

Custom.fsh文件像是這樣:

varying highp vec2 textureCoordinate;

uniform sampler2D inputImageTexture;

void main()
{
    highp vec2 sampleDivisor = vec2(1.0 / 200.0, 1.0 / 320.0);
    //highp vec4 colorDivisor = vec4(colorDepth);
    
    highp vec2 samplePos = textureCoordinate - mod(textureCoordinate, sampleDivisor);
    highp vec4 color = texture2D(inputImageTexture, samplePos );
    
    //gl_FragColor = texture2D(inputImageTexture, samplePos );
    mediump vec4 colorCyan = vec4(85.0 / 255.0, 1.0, 1.0, 1.0);
    mediump vec4 colorMagenta = vec4(1.0, 85.0 / 255.0, 1.0, 1.0);
    mediump vec4 colorWhite = vec4(1.0, 1.0, 1.0, 1.0);
    mediump vec4 colorBlack = vec4(0.0, 0.0, 0.0, 1.0);
    
    mediump vec4 endColor;
    highp float blackDistance = distance(color, colorBlack);
    highp float whiteDistance = distance(color, colorWhite);
    highp float magentaDistance = distance(color, colorMagenta);
    highp float cyanDistance = distance(color, colorCyan);
    
    mediump vec4 finalColor;
    
    highp float colorDistance = min(magentaDistance, cyanDistance);
    colorDistance = min(colorDistance, whiteDistance);
    colorDistance = min(colorDistance, blackDistance);
    
    if (colorDistance == blackDistance) {
        finalColor = colorBlack;
    } else if (colorDistance == whiteDistance) {
        finalColor = colorWhite;
    } else if (colorDistance == cyanDistance) {
        finalColor = colorCyan;
    } else {
        finalColor = colorMagenta;
    }
    
    gl_FragColor = finalColor;
}
從靜態(tài)圖片中捕獲并添加濾鏡

作者暫未實現(xiàn)

添加濾鏡并轉(zhuǎn)碼視頻

作者暫未實現(xiàn)

注意問題

使用Cocoapods安裝

作者暫不支持。但是有網(wǎng)友制作了EVGPUImage2這個倉庫來間接使用GPUImage2缤言,有興趣可以嘗試一下宝当。

使用ACV文件創(chuàng)建濾鏡

GPUImage中可以通過ACV文件快速創(chuàng)建自定義濾鏡。AVC可以通過photoShop進(jìn)行圖片顏色曲線處理得到胆萧,但是GPUImage2暫未移植這個功能庆揩。

與Core Image比較

Core Image是iOS內(nèi)置的圖像處理框架俐东,兩者相比各有優(yōu)點:

GPUImage 優(yōu)勢

  • 最低支持 iOS 4.0,iOS 5.0 之后就支持自定義濾鏡订晌。
  • 在低端機(jī)型上虏辫,GPUImage 有更好的表現(xiàn)。(這個我沒用真正的設(shè)備對比過锈拨,GPUImage 的主頁上是這么說的)
  • GPUImage 在視頻處理上有更好的表現(xiàn)砌庄。
  • GPUImage 的代碼完成公開,實現(xiàn)透明奕枢。
  • 可以根據(jù)自己的業(yè)務(wù)需求娄昆,定制更加復(fù)雜的管線操作》毂颍可定制程度高萌焰。

Core Image 優(yōu)勢

  • 官方框架,使用放心谷浅,維護(hù)方便扒俯。
  • 支持 CPU 渲染,可以在后臺繼續(xù)處理和保存圖片一疯。
  • 一些濾鏡的性能更強(qiáng)勁撼玄。例如由 Metal Performance Shaders 支持的模糊濾鏡等。
  • 支持使用 Metal 渲染圖像墩邀。而 Metal 在 iOS 平臺上有更好的表現(xiàn)掌猛。
  • 與 Metal,SpriteKit磕蒲,SceneKit留潦,Core Animation 等更完美的配合。
  • 支持圖像識別功能辣往。包括人臉識別兔院、條形碼識別、文本識別等站削。
  • 支持自動增強(qiáng)圖像效果坊萝,會分析圖像的直方圖,圖像屬性许起,臉部區(qū)域十偶,然后通過一組濾鏡來改善圖像效果。
  • 支持對原生 RAW 格式圖片的處理园细。
  • 濾鏡鏈的性能比 GPUImage 高惦积。(沒有驗證過,GPUImage 的主頁上是這么說的)猛频。
  • 支持對大圖進(jìn)行處理狮崩,超過 GPU 紋理限制 (4096 * 4096)的時候蛛勉,會自動拆分成幾個小塊處理(Automatic tiling)。GPUImage 當(dāng)處理超過紋理限制的圖像時候睦柴,會先做判斷诽凌,壓縮成最大紋理限制的圖像,導(dǎo)致圖像質(zhì)量損失坦敌。

總結(jié)

GPUImage是一套主流的圖像處理框架侣诵,很多直播、美圖APP都采用此技術(shù)狱窘,當(dāng)你的項目是以Swift為主時杜顺,GPUImage2就是你的首選。
當(dāng)然训柴,你可以根據(jù)業(yè)務(wù)需要決定使用GPUImage還是Core Image哑舒,它們都是相當(dāng)成熟的工具。

本文所有示例代碼或Demo可以在此獲然媚佟:https://github.com/WillieWangWei/SampleCode_GPUImageUsage.git

如果本文對你有所幫助,請給個Star??

相關(guān)文章
GPUImage2(二)濾鏡大全:圖像生成
GPUImage2(三)濾鏡大全:色彩調(diào)校
GPUImage2(四)濾鏡大全:圖像處理
GPUImage2(五)濾鏡大全:混合模式
GPUImage2(六)濾鏡大全:視覺特效

參考資料:
https://github.com/BradLarson/GPUImage2
https://colin1994.github.io/2016/10/21/Core-Image-OverView/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末越锈,一起剝皮案震驚了整個濱河市仗嗦,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌甘凭,老刑警劉巖稀拐,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異丹弱,居然都是意外死亡德撬,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門躲胳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蜓洪,“玉大人,你說我怎么就攤上這事坯苹÷√矗” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵粹湃,是天一觀的道長恐仑。 經(jīng)常有香客問我,道長为鳄,這世上最難降的妖魔是什么裳仆? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮孤钦,結(jié)果婚禮上歧斟,老公的妹妹穿的比我還像新娘记某。我一直安慰自己,他們只是感情好构捡,可當(dāng)我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布液南。 她就那樣靜靜地躺著,像睡著了一般勾徽。 火紅的嫁衣襯著肌膚如雪滑凉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天喘帚,我揣著相機(jī)與錄音畅姊,去河邊找鬼。 笑死吹由,一個胖子當(dāng)著我的面吹牛若未,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播倾鲫,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼粗合,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了乌昔?” 一聲冷哼從身側(cè)響起隙疚,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎磕道,沒想到半個月后供屉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡溺蕉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年伶丐,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片疯特。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡哗魂,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出辙芍,到底是詐尸還是另有隱情啡彬,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布故硅,位于F島的核電站庶灿,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏吃衅。R本人自食惡果不足惜往踢,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望徘层。 院中可真熱鬧峻呕,春花似錦利职、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至讯私,卻和暖如春热押,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背斤寇。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工桶癣, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人娘锁。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓牙寞,卻偏偏與公主長得像,于是被迫代替她去往敵國和親莫秆。 傳聞我的和親對象是個殘疾皇子间雀,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容