FPGAでの画像フィルタリング





この記事は、 FPGAモーション検出に関する前回の記事の続きです。 その中で、3つの画像フィルタリングアルゴリズムの実装を検討します。そのうちの1つは、動き検出器を開発するときに最も重要です。



フィルタリングを適用する必要があるのは、フレームに存在するノイズが原因です。 これらのノイズは性質が異なります。カメラ自体を作るもの、変換アルゴリズムを作るもの、環境を作るものがありますが、それらはすべて物体を検出するための障害を作ります。



このプロジェクトでは、カメラと画像の二値化アルゴリズム(バックグラウンド減算とフレーム差分)によって発生するノイズに遭遇しました。 これらのノイズは、局所的にもフレーム全体でも、別々のポイントまたはそれらのクラスターの形で現れます。 使用される検出アルゴリズムに応じて、それらは無視されるか、オブジェクトと間違われる可能性があります。



このような誤検出を回避するか、その可能性を最小限に抑えるために、フィルタリングを適用します。



メディアンフィルター



私の意見では、中央値フィルターは、このプロジェクトの開発中に対処しなければならなかった干渉を抑制するために最も重要です。 あらゆる種類の小さな分散介在物を排除するのに適していますが、理想的なツールではありません。 集中的な干渉のわずかに大きな領域を見逃しています。



メディアンフィルターはスライディングウィンドウであり、この場合は3x3ピクセルの次元です。 入力には9つの値(ピクセル)が必要で、出力には1つの値が出力されます。 中央値フィルターは次のように機能します。入力データ(ピクセル)を昇順で並べ替え、中央値結果(中央値)を返します。







このアルゴリズムはCで非常に簡単に実装できますが、FPGAではその実装は多少異なります。 機能的なクラシックフィルター回路を次の図に示します。 19の基本要素で構成されています。







各基本要素(ノード)は、コンパレータとマルチプレクサです。







Verilogでは、次のようになります。



メディアンフィルターノード
module median_node #( parameter DATA_WIDTH = 8, parameter LOW_MUX = 1, // disable low output parameter HI_MUX = 1 // disable high output )( input wire [DATA_WIDTH-1:0] data_a, input wire [DATA_WIDTH-1:0] data_b, output reg [DATA_WIDTH-1:0] data_hi, output reg [DATA_WIDTH-1:0] data_lo ); wire sel0; alt_compare cmp( .dataa(data_a), .datab(data_b), .agb(sel0), .alb() ); always @(*) begin : mux_lo_hi case (sel0) 1'b0 : begin if(LOW_MUX == 1) data_lo = data_a; if(HI_MUX == 1) data_hi = data_b; end 1'b1 : begin if(LOW_MUX == 1) data_lo = data_b; if(HI_MUX == 1) data_hi = data_a; end default : begin data_lo = {DATA_WIDTH{1'b0}}; data_hi = {DATA_WIDTH{1'b0}}; end endcase end endmodule
      
      









メガファンクションalt_compareはコンパレータとして使用されます。 さらに、すべてのノードはスキームに従って接続されます。 ModelSimのフィルターのシミュレーションは次のようになります。









赤いボックスは入力、黄色はフィルター出力です。 出力信号は1クロック遅延します フィルタには同期レジスタ出力があります。



したがって、中央値フィルターを使用すると、すべてが明確になり、3x3のウィンドウを処理できます。



3x3スライディングウィンドウ



これが実際の動作です。 これは、写真をスライドするウィンドウとしてではなく、静的なウィンドウを通過する写真として想像しますが、本質は変わりません。









FPGAでは、ウィンドウを作成するのは難しくありませんが、1行のサイズの2つのFIFOエレメント、この場合は320エレメントが必要です。 次のようになります。









下部のラインバッファーはライン1、上部のラインバッファーはライン2、両方のFIFOが320(この場合は要素)で満たされると、ライン3のデータがストリームから直接取得されます。

Verilogでは、これは次のように行われます。



