WWDC20 CoreImage 專題

2021-02-14 SwiftGG翻譯組

作者:Lucca,iOS 初學者,目前就職於字節跳動抖音iOS業務安全組。

Sessions: 

https://developer.apple.com/videos/play/wwdc2020/10008/ 

https://developer.apple.com/videos/play/wwdc2020/10021/https://developer.apple.com/videos/play/wwdc2020/10089/

同時感謝 @Puttin 和 @jojotov 的幫助

概覽

本文含括了本次WWDC2020裡Core Image專題三個文章的脫水翻譯。分別是:

優化視頻處理終端Core Image工作流(10008-Optimize the Core Image pipeline for your video app[1])如何構建基於Metal的Core Image內核(10021-Build Metal-based Core Image kernels with Xcode[2])探索Core Image的debug技巧(10089-Discover Core Image debugging techniques[3])

這幾篇Session主要講的是對於CoreImage處理視頻濾鏡時的一些最佳實踐,同時也簡單介紹了怎麼更好的去debug整個Core Image的渲染流程。基於文章內容以及筆者自己的一些理解,將文章的整體大綱定義如下:

優化視頻處理終端Core Image工作流創建 CIContext 的建議每個視圖只創建一個 CIContext

Context 的創建比較容易,但初始化需要花費較多的時間和內存,因此我們需要儘量減少重複創建 CIContext 所帶來的性能損耗。

通過 CIContextOption 創建 CIContext

創建 CIContext 時可以傳入需要的 CIContextOption(更多信息請參考 蘋果官方文檔[4]),其中有一些選項可以幫助我們優化所創建的 CIContext:

.cacheIntermediates:在渲染過程中,是否需要緩存中間像素緩衝區的內容。由於我們面對的是視頻處理,而在視頻中,絕大部分情況下每一幀的內容都與上一幀不同,因此關閉緩存可以非常有效地減少內存佔用。把這個選項設置為 false 對我們的優化非常重要!

.name:為 context 設置一個名字可以幫助我們調試 Core Image,更多詳細內容可以參考 [CoreImage Debugging Techniques]( "CoreImage Debugging Techniques")。

結合使用 Metal 和 CIContext

在有些時候,我們可能需要混合使用 Core Image 和其他的 Metal 特性,例如我們 Core Image 的輸入或者輸出是 MTLTexture時。在這種情況下,session 中提到一種推薦的處理方法:使用 MTLCommandQueue 來構造 CIContext 實例。

為了解釋為何需要通過這種方式構造 CIContext,我們首先考慮一下此情況下的流程時間線,假設我們的應用使用一條 MTLCommandQueue 隊列來渲染一個 Metal 紋理,此時 CPU 和 GPU 都會進行相應的工作:

接下來,我們把渲染好的 Metal 紋理傳入 Core Image,假如此時 Core Image 沒有顯示地設置隊列,它會使用內置的獨立  Metal 隊列進行工作:

最後,Core Image 處理完成後的紋理對象會再次在一開始的 Metal 隊列中進行其他處理:

可以看到,由於 CoreImage 和 Metal 的工作都在不同隊列中進行,在不同的工作切換時,應用必須發出等待的指令來保證接收到正確的結果,這造成了不必要的性能和時間損耗:

為了解決這個問題,消除等待造成的浪費,我們可以讓 Core Image 和 Metal 公用同一條 MTLCommandQueue 隊列,這樣的話 Metal 的渲染工作和 Core Image 處理工作之間就不會有等待的間隙,整個流程會變得更加高效:

蘋果文檔中關於其他 Core Image 性能相關的最佳實踐請參考 Getting the Best Performance[5]

儘量使用內置的 CIFilter

為了獲得更好的性能,使用內置的 CIFilter 是最簡單有效的方法:

Built-in 的 CIFilters 為 Metal 單獨作了優化,目前所有的內置 CIFilter 都是使用 Metal 實現的

步驟:

使用 Built-in 的 CIFilter 添加模糊濾鏡:

import CoreImage.CIFilterBuiltins

func motionBlur(inputImage: CIImage) -> CIImage? {
    let motionBlurFilter = CIFilter.motionBlur()
    motionBlurFilter.inputImage = inputImage
    motionBlurFilter.angle = 0
    motionBlurFilter.radius = 20
    return motionBlurFilter.outputImage
}

更多關於使用 Core Image 內置濾鏡的信息,請參考蘋果官方文檔 Processing an Image Using Built-in Filters[6]。

編寫並應用自定義的 CI Kernels為什麼要使用自定義的CI Kernels

提升語言的運行性能(例如聚集讀取、組寫入、half-float的數學計算)

在應用程式中加入基於Metal的自定義Core Image內核

這裡只需要簡單的五步操作就可以把自定義Core Image內核加到你的應用中

添加自定義的構建規則到你的工程中

首先我們針對.ci.metal結尾的文件添加構建規則,對於所有以此結尾的文件我們都將調用如下腳本,該腳本的-fcikernel標誌表示此類文件會使用metal編譯器構建出一個.ci.air結尾的二進位文件。

其次我們針對.ci.air的文件也添加一條構建規則,該規則運行的腳本的-cikernel標誌會調用metal的連結程序,最後在應用程式的目錄中產出一個.ci.mentallib結尾的文件。

添加.ci.metal結尾的源文件到你的工程中

通過文件-新建,我們可以新建一個Meta File類型的文件,然後命名需要以.ci結尾,以便最後產出的文件是以.ci.metal結尾

<<< 左右滑動見更多 >>>

編寫Metal內核

本次使用的內核是在Session*[Edit and Playback HDR video with AVFoundation]( "Edit and Playback HDR video with AVFoundation")*中使用的內核

在這個 Demo 中我們實現了一種斑馬條紋的效果,可以突出顯示HDR視頻的明亮部分、擴展其中的中心區域。

下面我們通過代碼來演示如何通過自定義內核實現這個效果:

定義這個效果的函數,這個函數必須標識extern "C"(表示這個函數會使用C語言編譯)

// MyKernels.ci.metal
#include <CoreImage/CoreImage.h> // includes CIKernelMetalLib.h
using namespace metal;

extern &quot;C&quot; float4 HDRZebra (coreimage::sample_t s, float time, coreimage::destination dest) 
{
 float diagLine = dest.coord().x + dest.coord().y;
 float zebra = fract(diagLine/20.0 + time*2.0);
 if ((zebra > 0.5) &amp;&amp; (s.r > 1 || s.g > 1 || s.b > 1))
  return float4(2.0, 0.0, 0.0, 1.0);
 return s;
}

因為這是一個CIColorKernel,所以返回值必須是float4,這裡接受的第一個參數是sample_t_s,表示輸入圖片的像素。最後一個參數是一個提供返回像素的坐標(destination)的一個結構體。在代碼實現中,我們根據dest來確定處於哪個對角線,然後通過一些簡單的計算判斷是否處於斑馬紋上,且這個像素的亮度超過了正常亮度標準,我們就返回一個亮紅色的像素。其他情況就返回原像素。

最終實現的效果就是如下這樣:

關於更多Core Image內核中Metal Shader Language的知識,你可以訪問我們的官方網站來下載更多相關內容。Metal Shader Language For Core Image Kernels[7]

使用 Swift 代碼加載內核並生成一張圖片

通過以下代碼我們就能加載內核並用它來創建圖片

class HDRZebraFilter: CIFilter {
    var inputImage: CIImage?
 var inputTime: Float = 0.0

    static var kernel: CIColorKernel = { () -> CIColorKernel in 
     let url = Bundle.main.url(forResource: &quot;MyKernels&quot;, 
                                withExtension: &quot;ci.metallib&quot;)!
  let data = try! Data(contentsOf: url)
  return try! CIColorKernel(functionName: &quot;HDRzebra&quot;, 
                          fromMetalLibraryData: data)
 }()

   override var outputImage : CIImage? {
  get {
   guard let input = inputImage else {return nil}
   return HDRZebraFilter.kernel.apply(extent: input.extent, 
            arguments: [input, inputTime])
  }
 }
}

