FPGAのビデオストリームでの動き検出







まえがき



私はビデオ処理のトピックに長い間興味を持っていましたが、第7および第9 ARMのデバッグボードでのみ非常にゆっくりと判明し、これからは面白くなくなりました。



現在、強力なマルチコアアイロンがいっぱいで、ビデオを操作するための多くのライブラリが作成されていますが、私の選択はFPGAにかかっていました。



このプロジェクトは、5年または6年前に、Aliexpressの店舗や類似の店舗がなかったときに、FPGAを搭載したデジタルカメラモジュールやデバッグボードをとんでもないお金で購入できるようになりました。 プロジェクトの最初のバージョンは、仮設ボード上の携帯電話からのHV7131GPカメラ、Siemens S65からのディスプレイ、およびTerasic DE2デバッグボードを使用して開始されました。 それから約5年で、プロジェクトは棚とディスクにほこりを集めていました。



このように見えた:







その後、このプロジェクト専用のアルテラCyclone II EP2C8F256 FPGAボードとOV7670カメラモジュールが購入されました。 ボードを購入した後、ボードがなく、売り手がリクエストに応答しなかったことが判明しました。 ネットワークを長時間掘り下げた後、このボードで作成されたプロジェクトを見つけ、そこから割り当てを借りました。









この記事では、カメラから画像をキャプチャし、色空間を変換し、ズームし、HDMIインターフェイスを介して画像を表示し、アルテラFPGAを使用してビデオストリーム内のオブジェクトの動きを検出する方法を紹介します。



FPGAプログラミングは私の主な専門分野ではなく、空き時間の趣味であることにすぐに気付きます。 したがって、私が下した結論に誤っている可能性があり、私の決定は最適とはほど遠いかもしれません。 Fmaxを追求して、コードの多くのセクションは、冗長で、奇妙で、意味がなく、最適ではないように書かれています。



ツールキット



主な開発環境として、Mentor GraphicsのHDL Designerを選択しました。 すべてのグラフィックブロックとそれらの間のバンドルが含まれています。 アルテラQuartus IIは、合成とトレースに使用されます。



プロジェクト構造



プロジェクトの構造図を次の図に示します。 以下に詳細に説明する主な機能ユニットのみを反映しています。







HDL Designerエディターでは、次のようになります。









すべてのプロジェクトブロックが図に表示されるわけではありません。 それらはより高いレベルにあります。







キャプチャモジュール



ビデオキャプチャモジュールは、YCbCr 4:2:2またはRGB:565形式のピクセルデータカメラから入力を受信し、hsync、vsyncフレームおよびラインスキャン制御信号をclkドメイン(50 MHz)に転送し、制御信号out_pixel_validを生成し、 out_vclkとそれらをデータ形式変換モジュールに渡します。 また、このモジュールは、1フレームあたりの受信データ量に関するout_stat統計を生成します。 統計はUART経由で読み取ることができます。 モジュールは、外部capt_enデータキャプチャイネーブル信号によって制御されます。 この信号は、設定の完了時にカメラ設定モジュールによって設定されます。 Verilogコード:



