Core Image:更強的性能並支持基於 Python 快速開發

相信絕大多數 iOS 開發者對 Core Image 都不陌生,作為系統標配的、異常強大的圖像處理庫,在絕大多數場景下都能滿足 App 的圖像處理需求。加上目前 Core Image 已經支持在 iOS 上也能做自定義 filter,使得其已經成為流行的圖像處理框架 GPUImage 最強大的競爭對手。加上 iOS12 蘋果打算 deprecate OpenGL 和 OpenGL ES, 推廣 Metal。那和 Metal 聯繫緊密的 Core Image 無疑更有勝算。

這個 Session 講的內容主要包括三個部分:

  1. Core Image 新的性能 API;
  2. 在 Core Image 體系上快速搭建濾鏡原型;
  3. 在 Core Image 體系上應用機器學習;

1. Core Image 新的性能 API

中間緩存

在講中間緩存之前,需要先複習一下 Core Image。在 Core Image 中,我們能夠對圖像鏈式的執行 Filter,如下圖所示:

Core Image:更強的性能並支持基於 Python 快速開發

func applyFilterChain(to image: CIImage) -> CIImage { // The CIPhotoEffectInstant filter takes only an input image let colorFilter = CIFilter(name: "CIPhotoEffectProcess", withInputParameters: [kCIInputImageKey: image])!  // Pass the result of the color filter into the Bloom filter // and set its parameters for a glowy effect. let bloomImage = colorFilter.outputImage!.applyingFilter("CIBloom", withInputParameters: [ kCIInputRadiusKey: 10.0, kCIInputIntensityKey: 1.0 ])  // imageByCroppingToRect is a convenience method for // creating the CICrop filter and accessing its outputImage. let cropRect = CGRect(x: 350, y: 350, width: 150, height: 150) let croppedImage = bloomImage.cropping(to: cropRect)  return croppedImage}

整個過程很直觀,我們將圖片喂到第一個 Filter,然後得到第一個 Filter 的 outputImage ,然後再把該對象喂到第二個 Filter……以此類推建立 Filter 鏈。

Core Image 的 Lazy

值得注意的一點是,當上述代碼執行時,圖像處理並沒有發生,只是 Core Image 內部進行了一些關係的建立,只有當圖像需要被渲染的時候,才會實際去執行各個 Filter 的圖像處理過程。

因為有 Lazy 的特性,所以 Core Image 上最重要的一個優化就是 "自動連接(Filter Concatenation)", 因為最終圖像處理的過程都發生在所有 Filter 成鏈之後。所以 Core Image 可以將鏈式的多個 Filter 合併 成一個來執行,省去不必要的開銷。如下圖所示:

Core Image:更強的性能並支持基於 Python 快速開發

中間緩存

現在回過頭來看這樣一個場景:

Core Image:更強的性能並支持基於 Python 快速開發

三個 Filter,第一個計算很耗時,而第三個的參數可以讓用戶手動調節。這意味著每次用戶調節後都需要重新計算這三個 Filter。但其實前兩份 Filter 的參數是不變的,也就是說前兩個 Filter 的運算過程和結果都是不隨著用戶調整第三個 Filter 的參數改變而改變的。這裡重複的計算是否有可能進行優化呢?

我們很容易就想到,我們只需要把前兩次運算的結果 cache 下來就可以了,如下圖所示:

Core Image:更強的性能並支持基於 Python 快速開發

但是上文提到,Core Image 會把 Filter 鏈自動合併為一個 Filter,我們如何訪問中間結果呢?

蘋果在 iOS12的 Core Image 中,給 CIImage 新增了一箇中間緩存的屬性( insertingIntermediate ), 來解決這個問題,如下圖所示:

Core Image:更強的性能並支持基於 Python 快速開發

我們希望保存第二個 Filter 的結果,只需要在第二個 Filter 的 outputImage 調用 insertingIntermediate() 來生成一個新的 CIImage 傳到後面的流程即可。這樣第三個 Filter 的參數調整就不會導致前兩個 Filter 的重複計算。

怎麼做的呢? 其實就是自動合併的邏輯會根據 insertingIntermediate 進行調整。如下圖所示:

Core Image:更強的性能並支持基於 Python 快速開發

Core Image 的 CIContext 可以設置是否要打開 cacheIntermediate , 但這次新增的 insertingIntermediate 有更高的優先級。具體一些使用上的建議可以參考下圖:

Core Image:更強的性能並支持基於 Python 快速開發

Kernal 語言的新特性

兩種模式

iOS 上支持自定義 Filter,自定義 Filter 使用 Kernal 語言進行開發(一種類似 GLSL 的腳本語言)。目前一共有兩種開發 CIKernel 程序的模式:

Core Image:更強的性能並支持基於 Python 快速開發

第一種是傳統的基於 CIKernal 開發語言進行編寫,然後編譯成 Metal 或者 GLSL 的方式,第二種是直接使用 Metal Shading 語言進行開發,然後在 build 期間就生成二進制的庫,執行階段 load 之後直接轉換為 GPU 的指令。

目前因為蘋果主推 MPS(Metal Performance Shader), 所以方式一已經被標記為 deprecated

按組讀寫

使用 Metal 來開發 CIKernel 的優勢:

  1. 支持半精度浮點數;
  2. 支持按組讀寫(Group read & Group write);

半精度浮點是純運算性質方面的優化,在 A11芯片上運算更快,而且因為用到的寄存器小所以也有較大的優化空間。

接下來重點介紹一個按組讀寫。

假設我們對左圖紅框像素做一個3x3的卷積運算,結果為存入右邊的綠色框。顯而易見,對於每個新的像素,都需要讀取輸入圖像9次像素值。

Core Image:更強的性能並支持基於 Python 快速開發

但如果是按組讀寫,如下圖所示。我們一次性讀取16個像素來計算並寫入右邊的四個像素,那我們整個過程中寫了4次,讀取了16次。每個新像素平均需讀取的數量為4,比上述的單像素需要9次顯著降低。

Core Image:更強的性能並支持基於 Python 快速開發

按組讀寫的原理是很簡單的,接下來介紹一下如果我們有一個之前使用 CIKernal Language 開發的 kernal,如果修改使其能夠使用按組讀寫這樣高速的優化。

假設我們的 kernal 如下圖所示:

Core Image:更強的性能並支持基於 Python 快速開發

第一步,轉換為 metal:

Core Image:更強的性能並支持基於 Python 快速開發

第二步, 改造為按組讀寫的模式。核心就是使用了 s.gatherX 來實現。

Core Image:更強的性能並支持基於 Python 快速開發

在使用了按組讀寫和半浮點經典的優化後,基本都可以得到 2倍 的性能提升。

2. 在 Core Image 體系上快速搭建濾鏡原型

一般來講,一個濾鏡典型的研發流程是首先在電腦上進行快速原型的測試,之後再移植到生產環境,電腦上有大量的工具(OpenCV,SciPy,Numpy,Tensorflow,等等)來進行原型,而生產環境的技術棧卻是 Core Image, vImage,Metal等完全不同的技術架構棧,這往往會導致一個問題:在電腦上原型測得好好的,結果到手機上效果卻撲街了。

蘋果為了解決這個問題,發佈了一個神器 —— PyCoreImage。

Core Image:更強的性能並支持基於 Python 快速開發

初次看到這個名詞是不是感到非常穿越? 但其實很明顯,就是可以在 Python 中調用 Core Image 的接口

我們在 prototype 的時候使用 Python + PyCoreImage 這樣的方式,那就最大程度的模擬了真實的運行環境,基本上移植到手機上效果也不會打折。而且最關鍵的是, 只要學一個框架就好了啊,多的學不完啊!!!!

在使用 PyCoreImage 時,最關鍵是要了解 PyObjc 的用法,PyObjc 在 OS X 10.5 發佈,實現了在 Python 可以調用 Objective-C 的代碼,其中最主要的轉換規則就是冒號變下劃線,具體可以參考圖中的例子。

Core Image:更強的性能並支持基於 Python 快速開發

說回 PyCoreImage,其中的原理其實大概也可以想到,如下圖所示,PyCoreImage 通過 PyObjC 和 macOS 的 Core Image 進行交互,並將結果輸出回 NumPy。

Core Image:更強的性能並支持基於 Python 快速開發

下圖中的代碼首先導入了一個圖片,然後對其應用高斯模糊的 Filter,然後將結果輸出到變量 bar 中。

Core Image:更強的性能並支持基於 Python 快速開發

剩下的更多關於 PyCoreImage 的用法可以參考 Session 的 ppt,這裡不再贅述。

3. 在 Core Image 體系上應用機器學習

圖像處理和計算機發展至今,已經大量通過使用機器學習和深度學習來提升算法的效果。Core Image 也對機器學習提供了非常有好的支持。

CoreML Filter

Core Image 現在可以直接將圖片 apply 到一個 CoreML 的模型裡,相對於給 Core Image 的 Filter 鏈接上了深度學習的能力。

iOS12 中的 Core Image 提供了 CICoreMLModelFilter 類來將 CoreML 的 model 封裝成 Core Image 能夠識別的 Filter 格式。

下圖是一個 ML 領域的經典應用的例子(風格遷移)

Core Image:更強的性能並支持基於 Python 快速開發

不過現在在網上還完全搜不到 CICoreMLModelFilter ,(大霧

數據填補

對於機器學習而言,訓練集的完整性、覆蓋度能夠很大程度上決定最後模型的精確程度。但是現實情況是,我們往往沒有那麼多訓練集,在這樣的情況下,學術界一般都採用對現有訓練集的圖片進行相應的變化來起到擴充數據集的作用。這類技術統稱 數據填補(Data Augmentation

Core Image 對於這類任務天生支持的很好,支持包括以下幾種類型的變化:

  1. 圖像外觀;
  2. 圖像噪聲;
  3. 幾何變換;

以下是幾種使用 Core Image 的不同 Filter 來將一張圖變多多張訓練圖片的例子:

Core Image:更強的性能並支持基於 Python 快速開發

小結

這個 Session 帶來的內容總體來說還是激動人心的,雖然有的同學可能覺得比較小,沒有那種顛覆式的創新,但對於從事圖像領域工作的同學而言,毫無疑問這幾個工作都給人一種 mind opener 的感覺,切實的反應了蘋果對於多媒體、用戶體驗這兩個領域非常超前的思考。


分享到:


相關文章: