1. MediaPipe加速流程
1.1 OpenGL ES准備
(1) OpenGL上下文(Context)
在調用任何OpenGL指令之前,需要創建一個OpenGL上下文,該上下文是一個非常龐大的狀態機,保存了OpenGL的各種狀態。由於OpenGL上下文是一個巨大的狀態機,因此切換上下文需要較大的開銷,但是不同的繪制模塊需要完全獨立的狀態管理。因此可以通過在應用程序中分別創建多個不同上下文,在不同線程中使用不同上下文,同時上下文之間共享紋理,緩沖區等資源,避免反復切換上下文,造成較大開銷。(OpenGL的一個上下文不能被多個線程同時訪問。此外,在同一線程上切換上下文可能會很慢。因此更有效的做法是為每個上下文設置一個專用線程,每個線程發出GL命令,在其上下文建立一個串行命令隊列,然后由GPU異步執行)
(2) 幀緩沖區(FrameBuffer)
OpenGL可以理解成圖形API,因此所有運算和結果輸出都需要通過圖像進行輸出。繪圖需要有一塊畫板,那么幀緩沖區就是這塊畫板。
(3) 附着(Attachment)
附着可以理解成畫板上的夾子,夾住哪塊畫布,就往對應的畫布上輸出數據。
(4) 紋理(Texture)和渲染緩沖區(RenderBuffer)
幀緩沖區並不是實際存儲數據的地方,實際存儲圖像數據數據的對象就是紋理和渲染緩沖區。(注意,一般來說渲染緩沖區和紋理不能同時掛載在同一幀緩沖區上)
(5) 頂點數組(VertexArray)和頂點緩沖區(VertexBuffer)
有了畫板,夾子,畫布就可以開始繪畫了,畫圖先畫好圖像骨架,然后往骨架里面添加顏色,頂點數據就是要畫的圖像的骨架,OpenGL圖像都是由圖元組成的(點,線,三角形),頂點數據預先傳入顯存當中,這部分顯存稱為頂點緩沖區。
(6) 索引數組(ElementArray)和索引緩沖區(ElementBuffer)
索引數據的目的是為了實現頂點復用,在繪制圖像時,總是會有一些頂點被多個圖元共享,避免反復對這個頂點進行計算。索引數據存儲在顯存中,這部分顯存稱為索引緩沖區。
(7) 着色器程序(Shader)
-
頂點着色器(VertexShader)
頂點着色器是OpenGL中用於計算頂點屬性的程序。頂點着色器是逐頂點運算的程序,每個頂點數據都會執行一次頂點着色器,每個頂點執行時並行的,並且頂點着色器運算過程中無法訪問其他頂點數據。 -
片段着色器(FragmentShader)
片段着色器是OpenGL中用於計算像素顏色的程序。每個像素都會執行一次片段着色器,每個像素運行時同樣也是並行的。
(8) 逐片段操作(Per-Fragment Operation)
-
測試(Test)
着色器程序完成后,我們就得到了像素數據,這些數據必須通過測試才能最終繪制到畫布,也就是幀緩沖上的顏色附着上。 -
混合(Blending)
-
抖動(Dithering)
抖動是針對可用顏色較少的系統,可以犧牲分辨率為代價,通過顏色值的抖動來增加可用顏色數量的技術。機器分辨率足夠高的情況下,激活抖動操作沒有太大意義。
(9) 渲染到紋理
OpenGL程序並不希望渲染出來的圖像立即顯示在屏幕上,而是需要多次渲染。其中一次的渲染結果作為下一次渲染的輸入。如果幀緩沖區的顏色附着設置為一張紋理,那么渲染完成之后,可以重新構造新的幀緩沖區,並將上次渲染出來的紋理作為輸入,重復上述流程。
(10) 渲染上屏/交換緩沖區(SwapBuffer)
無
1.2 CameraX 准備
在 Android 應用中要實現 Camera 功能還是比較困難的,為了保證在各品牌手機設備上的兼容性、響應速度等體驗細節,Camera 應用的開發者往往需要花很大的時間和精力進行測試,甚至需要手動在數百種不同設備上進行測試。CameraX 正是為解決這個痛點而誕生的。
優勢:
- CameraX和生命周期結合在一起,方便開發者管理生命周期,相比camera2減少了大量的樣板代碼的使用
- CameraX是基於Camera2 API實現的,兼容Andorid L(API 21),保證兼容市面上的絕大多數手機
- 開發者可以通過擴展形式使用和原生攝像頭應用相同的功能(人像,夜間模式,HDR,濾鏡,美顏)
- Google對CameraX進行了深度測試,確保能夠給覆蓋到更加廣泛的設備中。
(1) 添加CameraX依賴
(2) 顯示相機預覽
(3) 拍照和存儲圖片
(4) 實時分析圖像幀
1.3 Android平台上加速流程
(1) 設置
- 系統中安裝MediaPipe
- 安裝Android Development SDK和Android NDK
- Android設備開發模式
- 設置Bazel編譯部署Android應用
(2) 特定功能的圖結構
(3) 初始最小應用程序設置
(4) 調用CameraX相機驅動
- 相機權限
- 相機訪問
(5) 設置外部紋理轉換器
表面紋理(SurfaceTexture)從流中捕獲圖像幀作為OpenGL ES紋理。要使用MediaPipe圖形,從攝像機捕獲的幀應該存儲在一個常規的Open GL紋理對象中。MediaPipe提供了一個名為ExternalTextureConverter
的類,用於將存儲在SurfaceTexture
對象中的圖像轉換為常規OpenGL紋理對象。要使用ExternalTextureConverter
,我們需要EglManager
對象創建和管理EGLContext
。向構建(BUILD)文件添加依賴項以使用EglManager
。
- 計算攝像頭的幀在設備屏幕上的適當的顯示尺寸
- 傳入
previewFrameTexture
和displaySize
到convert
現在攝像頭獲取到的圖像幀已傳輸到MediaPipe graph中了。
(6) 在Android中調用MediaPipe圖結構
- 添加相關依賴
- 主活動
MainActivity
中使用圖
2. MediaPipe加速原理
2.1 MediaPipe源碼結構
MediaPipe整個技術棧如圖所示
MediaPipe中核心源碼的結構如下,BUILD
為Bazel編譯文件、calculators
為圖結構的計算單元、docs
為開發文檔、examples
為mediapipe的應用示例、framework
為框架包含計算單元屬性,上下文環境,數據流管理,調度隊列,線程池,時間戳等、gpu
為OpenGL的依賴文件、graphs
為mediapipe各項示例的圖結構(邊緣檢測,人臉檢測,姿態追蹤等等)、java
為安卓應用開發的依賴項、MediaPipe.tulsiproj
為相關配置文件、models
為各個應用的tflite模型,modules
為示例組件、objc
為 objective-c語言相關文件、util
為工具代碼。
2.2 框架加速組件和原理
框架的加速部分主要在framework
中,源碼中包括計算單元基類,計算單元數據類型定義、計算單元的狀態控制、計算單元的上下文環境管理、圖結構中輸入流和輸出流、調度器隊列、線程池、時間戳同步等。下面主要分析調度器隊列(scheduler_queue
),線程池(thread_pool
),時間戳(timestamp)怎樣通過調度數據流實現時數據流時間戳同步,再GPU計算渲染,從而達到mediapipe管線的最大數據吞吐量。
-
調度器機制
優先級隊列、線程池的原理可以看這個鏈接:https://www.cnblogs.com/zhongzhaoxie/p/13630795.html
MediaPipe圖是由計算單元構成的,整個調度機制決定何時運行每個計算單元。每個圖至少有一個調度隊列,每個調度隊列只有一個執行器。默認情況下,執行器是一個線程池,根據系統的能力決定線程數量。每個計算單元作為一個節點都有一個調度狀態(未就緒,就緒或者正在運行)。
對於源節點,沒有數據流輸入的節點成為源節點,源節點總是處於准備運行的狀態,一直到整個圖結構沒有數據輸出,源節點才會關閉。對於非源節點有要處理的輸入時,根據節點的輸入策略(下面將討論),形成一個有效的輸出集,此時非源節點保持准備狀態。當一個節點准備就緒時,意味着一個任務添加到優先調度程序隊列中,優先級函數目前時固定的,考慮到節點的靜態屬性及其圖中的拓撲排序。靠近圖輸出端的節點具有更高的優先級,而源節點具有最低的優先級。 -
時間戳同步
MediaPipe圖結構執行時去中心化的:沒有全局鎖,不同的節點能夠在同一時間處理不同時間戳的數據。這允許管道有更高的吞吐量。然而時間信息對於許多感知工作流非常重要。同時接收多個輸入流的節點需要以某種方式取協調它們。例如,一個目標檢測器可能產生一系列候選框,然后再將這個信息輸送到渲染節點,該節點應該與原始幀一起處理。
因此MediaPipe主要功能之一就是讓節點輸入同步。就框架而言,時間戳的主要作用是充當同步鍵。此外,MediaPipe被設計為支持確定性操作,這在許多場景(測試、模擬、批處理等)中非常重要,同時允許圖設計者在需要滿足實時約束的地方放松確定性。
同步和決定論這兩個目標是幾種設計選擇的基礎。值得注意的是,推入給定流的數據包必須有單調遞增的時間戳:這不僅是對許多節點有用的假設,而且同步邏輯也依賴於此。每個數據流有時間戳限制,這是數據流上新包允許的最低時間戳。當一個時間戳為T的數據包到達時,邊界自動推進到T+1,反映了單調要求。這允許框架確定沒有時間戳小於T的數據包會到達。 -
輸入策略
由DefaultInputStreamHandler
定義的默認輸入策略提供了確定性的輸入同步,可以保證多個輸入流上具有相同時間戳的數據包,輸入數據流嚴格按照時間戳升序處理。基於計算單元的方法使圖可以控制在哪里丟棄數據包,並允許根據資源約束靈活的適應和定制圖行為。 -
GPU計算和渲染
MediaPipe支持用於GPU計算和渲染的計算單元節點,並允許合並多個GPU節點,以及將它們與基於CPU的計算單元節點混合。MediaPipe中GPU設計原則是保證GPU計算單元可以出現在圖的任何地方,幀數據在GPU計算單元到另一個計算單元應該不需要復制操作,CPU和GPU之間的數據傳輸應該高效。
MediaPipe允許圖在多個GL上下文中運行OpenGL。舉例來說,這可能是在圖結構中非常有用,結合較慢的GPU推理路徑(例如,在10幀/秒)和更快的GPU渲染路徑(如30 FPS):因為一個GL上下文對應於一個連續的命令隊列,所以這兩個任務使用相同的上下文將會減少渲染的幀速率。
MediaPipe使用多個上下文解決的一個挑戰是跨它們進行通信的能力。比如這樣一個示例場景,同時發送輸入視頻到顯示和推理路徑,顯示需要先訪問推理的結果。
一個OpenGL上下文不能被多個線程同時訪問。此外,在某些Android設備上,在同一線程上切換活動GL上下文可能會很慢。因此,我們的方法是為每個上下文設置一個專用線程。每個線程發出GL命令,在其上下文上建立一個串行命令隊列,然后由GPU異步執行。
2.3 人手姿態估計示例
參考文獻:
[1] https://zhuanlan.zhihu.com/p/56693625
[2] OPENGL ES 3.0編程指南
[3] https://codelabs.developers.google.com/codelabs/camerax-getting-started/#0
[4] https://zhuanlan.zhihu.com/p/110411044