內核經常被用於子類化CIFilter,他一般會接受一個inputImage的CIImage對象和一些其他參數。我們建議將kernel定義為靜態變量,這樣我們就只需要在第一次使用到這個內核對象的時候去加載metallib資源。

接下來我們需要重寫outputImage這個變量,在這個變量的getter方法裡,我們可以拿到靜態變量加載好的內核,然後通過編寫好的斑馬紋函數來生成一張新的圖片!

如何更好地渲染到視圖

務必避免使用 UIImageView 和 NSImageView,他們是為靜態圖片而設計的

最簡單的選擇:AVPlayerView:自動播放視頻、

使用AVPlayerView

使用AVPlayerView是非常方便的,代碼如下:

let videoComposition = AVMutableVideoComposition(
    asset: asset, 
    applyingCIFiltersWithHandler:
    { (request: AVAsynchronousCIImageFilteringRequest) -> Void in
        let filter = HDRZebraFilter()
        filter.inputImage = request.sourceImage
        let output = filter.outputImage

        if (output != nil) {
            request.finish(with: output, context: myCtx)
        }
        else { request.finish(with: err) }
    }
)

關鍵的對象是AVMutableVideoComposition,他通過一個video asset和一個block初始化。這個block傳遞過來一個AVAsynchronousCIImageFilteringRequest的request對象。block會在視頻的每一幀被調用,你所需要做的就是創建一個CIFilter,設置好輸入圖像(也就是當前視頻幀的圖片request.sourceImage),拿到濾鏡渲染好的圖像傳遞給request對象就大功告成了。

同時你也可以通過debug過程的快速預覽功能了解到更多渲染過程中的信息

比如你可以看到輸入的視頻幀是一個10位的HDR圖形。關於更多的debug技巧我們會在下文深入了解

使用MTKView

第一步你需要做的是重寫MTKView的init方法

class MyView : MTKView {
    var context: CIContext
    var commandQueue : MTLCommandQueue
    
