FPGAビデオストリーム内の複数のオブジェクトの検出と追跡







この記事では、ビデオストリーム内の複数のオブジェクトを検出および追跡するためのシステムの実装について説明します。 この記事は、前の2つ( FPGAのビデオストリームでの動き検出とFPGAの 数学的形態を使用した画像フィルタリング )に基づいています。 画像のキャプチャと一次処理は、最初の記事で説明した方法を使用して実行され、画像のフィルタリングは2番目の記事で説明します。



最初の記事で設定した目標に従って、検出されたオブジェクトの周囲にフレームを描画するアルゴリズムを実装することにしました。 このタスクを実行する過程で、私は質問に出くわしました。しかし、どの特定のオブジェクトの周りにフレームを描く必要があるのでしょうか? フィルタリング後、フレームに多くのオブジェクトが含まれる場合があります。それらのいくつかは小さく、他は大きいです。 フレームに落ちたすべてのオブジェクトの周囲に1つのフレームを描画する場合、これは難しくありませんが、そのようなシステムの結果に関心を持つ人はほとんどいません。



ネットワークで大騒ぎして、複数のオブジェクトを検出するためのさまざまなアプローチを説明する一連のドキュメントに精通しました。 FPGAでの実装には適さないものもあれば、フレーム全体を保存するために大量のブロックメモリを必要とするものもありますが、画像のいくつかのパスで作業を行うものもあります。 欲しいです。



それにもかかわらず、 ドキュメントの 1つが私の注目を集めました。 画像セクションの分析用のリンクリストの実装を使用して、画像の1回のパスで複数のオブジェクトの検出を実装するアルゴリズムについて説明します。 この資料を分析した後、オブジェクト検出器自体が検出器にデータを入力して画面に画像を表示するロジックよりも高速なブロックドメインで動作するという条件で、このようなアルゴリズムが適していることに気付きました。 これは、このアルゴリズムが入力で受信した新しいデータごとにリンクリストを検索し、リンクリストの最大数が行のピクセル数の半分に達するためです。 つまり、最悪の場合、画像の線幅が320ピクセルの場合、新しいデータごとに160個のリンクリストを処理する必要があります。



以下は、複数のオブジェクトの検出器の機能図です。





検出器は、 RUN検出器BLOB検出器バウンディングボックスジェネレーターの3つのメインブロックで構成されています 。 各ブロックは独自のブロックドメインで動作し、非同期FIFOを介して別のブロックに接続されます。



検出器を実行する



このブロックへの入力は、 数学的形態に基づいフィルターからの出力です。

このブロックは、入力ストリーム内で、いわゆるRUNという1つのユニットで構成されるピクセルシーケンスを検索します。 この名前は、JPEG圧縮などで使用されるRLE(Run Length Encoding)圧縮アルゴリズムから取られています 。 以下の図は、いくつかのタイプのRUN( class-lengthclass-startおよびstart-end)を示しています。









検出器では、最後の( start-end )が使用されます 私たちのシステムに最適です。 ユニットシーケンスの開始位置の値は開始フィールドに書き込まれ、 終了位置は終了フィールドに書き込まれます。生成された開始終了フィールドは検出器のRUN出力に送信され、FIFOに記録されます。 RUNとともに、次のブロックが正しく動作するように、行の終わりとフレームの終わりのデータがFIFOに記録されます。



Verilog Detectorコード



