一、幀差法運動目標跟蹤概述
1.1 基本原理
幀差法顧名思義就是對輸入的前后兩幀圖像做差值,然后檢測出兩幀圖像不同的地方,并且可以實時跟蹤運動的目標輪廓。
本設(shè)計是基于ZYNQ7010和VIVADO2018.3實現(xiàn)的幀差法運動目標檢測,針對運動目標檢測算法在傳統(tǒng) PC端上實時性較差的問題,設(shè)計了一種基于 ZYNQ 硬件加速的運動目標實時檢測系統(tǒng)。將攝像頭采集的彩色視頻流轉(zhuǎn)換為灰度視頻流并進行圖像處理來實現(xiàn)運動目標檢測,并將檢測后的結(jié)果與原彩色視頻流疊加來顯示實時檢測結(jié)果。
1.2 效果展示
本設(shè)計使用到的硬件有ZYNQ7010、768P顯示屏、OV5640攝像頭這些硬件,在原有的攝像頭顯示例子上搭建完成,關(guān)于顯示環(huán)境的搭建可以參照前面的文章。
幀差法項目通過pl端兩個按鍵分別控制幀差的閾值和輸出圖像的類型。按鍵1控制閾值為40-100遞增,按鍵2控制輸出圖像類型為原始RGB圖像、灰度圖像、幀差結(jié)果圖像以及RGB疊加檢測框圖像。
原始圖像
灰度圖像
幀差結(jié)果圖像
原始圖像迭代幀差結(jié)果圖像
二、幀差法的實現(xiàn)
2.1 幀差法項目架構(gòu)
這邊的項目架構(gòu)主要是參照了《基于ZYNQ加速的幀差法運動目標檢測》這樣一篇論文,大家可以去下載看看。然后視頻教程的話,我是看了B站up主大磊FPGA的視頻,大家也可以去看看。
下圖是我的幀差法實現(xiàn)運動目標檢測跟蹤的整體框圖了。大致的原理過程是這樣子的,首先OV5640采集圖像,將采集到的圖像分別送入到vdma0和vdma1中,然后兩個vdma緩存不同的幀數(shù),達到幀差的效果;之后將兩幀圖像送入到自己編寫的幀差模塊進行差值計算,之后將結(jié)果輸出到vdma2;最后經(jīng)過DVI模塊將信號送至hdmi顯示屏上顯示。
下面這張圖呢是我參照b站視頻里面的,幀差代碼也主要完成下面這個框圖。將其中一個vdma,比如vdma0中的數(shù)據(jù)送到fifo進行緩存。然后當vdma1中的數(shù)據(jù)的user來到時,從fifo中讀數(shù)據(jù),這樣可以對齊信號。因為從fifo中讀數(shù)據(jù)需要耗費兩個時鐘周期,所以vdma1的數(shù)據(jù)延遲2clk,之后就是轉(zhuǎn)灰度,做差值,都比較簡單。
然后這邊需要說一下這個握手的協(xié)議。它是有五組信號組成,data是數(shù)據(jù)信號,user是一幀圖像的開始信號,last是一幀圖像每一行結(jié)束的信號,當valid和ready拉高時數(shù)據(jù)有效。
2.2 幀差法模塊代碼
首先是信號的定義,有三組信號,分別是vdma0和vdma1的輸入,以及輸出到vdma2這樣的三組信號。
module frame_diff(
input clk,
input rst_n,
//control key
input key0,
input key1,
//data stream from vdma0
input [23:0] s0_axis_tdata,
input s0_axis_tvalid,
output s0_axis_tready,
input s0_axis_tuser,
input s0_axis_tlast,
//data stream from VDMA1
input [23:0] s1_axis_tdata,
input s1_axis_tvalid,
output s1_axis_tready,
input s1_axis_tuser,
input s1_axis_tlast,
//data stream to VDMA2
output reg [23:0] m_axis_tdata,
output reg m_axis_tvalid,
input m_axis_tready,
output reg m_axis_tuser,
output reg m_axis_tlast
);
接著就是信號緩存了,對vdma0來的數(shù)據(jù)到user來臨時就開始存;然后當vdma1的user來臨時就開始從fifo中讀數(shù)據(jù)。注意vdma1的數(shù)據(jù)延遲2clk來同比,具體可以看代碼。
//-------------------------------------------------------
// fifo write and read data from vdma0
//-------------------------------------------------------
reg [23:0] data_in;
reg data_wr;
reg data_wr_en;
wire data_alfull;
wire [23:0] data_out;
reg data_rd;
reg data_rd_en;
reg [23:0] s0_axis_tdata_dy1;
reg [23:0] s0_axis_tdata_dy2;
//write data to fifo
assign s1_axis_tready=~data_alfull; //always ready until fifo is full
always @(posedge clk or negedge rst_n) begin
if(~rst_n)begin
data_wr<=1'b0;
data_wr_en<=1'b0;
data_in<=24'd0;
end
else begin
if(s1_axis_tvalid & s1_axis_tready & s1_axis_tuser)begin //when s0_user is come,wr_en is high,make corrsponding
data_wr_en<=1'b1;
end
if(s1_axis_tvalid & s1_axis_tready)begin //begin to write data to fifo
data_wr<=1'b1;
data_in<=s1_axis_tdata;
end
else begin
data_wr<=1'b0;
data_in<=data_in;
end
end
end
//read data from fifo
always @(posedge clk or negedge rst_n) begin
if(~rst_n)begin
data_rd<=1'b0;
data_rd_en<=1'b0;
end
else begin
if(s0_axis_tvalid & s0_axis_tready & s0_axis_tuser)begin//unfull //when fifo is alfull and data from camera is come,rd_en is high
data_rd_en<=1'b1;
end
if(s0_axis_tvalid & s0_axis_tready)begin
data_rd<=1'b1;
end
else begin
data_rd<=1'b0;
end
end
end
//fifo define 24*1024
fifo_generator_0 u_fifo_generator_0 (
.clk (clk), // input wire clk
.srst (~rst_n), // input wire srst
.din (data_in), // input wire [23 : 0] din
.wr_en (data_wr_en & data_wr), // input wire wr_en
.rd_en (data_rd_en & data_rd), // input wire rd_en
.dout (data_out), // output wire [23 : 0] dout
.full (), // output wire full
.almost_full(data_alfull), // output wire almost_full
.empty (), // output wire empty
.data_count () // output wire [10 : 0] data_count
);
//read from fifo cost 2 clk, so data from camera need delay 2 clk too
always @(posedge clk or negedge rst_n) begin
if(~rst_n)begin
s0_axis_tdata_dy1<=24'd0;
s0_axis_tdata_dy2<=24'd0;
end
else begin
s0_axis_tdata_dy1<=s0_axis_tdata;
s0_axis_tdata_dy2<=s0_axis_tdata_dy1;
end
end
然后我這邊又定義了兩個按鍵檢測和濾波信號,其實就是為了控制輸出的閾值大小和圖像類型,這部分內(nèi)容很簡單。
//-------------------------------------------------------
// key filter and throshold control and state chose
//-------------------------------------------------------
reg [7:0] Frame_Diff_Throshold;
reg [1:0] state;
wire key0_flag;
wire key1_flag;
key_filter u_key_filter0(
.clk ( clk ),
.rst_n ( rst_n ),
.key_in ( key0 ),
.key_flag ( key0_flag )
);
key_filter u_key_filter1(
.clk ( clk ),
.rst_n ( rst_n ),
.key_in ( key1 ),
.key_flag ( key1_flag )
);
always @(posedge clk or negedge rst_n) begin
if(~rst_n)begin
Frame_Diff_Throshold<=8'd70;
end
else if(key0_flag==1'b1 & Frame_Diff_Throshold<8'd100)begin
Frame_Diff_Throshold<=Frame_Diff_Throshold+4'd10;
end
else if (key0_flag==1'b1 & Frame_Diff_Throshold>=8'd100)begin
Frame_Diff_Throshold<=8'd40;
end
else begin
Frame_Diff_Throshold<=Frame_Diff_Throshold;
end
end
always @(posedge clk or negedge rst_n) begin
if(~rst_n)begin
state<=2'd0;
end
else if(key1_flag==1'b1)begin
state<=state+1'b1;
end
else begin
state<=state;
end
end
對讀出的兩組信號將RGB圖像轉(zhuǎn)換成灰度圖像,之后在把兩張灰度圖像進行幀差,最后注意fifo讀用了兩個clk、轉(zhuǎn)灰度用了3clk、幀差用了1clk,所以握手信號也要延遲6clk,經(jīng)過這些操作后,flag信號就是代表了幀差的結(jié)果了,接下來只需要提取出這些flag在圖像上的范圍和邊界即可,具體代碼如下所示。
更多的關(guān)于怎么找到邊間畫出邊界,大磊FPGA里面有很詳細的步驟,大家可以看視頻,或者看我工程,這里就不敘述了。
//-------------------------------------------------------
// current img data and past img data:RGB888 to RGB565
//-------------------------------------------------------
wire [23:0] s0_data_gray; //data from camera
wire [23:0] s1_data_gray; //data from vdma0
wire [7:0] s0_data_gray1;
wire [7:0] s1_data_gray1;
assign s0_data_gray1=s0_data_gray[23:16];
assign s1_data_gray1=s1_data_gray[23:16];
rgb2gray u_rgb2gray1(
.pclk ( clk ),
.rst_n ( rst_n ),
.rgb_data ( s0_axis_tdata_dy2 ),
.gray_data ( s0_data_gray )
);
rgb2gray u_rgb2gray2(
.pclk ( clk ),
.rst_n ( rst_n ),
.rgb_data ( data_out ),
.gray_data ( s1_data_gray )
);
//-------------------------------------------------------
// frame diff operation
//-------------------------------------------------------
reg frame_diff_flag;
reg [10:0] x_cnt;
reg [10:0] y_cnt;
always @(posedge clk or negedge rst_n) begin
if(~rst_n)begin
frame_diff_flag<=1'b0;
end
else begin
if(s0_data_gray1>s1_data_gray1)begin
if( ((s0_data_gray1-s1_data_gray1) > Frame_Diff_Throshold) && (x_cnt<H_DISP-5'd5) )begin
frame_diff_flag<=1'b1;
end
else frame_diff_flag<=1'b0;
end
else begin
if( ((s1_data_gray1-s0_data_gray1) > Frame_Diff_Throshold) && (x_cnt<H_DISP-5'd5) )begin
frame_diff_flag<=1'b1;
end
else frame_diff_flag<=1'b0;
end
end
end
//-------------------------------------------------------
// for signal of s0,delay 6 clk
//-------------------------------------------------------
//fifo:2clk; rgb2gray:3clk; frame diff:1clk
//so 6 clk delay needed
wire s0_axis_tvalid_dy;
wire s0_axis_tuser_dy;
wire s0_axis_tlast_dy;
reg [5:0] s0_axis_tvalid_reg;
reg [5:0] s0_axis_tuser_reg;
reg [5:0] s0_axis_tlast_reg;
assign s0_axis_tvalid_dy=s0_axis_tvalid_reg[5];
assign s0_axis_tuser_dy=s0_axis_tuser_reg[5];
assign s0_axis_tlast_dy=s0_axis_tlast_reg[5];
always @(posedge clk or negedge rst_n) begin
if(~rst_n)begin
s0_axis_tvalid_reg<=6'd0;
s0_axis_tuser_reg<=6'd0;
s0_axis_tlast_reg<=6'd0;
end
else begin
s0_axis_tvalid_reg<={s0_axis_tvalid_reg[4:0],s0_axis_tvalid & s0_axis_tready};
s0_axis_tuser_reg<={s0_axis_tuser_reg[4:0],s0_axis_tvalid & s0_axis_tready & s0_axis_tuser};
s0_axis_tlast_reg<={s0_axis_tlast_reg[4:0],s0_axis_tvalid & s0_axis_tready & s0_axis_tlast};
end
end
2.3 幀差法軟硬件設(shè)計
vivado工程中的block design就如下圖所示了,可以看到例化了3個vdma,然后自己編寫了frame diff模塊,原理是和上面的架構(gòu)是相同的。
綜合布局布線完成后,我們把它導入到sdk設(shè)計中,sdk中c代碼如下所示,就很基礎(chǔ),只是初始化3個vdma開啟讀和寫通道就可以了。文章來源:http://www.zghlxwxcb.cn/news/detail-472065.html
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "xil_types.h"
#include "xil_cache.h"
#include "xparameters.h"
#include "xaxivdma.h"
#include "xaxivdma_i.h"
#include "vdma_api/vdma_api.h"
//宏定義
#define VDMA_ID0 XPAR_AXIVDMA_0_DEVICE_ID
#define VDMA_ID1 XPAR_AXIVDMA_1_DEVICE_ID
#define VDMA_ID2 XPAR_AXIVDMA_2_DEVICE_ID
//frame buffer的起始地址
unsigned int const frame_buffer_addr0 =0x03000000;
unsigned int const frame_buffer_addr1 =0x06000000;
unsigned int const frame_buffer_addr2 =0x09000000;
//驅(qū)動實例
XAxiVdma vdma;
int main(void)
{
int i;
int j;
int k;
//配置VDMA
i=run_vdma_frame_buffer(&vdma, VDMA_ID0,1024,768,frame_buffer_addr0,0,0,BOTH);
j=run_vdma_frame_buffer(&vdma, VDMA_ID1,1024,768,frame_buffer_addr1,0,0,BOTH);
k=run_vdma_frame_buffer(&vdma, VDMA_ID2,1024,768,frame_buffer_addr2,0,0,BOTH);
printf("%d\r\n",i);
printf("%d\r\n",j);
printf("%d\r\n",k);
while(1);
return 0;
}
三、總結(jié)
這個工程采用了前后幀差法,完成了單運動目標的檢測,是比較基礎(chǔ)的實戰(zhàn)項目。對于后期想擴展的方向,可以使用背景幀差法來更好的定位目標,或者完成多運動目標的檢測和計數(shù)。文章來源地址http://www.zghlxwxcb.cn/news/detail-472065.html
資源下載鏈接:
https://download.csdn.net/download/qq_40995480/86880974
到了這里,關(guān)于ZYNQ圖像處理項目——幀差法運動目標跟蹤的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!