關鍵詞
濾鏡 GPUImage 顏色 Filter colorDistance 相機 識別 框架 紋理 渲染
本文所有示例代碼或Demo可以在此獲?。?a target="_blank" rel="nofollow">https://github.com/WillieWangWei/SampleCode_GPUImage2_Usage
如果本文對你有所幫助,請給個Star??
相關文章
GPUImage2(二)濾鏡大全:圖像生成
GPUImage2(三)濾鏡大全:色彩調校
GPUImage2(四)濾鏡大全:圖像處理
GPUImage2(五)濾鏡大全:混合模式
GPUImage2(六)濾鏡大全:視覺特效
概述
GPUImage
是一個基于OpenGL ES 2.0
的開源的圖像處理庫,作者是Brad Larson。GPUImage
將OpenGL ES
封裝為簡潔的Objective-C
或Swift
接口,可以用來給圖像、實時相機視頻、電影等添加濾鏡。對于諸如處理圖像或實況視頻幀的大規模并行操作,GPU相對于CPU具有一些顯著的性能優點。在iPhone 4上,簡單的圖像濾鏡在GPU上的執行速度比等效的基于CPU的濾鏡快100多倍。
目前它有兩個版本:
-
GPUImage。開發者使用最多的版本,它于2012年最早推出,使用
Objective-C
編寫,支持macOS
和iOS
。 -
GPUImage2。同一作者在2016年推出的版本,使用
Swift
編寫,是GPUImage
框架的第二代,支持macOS
、iOS
和Swift
代碼的Linux
或未來平臺。
本文以Swift
版的GPUImage2
為主題,從以下幾個方面進行講解:
- 在項目中集成
- 特性
- 示例代碼
- 注意問題
在項目中集成
- 下載壓縮包文件,下載地址。
- 解壓后目錄如下:
文件目錄
將framework
下的GPUImage-iOS.xcodeproj
項目和Source
文件夾復制到你的項目中。 - 在你的項目的
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
確認現在你的項目文件夾中存在GPUImage-iOS.xcodeproj
和Source
,像是這樣:
編譯條件
4.如果前幾步沒有問題,現在Build
。稍等會提示成功,但出現了一些警告:
過期警告
這是因為使用了過期的函數,但暫時不會造成功能上的問題。如果你覺得不爽,可以參考如何忽略警告。
特性
GPUImage2
可以進行多種模式的圖像處理,其邏輯類似于流水線的概念。流水線上有若干個工位(Filter
),每個工位接收來自上一個工位的產品(Data
),完成此工序的加工(Processing
)后交給下一個工位(Target
)處理。產品從開始端(Input
)經過整條流水線加工,到達結束端(Output
)變為成品。
雖然功能和GPUImage
相似,但GPUImage2
使用了大量Swift
語言的特性,在命名規則、代碼風格上都產生了很大的差別,比如:
-->運算符
-->
是GPUImage2
定義的一個中綴運算符
,它將兩個對象像鏈條一樣串聯起來,用起來像是這樣:
camera --> basicOperation --> renderView
左邊的參數遵循ImageSource
協議,作為數據的輸入,右邊的參數遵循ImageConsumer
協議,作為數據的輸出。這里的basicOperation
是BasicOperation
的一個實例,其父類ImageProcessingOperation
同時遵循ImageSource
和ImageConsumer
協議,所以它可以放在-->
的左邊或右邊。
-->
的運算是左結合的,類似于GPUImage
中的addTarget
方法,但是-->
有一個返回值,就是右邊的參數。在上面的示例中,先計算了前半部camera --> basicOperation
,然后右邊的參數basicOperation
作為返回值又參與了后半部basicOperation --> renderView
的計算。
-->
體現了鏈式編程的思想,讓代碼更加優雅,在GPUImage2
有著大量運用,這得益于Swift
強大的語法,關于Swift
中的高級運算符,請看這里。
示例代碼
GPUImage2
主要提供了這些功能:
- 處理靜態圖片
- 操作組
- 實時視頻濾鏡
- 從視頻中捕獲圖片
- 編寫自定義的圖像處理操作
- 從靜態圖片中捕獲并添加濾鏡(即將實現)
- 添加濾鏡并轉碼視頻(即將實現)
準備
導入頭文件
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)
}
處理靜態圖片
創建濾鏡實例
// 創建一個BrightnessAdjustment顏色處理濾鏡
let brightnessAdjustment = BrightnessAdjustment()
brightnessAdjustment.brightness = 0.2
// 創建一個ExposureAdjustment顏色處理濾鏡
let exposureAdjustment = ExposureAdjustment()
exposureAdjustment.exposure = 0.5
使用GPUImage
對UIImage
提供的擴展方法進行便利濾鏡處理
// 1.使用GPUImage對UIImage的擴展方法進行濾鏡處理
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
注意:如果要將圖片顯示在屏幕上或者進行多次濾鏡處理時,以上方法會讓
Core Graphics
產生更多開銷,建議使用處理鏈,最后指向RenderView
來顯示,如下:
// 2.使用管道處理
// 創建圖片輸入
let pictureInput = PictureInput(image: imageView.image!)
// 創建圖片輸出
let pictureOutput = PictureOutput()
// 給閉包賦值
pictureOutput.imageAvailableCallback = { image in
// 這里的image是處理完的數據,UIImage類型
}
// 綁定處理鏈
pictureInput --> brightnessAdjustment --> exposureAdjustment --> pictureOutput
// 開始處理 synchronously: true 同步執行 false 異步執行,處理完畢后會調用imageAvailableCallback這個閉包
pictureInput.processImage(synchronously: true)
操作組
你可以將若干個BasicOperation
的實例包裝成一個OperationGroup
操作組,通過給閉包賦值來定義組內濾鏡的處理流程,外部可以將OperationGroup
的實例作為一個獨立單位參與其他濾鏡處理。
// MARK: - 操作組
func operationGroup() {
// 創建一個BrightnessAdjustment顏色處理濾鏡
let brightnessAdjustment = BrightnessAdjustment()
brightnessAdjustment.brightness = 0.2
// 創建一個ExposureAdjustment顏色處理濾鏡
let exposureAdjustment = ExposureAdjustment()
exposureAdjustment.exposure = 0.5
// 創建一個操作組
let operationGroup = OperationGroup()
// 給閉包賦值,綁定處理鏈
operationGroup.configureGroup{input, output in
input --> brightnessAdjustment --> exposureAdjustment --> output
}
// 進行濾鏡處理
imageView.image = imageView.image!.filterWithOperation(operationGroup)
}
實時視頻濾鏡
從相機中獲取圖像數據,經過濾鏡處理后實時的顯示在屏幕上。
// MARK: - 實時視頻濾鏡
func CameraFiltering() {
// Camera的構造函數是可拋出錯誤的
do {
// 創建一個Camera的實例,Camera遵循ImageSource協議,用來從相機捕獲數據
/// Camera的指定構造器
///
/// - Parameters:
/// - sessionPreset: 捕獲視頻的分辨率
/// - cameraDevice: 相機設備,默認為nil
/// - location: 前置相機還是后置相機,默認為.backFacing
/// - captureAsYUV: 是否采集為YUV顏色編碼,默認為true
/// - Throws: AVCaptureDeviceInput構造錯誤
camera = try Camera(sessionPreset: AVCaptureSessionPreset1280x720,
cameraDevice: nil,
location: .backFacing,
captureAsYUV: true)
// Camera的指定構造器是有默認參數的,可以只傳入sessionPreset參數
// camera = try Camera(sessionPreset: AVCaptureSessionPreset1280x720)
} catch {
print(error)
return
}
// 創建一個Luminance顏色處理濾鏡
basicOperation = Luminance()
// 創建一個RenderView的實例并添加到view上,用來顯示最終處理出的內容
renderView = RenderView(frame: view.bounds)
view.addSubview(renderView)
// 綁定處理鏈
camera --> basicOperation --> renderView
// 開始捕捉數據
camera.startCapture()
// 結束捕捉數據
// camera.stopCapture()
}
從視頻中捕獲圖片
從視頻中獲取某一幀的圖片,可以以任一濾鏡節點作為數據源。
// MARK: - 從實時視頻中截圖圖片
func captureImageFromVideo() {
// 啟動實時視頻濾鏡
self.cameraFiltering()
// 設置保存路徑
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執行,防止截到黑屏
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .seconds(1)) {
// 保存相機捕捉到的圖片
self.camera.saveNextFrameToURL(originalURL, format: .png)
// 保存濾鏡后的圖片
self.basicOperation.saveNextFrameToURL(filteredlURL, format: .png)
// 如果需要處理回調,有下面兩種寫法
let dataOutput = PictureOutput()
dataOutput.encodedImageFormat = .png
dataOutput.encodedImageAvailableCallback = {imageData in
// 這里的imageData是截取到的數據,Data類型
}
self.camera --> dataOutput
let imageOutput = PictureOutput()
imageOutput.encodedImageFormat = .png
imageOutput.imageAvailableCallback = {image in
// 這里的image是截取到的數據,UIImage類型
}
self.camera --> imageOutput
}
}
編寫自定義的圖像處理操作
自定義濾鏡需要使用OpenGL著色語言
(GLSL)編寫Fragment Shader
(片段著色器),調用BasicOperation
的構造器讀取寫好的文件,可以創建自定義濾鏡。
// MARK: - 編寫自定義的圖像處理操作
func customFilter() {
// 獲取文件路徑
let url = URL(fileURLWithPath: Bundle.main.path(forResource: "Custom", ofType: "fsh")!)
var customFilter: BasicOperation
do {
// 從文件中創建自定義濾鏡
customFilter = try BasicOperation(fragmentShaderFile: url)
} catch {
print(error)
return
}
// 進行濾鏡處理
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;
}
從靜態圖片中捕獲并添加濾鏡
作者暫未實現
添加濾鏡并轉碼視頻
作者暫未實現
注意問題
使用Cocoapods安裝
作者暫不支持。但是有網友制作了EVGPUImage2
這個倉庫來間接使用GPUImage2
,有興趣可以嘗試一下。
使用ACV文件創建濾鏡
在GPUImage
中可以通過ACV文件快速創建自定義濾鏡。AVC可以通過photoShop
進行圖片顏色曲線處理得到,但是GPUImage2
暫未移植這個功能。
與Core Image比較
Core Image
是iOS內置的圖像處理框架,兩者相比各有優點:
GPUImage 優勢
- 最低支持 iOS 4.0,iOS 5.0 之后就支持自定義濾鏡。
- 在低端機型上,GPUImage 有更好的表現。(這個我沒用真正的設備對比過,GPUImage 的主頁上是這么說的)
- GPUImage 在視頻處理上有更好的表現。
- GPUImage 的代碼完成公開,實現透明。
- 可以根據自己的業務需求,定制更加復雜的管線操作。可定制程度高。
Core Image 優勢
- 官方框架,使用放心,維護方便。
- 支持 CPU 渲染,可以在后臺繼續處理和保存圖片。
- 一些濾鏡的性能更強勁。例如由 Metal Performance Shaders 支持的模糊濾鏡等。
- 支持使用 Metal 渲染圖像。而 Metal 在 iOS 平臺上有更好的表現。
- 與 Metal,SpriteKit,SceneKit,Core Animation 等更完美的配合。
- 支持圖像識別功能。包括人臉識別、條形碼識別、文本識別等。
- 支持自動增強圖像效果,會分析圖像的直方圖,圖像屬性,臉部區域,然后通過一組濾鏡來改善圖像效果。
- 支持對原生 RAW 格式圖片的處理。
- 濾鏡鏈的性能比 GPUImage 高。(沒有驗證過,GPUImage 的主頁上是這么說的)。
- 支持對大圖進行處理,超過 GPU 紋理限制 (4096 * 4096)的時候,會自動拆分成幾個小塊處理(Automatic tiling)。GPUImage 當處理超過紋理限制的圖像時候,會先做判斷,壓縮成最大紋理限制的圖像,導致圖像質量損失。
總結
GPUImage
是一套主流的圖像處理框架,很多直播、美圖APP都采用此技術,當你的項目是以Swift
為主時,GPUImage2
就是你的首選。
當然,你可以根據業務需要決定使用GPUImage
還是Core Image
,它們都是相當成熟的工具。
本文所有示例代碼或Demo可以在此獲?。?a target="_blank" rel="nofollow">https://github.com/WillieWangWei/SampleCode_GPUImageUsage.git
如果本文對你有所幫助,請給個Star??
相關文章
GPUImage2(二)濾鏡大全:圖像生成
GPUImage2(三)濾鏡大全:色彩調校
GPUImage2(四)濾鏡大全:圖像處理
GPUImage2(五)濾鏡大全:混合模式
GPUImage2(六)濾鏡大全:視覺特效
參考資料:
https://github.com/BradLarson/GPUImage2
https://colin1994.github.io/2016/10/21/Core-Image-OverView/