検出器を実行する
module run_detector #( parameter RES_X=10'd320, parameter RES_Y=10'd240, parameter XOFFSET=10'd320, parameter YOFFSET=10'd0) ( input wire clk, input wire nRst, input wire data_valid, input wire [0:0] data_in, input wire [10:0] xi, input wire [10:0] yi, output reg [9:0] run_start, output reg [9:0] run_end, output reg row_end, output reg frame_end, output reg new_run, output reg rd_req ); localparam ST_IDLE=0, ST_RUN_START=1, ST_RUN_END=2, ST_ROW_END=3, ST_FRAME_END=4; localparam XRES = RES_X + XOFFSET - 1; localparam YRES = RES_Y + YOFFSET - 1; reg [2:0] run_state = 0; reg [9:0] run_start_d; reg [9:0] run_end_d; wire row_done_w = ((xi == XRES) && (yi <= YRES)) ? 1'b1:1'b0; wire frame_done_w = ((xi == XRES) && (yi == YRES)) ? 1'b1:1'b0; always @(posedge clk or negedge nRst) if (!nRst) begin run_state <= ST_IDLE; run_start <= 10'd0; run_end <= 10'd0; row_end <= 1'b0; frame_end <= 1'b0; new_run <= 1'b0; rd_req <= 1'b0; end else begin new_run <= 1'b0; row_end <= 1'b0; frame_end <= 1'b0; case (run_state) ST_IDLE: begin if (data_valid) begin if (data_in) begin run_state <= ST_RUN_START; run_start <= xi[9:0]; end end end ST_RUN_START: begin if (!data_in) begin run_state <= ST_RUN_END; run_end <= xi[9:0] - 1'b1; new_run <= 1'b1; end end ST_RUN_END: begin if (data_in) begin run_start <= xi[9:0]; run_state <= ST_RUN_START; end else begin run_state <= ST_IDLE; end end ST_ROW_END, ST_FRAME_END: begin if (!data_valid) begin run_state <= ST_IDLE; end end endcase if (row_done_w || frame_done_w) begin run_state <= frame_done_w ? ST_FRAME_END : ST_ROW_END; row_end <= row_done_w ? 1'b1 : 1'b0; frame_end <= frame_done_w ? 1'b1 : 1'b0; run_start <= 10'd0; run_end <= 10'd0; new_run <= 1'b1; end end endmodule
      
      







ブロブ検出器



検出器の動作アルゴリズムのブロック図を以下に示します。









検出器の動作をより詳細に検討してください。 私の実装では、検出器は大型の同期状態マシンです。 また、2つのブロックメモリモジュールを使用して検出器を操作します。1つ現在および前の行の実行メモリを保存し、もう1つは検出されたオブジェクト( オブジェクトメモリ )のプロパティを保存します。 32個のオブジェクトのみ。



初期化

検出器はINIT状態で起動します。 この状態では、 実行メモリバッファの1つが無効なラベル値で初期化されます。 その後、検出器はIDLE状態に入ります。



アイドル

この状態では、ディテクターはディテクターRUNからの入力FIFO内のデータを待ってから、 FIND_RUN状態に入ります。



FIND_RUN

ここで、検出器はFIFOからRUNの開始および終了座標を読み取り、 FIND_OVERLAP状態に入り、現在のRUNと前の行のRUNの交差を検索します。 新しいRUNには無効なラベルが割り当てられます。



FIND_OVERLAP

実行メモリ内のデータは、現在と以前の2行に配置されています。 FIFOからの新しいRUNはそれぞれ、現在のメモリブロックに記録され、交差点(オーバーラップ)と比較して、前の行のブロック全体を通過します。 次の図は、RUNが実行メモリにどのように保存されるかを示しています









前の行のメモリから読み取られたRUNに無効なラベルがある場合、これはこのブロックに前の行のRUNがもうないことを意味し、検出器はCREATE_OBJ状態に切り替わり、現在のRUNから新しいオブジェクトを作成します。 それ以外の場合、交差が確立されると、 オブジェクトは RUNラベルのアドレスでオブジェクトメモリから読み取られ、検出器はUPDATE_OBJ状態になり、既存のオブジェクトのプロパティを更新します。 現在のRUNタグが無効ではなく、FIFOから読み取るときに与えられた場合、これは、このRUNが特定のオブジェクトに属し、別の既存のオブジェクトとの交差点があり、これらのオブジェクトを1つのオブジェクトにマージする必要があることを意味します。検出器はMERGE_OBJ状態に入ります。



CREATE_OBJ

ここに新しいオブジェクトが作成され、そのすべてのプロパティが入力されます。





作成されたオブジェクトは、次の空きアドレス(現在のRUNのラベル)のオブジェクトメモリに保存されます 。 検出器は、FIFOから新しいRUNを読み取るアイドル状態に入ります。



UPDATE_OBJ

この状態では、 オブジェクトメモリからの既存のオブジェクトのプロパティが更新され、オブジェクトの新しい座標は、それに接続されている現在のRUNの長さに基づいて計算されます。 オブジェクトを更新した後、検出器は、実行メモリからの次のRUNとの交差検索のFIND_OVERLAP状態に入ります。