キャプチャー
always @(posedge clk) begin hs_sync_1 <= hsync;hs_sync_2 <= hs_sync_1; vs_sync_1 <= vsync;vs_sync_2 <= vs_sync_1; vclk_sync_1 <= pclk;vclk_sync_2 <= vclk_sync_1; pixdata_sync_1 <= pixel_data;pixdata_sync_2 <= pixdata_sync_1; end reg vclk_old; always @(posedge clk)vclk_old <= vclk_sync_2; wire vclk_posedge = (vclk_old == 1'b0) && (vclk_sync_2 == 1'b1); reg sample_new,sample_hsync,sample_vsync; reg [7:0] sample_pixel; always @(posedge clk) begin sample_new <= vclk_posedge; if (vclk_posedge) begin sample_hsync <= hs_sync_2; sample_vsync <= vs_sync_2; sample_pixel <= pixdata_sync_2; end End reg last_vsync_sample,P2_vsync_triggered,P2_vsync_end_triggered; reg P2_sample_vsync,P2_sample_new,P2_sample_hsync; reg [7:0] P2_sample_pixel; reg P2_new_frame,capt_done,capt_enable; always @(posedge clk) begin if (capt_en == 1'b1 || P2_vsync_triggered == 1'b1) capt_enable <= 1'b1; else capt_enable <= 1'b0; end always @(posedge clk) if (!nRst) begin last_vsync_sample <= 1'b0,P2_vsync_triggered <= 1'b0; P2_vsync_end_triggered <= 1'b0,P2_new_frame <= 1'b0; capt_done <= 1'b0; end else begin if (capt_enable) begin if (sample_new) begin last_vsync_sample <= (sample_vsync/* && capt_en*/); P2_sample_pixel <= sample_pixel; P2_sample_hsync <= sample_hsync; P2_sample_vsync <= sample_vsync; end // Pipeline Step P2_sample_new <= sample_new; if (!P2_vsync_end_triggered) begin if ((last_vsync_sample == 1'b1) && (sample_vsync == 1'b0)) begin P2_vsync_triggered <= 1'b1; P2_new_frame <= 1'b1; end if (P2_vsync_triggered && sample_vsync) begin P2_vsync_end_triggered <= 1'b1; P2_vsync_triggered <= 1'b0; capt_done <= ~capt_done; end end else begin P2_vsync_end_triggered <= 1'b0; P2_vsync_triggered <= 1'b0; end if (P2_new_frame) P2_new_frame <= 1'b0; end else begin last_vsync_sample <= 1'b0;P2_vsync_triggered <= 1'b0; P2_vsync_end_triggered <= 1'b0;P2_new_frame <= 1'b0;capt_done <= 1'b0; end end
      
      







フォーマット変換モジュール



YCbCr 4:2:2形式は、以降の作業にはあまり便利ではありません。 データは次の順序で続きます。Y0 Cb0 Y1 Cr1 Y2 Cb2 Y3 Cr3 ...したがって、YCbCr 4:4:4形式に変換します。 実際、変換全体は、data_strob信号の1クロックサイクルごとにY Cb Crデータを出力することになります。 Verilogでは、次のようになります。



YCbCr 4:2:2 => 4:4:4
 always @(posedge clk) if (!nRst) pix_ctr <= 2'b0; else begin if (pixel_valid) begin if (vclk) pix_ctr <= pix_ctr + 1'b1; end else pix_ctr <= 2'd0; end always @(posedge clk) case (pix_ctr) 2'd0:begin YYY <= pixel_data; CCr <= Crr; CCb <= Cbb; Ypix_clock <= 1'b1;end 2'd1:begin Cbb <= pixel_data; YY <= YYY; end 2'd2:begin YYY <= pixel_data; CCr <= Crr; CCb <= Cbb; Ypix_clock <= 1'b1;end 2'd3:begin Crr <= pixel_data; YY <= YYY; end endcase assign data_strob = Ypix_clock; assign Y = YY; assign Cb = CCb; assign Cr = CCr;
      
      







色空間変換モジュール



最終的には、常にRGB形式のデータを処理するため、YCbCrから取得する必要があります。 これは、データシートからカメラへの式に従って行われます。



R = Y + 1.402(Cr-128)

G = Y-0.714(Cr-128)-0.344(Cb-128)

B = Y + 1.772(Cb-128)



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



YCbCr => RGB
 parameter PRECISION = 11; parameter OUTPUT = 8; parameter INPUT = 8; parameter OUT_SIZE = PRECISION + OUTPUT; parameter BUS_MSB = OUT_SIZE + 2; always @ (posedge clk) if (!nRst) begin R_int <= 22'd0; G_int <= 22'd0; B_int <= 22'd0; end else begin if (istrb) begin //R = Y + 1.371(Cr - 128) R_int <= (Y_reg << PRECISION)+(C1*(Cr_reg-8'd128)); //G = Y - 0.698(Cr-128)-0.336(Cb-128) G_int <= (Y_reg << PRECISION)-(C2*(Cr_reg-8'd128))-(C3*(Cb_reg-8'd128)); //B = Y + 1.732(Cb-128) B_int <= (Y_reg << PRECISION)+(C4*(Cb_reg-8'd128)); end end assign R = (R_int[BUS_MSB]) ? 8'd16 : (R_int[OUT_SIZE+1:OUT_SIZE] == 2'b00) ? R_int[OUT_SIZE-1:PRECISION] : 8'd240; assign G = (G_int[BUS_MSB]) ? 8'd16 : (G_int[OUT_SIZE+1:OUT_SIZE] == 2'b00) ? G_int[OUT_SIZE-1:PRECISION] : 8'd240; assign B = (B_int[BUS_MSB]) ? 8'd16 : (B_int[OUT_SIZE+1:OUT_SIZE] == 2'b00) ? B_int[OUT_SIZE-1:PRECISION] : 8'd240;
      
      







