Vulkan移植GpuImage(二)Harris角點檢測與導向濾波
阿新 • • 發佈:2021-03-26
## Harris角點檢測
![avatar](https://pic1.zhimg.com/80/v2-92ed0434e8da0d433c9c1c31d8542714_720w.jpg "Harris角點檢測")
UI還是用的上次扣像的,只有前後置可以用,別的沒有效果,只看實現就好.
[相應原始碼](https://github.com/xxxzhou/aoce/blob/master/code/aoce_vulkan_extra/layer/VkHarrisCornerDetectionLayer.cpp)
在實現之前,我先重新整理編譯glsl的生成工具,如Harris角點檢測中間計算過程需要針對rgba32f做高斯模糊,我們前面針對rgba8實現過,現在使用glslangValidator針對一份檔案生成一編譯檔案會導致維護麻煩,很多無意義的重複程式碼,暫時還不想把glslangValidator整合到程式碼中動態生成,所以在這,先搞定glsl根據編譯條件生成多份檔案的工具.
其所有glsl程式碼全統一移到根目錄glsl/source下,編寫一個py檔案,在vscode裡用python指令碼編寫工具確實很方便,寫好了可以在vscode裡直接執行py指令碼,其中針對多條件編譯定義如下檔案.
```text
blend.comp
chromaKey.comp
filterColumn.comp filterColumn.comp CHANNEL_RGBA=1
filterColumn.comp filterColumnC1.comp CHANNEL_R8=1
filterColumn.comp filterColumnF4.comp CHANNEL_RGBAF32=1
filterRow.comp filterRow.comp CHANNEL_RGBA=1
filterRow.comp filterRowC1.comp CHANNEL_R8=1
filterRow.comp filterRowF4.comp CHANNEL_RGBAF32=1
```
glsl程式碼修改如下
```glsl
#if CHANNEL_RGBA
layout (binding = 0, rgba8) uniform readonly image2D inTex;
layout (binding = 1, rgba8) uniform image2D outTex;
#elif CHANNEL_R8
layout (binding = 0, r8) uniform readonly image2D inTex;
layout (binding = 1, r8) uniform image2D outTex;
#elif CHANNEL_RGBAF32
layout (binding = 0, rgba32f) uniform readonly image2D inTex;
layout (binding = 1, rgba32f) uniform image2D outTex;
#endif
// 共享塊,擴充前後二邊HALO_SIZE(分為上HALO_SIZE,中間自身*PATCH_PER_BLOCK,下HALO_SIZE)
#if CHANNEL_RGBAF32
shared vec4 column_shared[16*(PATCH_PER_BLOCK+HALO_SIZE*2)][16];//vec4[local_size_y][local_size_x]
#define packUnorm4x8
#define unpackUnorm4x8
#else
shared uint column_shared[16*(PATCH_PER_BLOCK+HALO_SIZE*2)][16];//vec4[local_size_y][local_size_x]
#endif
```
[python指令碼](https://github.com/xxxzhou/aoce/blob/master/glsl/compileglsl.py)流程,針對傳入的檔案分析每行需要編譯的檔案,確認是否需要條件編譯,根據條件編譯每個檔案,錯誤的話提示錯誤檔案,正確則把所有檔案複製到執行目錄,安裝目錄.其中android則使用build.gradle複製生成目錄下的編譯檔案到assets目錄下.
相關harris檢測原理可以參考:[harris邊角(興趣點)檢測演算法](https://zhuanlan.zhihu.com/p/42490675)
移植Harris角點檢測的程式碼,實現比較簡單,根據GPUImage原始碼,按XYDerivative/ GaussianBlur/ HarrisCornerDetection/ ThresholdedNonMaximumSuppression四層連線起來就行了,根據GPUImage的程式碼移植到Compute shader還是很快的,有興趣可以檢視[VkHarrisCornerDetectionLayer](https://github.com/xxxzhou/aoce/blob/master/code/aoce_vulkan_extra/layer/VkHarrisCornerDetectionLayer.cpp)的實現.
把角點和原圖加一起顯示倒是取了巧,1080P下,角點顯示一個畫素還是很難看清的,於是想根據原圖上的點周邊是否包含角點,然後顯示成紅色,發現這麼簡單一個問題,我想不到適合GPU來算的方法,取個巧,把檢測的角點圖模糊一下,1.0周邊根據模糊半徑都大於0了,然後直接比對大於0的就顯示.
## 導向濾波
嗯,我發現GPUImage好像沒這實現,不過這個演算法效果不錯,如下效果圖.
原圖:
![avatar](https://pic2.zhimg.com/80/v2-1f9c4f578d9d34b4f85385d3a36dcce9_720w.jpg "要扣像的圖")
綠色扣圖:
![avatar](https://pic4.zhimg.com/80/v2-3b1c3aff559e0b4661fda501b94fdb07_720w.jpg "綠色扣圖")
扣圖經過導向濾波處理:
![avatar](https://pic3.zhimg.com/80/v2-2267352717ed63e85e47bb4f1c7948d6_720w.jpg "導向濾波處理")
我原來移植到CUDA過裡,有興趣移步[CUDA加opencv復現導向濾波演算法](https://www.cnblogs.com/zhouxin/p/10203954.html).
我總結下了GPU裡比較容易實現的流程.
![avatar](https://pic4.zhimg.com/80/v2-c8ff303ad03f166e95e7f6391a4cd657_720w.jpg "導向濾波流程")
看了這圖,我忽然理解GPUImage為什麼不實現這個演算法了,演算法不復雜,需要節點多輸入多輸出以及流程正確順序保證,先看下類的主要流程實現,有興趣可以檢視[詳細程式碼](https://github.com/xxxzhou/aoce/blob/master/code/aoce_vulkan_extra/layer/VkGuidedLayer.cpp).
```c++
void VkGuidedLayer::onInitGraph() {
VkLayer::onInitGraph();
// 輸入輸出
inFormats[0].imageType = ImageType::rgba32f;
inFormats[1].imageType = ImageType::rgba32f;
outFormats[0].imageType = ImageType::rgba8;
pipeGraph->addNode(convertLayer.get())
->addNode(resizeLayer->getLayer())
->addNode(toMatLayer.get());
pipeGraph->addNode(box1Layer->getLayer());
pipeGraph->addNode(box2Layer->getLayer());
pipeGraph->addNode(box3Layer->getLayer());
pipeGraph->addNode(box4Layer->getLayer());
pipeGraph->addNode(guidedSlayerLayer->getLayer());
pipeGraph->addNode(box5Layer->getLayer());
pipeGraph->addNode(resize1Layer->getLayer());
}
void VkGuidedLayer::onInitNode() {
resizeLayer->getNode()->addLine(box1Layer->getNode(), 0, 0);
toMatLayer->getNode()->addLine(box2Layer->getNode(), 0, 0);
toMatLayer->getNode()->addLine(box3Layer->getNode(), 1, 0);
toMatLayer->getNode()->addLine(box4Layer->getNode(), 2, 0);
box1Layer->getNode()->addLine(guidedSlayerLayer->getNode(), 0, 0);
box2Layer->getNode()->addLine(guidedSlayerLayer->getNode(), 0, 1);
box3Layer->getNode()->addLine(guidedSlayerLayer->getNode(), 0, 2);
box4Layer->getNode()->addLine(guidedSlayerLayer->getNode(), 0, 3);
guidedSlayerLayer->getNode()->addLine(box5Layer->getNode());
box5Layer->getNode()->addLine(resize1Layer->getNode());
convertLayer->getNode()->addLine(getNode(), 0, 0);
resize1Layer->getNode()->addLine(getNode(), 0, 1);
getNode()->setStartNode(convertLayer->getNode());
}
```
如何保證層的執行順序,可以檢視[PipeGraph](https://github.com/xxxzhou/aoce/blob/master/code/aoce/Layer/PipeGraph.cpp)的resetGraph的實現,簡單來說,pipegraph新增節點的順序不重要,重要的是addLine接入接出正確,PipeGraph會自動根據節點連線線來重構執行順序.
可以看到雖然有很多計算層,但是效率非常高,N卡2070下,1080P的影象 ,快速導向resize長寬/8下,關於導向濾波的處理差不多就1ms,主要是導向濾波與影象的解析度無關,中間所有計算可以在很少的解析度下進行.
![avatar](https://pic2.zhimg.com/80/v2-517a9db0742dd3c1fe99d67bdca8f525_720w.jpg "導向濾波效能圖")
可以看到中間很多層大多全是0.02ms,主要就是因為導向濾波的解析度無關性.
在安卓機器Redmi 10X Pro下測試,720P能流暢跑此