MERGE_OBJ

ここで、2つの既存のオブジェクトが1つにマージされ、それらのプロパティが更新されて最初のオブジェクトに割り当てられ、2番目のオブジェクトが無効になって以降の処理から除外されます。 マージ後、ディテクタは、 実行メモリからの次のRUNとの交差検索のFIND_OVERLAP状態に入ります



以下の図は、赤でハッチングされた共通のRUNによるオブジェクト12のマージを示しています。









FINISH_OBJ

検出器は、FIFOから行の終わりまたはフレームの終わりの符号を読み取るたびにこの状態に入ります。 ラインターミネータの場合、 実行メモリの書き込み領域と読み取り領域が交換され、検出器はFIFOからのデータ入力を待機状態になります。 フレームの終わりの場合、検出器は、見つかったオブジェクトをオブジェクトメモリから出力FIFOにアンロードするUPLOAD_DATA状態に入ります



UPLOAD_DATA

オブジェクトメモリ全体を渡すことにより、オブジェクトプロパティが出力FIFOにアンロードされます。 FIFOでページングされる単語は、オブジェクトアドレスと4つの座標XstartXendYstart 、およびYendのレイアウトです。 合計32個のオブジェクトが実現され、 320x240ピクセルのフレームにはこれで十分です。 ただし、見つかったすべてのオブジェクトが有効なわけではありません。 100ピクセル未満のオブジェクトは無効としてFIFO出力にアップロードされ、その後フレームジェネレーターによって処理されません。 すべてのオブジェクトをアンロードした後、検出器はすべてのオブジェクトメモリを消去し、その状態をSTARTに変更し、オブジェクトの新しい蓄積サイクルが始まります。



Verilog Detectorコード