RGB:24からRGB:565変換モジュール



このモジュールは、24ビットRGB形式から16ビットを作成します。 私たちにとって便利です メモリスペースを節約し、ビットレートを削減し、目的に合ったカラーレンダリングを備えています。最も重要なことは、SDRAMデータの1ワードに収まるため、作業が大幅に簡素化されます。 データストローブ信号は、前のモジュールから単純に送信されます。



モジュールコードは非常に簡単です。



 assign oRGB = {iR[7:3], iG[7:2], iB[7:3]}; assign ostrb = istrb;
      
      





リスケーラー



このモジュールは最初からプロジェクトに導入されました。 その目標は、640x480ピクセルの入力ストリームを320x240、160x120、128x120、80x60および320x480のストリームに変換することです。 これらのフォーマットは、Arduinoボード用のTFTディスプレイであるSiemens S65のLCDディスプレイで動作し、CORDICアルゴリズムを使用してFPGAおよびSDRAMブロックメモリに画像回転を実装するために必要でした。 つまり、これは他のプロジェクトの遺産です。 このプロジェクトでは、画面の解像度をその場で変更することができ、このモジュールはここで最初のバイオリンを演奏します。 このモジュールは、デバッグ用にフレームごとのデータ量の統計も生成します。 モジュールは長い間作成されており、そのコードはサニタイズする必要がありますが、動作している間は触れません。



モジュールコードは非常に容量が大きいため、この記事ではその主要部分のみを説明します。