ラインバッファ
 wire [7:0] line3_data = data_in; wire line2_empty; wire line2_wr_rq = (data_in_en && !line2_data_ready); wire line2_data_valid = !line2_empty; wire line2_data_ready; wire [7:0] line2_data; wire [7:0] median_out_t, sobel_out_t, gaus_out_t; reg [7:0] filter_out_r = 0; // row 3 FIFO alt_fifo_512x8 LINE2_FIFO ( .aclr(), .clock(clk), .data(line3_data), .rdreq(line1_wr_rq), .wrreq(line2_wr_rq), .almost_full(line2_data_ready), .empty(line2_empty), .full(), .q(line2_data), .usedw() ); wire line1_wr_rq = (line2_data_valid && !line1_data_ready); wire line1_data_ready; wire [7:0] line1_data; // row 2 FIFO alt_fifo_512x8 LINE1_FIFO ( .aclr(), .clock(clk), .data(line2_data), .rdreq(line1_rd_rq), .wrreq(line1_wr_rq), .almost_full(line1_data_ready), .empty(), .full(), .q(line1_data), .usedw() ); // median filter top median_top #( .DATA_WIDTH(8) ) median_top ( .clk(clk), .a0(a0), .b0(b0), .c0(c0), .a1(a1), .b1(b1), .c1(c1), .a2(a2), .b2(b2), .c2(c2), .median(median_out_t) );
      
      







ソーベル検出器



Sobel検出器は、おおよその輝度勾配を計算する演算子です。 メディアンフィルターと同様に、Sobel検出器は、9つの入力と1つの出力を備えたウィンドウ関数(この場合は3x3)です。 クラシックバージョンでは、この関数の出力は、X軸とY軸に沿った勾配の平方の合計の平方根になります検出器の操作の結果は、黒い背景上の対照的なオブジェクトの白い輪郭のように見えます。



フィルター係数行列:









勾配は、次の式により、フィルター値とピクセル値の畳み込みによって計算されます。









メディアンフィルターの場合と同様に、このフィルターを使用するには3x3ピクセルウィンドウの形成を使用する必要があります。









機能図のシフトレジスタ 1、2、および3はFIFOからのパイプライン入力であり、Verilogでは次のようになります。



 reg [7:0] a0,b0,c0,a1,b1,c1,a2,b2,c2; always @(posedge clk or negedge nRst) if (!nRst) begin a0 <= 8'd0; b0 <= 8'd0; c0 <= 8'd0; a1 <= 8'd0; b1 <= 8'd0; c1 <= 8'd0; a2 <= 8'd0; b2 <= 8'd0; c2 <= 8'd0; end else begin a0 <= line1_data; b0 <= line2_data; c0 <= line3_data; //pipeline step 1 a1 <= a0; b1 <= b0; c1 <= c0; //pipeline step 2 a2 <= a1; b2 <= b1; c2 <= c1; end
      
      





検出器自体のコードは非常に簡単です。



エッジ検出器
 module sobel_detector (clk,z0,z1,z2,z3,z4,z5,z6,z7,z8,edge_out); input clk; input [7:0] z0,z1,z2,z3,z4,z5,z6,z7,z8; output [7:0] edge_out; reg signed [10:0] Gx; reg signed [10:0] Gy; reg signed [10:0] abs_Gx; reg signed [10:0] abs_Gy; reg [10:0] sum; always @ (posedge clk) begin //original //Gx<=((z2-z0)+((z5-z3)<<1)+(z8-z6)); //masking in x direction //Gy<=((z0-z6)+((z1-z7)<<1)+(z2-z8)); //masking in y direction // modified Gx <= (z4-z3); Gy <= (z4-z1); abs_Gx <= (Gx[10]?~Gx+1'b1:Gx);//if negative, then invert and add to make pos. abs_Gy <= (Gy[10]?~Gy+1'b1:Gy);//if negative, then invert and add to make pos. sum <= abs_Gx+abs_Gy; end //Apply Threshold assign edge_out = (sum > 20) ? 8'hff : 8'h00; endmodule sobel_detector sobel ( .clk(clk), .z0(a0), .z1(a1), .z2(a2), .z3(b0), .z4(b1), .z5(b2), .z6(c0), .z7(c1), .z8(c2), .edge_out(sobel_out_t) );
      
      