ブロブ検出器
 module blob_detector #( parameter RES_X=10'd320, parameter RES_Y=10'd240) ( input wire clk, input wire nRst, // input RUN FIFO input wire fifo_empty, input wire [21:0] fifo_data, output wire fifo_rd_en, // output boxes interface output reg we, output reg [47:0] data_o ); localparam ST_START = 0, ST_INIT = 1, ST_IDLE = 2, ST_FIND_RUN = 3, ST_FIND_OVERLAP = 4, ST_FIND_OVERLAP_0 = 5, ST_CREATE_OBJ = 6, ST_UPDATE_OBJ_2 = 7, ST_UPDATE_OBJ = 8, ST_MERGE_OBJ = 9, ST_MERGE_OBJ_2 = 10, ST_FINISH_OBJ = 11, ST_FINISH_OBJ_2 = 12, ST_UPLOAD_DATA_START = 13, ST_UPLOAD_DATA = 14, ST_UPLOAD_DATA_END = 15; localparam MAX_RUNS = RES_X >> 1; //`define DETECTOR_DEBUG_MODE //is using for modeling // RUN memory reg [7:0] run_mem_wr_addr = 0; reg [7:0] run_mem_rd_addr = 0; reg [7:0] run_mem_saved_addr = 0; reg run_mem_wr_en = 0; reg run_mem_flip = 0; // RUN memory data wire [9:0] run_rd_start_x; wire [9:0] run_rd_end_x; wire [9:0] run_rd_label; // RUN FIFO data reg [9:0] run_cur_start_x = 0; reg [9:0] run_cur_end_x = 0; reg [9:0] run_cur_label = 0; wire [1:0] eb; reg [9:0] free_label = 0; `define OBJ_MASS_THR 19'd100 `define OBJ_LIMIT 32 `define FRAME_END fifo_data[21] `define ROW_CHANGED fifo_data[20] `define RUN_EMPTY_LABEL 10'h200 `define EMPY_RUN_SLOT 10'h1FF // FSM reg [5:0] blob_detector_fsm_state = 0; // Internal reg [9:0] current_row = 0; reg [9:0] temp_label = 0; reg frame_end_r = 0; `ifdef DETECTOR_DEBUG_MODE // DEBUG reg create_obj = 0; reg merge_obj = 0; reg update_obj = 0; reg skip_obj = 0; reg finish_obj = 0; wire [8:0] rd_ram_addr = {~run_mem_flip, run_mem_rd_addr}; wire [8:0] wr_ram_addr = {run_mem_flip, run_mem_wr_addr}; `endif // read the input FIFO assign fifo_rd_en = (!fifo_empty && blob_detector_fsm_state == ST_FIND_RUN); // RUN memory alt_ram_30x512 run_ram ( .clock(clk), .data({2'h0, run_cur_start_x, run_cur_end_x, run_cur_label}), .rdaddress({~run_mem_flip, run_mem_rd_addr}), .wraddress({run_mem_flip, run_mem_wr_addr}), .wren(run_mem_wr_en), .q({eb, run_rd_start_x, run_rd_end_x, run_rd_label}) ); reg [4:0] obj_wr_addr = 0, obj_rd_addr = 0, obj_saved_rd_addr = 0; reg obj_mem_wr_en = 0; // object's fields for writing reg [9:0] obj_start_x = 0, obj_start_y = 0, obj_end_x = 0, obj_end_y = 0; reg [21:0] obj_mass = 0; reg obj_valid = 0, obj_updated = 0; // object's wires for reading wire [9:0] obj_rd_start_x, obj_rd_start_y, obj_rd_end_x, obj_rd_end_y; wire [21:0] obj_rd_mass; wire obj_rd_valid, obj_rd_updated; // object detection condition wire obj_detected_valid = (obj_rd_valid && (obj_rd_mass > `OBJ_MASS_THR)); // Object memory object_ram obj_ram ( .clock(clk), .data({obj_valid,obj_updated,obj_start_x,obj_end_x,obj_start_y,obj_end_y,obj_mass}), .rdaddress(obj_rd_addr), .wraddress(obj_wr_addr), .wren(obj_mem_wr_en), .q({obj_rd_valid,obj_rd_updated,obj_rd_start_x,obj_rd_end_x,obj_rd_start_y,obj_rd_end_y,obj_rd_mass}) ); /* * The main detector process */ always @(posedge clk or negedge nRst) if (!nRst) begin blob_detector_fsm_state <= ST_START; run_mem_flip <= 1'b0; free_label <= 10'd0; temp_label <= 10'd0; current_row <= 10'd0; frame_end_r <= 1'b0; run_mem_wr_en <= 1'b0; obj_mem_wr_en <= 1'b0; we <= 1'b0; end else begin run_mem_wr_en <= 1'b0; obj_mem_wr_en <= 1'b0; we <= 1'b0; `ifdef DETECTOR_DEBUG_MODE // DEBUG create_obj <= 1'b0; merge_obj <= 1'b0; update_obj <= 1'b0; skip_obj <= 1'b0; finish_obj <= 1'b0; `endif case (blob_detector_fsm_state) ST_START: begin current_row <= 10'd0; free_label <= 10'd0; run_mem_rd_addr <= 8'd0; run_mem_wr_addr <= 8'd0; obj_rd_addr <= 5'd0; obj_wr_addr <= 5'd0; frame_end_r <= 1'b0; run_mem_flip <= 1'b0; blob_detector_fsm_state <= ST_INIT; run_cur_start_x <= `EMPY_RUN_SLOT; run_cur_end_x <= `EMPY_RUN_SLOT; run_mem_wr_en <= 1'b1; end ST_INIT: begin if (run_mem_wr_addr >= MAX_RUNS) begin run_mem_wr_addr <= 8'd0; run_mem_flip <= ~run_mem_flip; run_mem_wr_en <= 1'b0; blob_detector_fsm_state <= ST_IDLE; end else begin run_mem_wr_addr <= run_mem_wr_addr + 1'b1; run_mem_wr_en <= 1'b1; end end ST_IDLE: begin if (!fifo_empty) begin blob_detector_fsm_state <= ST_FIND_RUN; end end ST_FIND_RUN: begin // fifo is already read by now // ALWAYS set the empty label to the new run temp_label <= `RUN_EMPTY_LABEL; run_cur_start_x <= fifo_data[19:10]; run_cur_end_x <= fifo_data[9:0]; if (`ROW_CHANGED) begin frame_end_r <= `FRAME_END; blob_detector_fsm_state <= ST_FINISH_OBJ; end else begin // set first read addres of a RUN run_mem_rd_addr <= 8'd0; blob_detector_fsm_state <= ST_FIND_OVERLAP_0; end end ST_FIND_OVERLAP_0: begin // an empty case (altera's altsyncram is read in 2 cycles, one of them is empty) blob_detector_fsm_state <= ST_FIND_OVERLAP; end ST_FIND_OVERLAP: begin if ((run_rd_start_x == `EMPY_RUN_SLOT) || (run_rd_start_x > run_cur_end_x + 1'b1)) begin // create new object if (temp_label == `RUN_EMPTY_LABEL) begin // asssign the run a free label run_cur_label <= free_label; // ??? obj_saved_rd_addr <= obj_rd_addr; obj_rd_addr <= free_label[4:0]; // store current run into the memory run_mem_wr_en <= 1'b1; blob_detector_fsm_state <= ST_CREATE_OBJ; end else begin // some garbage was read from the FIFO run_mem_rd_addr <= 8'd0; blob_detector_fsm_state <= ST_IDLE; end end else begin // not empty slot in memory if (((run_rd_start_x >= run_cur_start_x) && (run_rd_start_x <= run_cur_end_x)) || ((run_rd_end_x >= run_cur_start_x) && (run_rd_end_x <= run_cur_end_x)) || ((run_rd_end_x >= run_cur_start_x) && (run_rd_start_x <= run_cur_end_x)) ) begin // overlap if (temp_label == `RUN_EMPTY_LABEL) begin run_cur_label <= run_rd_label; temp_label <= run_rd_label; // write current RUN run_mem_wr_en <= 1'b1; // read the object with the overlaped RUN, save the current read addres obj_saved_rd_addr <= obj_rd_addr + 1'b1; obj_rd_addr <= run_rd_label[4:0]; // leave the RUN read address the same run_mem_saved_addr <= run_mem_rd_addr; blob_detector_fsm_state <= ST_UPDATE_OBJ; end else begin // if label exists (we came from UPDATE state) if (temp_label != run_rd_label) begin // merge read RUN and existent object (objects might NOT be overlaped) obj_rd_addr <= run_rd_label[4:0]; // goto merge state blob_detector_fsm_state <= ST_MERGE_OBJ; end else begin // otherwise this is the same RUN, skip it run_mem_rd_addr <= run_mem_rd_addr + 1'b1; blob_detector_fsm_state <= ST_FIND_OVERLAP_0; end end end else begin // not overlaps, skip it run_mem_rd_addr <= run_mem_rd_addr + 1'b1; blob_detector_fsm_state <= ST_FIND_OVERLAP_0; end end end ST_CREATE_OBJ: begin `ifdef DETECTOR_DEBUG_MODE // DEBUG create_obj <= 1'b1; `endif // the label of the current RUN obj_wr_addr <= run_cur_label[4:0]; // features obj_valid <= 1'b1; obj_updated <= 1'b1; obj_start_x <= run_cur_start_x; obj_end_x <= run_cur_end_x; obj_start_y <= current_row; obj_end_y <= current_row; obj_mass <= run_cur_end_x - run_cur_start_x + 1'b1; free_label <= free_label + 1'b1; // write this OBJ into mem obj_mem_wr_en <= 1'b1; // increment the next RUN write address run_mem_wr_addr <= run_mem_wr_addr + 1'b1; // RUN read address starts from the begining run_mem_rd_addr <= 8'd0; // check the FIFO for the next RUN blob_detector_fsm_state <= ST_IDLE; end ST_UPDATE_OBJ: begin // we mut take altsyncram's latency into account (one empty cycle) obj_rd_addr <= run_rd_label[4:0]; blob_detector_fsm_state <= ST_UPDATE_OBJ_2; end ST_UPDATE_OBJ_2: begin `ifdef DETECTOR_DEBUG_MODE // DEBUG update_obj <= 1'b1; `endif // update the object if only it was previously valid // thus we avoid updating unused objects came from the // merge state if (obj_rd_valid) begin obj_valid <= 1'b1; obj_updated <= 1'b1; obj_start_x <= (run_cur_start_x < obj_rd_start_x) ? run_cur_start_x : obj_rd_start_x ; obj_end_x <= (run_cur_end_x > obj_rd_end_x) ? run_cur_end_x : obj_rd_end_x; obj_start_y <= (current_row < obj_rd_start_y) ? current_row : obj_rd_start_y; obj_end_y <= (current_row > obj_rd_end_y) ? current_row : obj_rd_end_y; obj_mass <= obj_rd_mass + (run_cur_end_x - run_cur_start_x); // save updated obj to it's original address obj_wr_addr <= obj_rd_addr; // restore saved read address run_mem_rd_addr <= run_mem_saved_addr; // write updated object obj_mem_wr_en <= 1'b1; end `ifdef DETECTOR_DEBUG_MODE else begin // just skip this object // perhaps it was updated previously or going to be updated on the next RUN skip_obj <= 1'b1; end `endif // let store current run into the memory blob_detector_fsm_state <= ST_FIND_OVERLAP_0; // increment the next RUN write address run_mem_wr_addr <= run_mem_wr_addr + 1'b1; end ST_MERGE_OBJ: begin // invalidate the (second) object // !!!free list MUST be updated ??? I can't figure out how it should be done... obj_valid <= 1'b0; obj_wr_addr <= run_rd_label[4:0]; obj_mem_wr_en <= 1'b1; blob_detector_fsm_state <= ST_MERGE_OBJ_2; end ST_MERGE_OBJ_2: begin `ifdef DETECTOR_DEBUG_MODE // DEBUG merge_obj <= 1'b1; `endif if (obj_rd_valid) begin obj_valid <= 1'b1; obj_start_x <= (obj_start_x < obj_rd_start_x) ? obj_start_x : obj_rd_start_x; obj_end_x <= (obj_end_x > obj_rd_end_x) ? obj_end_x : obj_rd_end_x; obj_start_y <= (obj_start_y < obj_rd_start_y) ? obj_start_y : obj_rd_start_y; obj_end_y <= (obj_end_y > obj_rd_end_y) ? obj_end_y : obj_rd_end_y; obj_mass <= obj_mass + obj_rd_mass; obj_wr_addr <= temp_label[4:0]; // write updated (first) object obj_mem_wr_en <= 1'b1; // read just written object obj_rd_addr <= temp_label[4:0]; end blob_detector_fsm_state <= ST_FIND_OVERLAP_0; run_mem_rd_addr <= run_mem_rd_addr + 1'b1; end ST_FINISH_OBJ: begin // if there are NO opened objects in the row run_cur_start_x <= `EMPY_RUN_SLOT; run_cur_end_x <= `EMPY_RUN_SLOT; run_mem_rd_addr <= 8'd0; blob_detector_fsm_state <= ST_FINISH_OBJ_2; run_mem_wr_en <= 1'b1; end ST_FINISH_OBJ_2: begin run_mem_flip <= ~run_mem_flip; run_mem_wr_addr <= 8'd0; current_row <= current_row + 1'b1; if (frame_end_r) begin `ifdef DETECTOR_DEBUG_MODE // DEBUG finish_obj <= 1'b1; `endif // do some stuff around bounding boxes frame_end_r <= 1'b0; obj_rd_addr <= 5'd0; obj_saved_rd_addr <= 5'd0;; blob_detector_fsm_state <= ST_UPLOAD_DATA_START; end else begin blob_detector_fsm_state <= ST_IDLE; end end ST_UPLOAD_DATA_START: begin // an empty case (altera's altsyncram is read in 2 cycles, one of them is empty) obj_saved_rd_addr <= obj_rd_addr; blob_detector_fsm_state <= ST_UPLOAD_DATA; end ST_UPLOAD_DATA: begin if (obj_rd_addr >= (`OBJ_LIMIT - 1)) begin blob_detector_fsm_state <= ST_UPLOAD_DATA_END; end else begin data_o <= {2'h0, obj_saved_rd_addr, obj_detected_valid, obj_rd_start_x, obj_rd_end_x, obj_rd_start_y, obj_rd_end_y}; //data_o <= {2'h0, obj_saved_rd_addr, obj_rd_valid, obj_rd_start_x, obj_rd_end_x, obj_rd_start_y - 10'd320, obj_rd_end_y - 10'd320}; //data_o <= {2'h0, obj_saved_rd_addr, 1'b1, 10'd340, 10'd380, 10'd120, 10'd160}; we <= 1'b1; // clean obj memory obj_wr_addr <= obj_rd_addr; obj_valid <= 1'b0; obj_updated <= 1'b0; obj_start_x <= 10'd0; obj_end_x <= 10'd0; obj_start_y <= 10'd0; obj_end_y <= 10'd0; obj_mass <= 20'd0; obj_mem_wr_en <= 1'b1; // set box generator's address //if (obj_detected_valid) obj_saved_rd_addr <= obj_saved_rd_addr + 1'b1; // set the next read address obj_rd_addr <= obj_rd_addr + 1'b1; blob_detector_fsm_state <= ST_UPLOAD_DATA_START; end end ST_UPLOAD_DATA_END: begin obj_rd_addr <= 5'd0; blob_detector_fsm_state <= ST_START; end default: blob_detector_fsm_state <= ST_START; endcase end endmodule
      
      