リスケーラー
 always @(posedge clk) if (!nRst) begin w_ctr <= 16'd0;h_ctr <= 16'd0;frame_start <= 1'b0; rsmp_w <= 8'd0;rsmp_h <= 8'd0; end else begin if (resampler_init) begin w_ctr <= 16'd0;h_ctr <= 16'd0;frame_start <= 1'b0; rsmp_w <= 8'd0;rsmp_h <= 8'd0; end else begin /* This case works ONLY if the input strobe is valid */ if (istrb) begin if (w_ctr == I_WIDTH-1'b1) begin w_ctr <= 16'd0; if (h_ctr == I_HEIGHT-1'b1) begin h_ctr <= 16'd0; frame_start <= 1'b1; end else begin h_ctr <= h_ctr + 1'b1;frame_start <= 1'b0; end if (rsmp_h == H_FACT-1'b1) begin rsmp_h <= 8'd0; end else begin rsmp_h <= rsmp_h + 1'b1; end end else begin w_ctr <= w_ctr + 1'b1; frame_start <= 1'b0; end if (rsmp_w == W_FACT-1'b1) begin rsmp_w <= 8'd0; end else begin rsmp_w <= rsmp_w + 1'b1; end end end end reg pix_valid; always @(rsmp_w or rsmp_h or wh_multiply or H_FACT) begin if (wh_multiply == 1'b1) begin pix_valid = ((rsmp_w == 8'd0) && (rsmp_h == 8'd0))?1'b1:1'b0; end else begin pix_valid = ((rsmp_w == 8'd0) && (rsmp_h != 8'd0 ))?1'b1:1'b0; end end assign pixel_valid = pix_valid; always @(posedge clk) if (!nRst) begin frame_enable <= 1'b0; end else begin if (resampler_init) begin frame_enable <= 1'b0; end else begin if (frame_start) begin if (!lcd_busy) frame_enable <= 1'b1; else frame_enable <= 1'b0; end end end reg local_frame_start = 1'b0; always @(posedge clk) if (!nRst) begin ostrb_port <= 1'b0; dout_port <= 17'd0; local_frame_start <= 1'b0; end else begin local_frame_start <= frame_start ? 1'b1: local_frame_start; if (istrb && !resampler_init && !lcd_busy) begin if (pixel_valid) begin // if our column and our row if (frame_enable && !dout_dis) begin dout_port[16:0] <= {local_frame_start, din[15:0]}; ostrb_port <= 1'b1; local_frame_start <= 1'b0; end else begin ostrb_port <= 1'b0; end end else ostrb_port <= 1'b0; end else ostrb_port <= 1'b0; end
      
      







FIFO IN



これは、2クラッドFIFO dcfifo、メガファンクションアルテラ256x17です。 16番目のビット-frame_start信号は、リスケーラの後の新しいフレームの開始を示すために追加されます。



書き込みclocは50 MHz、読み取りclocは100 MHz、コントローラのSDRAM clocでもあります。



読み取り/書き込みコントローラー



このかさばるモジュールは、FIFO INモジュールからデータを取得して、偶数フレームと奇数フレームの異なるメモリ領域で交互にSDRAMに書き込むライターと、SDRAMからデータを読み取り、それぞれ独自のメモリ領域から週末に書き込む2人のリーダーです。 FIFO リーダーは25 MHz(640x480)の周波数のHDMIコントローラーで動作し、遅延に耐えられないため、リーダーが優先されます。処理および表示用のデータは常にFIFOにある必要があります。 出力FIFO時間を埋めるのに残っている時間は、画面の非アクティブ領域の時間にFIFOを空にする時間を足したもので、ライターは動作します。



このモジュールを開発するとき、私は問題に遭遇しました。FIFOフルおよび空の信号を使用すると、FIFOがクラッシュし、データが壊れ始めます。 これはFIFO INでは起こりません。 その中の書き込みブロックの頻度は、そこからの読み取りの頻度よりもかなり低いです。 このバグは、FIFOの週末に現れます。 100 MHzの書き込みブロックは、25 MHzの読み取りブロックよりも4倍高いため、私の推測によれば、書き込みポインターは読み取りポインターに追いつき、追い越します。 Alter FIFOの特定のバグに関するネットワーク上の参照を見つけましたが、それが問題に関連しているかどうかはわかりません。 wr_fullおよびrd_emptyシグナルを使用するのではなく、wrusedwおよびrdusedwシグナルを使用して、問題を解決することができました。 fifo_almost_fullおよびfifo_almost_emptyチェーンに沿ってFIFO状態コントローラーを作成しました。 次のようになります。



 // FIFO 1 wire out_fifo_almost_full = &fifo_wr_used[9:4]; wire out_fifo_almost_empty = !(|fifo_wr_used[10:8]); // FIFO 2 wire out_fifo_almost_full_2 = &fifo_wr_used_2[9:4]; wire out_fifo_almost_empty_2 = !(|fifo_wr_used_2[10:8]);
      
      





また、モジュールは動作モードの変更を実装します:バックグラウンド減算またはフレーム差分。 これは、ボード上のクロックボタンに接続されている学習信号によって実現されます。



モジュールのコード全体を紹介するわけではありません。かなり多く、ノウハウもありません。 このモジュールは、SDRAM 100 MHzの周波数で動作します。



SDRAMコントローラー



サイトfpga4fun.comからのモジュールは基礎として採用され、チップ初期化の追加と一時クロックの遵守のための追加の遅延により、SDRAM K4S561632チップのタイプにわずかにやり直されました。



行アクティブから行アクティブ遅延:tRRD 15 n秒

行のプリチャージ時間:tRP 20 n秒



モジュールコードは、上記のリンクのサイトからダウンロードできます。 主な問題は、SDRAMの正しい動作とPLLを使用したSDRAM_CLKピンへのロックの位相シフトの選択のために、TimeQuestに定数を書き込むことでした。 そうでなければ、すべてがすぐに機能しました。 書き込みと読み取りはバーストによって行われ、アクティブバンクは1つだけが4メガソードに使用され、リフレッシュは使用されません。



FIFO出力



FIFO INと同様に、これらのFIFOは2ブロックの1024x16 dcfifoメガファンクションです。



書き込みクロックは100 MHz、読み取りクロックは25 MHzです。











動き検出器



それで、このプロジェクトの地球の塩であるモジュールにたどり着きました。 ご覧のとおり、両方の出力FIFO、25 MHz HDMIコントローラークロックpixel_clock、カウンターカウンターピクセルcounter_x、counter_y、およびアクティブなディスプレイ信号ブランクからデータと制御信号を受信します。 RGB信号が出力され、表示の準備が整います。













また、FIFO占有チェーンも実装します。



 // FIFO 1 wire in_fifo_data_avail = |fifo_rd_used[10:4]; wire in_fifo_almost_empty = !(|fifo_rd_used[10:4]); // FIFO 2 wire in_fifo_data_avail_2 = |fifo_rd_used_2[10:4]; wire in_fifo_almost_empty_2 = !(|fifo_rd_used_2[10:4]); wire fifos_available = in_fifo_data_avail & in_fifo_data_avail_2; wire fifos_almost_empty = in_fifo_almost_empty | in_fifo_almost_empty_2;
      
      





カメラからの画像を表示する画面の領域を制御する必要があります。



 wire in_frame = ((counter_x < RES_X) && (counter_y < RES_Y))?1'b1:1'b0; wire frame_start = ((counter_x == 0) && (counter_y == 0))?1'b1:1'b0;
      
      





両方のFIFOは、両方のデータ可用性フラグによって同時に読み取られます。



 // Reader FIFO 1 & 2 always @(posedge pix_clk or negedge nRst) if (!nRst) begin fifo_rd_req <= 1'b0; fifo_rd_req_2 <= 1'b0; pixel_data <= 16'h0000; worker_state <= 2'h1; end else begin case (worker_state) 2'h0: begin if (in_frame) begin if (fifos_almost_empty) begin //worker_state <= 2'h1; fifo_rd_req <= 1'b0; fifo_rd_req_2 <= 1'b0; end else begin pixel_data <= fifo_data; pixel_data_2 <= fifo_data_2; fifo_rd_req <= 1'b1; fifo_rd_req_2 <= 1'b1; end end else begin fifo_rd_req <= 1'b0; fifo_rd_req_2 <= 1'b0; end end 2'h1: begin if (blank) begin worker_state <= 2'h2; end end 2'h2: begin // start reading if more than 16 words are already in the fifo if (fifos_available && frame_start) begin fifo_rd_req <= 1'b1; fifo_rd_req_2 <= 1'b1; worker_state <= 2'h0; nd end endcase end
      
      





FIFOから読み取られたデータはRGB:565形式です。この目的のためには、白黒に変換する必要があります。 これは次のように行われます。



 // Convert to grayscale frame 1 wire [7:0] R1 = {pixel_data[15 : 11], pixel_data[15 : 13]}; wire [7:0] G1 = {pixel_data[10 : 5], pixel_data[10 : 9]}; wire [7:0] B1 = {pixel_data[4 : 0], pixel_data[4 : 2]}; wire [7:0] GS1 = (R1 >> 2)+(R1 >> 5)+(G1 >> 1)+(G1 >> 4)+(B1 >> 4)+(B1 >> 5); // Convert to grayscale frame 2 wire [7:0] R2 = {pixel_data_2[15 : 11], pixel_data_2[15 : 13]}; wire [7:0] G2 = {pixel_data_2[10 : 5], pixel_data_2[10 : 9]}; wire [7:0] B2 = {pixel_data_2[4 : 0], pixel_data_2[4 : 2]}; wire [7:0] GS2 = (R2 >> 2)+(R2 >> 5)+(G2 >> 1)+(G2 >> 4)+(B2 >> 4)+(B2 >> 5);
      
      





GS1およびGS2信号は、白黒のパフォーマンスです。



アルゴリズムについて少し説明します。 動きを検出する方法はたくさんあります。 この記事では、このプロジェクトのフレームワーク内で最も簡単で最も簡単に実装できる、そのうちの2つだけを検討します。



最初の方法。 バックグラウンド減算。



その考え方は、減算を使用してビデオストリーム内の動きまたはオブジェクトを見つけることです。



P [F(t)] = P [I(t)]-P [B]



P [F(t)]は結果の差です。

P [I(t)]-カメラの現在のフレーム、

P [B]-参照フレームまたは背景



基準フレームまたは背景は通常、動きがないときに撮影されます。 たとえば、部屋の1つのコーナーで動きを検出する場合、その前に動きがないときにこのコーナーの写真を撮って記憶し、ピクセルごとに後続の各画像からこの背景を減算する必要があります。 すべてが非常に簡単です。 ただし、画像のノイズ、カメラの自動ホワイトバランス、およびその他の要因により、検出器のしきい値を適用する必要があります。 このしきい値は、フレームの差に適用されます。 差がしきい値より大きい場合は動きがあり、そうでない場合はありません。



P [F(t)]>しきい値



この方法の短所は長所以上ですが、実装が容易なため、モーション検出に使用されます。 欠点は次のとおりです。





外部要因の変化は、動きの検出と検出器の誤検出につながります。



比Fig的に、検出回路は次のようになります。







2番目の方法。 フレーム差



この実装方法は、以前の実装方法と大差ありません。 すべての違いは、背景ではなく、前のフレームが現在のフレームから差し引かれ、その差がしきい値と比較されることです。



数学的な表現は次のようになります。



P [F(t)] = P [I(t)]-P [I(t-1)]>しきい値



この方法の利点は、外部要因に対する相対的な耐性です。 カメラの位置や照明の変化があっても、これは長期的な誤応答を引き起こさず、2つの連続したフレーム内の短い誤応答のみを引き起こします。



欠点は次のとおりです。





上記の欠点のため、この方法は純粋な形で広く使用されていません。



Verilogの実装。



私たちの場合、どのフレームから減算するかに関係なく、それらの間の絶対的な差は重要です。



 reg [7:0] difference = 0; wire [7:0] max_val = (GS1 > GS2) ? GS1 : GS2; wire [7:0] min_val = (GS1 < GS2) ? GS1 : GS2; always @(posedge pix_clk) begin if (in_frame) begin difference <= max_val - min_val; end else difference <= 8'h00; end wire [15:0] out_val = in_frame ? (difference > `BS_THRESHOLD) ? 16'hF1_00 : pixel_data_2 : in_frame2 ? pixel_data_diff : 16'h00_00;
      
      





コードからわかるように、差がBS_THRESHOLDしきい値より大きい場合、ピクセルを赤色(16'hF1_00)に置き換えます。



画面に表示するには、データをRGB:565形式からRGB:24形式に変換する必要があります



 // VGA 24 bit assign R = {out_val[15 : 11], out_val[15 : 13]}; assign G = {out_val[10 : 5], out_val[10 : 9]}; assign B = {out_val[4 : 0], out_val[4 : 2]};
      
      





HDMIコントローラー









一部、このモジュールは同じサイトfpga4fun.com から取得され、 marsohod.orgの記事に従ってやり直されました 。 差分を使用する代わりに。 LVDSペアメガファンクションDDIOを使用しました。 これが行われる理由は、上記のリンクの記事を読むことで見つけることができます。



ズタズタ









50 MHzブロックは、システム上のジェネレーターとしてボード上のジェネレーターから取得されました。 SDRAMコントローラおよびSDRAMチップのクロックは、それで構成されています。 これらのシュレッドの周波数は同じ100 MHzですが、位相が90度ずれています。 これには、メガファンクションPLLが使用されます。



Clok 125 MHz(clk_TMDS2)はDDIOに使用され、その後250 MHzに変わります。 そのようなトリック。



pixel_clockビデオデータブロックは25 MHzで、50 MHzを2つのシステムブロックに分割して作成されます。



OV7670カメラのセットアップ



カメラを設定するには、 サードパーティのSCCBインターフェイスモジュールが使用されます。 プロジェクトのニーズに合わせてわずかにやり直し、UARTインターフェイスからコマンドをオンザフライでカメラレジスタの値を記録することができます。



















UART



モジュールは、UARTレシーバーおよびトランスミッターとio_controllerモジュールで構成されています



受信機と送信機のモジュールのコードはインターネットから取得されました。 モジュールは、8N1設定で115200ボーで動作します。















このモジュール(io_controller)は、UARTトランシーバーとプロジェクトの外部モジュール間のリンクです。 UARTで統計出力を提供し、コマンドを受信および処理します。 これにより、表示解像度の変更、カメラからのデータの出力形式(YCbCrまたはRGB)の変更、レジスターの記録、要求された統計の表示を行うことができます。





結果を示すビデオ



ビデオ品質
私はビデオの品質をおpoびします、私はそのような電話を持っています。



ビデオ1.フレームの違い





320x240形式のカメラからの画像は画面の左側に表示され、しきい値のフレームの違いは右側にあります。 左の画像は、動きを検出した場所で赤く染まっています。



ビデオは、オブジェクトが停止しているときは動きが検出されず、オブジェクトの速度が低下すると検出が著しく悪化することを示しています。



ビデオ2.バックグラウンド減算





オブジェクトがカメラに近づくと、ホワイトバランスが変化し、検出器から誤った応答を受け取ることに気付くかもしれません。 このような現象はフィルタリングまたは補正できます。 補正方法の1つは、参照画像(近似メディアンフィルター)を平均化するトレーニングです。



結論



この開発は、検出アルゴリズムを複雑にすることで改善できます。 また、オブジェクトの周囲に長方形のフレームを描画することにより、移動するオブジェクトの追跡を実装すると便利です。



ビデオには水平の長方形が表示されます。 この現象は、SDRAMコントローラーの読み取りバグによるもので、まだ完全には解決できていません。



関連資料



OpenCVのモーション検出器に関する記事

OpenCVのもう1つの検出器

バックグラウンド減算

検出強化方法



UPD



約束どおり、プロジェクトを公開します。 Yandexディスクで利用可能。 これはQuartusで作成されたプロジェクトのコピーです。HDLDesignerには投稿しませんが、投稿しても誰でも開始されることはほとんどありません。

プロジェクトリンク



All Articles