Vulkan移植GpuImage(四)從D到O的濾鏡

天天不在發表於2021-05-06

現把D到O的大部分濾鏡用vulkan的ComputeShader實現了,列舉其中一些有點特殊的說明.

GaussianBlurPosition 指定區域高斯模糊

沒有按照GPUImage裡的方式實現,按照類似GaussianSelectiveBlur方式實現,一張高斯模糊圖,一張原圖,二圖進行混合,這種實現方式更靈活(模糊半徑等引數),並且並不會降低效能(單獨的高斯模糊更容易優化).

SphereRefraction 環境對映

環境對映技術漫談

和GlassSphere一樣,主要就是球形座標系與UV座標系的轉化,剛開始完成後,我測試效果,發現圈外在閃爍,後面發現沒把圈外接0,其中withinSphere表示圈外圈內.

Halftone 半色調效果,類似新聞列印

這個類繼承PixellateFilter(馬賽克),順便也把這個實現了,他們引數一樣,實現主要是把一小快矩形範圍的UV統一成一個,然後處理.

HighPass 高通濾波,檢測影像畫素變動

實現這個類,首先要實現LowPass,這個濾境有點特殊,需要儲存上一楨的傳入影像,然後比較當前楨與上一楨.

我設定執行圖使用的有向無環圖,中間可以根據需求自動排除不可用節點並自動連結下層,所以不能有迴環線,從一層儲存一楨然後再拿回來使用,這樣構成迴環,所以我定義VkSaveFrameLayer層為bInput = true,當作輸入層,告訴外面不需要自動連線別層輸入,在相應介面手動指定別的層需要儲存的紋理.

最開始我直接呼叫vkCmdCopyImage,發現在Nsight顯示佔用0.3ms-0.7ms左右的時間,從邏輯上來說,應該不可能啊,這個copy應該比最簡單的運算層佔去的時間要更少才對,於是我測試了二種方案,對應引數bUserPipe,表示用不用管線,用管線控制到0.2ms內,用vkCmdCopyImage在0.3ms以上,以後找下資料看看是什麼問題.

LowPass有二個輸入,然後就是第一輸入節點是上層,第二個輸入節點是SaveFrameLayer,在LowPass執行前,SaveFrameLayer提供輸入,在LowPass後(混合前後楨,可以去掉類似摩爾紋的東東),把結果又儲存在SaveFrameLayer裡.

HighPass就是比較與LowPass的差異,可用來顯示變化大的畫素.

相應原始碼,有興趣可以自己檢視VkLowPassLayer

Histogram 直方圖

GPUImage通過回讀到CPU然後計算,這種方式直接放棄,檢視相應opencv cuda模組類似實現.

執行緒組定成256個,然後使用數值對應索引位置+1,結果就是索引上的數值表示對應值的個數.

有二種方式,儲存到臨時buffer,二是用原子集合(int/uint),這裡因為int滿足,使用原子集合.

儲存到臨時buffer,我在開始四通道時使用類似reduce2分二段,不用原子操作的方式,但是效果並不好,一是第一次把16*16塊的方式轉換成對應的一個個直方圖,模組並沒的縮小,導致第二塊把這一個直方圖通過for加在一起需要迴圈1920/16x1080/16(假設是1080P的圖),這個會花費超過2ms,這種方式就pass掉,我直接使用原子操作匯出四個圖然後再結合都比這個快.

avatar

可以看到,在這之前,要先vkCmdClearColorImage結果,幾乎不消費時間,我也是這樣理解的,但是為什麼vkCmdCopyImage會導致那麼大的時間等待了?

時間差不多在0.2ms左右,不過隨機的亮度圖,亮度比較分散,後面使用只有0,1的二種圖看看會不會影響這個時間.

一通道在0.18ms左右,四通道在0.52ms+0.01ms,比0.18*4快些.

直方圖的顯示可以使用VkAlphaShow2Layer顯示,沒有用GPUImage裡的顯示方式,其對應輸出分別為256x1x(R32UI/RGBA32UI)紋理,自己用glsl顯示各種需求應該更方便.要輸出到CPU的話,直接接一個VkOutputLayer也方便.