    override init(frame frameRect: CGRect, device: MTLDevice?) {
        let dev = device ?? MTLCreateSystemDefaultDevice()!
        context = CIContext(mtlDevice: dev, options: [.cacheIntermediates : false] )
        commandQueue = dev.makeCommandQueue()!
        
        super.init(frame: frameRect, device: dev)

        framebufferOnly = false  // allow Core Image to use Metal Compute
        colorPixelFormat = MTLPixelFormat.rgba16Float
        if let caml = layer as? CAMetalLayer {
            caml.wantsExtendedDynamicRangeContent = true
        }
    }

在init過程中是我們創建CIContext的最佳過程,確保設置.cacheIntermediates為false,這樣Core Image才能使用到Metal compute。在macOS中如果你的視圖需要支持HDR,你需要設置colorPixelFormat為rgba16Float,同時設置caml.wantsExtendedDynamicRangeContent為true。接下來我們需要重寫draw方法

func draw(in view: MTKView) {

        let size = self.convertToBacking(self.bounds.size)
        let rd = CIRenderDestination(width: Int(size.width),
                                     height: Int(size.height),
                                     pixelFormat: colorPixelFormat,
                                     commandBuffer: nil)
                  { () -> MTLTexture in return view.currentDrawable!.texture }

        context.startTask(toRender:image, from:rect, to:rd, at:point)

        // Present the current drawable
        let cmdBuf = commandQueue.makeCommandBuffer()!
        cmdBuf.present(view.currentDrawable!)
        cmdBuf.commit()
   }

在draw方法中我們需要通過特殊的方法創建CIRenderDestination對象,我們通過正確的寬高和像素格式來創建destination,但是過程中我們不是直接返回Metal紋理(texture),而是通過一個block的形式返回紋理。這個可以讓CIContext在上一幀完成前就可以把Metal的任務入隊,接下來我們告訴CIContext可以開始任務。最後一步就是創建一個命令緩衝區去將當前繪製的圖像呈現到視圖上。

探索更強大的 Core Image Debug 技巧CI_PRINT_TREE 是什麼

CI_PRINT_TREE基於的原理和為XCode提供Core Image預覽功能的原理是一樣的。例如對於下面代碼,如果我們把滑鼠聚焦在output上,會有一個浮窗展示這個變量的對象地址,如果再點擊上面的眼睛圖標,就能看到一個可視化的界面展示產生這個圖片過程中的一些方法調用以及一些過程信息。

而圖片的快速預覽功能只是CI_PRINT_TREE的一個基礎應用,CI_PRINT_TREE是一個非常靈活的環境變量,他可以設置多種的模式和操作,讓你可以了解到Core Image是如何優化和渲染圖片的。

如何開啟和控制CI_TREE_PRINT開啟CI_PRINT_TREE在啟動APP之前通過終端開啟CI_PRINT_TREE的環境變量控制CI_PRINT_TREE

CI_PRINT_TREE主要接收三個參數:graph type,output type以及options

graph type表示了Core Image渲染過程的三個階段:

1表示初始化階段,這個階段對於了解本次渲染使用了什麼顏色空間是很有幫助的。

2表示圖像優化階段,可以看到core image的優化過程。

4表示Core Image把圖像連結到GPU中的過程,這對於了解渲染過程中使用了多少個中間緩衝區很有幫助。

我們可以通過組合的方式同時列印上述幾個階段。例如:7表示3個階段都列印,3表示初始化和優化階段。

<<< 左右滑動見更多 >>>

output type用來指定生成文檔的輸出類型。可以指定為pdf和png,最終輸出的結果將會保存在macOS的緩存路徑以及iOS的文檔目錄(Documents)。如果未指定輸出類型,那文本將會以標準格式的壓縮文本形式輸出。同時你可以通過指定CI_LOG_FILE =「 oslog」將文本信息輸出到Console.app裡,在OS開發調試中這樣會更加的方便。

options可以指定很多選項來更精確的輸出信息。例如:

指定context==name,可以只輸出特定名字上下文的相關信息。

frame-n,可以只記錄每個上下文的第n次的渲染過程。

剩下幾個選項可以將輸入圖像中間圖像以及輸出圖像都輸出到文檔中,這會提供很多有用的信息,但同時也會生成文檔的時間和內存,確保在你需要這些信息的時候選擇這些參數。

如何獲取和理解CI_PRINT_TREE文件如何獲取CI_PRINT_TREE文件

在macOS中這將會非常容易,只要定位到緩存文件目錄就可以看到生成的CI_PRINT_TREE文檔:

<<< 左右滑動見更多 >>>

在iOS中首先要確保info.plist中的Application supports iTunes file sharing的值是YES

然後連接手機到電腦,在Finder側邊欄找到你的設備並切換到files的窗口,這裡就可以看到所有的CI_PRINT_TREE文件,然後複製到macOS中就可以了

如何理解CI_PRINT_TREE文件

首先輸出在底部,輸入在頂部。綠色的節點是裝飾內核(wrap kernels),紅色的節點是色彩內核。

在初始化的樹種尋找顏色空間是很方便的,可以看到這裡是HLG的顏色空間

同時每個節點會顯示他的ROI,表示「region of interest」,意思是這次渲染過程中每個節點需要的區域大小

如果指定了graph type為4,且options為dunp-intermediates。文檔會輸出除了輸出通道以外的所有中間緩衝區,這對於定位渲染錯誤是如何發生是很有幫助的。

同時可以看到每個過程中各個中間緩衝區的執行時間、像素數量和像素格式,這對於理解哪個過程消耗了更多的內存和時間是很有幫助的。

<<< 左右滑動見更多 >>>

結語

本次Core Image的專題講的東西其實不是很高深,但也需要一定的Core Image基礎才能夠更好的了解。而且其中對於很多知識點的描述都是比較簡略的,如果大家對於這塊感興趣還是要去更詳細的閱讀CoreImage的官方專題。Core Image[8]

推薦閱讀

#Metal

關注我們

我們是「老司機技術周報」,每周會發布一份關於 iOS 的周報,也會定期分享一些和 iOS 相關的技術。歡迎關注。

關注有禮,關注【老司機技術周報】,回復「2020」,領取學習大禮包。

支持作者

這篇文章的內容來自於 《WWDC20 內參》。在這裡給大家推薦一下這個專欄,專欄目前已經創作了 108 篇文章,只需要 29.9 元。點擊【閱讀原文】,就可以購買繼續閱讀 ~

WWDC 內參 系列是由老司機周報、知識小集合以及 SwiftGG 幾個技術組織發起的。已經做了幾年了,口碑一直不錯。 主要是針對每年的 WWDC 的內容,做一次精選,並號召一群一線網際網路的 iOS 開發者,結合自己的實際開發經驗、蘋果文檔和視頻內容做二次創作。

參考資料[1]

Optimize the Core Image pipeline for your video app: https://developer.apple.com/videos/play/wwdc2020/10008/

[2]

Build Metal-based Core Image kernels with Xcode: https://developer.apple.com/videos/play/wwdc2020/10021/

[3]

Discover Core Image debugging techniques: https://developer.apple.com/videos/play/wwdc2020/10089/

[4]

蘋果官方文檔: https://developer.apple.com/documentation/coreimage/cicontextoption

[5]

Getting the Best Performance: https://developer.apple.com/library/archive/documentation/GraphicsImaging/Conceptual/CoreImaging/ci_performance/ci_performance.html#//apple_ref/doc/uid/TP30001185-CH10-SW1

[6]

Processing an Image Using Built-in Filters: https://developer.apple.com/documentation/coreimage/processing_an_image_using_built-in_filters

[7]

Metal Shader Language For Core Image Kernels: https://developer.apple.com/metal/MetalCIKLReference6.pdf

[8]

Core Image: https://developer.apple.com/documentation/coreimage

相關焦點