ただし、この検出器をテストすると、出力に多くのノイズが入り、黒い背景に美しい白い輪郭ではなく、白い斑点があることがわかりました。 この結果の理由は何ですか、私は理解していませんでした、古典的な検出器の異なるしきい値の使用は望ましい結果を与えませんでした。



私はそのような検出器の動作の理由を探し始めませんでした。 この検出器は実用的ではなく、参考になるだけです。 代わりに、Sobel演算子の行列を変更し、許容可能な結果を​​得ました。



古典行列:









変更されたマトリックス:









ガウスフィルター



メディアンフィルターと同様に、ガウスフィルターはフレーム内のノイズを除去するために使用されますが、副作用もあります-画像のぼけ。 私たちのプロジェクトでは、ガウスフィルターで除去する必要のあるこのようなノイズはないため、このフィルターの実装は学術的にのみ重要です。



前述の2つのフィルターと同様に、Gauss演算子も3x3ウィンドウ関数です。









概略的に、その実装は次のようになります。









Verilogコード:



ガウスフィルター
 module gaus_filter #( parameter DATA_IN_WIDTH = 8 )( input wire [DATA_IN_WIDTH-1:0] d00_in, input wire [DATA_IN_WIDTH-1:0] d01_in, input wire [DATA_IN_WIDTH-1:0] d02_in, input wire [DATA_IN_WIDTH-1:0] d10_in, input wire [DATA_IN_WIDTH-1:0] d11_in, input wire [DATA_IN_WIDTH-1:0] d12_in, input wire [DATA_IN_WIDTH-1:0] d20_in, input wire [DATA_IN_WIDTH-1:0] d21_in, input wire [DATA_IN_WIDTH-1:0] d22_in, output wire [DATA_IN_WIDTH-1:0] ftr_out, ); wire [10:0] s1 = d00_in+(d01_in<<1)+d02_in+(d10_in<<1); wire [10:0] s2 = (d11_in<<2)+(d12_in<<1)+d20_in+(d21_in<<1); wire [11:0] s3 = s1+s2+d22_in; assign ftr_out = s3>>4; endmodule gaus_filter #( .DATA_IN_WIDTH(8) ) gaus_filter_inst( .d00_in (a0), .d01_in (a1), .d02_in (a2), .d10_in (b0), .d11_in (b1), .d12_in (b2), .d20_in (c0), .d21_in (c1), .d22_in (c2), .ftr_out (gaus_out_t), );
      
      







フィルター入力



中央値フィルター入力には、その後のフィルタリングのためにグレースケール表現フレームの絶対差が提供されますが、Sobel検出器とガウスフィルターの入力には、差ではなくグレースケール表現が提供されます。



結論



上記のフィルターは完全ではありませんでした。 このプロジェクトは必要ありませんでした。 必要に応じて、特定のタスクに対してより複雑でリソース集約型のフィルター設計を実装および使用できます。この記事はガイダンスにすぎません。



このプロジェクトをさらに発展させるには、中央値フィルターのみが必要です。



結果のデモンストレーション





画面の右半分には、異なる操作モードが交互に表示され、カラーマーカー( 画像の隅にある四角形)で示されます



黒-フィルタリングなしのグレースケールビュー

赤-フィルタリングなしのフレーム差

緑-中央値フィルターでフィルター処理されたフレームの差

青-ソーベル検出器

白-ガウスフィルター



ビデオの最初のパスは、フレーム差分アルゴリズムの操作です。 メディアンフィルターによるフィルタリングは、ビデオではあまり目立ちません。 2番目のパスはバックグラウンド減算です。 フィルタリングなしとフィルタリングありの違いには顕著な違いがあります-いくつかの個々の白い点が消えました。



ガウスフィルターの後、画像はグレースケールに変化し、差異が顕著になります。ガウスでは、画像はグレースケールよりも鮮明ではありません。



素材



メディアンフィルターFPGAの実装

Sobelフィルターの実装

FPGAのガウスフィルター



All Articles