iOSBlur 一種特定模糊實現

把原來的幾個已經實現的層downSampling/saturation/gaussian blur/luminance range/upSampling組合一下輸出效果就是.

Kuwahara 實現類似油畫風格效果

kuwahara filter 實現

在GPUImage上最開始就說了,只適合靜止影像,因為很耗效能.原理比較簡單,查詢當前畫素周邊四個方向區域方差最小的區域.

因其演算法特點,卷積分離沒有使用,只使用了區域性共享視訊記憶體優化,在PC 2070N卡1080P下,半徑5需要3.3ms,半徑10需要9.7ms.

測試手機Redmi 10X Pro 用半徑3在720P下非常流暢,可以滿足30fps執行.

PC平臺Vulkan運算層時間記錄裡可以看到,3x3時使用區域性共享視訊記憶體並不一定會佔優,但是隻要核大於3x3,使用區域性共享視訊記憶體的方案就一定會提速,核越大提速越多.

Kuwahara3x3因為演算法特性,需要4x4x4,與8x8相當,就算專門去掉重複的那幾行,也要讀取49個資料,相當於7x7,所以下面專門優化的Kuwahara3x3就沒用了.

Laplacian 使用拉普拉斯運算元查詢邊緣

比較常見,一個畫素與一個特定3x3矩陣的結果,單獨拿出來說,是因為在ubo中,直接使用mat3/mat4,在CPU中使用類似的的Mat3x3,Mat4x4對齊會有問題,並且我在Nsight檢視到對應的UBO裡放著正確的mat3對應資料,但是結果就是不對.最不容易出錯的方法就是在ubo中全使用單個float+std140表示類似vec3/vec4/mat3/mat4結構,在CPU端就不需要特殊處理結構,和ubo一樣順序就行,這樣最不容易出現CPU-GPU中的UBO對齊問題.

Lookup 顏色查詢表

這個在前面Vulkan移植GpuImage(三)從A到C的濾鏡已經說過,但是感覺由使用者自己來接一個輸入層不太方便,因此修改邏輯,內建一個輸入層,提供loadLookUp方法,由使用者提供三通道/四通道512x512x(3/4)的lookup影像資料就行.

下面的MissEtikate匯入lookup_miss_etikate影像資料就行,本框架現沒有讀各種影像格式的模組與第三方庫.

Median 中值濾波

中值濾波原理及其C++實現與CUDA優化

開始和Kuwahara一樣,使用區域性共享視訊記憶體來優化,在結合上面文章裡的直方圖方式,但是結果並不好,詳細情況可以看PC平臺Vulkan運算層時間記錄記錄.

總的來說,優化了個寂寞,還是移植了GPUImage裡的3x3的實現,雖然核大會導致排序也是指數增長,但是這次優化明顯不成功,只能說是暫時可用大於3核的情況,後續找找更多的資料試試改進.

看了下相關opencv cuda裡的median裡的寫法,在前面會宣告一個影像元素*256的buffer?嗯,這個buffer是為了替換裡面單個執行緒uint hist[256]?在CS裡,一個執行緒宣告大堆疊資料會導致效能問題嗎?先暫停,opencv cuda這個演算法寫的太麻煩了,後面有時間研究.

MotionBlur 運動模糊

在沒看GPUImage實現前,我還在想HighPass就有點運動模糊的味道,但是其在GPUImage實現裡,就是簡單的給定一個方向,然後由這個方向模糊,並沒有前後楨的疊加比較,因其實現簡單,所以也移植了.

MotionDetector 運動檢測

這個倒是和HighPass很像,區別在於最後會聚合統計給CPU輸出,這個確實有必要,所以在本MotionDetector實現中,整合一個VkOutputLayer,並轉化輸出資料與GPUImage類似,可以看到運動的大小統計.