中間状態の存在: FIND_OVERLAP_0UPDATE_OBJ_2MERGE_OBJ_2FINISH_OBJ_2およびUPLOAD_DATA_STARTは、2ポートFPGAメモリの読み取りに関連付けられています。 1クロックサイクルのメモリの読み取りは不可能であることがわかりました。つまり、現在のクロックサイクルでアドレスを設定すると、次のクロックサイクルではメモリから有効なデータを受信しませんが、 altsyncram メガファンクション入力はレジスタであり、記録ブロックに同期しています。 ここから、1ステップの遅延が判明します。 私はそれが正しかったことを願っています:)



バウンディングボックスジェネレーター



このブロックは、見つかったオブジェクトの周囲に長方形のフレームを描くために使用されます。 32個のオブジェクトが実装されているため、32個のフレームジェネレータもあり、それらはすべて直列に接続されており、それぞれに固有のアドレスがあります。 このアドレスBlob Detectorモジュールのオブジェクトメモリ内のオブジェクトのアドレスと一致します。 FIFOからデータを読み取るとき、フレームジェネレーターはFIFOからのアドレスを一意のアドレスと比較し、オブジェクトの座標を読み込んでフレームをそれ自体に描画するか、スキップします。



ジェネレータのさらなる作業はかなり簡単です。このピクセルの座標は、ピクセルデータとともにフレームジェネレータに送信され、検出器はそれらをFIFOからロードされた座標と比較します。 入力座標がロードされた座標内にある場合、ピクセルの色はフレームの色に変わります。そうでない場合、ピクセルは変更されません。



