基於HLS的sobelIP實現--轉載我之前的blog的內容
以個人的理解,賽靈思將HLS(高層次綜合)定位於更方便的將複雜演算法轉化為硬體語言,通過新增某些配置條件HLS工具可以把可並行化的C / C ++的程式碼轉化為VHDL或Verilog的,相比於純人工使用vhdl實現影象演算法,該工具綜合出的程式碼的硬體資源佔用可能較多,但並沒有相差太大。但是卻能提高我們的效率,縮短開發週期。下面開始介紹我實現的一個sobel檢測,可以把這個模組換成其它的各個加速演算法,Sobel原理介紹索貝爾運算元(Sobel operator)主要用作邊緣檢測,在技術上,它是一離散性差分運算元,用來運算影象亮度函式的灰度之近似值。在影象的任何一點使用此運算元,將會產生對應的灰度向量或是其法向量Sobel卷積因子為:
該運算元包含兩組3x3的矩陣,分別為橫向及縱向,將之與影象作平面卷積,即可分別得出橫向及縱向的亮度差分近似值。如果以A代表原始影象,Gx及Gy分別代表經橫向及縱向邊緣檢測的影象灰度值,其公式如下:
Sobel運算元根據畫素點上下,左右鄰點灰度加權差,在邊緣處達到極值這一現象檢測邊緣。對噪聲具有平滑作用,提供較為精確的邊緣方向資訊,邊緣定位精度不夠高。當對精度要求不是很高時,是一種較為常用的邊緣檢測方法。Sobel的實現在MATLAB和OpenCV的實現都是相對簡單的,可是我們卻無法保證執行的效率,眾所周知,使用FPGA進行加速處理能對效率的提高帶來顯著的提升.Sobel運算元在HLS上的實現核心程式碼:
#include“top.h”
<span style="color:#404040"><span style="color:#333333"><code>void hls_sobel(AXI_STREAM& INPUT_STREAM, AXI_STREAM& OUTPUT_STREAM, int rows, int cols) { //Create AXI streaming interfaces for the core #pragma HLS INTERFACE axis port=INPUT_STREAM #pragma HLS INTERFACE axis port=OUTPUT_STREAM #pragma HLS RESOURCE core=AXI_SLAVE variable=rows metadata="-bus_bundle CONTROL_BUS" #pragma HLS RESOURCE core=AXI_SLAVE variable=cols metadata="-bus_bundle CONTROL_BUS" #pragma HLS RESOURCE core=AXI_SLAVE variable=return metadata="-bus_bundle CONTROL_BUS" #pragma HLS INTERFACE ap_stable port=rows #pragma HLS INTERFACE ap_stable port=cols RGB_IMAGE img_0(rows, cols); RGB_IMAGE img_1(rows, cols); RGB_IMAGE img_2(rows, cols); RGB_IMAGE img_3(rows, cols); RGB_IMAGE img_4(rows, cols); RGB_IMAGE img_5(rows, cols); RGB_PIXEL pix(50, 50, 50); #pragma HLS dataflow hls::AXIvideo2Mat(INPUT_STREAM, img_0); hls::Sobel<1,0,3>(img_0, img_1); hls::SubS(img_1, pix, img_2); hls::Scale(img_2, img_3, 2, 0); hls::Erode(img_3, img_4); hls::Dilate(img_4, img_5); hls::Mat2AXIvideo(img_5, OUTPUT_STREAM); } </code></span></span>
我們基本用hls自帶的視訊處理庫函式方向的邊緣檢測,ug902技術手冊中找到這些函式的介紹。
檢測結果如下:
在加入各種優化後,綜合後內部的一些資源消耗。
於是將我們的sobel HLS封裝成一個IP,然後在我們的zynq工程中去呼叫改IP,實現我們的加速演算法。整體框架圖:
硬體系統如此搭建,下面就是來驅動改hls的IP核.1,首先在記憶體空間裡定義一段記憶體:
<span style="color:#404040"><span style="color:#333333"><code>#define SOBEL_S2MM 0x08000000
#define SOBEL_MM2S 0x0A000000
</code></span></span>
使用了兩個VDMA,其中VDMA只有一個寫通道,負責完成顯示的快取功能,VDMA1有兩個通道,負責將我們取模的陣列送入記憶體和SOBEL處理後的資料快取的作用.2,初始化顯示VDMA:
<span style="color:#404040"><span style="color:#333333"><code>vdmaConfig = XAxiVdma_LookupConfig(VGA_VDMA_ID);
Status = XAxiVdma_CfgInitialize(&vdma, vdmaConfig, vdmaConfig->BaseAddress);
</code></span></span>
3,初始化顯示的模組,
<span style="color:#404040"><span style="color:#333333"><code>Status = DisplayInitialize(&dispCtrl, &vdma, DISP_VTC_ID, DYNCLK_BASEADDR,&pFrames[2], 640*4);
</code></span></span>
&P-幀[2]就是我們需要顯示的記憶體空間的起始地址。
4,以上是驅動負責顯示的VDMA,下面就是驅動索貝爾
<span style="color:#404040"><span style="color:#333333"><code>status = XHls_sobel_Initialize(&sobel,XPAR_XHLS_SOBEL_0_S_AXI_CONTROL_BUS_BASEADDR );
vdmaConfig = XAxiVdma_LookupConfig(SOBEL_VDMA_ID);
Status = XAxiVdma_CfgInitialize(&vdma, vdmaConfig, vdmaConfig->BaseAddress);
</code></span></span>
5,啟動索貝爾:
<span style="color:#404040"><span style="color:#333333"><code>SOBEL_Setup();
</code></span></span>
在生成的BSP中能找到對應的驅動函式,實際上就是相應的暫存器寫入資料。
<span style="color:#404040"><span style="color:#333333"><code>void SOBEL_Setup()
{
XHls_sobel_SetRows(&sobel, SOBEL_COL);
XHls_sobel_SetCols(&sobel, SOBEL_ROW);
XHls_sobel_DisableAutoRestart(&sobel);
XHls_sobel_InterruptGlobalDisable(&sobel);
SOBEL_VDMA_setting(SOBEL_ROW,SOBEL_COL,SOBEL_S2MM,SOBEL_MM2S);
SOBEL_DDRWR(SOBEL_MM2S,SOBEL_ROW,SOBEL_COL);
XHls_sobel_Start(&sobel);
}
</code></span></span>
整個框架下來就是VDMA1將取模的陣列刷入記憶體,通過VDMA1的寫通道送給SOBEL處理,然後將處理後的結果放在VDMA1的讀通道中,然後再操作VDMA0的寫通道將VDMA1 S2MM裡SOBEL的結果顯示在螢幕上,思想比較簡單易懂。由於程式碼是僅僅對一個方向進行sobel檢測,所以我便開始查閱hls手冊,進行x,y連個方向檢測,然後實現一個新增。修改程式碼如下:
<span style="color:#404040"><span style="color:#333333"><code>void hls_sobel(AXI_STREAM& INPUT_STREAM1,AXI_STREAM& OUTPUT_STREAM, int rows, int cols)
{
//Create AXI streaming interfaces for the core
#pragma HLS INTERFACE axis port=INPUT_STREAM1
#pragma HLS INTERFACE axis port=OUTPUT_STREAM
#pragma HLS RESOURCE core=AXI_SLAVE variable=rows metadata="-bus_bundle CONTROL_BUS"
#pragma HLS RESOURCE core=AXI_SLAVE variable=cols metadata="-bus_bundle CONTROL_BUS"
#pragma HLS RESOURCE core=AXI_SLAVE variable=return metadata="-bus_bundle CONTROL_BUS"
#pragma HLS INTERFACE ap_stable port=rows
#pragma HLS INTERFACE ap_stable port=cols
</code></span></span>
RGB_IMAGE img_0(rows,cols); RGB_IMAGE img_1(rows,cols);
<span style="color:#404040"><span style="color:#333333"><code> RGB_IMAGE img_2(rows, cols);
RGB_IMAGE img_3(rows, cols);
RGB_IMAGE img_4(rows, cols);
RGB_IMAGE img_5(rows, cols);
RGB_IMAGE img_6(rows, cols);
RGB_IMAGE img_7(rows, cols);
RGB_IMAGE img_8(rows, cols);
RGB_IMAGE img_9(rows, cols);
RGB_PIXEL pix(50, 50, 50);
#pragma HLS dataflow
hls::AXIvideo2Mat(INPUT_STREAM1, img_0);
/Copies the input image src to two output images dst1 and dst2, for divergent point of two datapaths.
hls::Duplicate(img_0,img_1,img_2);
hls::Sobel<0,1,3>(img_1, img_3);
hls::Sobel<1,0,3>(img_2, img_4);
hls::AddWeighted(img_3,1,img_4,1,0,img_5);
hls::SubS(img_5, pix, img_6);
hls::Scale(img_6, img_7, 2, 0);
hls::Erode(img_7, img_8);
hls::Dilate(img_8, img_9);
hls::Mat2AXIvideo(img_9, OUTPUT_STREAM);
} 由於hls映射出來就是具體的電路,所以就不能只定義幾個變數來儲存對應的img,要看有幾個過程,就定義幾個RGB_IMAGE,每一次處理的結果都放到對應的RGB_IMAGE中去,簡而言之就是在資料寫入RGB_IMAGE之前,必須保證裡面是empty。 這裡用到了一個關鍵函式就是
hls::Duplicate(img_0,img_1,img_2);
</code></span></span>
他就是將輸入影象src複製到兩個輸出影象dst1和dst2,用於兩個資料路徑的發散點。然後我又把資料加起來
<span style="color:#404040"><span style="color:#333333"><code>hls::AddWeighted(img_3,1,img_4,1,0,img_5);
</code></span></span>
然後進行腐蝕和膨脹。綜合如下:因為又加入了y方向的檢測,因此資源消耗可能會增加。
軟體模擬結果如下:
硬體驅動HDMI如下:
程式碼工程可以發我QQ郵箱[email protected]索取