  • Core ML & Vision 入門教程
    深度學習自 20 世紀 50 年代以來,人工智慧研究人員開發了許多機器學習方法。Apple 的 Core ML 框架支持神經網絡、樹集合、支持向量機、廣義線性模型、特徵工程和管道模型。從 2012 年穀歌使用 YouTube 視頻來訓練 AI 以識別貓和人開始,神經網絡已取得了很多的成功。5 年之後,谷歌又發起了一次比賽,來確定 5000 種動植物。
  • Core values?
    Reader question:Please explain this sentence – 「Customer satisfaction is one of our core values,」 – and 「core values
  • linux-kernel-pwn qwb2018 core
    通過強網杯2018內核題core來了解如何利用基本的棧溢出來進行提權。根據insmod /core.ko大概知道了存在漏洞的模塊為core.ko,是主要分析的目標。將core.ko拖進去IDA中,init_module函數:__int64 init_module(){ core_proc = proc_create("core", 438LL, 0LL, &core_fops); printk(&unk_2DE);return0LL;}調用proc_create創建一個
  • Spitting image
    That’s what 「spitting image」 means.he said 「spit image」 instead.「Spitting image」 is particularly hard to pick apart, because we don’t understand why an image would be spitting.
  • 蘋果WWDC 2017時間確定 從舊金山搬到了聖何塞
    屆時,大會會在蘋果開發者網站(https://developer.apple.com/wwdc/)進行直播,人們還能用iPhone、iPad、Apple TV上的WWDC應用進行觀看。蘋果去年的WWDC大會在舊金山比爾·格雷厄姆市政禮堂舉行,發布了iOS 10作業系統,如無意外,今年的重頭戲仍然是最新的iOS——iOS 11。蘋果是否會提出新的功能,讓我們拭目以待。
  • ​Kubernetes中Coredns查詢記錄
    「【只做懂你de雲原生乾貨知識共享】」Kubernetes中coredns我之前還分享一篇coredns 5s延遲以及如何利用緩存cache來提升dns命中率的文章,對於這些我還遇到了,在pod當中外部內部dns都解析失敗的情況,或者說外部有時可以解析到,有時候解析不到,或者解析外部域名有時可以,有時不可以,內部解析正常,或者說內部外部都可以,但是cache
  • First global CO2 distribution image acquired by TanSat released
    Recently, China resealed the first global carbon dioxide distribution image acquired by China’s first Carbon Dioxide Observation Satellite (TanSat) to the public.
  • Spitting Image 相貌酷似的人
    Jo: And today we’re looking at a strange expression – 'to be the spitting image of'. 'To be the spitting image of'. Sun Chen: 那這個詞組又是什麼意思呢?
  • class ios 布局專題及常見問題 - CSDN
    關於size class的詳細解析,參考蘋果文檔和wwdc2014視:點擊打開連結 (What's New in Interface Builder)。,我們可以單獨設置每一種狀態,button的background image、image、text color等屬性,見下圖: 而不同版本的Xcode中對自動布局的重大變更有異曲同工之妙:開發者可以根據實際需要,針對size class的九種組合中的某一種或幾種分別進行自動布局的設置,這樣,當APP運行於不同屏幕、不同旋轉方向的時候,就可以根據當前環境的size class
  • Linux core dump有什麼用
    int *p = NULL;*p = 10;}編譯出可執行文件coretest01,運行列印出core dumped,應該出現core文件。但是在目錄下並沒有產生core文件,這是系統設置禁止了文件產生。用ulimit -a查看系統對core文件的設置core file size設置是0,也就是不允許core文件產生。修改配置,改為unlimited,對大小不做限制。
  • 你知道to the core是什麼意思嗎?
    對,可以用core表達。今天,我們就一起看一下core這個單詞。首先,我們看一下core做名詞的用法。1、Someone threw an apple core.有人扔了一個蘋果核。這句話中core的意思是(蘋果等的)果心、核兒。
  • core dump 產生的原因
    前言在現實中查看程序錯誤方法有很多, 比如添加日誌、使用 gdb 調試 和分析 core 文件等一、 產生coredum的一些條件
  • 我發現了7個關於 CSS backgroundImage 好用的技巧
    CSS3 中可以直接 指定多個背景路徑,如下所示:body { background-image: url(https://image.flaticon.com/icons/svg/748/748122.svg), url(https://images.unsplash.com/photo-1478719059408-592965723cbc
  • FEX 技術周刊 - 2016/06/20
    業界會議Apple WWDC 2016https://developer.apple.com/wwdc/
  • 雙核家庭 dual-core family
    Dual-core family refers to a family where both husband and wife are the only child or the single 「core」 of their
  • 好玩賽車類競速遊戲《怪雞卡丁車》遊俠專題站上線
    點擊進入遊俠網《怪雞卡丁車》專題站遊戲介紹   《怪雞卡丁車(Moorhuhn Kart)》是一款非常好玩的賽車類競速遊戲配置要求   最低配置:  作業系統: Windows 7  處理器: Multi-core 1.8GHz or faster  內存: 2 GB RAM  顯卡: Nvidia GTX 750  DirectX 版本: 11  存儲空間: 需要 600
  • Electronic Image Acquisition of 2021 International Graduates
    For International graduates abroad, Zhengfang educational management system may be used to upload electronic image and pay fees . The specific arrangements will be notified separately.
  • 一文概述用 python 的 scikit-image 模塊進行圖像分割
    indices = draw.circle_perimeter(80, 250,20)#from hereimage_labels[indices] = 1image_labels[points[:, 1].astype(np.int), points[:, 0].astype(np.int)] = 2image_show(image_labels);現在,讓我們使用隨機
  • C/C++程序core dump分析(一)
    那麼程序出core的情況有哪些的?如果程序core了之後,我們應該如何對這類問題進行定位呢?FourExperts小組的同學經過大量的案例收集、篩選和分析,產出了這份分析報告。希望大家從中了解程序出core的常見原因和定位方法。為了給大家一個直觀的認識,我們首先分析一下程序出core的常見原因及分類方法。通過這些分類,我們可以對分core的原因、定位方法有初步的認識。