ボックスジェネレーターコード



バウンディングボックスジェネレーター
 module box_generator #( parameter BADDR = 5'd0, parameter COLOR = 16'hF8_00 ) ( input clk, input nRst, input [4:0] addr, input [40:0] data, input we, input [10:0] hcount, input [10:0] vcount, input [15:0] pixel_i, output wire [15:0] pixel_o ); reg [10:0] xs = 0,xe = 0,ys = 0,ye = 0; reg box_valid = 0; reg [15:0] pixel_r; wire addr_valid = (addr == BADDR) ? 1'b1 : 1'b0; always @(posedge clk or negedge nRst) if (!nRst) begin xs <= 11'd0; ys <= 11'd0; xe <= 11'd0; ye <= 11'd0; box_valid <= 1'b0; end else begin if (we && addr_valid) begin xs = data[39:30]; xe = data[29:20]; ys = data[19:10]; ye = data[9:0]; box_valid = data[40]; end end always @(*) begin if ((hcount >= xs && hcount <= xe) && (vcount == ys || vcount == ye)) begin pixel_r = COLOR; end else begin if ((hcount == xs || hcount == xe) && (vcount >= ys && vcount <= ye)) pixel_r = COLOR; else pixel_r = pixel_i; end end assign pixel_o = box_valid ? pixel_r : pixel_i; endmodule
      
      