class VkMotionDetectorLayer : public VkLayer, public MotionDetectorLayer {
    AOCE_LAYER_QUERYINTERFACE(VkMotionDetectorLayer)
   private:
    std::unique_ptr<VkLowPassLayer> lowLayer = nullptr;
    std::unique_ptr<VkReduceLayer> avageLayer = nullptr;
    std::unique_ptr<VkOutputLayer> outLayer = nullptr;

   public:
    VkMotionDetectorLayer();
    virtual ~VkMotionDetectorLayer();

   private:
    void onImageProcessHandle(uint8_t* data, ImageFormat imageFormat,
                              int32_t outIndex);

   protected:
    virtual void onUpdateParamet() override;
    virtual void onInitGraph() override;
    virtual void onInitNode() override;
};

VkMotionDetectorLayer::VkMotionDetectorLayer(/* args */) {
    glslPath = "glsl/motionDetector.comp.spv";
    inCount = 2;

    lowLayer = std::make_unique<VkLowPassLayer>();
    avageLayer = std::make_unique<VkReduceLayer>(ReduceOperate::sum);
    outLayer = std::make_unique<VkOutputLayer>();

    paramet = 0.5f;
    lowLayer->updateParamet(paramet);
    outLayer->setImageProcessHandle(std::bind(
        &VkMotionDetectorLayer::onImageProcessHandle, this, _1, _2, _3));
}

void VkMotionDetectorLayer::onImageProcessHandle(uint8_t* data,
                                                 ImageFormat imageFormat,
                                                 int32_t outIndex) {
    if (onMotionEvent) {
        vec4 motion = {};
        memcpy(&motion, data, sizeof(vec2));
        onMotionEvent(motion);
    }
}

VkMotionDetectorLayer::~VkMotionDetectorLayer() {}

void VkMotionDetectorLayer::onUpdateParamet() {
    lowLayer->updateParamet(paramet);
}

void VkMotionDetectorLayer::onInitGraph() {
    VkLayer::onInitGraph();
    pipeGraph->addNode(lowLayer->getLayer());
    pipeGraph->addNode(avageLayer.get())->addNode(outLayer->getLayer());
}

void VkMotionDetectorLayer::onInitNode() {
    lowLayer->addLine(this, 0, 1);
    this->addLine(avageLayer.get());
    setStartNode(this, 0);
    setStartNode(lowLayer.get());
}

順便還通過CPU的輸出,查到了一個reduce2.comp裡divup誤寫導致的低階錯誤.

NobleCornerDetection Noble角點檢測

這個GPUImage中,實現方式和HarrisCornerDetection幾乎一樣,就是角點的選擇計算方式有點不同,會導致比HarrisCornerDetection多檢測很多角點出來.

Opening 開運算,先侵蝕後膨脹

和Closing一樣,由侵蝕與膨脹組合,專門提供一個類GroupLayer,本身不提供任何計算,只組合別的運算層.

中間沒有移植的濾鏡統計

  1. FASTCornerDetection,其GPUImage裡並沒有實現,只是寫個宣告.

  2. JFAVoronoi,看到其中需要CPU定迴圈多次GPU執行,其中UV與顏色對應相關程式碼暫時不理解,先放著,後期找更多資料確定移植方法.

  3. LanczosResampling,GPUImage的實現好像和其原理差別有點大,應該是特化實現,暫不移植.

  4. LineGenerator這個現在還沒想到如何能在GPGPU中高效畫多條線,普通的渲染管線方式倒是方便實現,後面查詢資料確定移植方法.

  5. MosaicFilter沒找到具體顯示效果,看到顯示效果應該就可以馬上移植了.

  6. 直方圖的顯示,現框架實現的VkHistogramLayer裡會輸出分別為256x1x(R32UI/RGBA32UI)紋理,自己根據你的需求去寫相應glsl顯示更合適.

其中從D到O的處理,有一部分因為前面實現別的層時,有一些已經實現過,也不在本文裡說明,現在GPUImager移植進度大約有60%,相應的效果可以在vulkanextratest,win端修改Win32.cpp,android修改Android.cpp檢視對應平臺效果,等所有效果移植完成後會寫配套專門的UI介面檢視.

相關文章