1. 程式人生 > >基於HLS的sobelIP實現

基於HLS的sobelIP實現

以個人的理解,賽靈思將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]索取