32個のフレームジェネレーターの挿入は、 生成演算子を使用して順次実行されます。 前の各モジュールの出力は、次のモジュールの入力に接続されます。



 wire [15:0] box_out [0:`OBJ_LIMIT-1]; genvar i; generate for(i = 0; i < `OBJ_LIMIT; i = i + 1 ) begin : box_gen if (i == 0) begin box_generator #( .BADDR(i), .COLOR(`CL_RED) ) BOX_GEN ( .clk(pix_clk), .nRst(nRst), .addr(box_data[45:41]), .data(box_data[40:0]), .we(box_fifo_rd_en), .hcount(counter_x), .vcount(counter_y), .pixel_i({morph_out[7:3], morph_out[7:2], morph_out[7:3]}), .pixel_o(box_out[0]) ); end else begin box_generator #( .BADDR(i), .COLOR(`CL_RED) ) BOX_GEN ( .clk(pix_clk), .nRst(nRst), .addr(box_data[45:41]), .data(box_data[40:0]), .we(box_fifo_rd_en), .hcount(counter_x), .vcount(counter_y), .pixel_i(box_out[i-1]), .pixel_o(box_out[i]) ); end end ndgenerate
      
      





上記のブロックは、次のように接続されています。



 wire [9:0] run_start, run_end; wire row_end, frame_end, new_run; wire [21:0] run_fifo_data_o; wire run_fifo_full, run_fifo_empty, run_fifo_wr_en, run_fifo_rd_en; run_detector #( .RES_X(10'd320), .RES_Y(10'd240), .XOFFSET(10'd320), .YOFFSET(10'd0) ) RUN_DETECTOR ( .clk(pix_clk), .nRst(nRst), .data_valid(in_frame2), .data_in(&morph_out), .xi(counter_x), .yi(counter_y), .run_start(run_start), .run_end(run_end), .row_end(row_end), .frame_end(frame_end), .new_run(new_run), .rd_req(run_read_req) ); wire [10:0] run_fifo_rd_used, run_fifo_wr_used; wire run_fifo_rd_avail = |run_fifo_rd_used[10:2]; wire run_fifo_almost_full = &run_fifo_wr_used[9:2]; assign run_fifo_wr_en = (!run_fifo_almost_full && new_run) ? 1'b1: 1'b0; alt_fifo_22x512 RUN_FIFO ( .wrclk(pix_clk), .data({frame_end, row_end, run_start, run_end}), .aclr(~nRst), .rdreq(run_fifo_rd_en), .wrreq(run_fifo_wr_en), .rdempty(run_fifo_empty), .rdclk(clk), .wrfull(), .q(run_fifo_data_o), .rdusedw(run_fifo_rd_used), .wrusedw(run_fifo_wr_used) ); wire box_we; wire [47:0] box_data; wire [47:0] box_data_det; blob_detector #( .RES_X(10'd320), .RES_Y(10'd240) ) BLOB_DET ( .clk(clk), .nRst(nRst), // input RUN FIFO .fifo_empty(run_fifo_empty), .fifo_data(run_fifo_data_o), .fifo_rd_en(run_fifo_rd_en), // output boxes interface .we(box_we), .data_o(box_data_det) ); wire [7:0] box_fifo_rd_used, box_fifo_wr_used; wire box_fifo_rd_avail = |box_fifo_rd_used[7:0]; wire box_fifo_almost_full = &box_fifo_wr_used[6:2]; wire box_fifo_wr_en = (box_we && !box_fifo_almost_full) ? 1'b1 : 1'b0; // read object FIFO right after valid first screen (this might be done anywhere within a frame) wire obj_read_ena = ((counter_y == 11'd0) && (counter_x < 10'd32)) ? 1'b1 : 1'b0; wire box_fifo_rd_en = box_fifo_rd_avail && obj_read_ena; dcfifo_41x128 BOX_FIFO ( .aclr(~nRst), .data(box_data_det), .rdclk(pix_clk), .rdreq(box_fifo_rd_en), .wrclk(clk), .wrreq(box_fifo_wr_en), .q(box_data), .rdempty(), .rdusedw(box_fifo_rd_used), .wrfull(), .wrusedw(box_fifo_wr_used) );
      
      





結果



複数のオブジェクトの検出器の結果はこのビデオで示されます:







結論



結果の複数オブジェクト検出器はスケーラブルです。 入力画像の大きな解像度で作業するには、オブジェクトとRUNのメモリサイズを増やすことで、検出されたオブジェクトの数を増やすことができます。



関連資料



FPGAでの組み込み画像処理のためのドナルドG.ベイリーデザイン

ランレングスエンコーディングを使用したシングルパスリアルタイムブロブ解析のFPGA実装

接続コンポーネント分析のためのリソース効率の高いハードウェアアーキテクチャ



All Articles