像素行與像素窗口
一幅圖像是由一個個像素點構成的,對于一幅480*272大小的圖片來說,其寬度是480,高度是272。在使用FPGA進行圖像處理時,最關鍵的就是使用FPGA內部的存儲資源對像素行進行緩存與變換。由于在圖像處理過程中,經常會使用到卷積,因此需要對圖像進行開窗,然后將開窗得到的局部圖像與卷積核進行卷積,從而完成處理。
??圖像數據一般按照一定的格式和時序進行傳輸,在我進行實驗的時候,處理圖像時,讓其以VGA的時序來進行工作,這樣能夠為我處理行緩存提供便利。
基于FIFO的行緩存結構
??在FPGA中對圖像的一行數據進行緩存時,可以采用FIFO這一結構,如上圖所示,新一行圖像數據流入到FIFO1中,FIFO1中會對圖像數據進行緩存,當FIFO1中緩存有一行圖像數據時,在下一行圖像數據來臨的時候,將FIFO1中緩存的圖像數據讀出,并傳遞給下一個FIFO,于此同時,將新一行的圖像數據緩存到FIFO1中,這樣就能完成多行圖像的緩存。
??若要緩存多行圖像,下面的菊花鏈式的結果更能夠直觀地表現圖像數據地流向。
??新輸入地圖像緩存到FIFO1當中,當FIFO中緩存有一行數據的時候,下一個輸入像素來臨的時候,會將數據從本FIFO中讀出,并給到下一個FIFO,來形成類似于一個流水線的結構。
??上面的圖中,就是實現一個5X5大小的窗口的一個結構圖。
代碼設計
實現一個可以生成任意尺寸大小的開窗的模塊,需要注意參數的使用,可以通過調節KSZ來調整窗口的大小。最終將窗口中的圖像像素,轉換成一個一維的數據輸出給到下一個模塊。
??在設計的時候,對于FIFO要選擇精準計數模式,這樣才能讓流水正常工作起來。
??在代碼中通過generate語句來實現多個line_buffer的例化,line buffer的個數可以根據卷積窗口的大小來選擇,例如3X3大小的卷積窗口需要緩存兩行,5X5大小的卷積窗口需要緩存4行,可以通過設置參數來選擇要例化多少個line_buffer。
時序設計
??在設計FIFO的菊花鏈結構時,需要根據當前FIFO中存儲的數據個數來判斷,這時候使用到精準計數模式,可以反應FIFO中的存儲的數據。當FIFO中存儲有一行數據的時候,使能pop_en信號,表示當前可以將數據從FIFO中讀出。
??在將數據寫入到FIFO中的時候,需要對數據進行擴充,也即需要對輸入的圖像的邊界補充數據,因為進行卷積之后的圖像將會比原始圖像數據尺寸減少,因此在形成卷積窗口時,將圖像擴充,能夠讓圖像處理完成之后,保持原來的尺寸,只是會在邊界出現黑邊。
??win_buf這個模塊的最終輸出,就是一個矩陣內的所有像素,組成一個信號輸出到外部,供進行卷積的處理。
/*============================================ # # Author: Wcc - 1530604142@qq.com # # QQ : 1530604142 # # Last modified: 2020-07-08 20:02 # # Filename: win_buffer.v # # Description: # ============================================*/ `timescale 1ns / 1ps module win_buf #( //========================================== //parameter define //========================================== parameterKSZ = 3,//卷積核大小 parameterIMG_WIDTH = 128,//圖像寬度,每個Line_buffer需要緩存的數據量 parameterIMG_HEIGHT= 128 //圖像高度 )( input wire clk , inputwirerst , inputwirepi_hs, inputwirepi_vs, inputwirepi_de, inputwirepi_dv, inputwire[7: 0]pi_data, //輸出圖像新有效信號 output wirepo_dv, inputwirepo_hs, inputwirepo_vs, inputwirepo_de, //輸出3X3圖像矩陣 outputwire[8*KSZ*KSZ - 1: 0]image_matrix ); //========================================== //internal signals //========================================== reg frame_flag;//當前處于一幀圖像所在位置 //========================================== //linebuffer 緩存數據 //========================================== //KSZ * KSZ 大小的卷積窗口,需要緩存(KSZ - 1行) wire [7:0]wr_buf_data[KSZ-2: 0];//寫入每個buffer的數據 wire wr_buf_en [KSZ-2: 0];//寫每個buffer的使能 wire full [KSZ-2: 0];//每個buffer的空滿信號 wire empty [KSZ-2: 0]; wire [11:0]rd_data_cnt [KSZ-2: 0];//每個bufeer的內存數據個數 wirerd_buf_en[KSZ-2: 0];//讀出每個buffer的使能信號 wire [7:0]rd_buf_data[KSZ-2: 0];//讀出每個buffer的數據 reg pop_en [KSZ-2: 0];//每個buffer可以讀出數據信號 reg pi_dv_dd [KSZ-2 : 0];//輸入有效數據延時 wireline_vld;//行圖像數據有效信號 wire [7:0]in_line_data; reg [7:0]pi_data_dd [KSZ-2 : 0]; reg [1:0]pi_vs_dd; reg [KSZ-2 : 0]po_dv_dd; reg [KSZ-2 : 0]po_de_dd; reg [KSZ-2 : 0]po_hs_dd; reg [KSZ-2 : 0]po_vs_dd; reg [0 : 0]po_dv_r; reg [0 : 0]po_de_r; reg [0 : 0]po_hs_r; reg [0 : 0]po_vs_r; reg [12:0]cnt_col ;//行列計數器 wireadd_cnt_col; wireend_cnt_col; reg [12:0]cnt_row ; wireadd_cnt_row; wireend_cnt_row ; localparamMATRIX_SIZE = KSZ * KSZ; //輸出列數據延時,形成矩陣 reg[7:0]matrix_data[MATRIX_SIZE - 1: 0]; reg [8*KSZ*KSZ - 1: 0]image_matrix_r; assign image_matrix = image_matrix_r; assign po_dv = po_dv_r; assign po_hs = po_hs_r; assign po_vs = po_vs_r; assign po_de = po_de_r; //----------------pi_vs_dd------------------ always @(posedge clk) begin if (rst==1'b1) begin pi_vs_dd <= 'd0; end else begin pi_vs_dd <= {pi_vs_dd[0], pi_vs}; end end //----------------frame_flag------------------ always @(posedge clk) begin if (rst==1'b1) begin frame_flag <= 1'b0; end //檢測到上升沿,結束上一幀 else if (pi_vs_dd[0] == 1'b1 && pi_vs_dd[1] == 1'b0) begin frame_flag <= 1'b0; end //檢測到下降沿,開始本幀 else if(pi_vs_dd[0] == 1'b0 && pi_vs_dd[1] == 1'b1) begin frame_flag <= 1'b1; end end //========================================== //pi_dv_dd //進行邊界的擴充,以3X3的窗口為例,需要在圖像的 //外圍各邊添加一行一列的的空白像素 //若是5X5的窗口,需要在圖像的外圍各邊,添加2行2列 //========================================== generate genvar i; //對于KSZ*KSZ的卷積核,每一行需要延時(KSZ-1)拍 for (i = 0; i < KSZ-1; i = i + 1) begin:Expand_the_boundary_dv if (i == 0) begin always @(posedge clk) begin if (rst==1'b1) begin pi_dv_dd[i] <= 1'b0; pi_data_dd[i] <= 'd0; end else begin pi_dv_dd[i] <= pi_dv; pi_data_dd[i] <= pi_data; end end end else begin always @(posedge clk) begin if (rst==1'b1) begin pi_dv_dd[i] <= 1'b0; pi_data_dd[i] <= 'd0; end else begin pi_dv_dd[i] <= pi_dv_dd[i - 1]; pi_data_dd[i] <= pi_data_dd[i - 1]; end end end end endgenerate assign line_vld = pi_dv_dd[KSZ-2] | pi_dv; assign in_line_data = pi_data_dd[(KSZ>>1) - 1]; //========================================== //行列計數器 //========================================== //----------------cnt_col------------------ always @(posedge clk) begin if (rst == 1'b1) begin cnt_col <= 'd0; end else if (add_cnt_col) begin if(end_cnt_col) cnt_col <= 'd0; else cnt_col <= cnt_col + 1'b1; end else begin cnt_col <= 'd0; end end assign add_cnt_col = frame_flag == 1'b1 && line_vld == 1'b1; assign end_cnt_col = add_cnt_col &&cnt_col == (IMG_WIDTH + KSZ-1) - 1; //----------------cnt_row------------------ always @(posedge clk) begin if (rst == 1'b1) begin cnt_row <= 'd0; end else if (add_cnt_row) begin if(end_cnt_row) cnt_row <= 'd0; else cnt_row <= cnt_row + 1'b1; end end assign add_cnt_row = end_cnt_col == 1'b1 ; assign end_cnt_row = add_cnt_row &&cnt_row == IMG_HEIGHT - 1; //========================================== //輸入到line_buffer,構成對齊的列 //通過例化FIFO的方式,來完成行緩存 //當FIFO緩存有一行數據時,將數據讀出,并填充到下一個FIFO中 //========================================== generate genvar j; for (j = 0; j < KSZ - 1; j = j + 1) begin:multi_line_buffer //第一個 line_buffer if (j == 0) begin : first_buffer //寫入第一個line_buffer的數據是從外部輸入的數據 assign wr_buf_data[j] = in_line_data; assign wr_buf_en[j] = line_vld; end //其他line_buffer else begin : other_buffer //寫入其他line_buffer的數據是上一個line_buffer中輸出的數據 assign wr_buf_en[j] = rd_buf_en[j - 1] ; assign wr_buf_data[j] = rd_buf_data[j - 1] ; end //----------------rd_buf_en------------------ //從buffer中讀出數據 //pop_en是當前FIFO中已經緩存了一行的圖像數據的指示信號 //wr_buf_en是當前新一行寫入的有效數據指示信號 assign rd_buf_en[j] = pop_en[j] & wr_buf_en[j]; always @(posedge clk) begin if (rst==1'b1) begin pop_en[j] <= 0; end //當前不處于圖像有效數據區域 else if (frame_flag == 1'b0) begin pop_en[j] <= 1'b0; end //當buffer中緩存有一行圖像數據時(擴充的圖像行) else if (rd_data_cnt[j] >= IMG_WIDTH+2) begin pop_en[j] <= 1'b1; end end line_buf line_buffer ( .wr_clk(clk), // input wire wr_clk .rd_clk(clk), // input wire rd_clk .din(wr_buf_data[j]), // input wire [7 : 0] din .wr_en(wr_buf_en[j]), // input wire wr_en .rd_en(rd_buf_en[j]), // input wire rd_en .dout(rd_buf_data[j]), // output wire [7 : 0] dout .full(full[j]), // output wire full .empty(empty[j]), // output wire empty .rd_data_count(rd_data_cnt[j]) // output wire [11 : 0] rd_data_count ); end endgenerate //========================================== //得到矩陣中的每一行的第一個數據 //========================================== generate genvar k; for (k = 0; k < KSZ; k = k + 1) begin:matrix_data_first_col if (k == KSZ -1) begin //最后一行數據為剛輸入的數據 always @(*) begin matrix_data[KSZ * k] = in_line_data; end end else begin //從buffer中讀取出來的圖像書籍 //以3X3為例,buffer是菊花鏈的結果 //最開始輸入的數據,在最后一個buffer中被讀出 //最后輸入的數據,在第一個buffer中被讀出 always @(*) begin matrix_data[KSZ * k] = rd_buf_data[(KSZ -2) - k]; end end end endgenerate //========================================== //延時得到矩陣數據 //以3X3為例 //matrix[0]~[2] ==>p13,p12,p11 //matrix[3]~[5] ==>p23,p22,p21 //matrix[6]~[8] ==>p33,p32,p31 //KSZ-1 clk //========================================== generate genvar r,c; for (r = 0; r < KSZ; r = r + 1) begin:row for (c = 1; c < KSZ; c = c + 1) begin:col always @(posedge clk) begin if (rst==1'b1) begin matrix_data[r*KSZ + c] <= 'd0; end else begin matrix_data[r*KSZ + c] <= matrix_data[r*KSZ + (c-1)]; end end end end endgenerate //========================================== //輸出圖像矩陣 //以3X3為例 //image_matrix [7:0] ==> p13 //image_matrix [15:8] ==> p12 //image_matrix [23:16] ==> p11 //1 clk //========================================== generate genvar idx; for (idx = 0; idx < KSZ*KSZ; idx = idx + 1) begin:out_put_data always @(posedge clk) begin if (rst==1'b1) begin po_dv_r <= 1'b0; po_de_r <= 1'b0; po_hs_r <= 1'b0; po_vs_r <= 1'b0; image_matrix_r[8*(idx+1) -1 : 8*(idx)] <= 'd0; end else begin po_dv_r <= po_dv_dd[KSZ-2] & line_vld; po_de_r <= po_de_dd[KSZ-2] & line_vld; po_hs_r <= po_hs_dd[KSZ-2] ; po_vs_r <= po_vs_dd[KSZ-2] ; if (po_dv_dd[KSZ-2] & line_vld == 1'b1) begin image_matrix_r[8*(idx+1) -1 : 8*(idx)] <= matrix_data[idx]; end else begin image_matrix_r[8*(idx+1) -1 : 8*(idx)] <= 'd0; end end end end endgenerate //========================================== //(KSZ-1) clk //========================================== always @(posedge clk) begin if (rst==1'b1) begin po_vs_dd <= 'd0; po_hs_dd <= 'd0; po_de_dd <= 'd0; po_dv_dd <= 'd0; end else begin po_vs_dd <= {po_vs_dd[KSZ-3:0], pi_vs}; po_hs_dd <= {po_hs_dd[KSZ-3:0], pi_hs}; po_de_dd <= {po_de_dd[KSZ-3:0], pi_de}; po_dv_dd <= {po_dv_dd[KSZ-3:0], pi_dv}; end end endmodule
仿真驗證
3X3開窗
輸入的第三行數據的前三個數據是:0x00,0x78,0x7c
輸入的第二行數據的前三個數據是:0x00,0x7d,0x7d
輸入的第一行數據的前三個數據是:0x00,0x7e,0x7f
輸出的第一個矩陣的值是:0x0078_7c00_7d7d_007e_7f
輸入行數據第一個數據是0x00這是因為擴充了邊界的原因。
??可以看到,設置KSZ為3,可以得到一個位寬為72bit的輸出數據,該數據包含了一個窗口中的9個數據。
5X5開窗
設置開窗大小為5x5之后,也可以看到輸出信號的位寬變為了8*25=200bit,也就是一個5X5大小的矩陣中的數據。
輸入的第5行數據的前5個數據是:0x00,0x00,0x7e,0x7c,0x7f
輸入的第4行數據的前5個數據是:0x00,0x00,0x7e,0x7e,0x7e,
輸入的第3行數據的前5個數據是:0x00,0x00,0x78,0x7c,0x7c
輸入的第2行數據的前5個數據是:0x00,0x00,0x7d,0x7d,0x7a
輸入的第1行數據的前5個數據是:0x00,0x00,0x7e,0x7f,0x7d
從輸出結果看,輸出的矩陣數據,剛好是這5行的前5數據,并且前兩個數據是0x00,這是因為在每一行前面補充了兩個0的原因。
??經過測試,這種開窗算子是能夠完成任意此村的開窗的。
實際應用
在實際應用中,我也將這個模塊正確地使用上了,完成了一個3x3的sobel算子和5x5的均值濾波。
原始圖像
3x3 Sobel
原文鏈接:
https://tencentcloud.csdn.net/678a0adeedd0904849a65d80.html
-
FPGA
+關注
關注
1630文章
21803瀏覽量
606435 -
圖像處理
+關注
關注
27文章
1304瀏覽量
56911 -
緩存
+關注
關注
1文章
242瀏覽量
26775
原文標題:FPGA圖像處理基礎----實現緩存卷積窗口
文章出處:【微信號:gh_9d70b445f494,微信公眾號:FPGA設計論壇】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論