GPUImage2(一)集成與使用

關(guān)鍵詞


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

本文所有示例代碼或Demo可以在此獲?。?a target="_blank" rel="nofollow">https://github.com/WillieWangWei/SampleCode_GPUImage2_Usage

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

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

概述


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

目前它有兩個(gè)版本:

  1. GPUImage。開發(fā)者使用最多的版本,它于2012年最早推出,使用Objective-C編寫,支持macOSiOS
  2. GPUImage2。同一作者在2016年推出的版本,使用Swift編寫,是GPUImage框架的第二代,支持macOSiOSSwift代碼的Linux或未來平臺。

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

  • 在項(xiàng)目中集成
  • 特性
  • 示例代碼
  • 注意問題

在項(xiàng)目中集成


  1. 下載壓縮包文件,下載地址
  2. 解壓后目錄如下:
    文件目錄

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

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

    點(diǎn)擊左上角的+,選擇New Copy Files Phase,在新建的Copy Files中將Destination選為Frameworks,并在欄目中添加GPUImage.framework。
    Copy Files

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

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

    這是因?yàn)槭褂昧诉^期的函數(shù),但暫時(shí)不會造成功能上的問題。如果你覺得不爽,可以參考如何忽略警告。

特性


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

處理流程

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

-->運(yùn)算符

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

camera --> basicOperation --> renderView

左邊的參數(shù)遵循ImageSource協(xié)議,作為數(shù)據(jù)的輸入,右邊的參數(shù)遵循ImageConsumer協(xié)議,作為數(shù)據(jù)的輸出。這里的basicOperationBasicOperation的一個(gè)實(shí)例,其父類ImageProcessingOperation同時(shí)遵循ImageSourceImageConsumer協(xié)議,所以它可以放在-->的左邊或右邊。
-->的運(yùn)算是左結(jié)合的,類似于GPUImage中的addTarget方法,但是-->有一個(gè)返回值,就是右邊的參數(shù)。在上面的示例中,先計(jì)算了前半部camera --> basicOperation,然后右邊的參數(shù)basicOperation作為返回值又參與了后半部basicOperation --> renderView的計(jì)算。
-->體現(xiàn)了鏈?zhǔn)骄幊痰乃枷?,讓代碼更加優(yōu)雅,在GPUImage2有著大量運(yùn)用,這得益于Swift強(qiáng)大的語法,關(guān)于Swift中的高級運(yùn)算符,請看這里。

示例代碼


GPUImage2主要提供了這些功能:

  • 處理靜態(tài)圖片
  • 操作組
  • 實(shí)時(shí)視頻濾鏡
  • 從視頻中捕獲圖片
  • 編寫自定義的圖像處理操作
  • 從靜態(tài)圖片中捕獲并添加濾鏡(即將實(shí)現(xiàn))
  • 添加濾鏡并轉(zhuǎn)碼視頻(即將實(shí)現(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)建濾鏡實(shí)例

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

// 創(chuàng)建一個(gè)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多個(gè)濾鏡疊加
filteredImage = imageView.image!.filterWithPipeline { (input, output) in
    input --> brightnessAdjustment --> exposureAdjustment --> output
}

// 不建議的
imageView.image = filteredImage

注意:如果要將圖片顯示在屏幕上或者進(jìn)行多次濾鏡處理時(shí),以上方法會讓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這個(gè)閉包
pictureInput.processImage(synchronously: true)
操作組

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

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

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

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

// MARK: - 實(shí)時(shí)視頻濾鏡
func CameraFiltering() {
    
    // Camera的構(gòu)造函數(shù)是可拋出錯誤的
    do {
        // 創(chuàng)建一個(gè)Camera的實(shí)例,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)建一個(gè)Luminance顏色處理濾鏡
    basicOperation = Luminance()
    
    // 創(chuàng)建一個(gè)RenderView的實(shí)例并添加到view上,用來顯示最終處理出的內(nèi)容
    renderView = RenderView(frame: view.bounds)
    view.addSubview(renderView)
    
    // 綁定處理鏈
    camera --> basicOperation --> renderView
    
    // 開始捕捉數(shù)據(jù)
    camera.startCapture()
    
    // 結(jié)束捕捉數(shù)據(jù)
    // camera.stopCapture()
}
從視頻中捕獲圖片

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

// MARK: - 從實(shí)時(shí)視頻中截圖圖片
func captureImageFromVideo() {
    
    // 啟動實(shí)時(shí)視頻濾鏡
    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)圖片中捕獲并添加濾鏡

作者暫未實(shí)現(xiàn)

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

作者暫未實(shí)現(xiàn)

注意問題

使用Cocoapods安裝

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

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

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

與Core Image比較

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

GPUImage 優(yōu)勢

  • 最低支持 iOS 4.0,iOS 5.0 之后就支持自定義濾鏡。
  • 在低端機(jī)型上,GPUImage 有更好的表現(xiàn)。(這個(gè)我沒用真正的設(shè)備對比過,GPUImage 的主頁上是這么說的)
  • GPUImage 在視頻處理上有更好的表現(xiàn)。
  • GPUImage 的代碼完成公開,實(shí)現(xiàn)透明。
  • 可以根據(jù)自己的業(yè)務(wù)需求,定制更加復(fù)雜的管線操作??啥ㄖ瞥潭雀摺?/li>

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 高。(沒有驗(yàn)證過,GPUImage 的主頁上是這么說的)。
  • 支持對大圖進(jìn)行處理,超過 GPU 紋理限制 (4096 * 4096)的時(shí)候,會自動拆分成幾個(gè)小塊處理(Automatic tiling)。GPUImage 當(dāng)處理超過紋理限制的圖像時(shí)候,會先做判斷,壓縮成最大紋理限制的圖像,導(dǎo)致圖像質(zhì)量損失。

總結(jié)

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

本文所有示例代碼或Demo可以在此獲?。?a target="_blank" rel="nofollow">https://github.com/WillieWangWei/SampleCode_GPUImageUsage.git

如果本文對你有所幫助,請給個(gè)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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容