ZYNQ图像处理_帧差法运动目标检测实现(开源)

实验的基本原理

所谓帧差法,就是比较相邻两帧图像的像素数据有没有差异,并对差异的大小做一个规定阈值,若超出规定阈值,即判定有运动物体经过,并且通过算法绘制矩形框对运动物体进行标定,借此来实现运动目标的检测,该实验缺点就是只能对单个目标进行框定。

实验的总体框图

帧差法运动目标检测模块框图

BD设计图

在这里插入图片描述

实验的重点部分

整个实验大致包含如下几个注意点:

1、选取单个分量进行前一帧与当前帧的差异比较,在本实验中选取的是Y分量(亮度分量)进行比较,因此需要对传输的RGB数据做RGB转YCbCr的转换,这一块在我的另一篇文章《FPGA图像处理_色彩空间转换(含源码)》中讲到过,只不过那一篇里是用vsync和hsync作为同步信号,而在这一篇里是用AXI4-Stream的tuser信号作为同步信号,稍作修改即可,至于为什么可以用tuser信号作为同步信号,可以去查看我的另一篇文章《【ZYNQ】IP核_关于视频IP核的详细介绍》有讲到。

2、帧差法运动目标检测模块是以自定义IP核的形式添加打到摄像头+LCD屏幕显示的实验中,这样简化了操作,关于如何封装自定义IP核,在我的另一篇文章《【ZYNQ】自定义IP核的封装》有讲到。

3、时序问题,做这个实验的关键就是一定要注意延时的问题,既然是做图像的差异对比,那么对比的对象一定要一致,比如通过FIFO对前一帧数据进行缓冲,当当前帧的第一个像素到来时,读取前一帧图像的第一个像素数据,这样就保证了二者的一致性。

4、检测边框该如何绘制,这个只能通过代码去琢磨其中奥妙,代码我就放这:

`timescale 1ns / 1ps

module ip_movings(
//    input   clk                         ,
//    input   rst_n                       ,
    
    //AXI4_Stream Slave接口0,来自摄像头,获取当前帧数据
    input               s0_axis_aclk    ,
    input               s0_axis_aresetn ,
    input [23:0]        s0_axis_tdata   ,
    input               s0_axis_tvalid  ,
    output              s0_axis_tready  ,
    input               s0_axis_tuser   ,   //tuser == start of frame(SOF) 
    input               s0_axis_tlast   ,   //tlast == end of line(EOL)
    
     //AXI4_Stream Slave接口1,来自VDMA_0,获取前一帧数据
    input               s1_axis_aclk    ,
    input               s1_axis_aresetn ,
    input [23:0]        s1_axis_tdata   ,
    input               s1_axis_tvalid  ,
    output              s1_axis_tready  ,
    input               s1_axis_tuser   ,
    input               s1_axis_tlast   ,
    
    //AXIS4_Stream Master接口,输出到VDMA_1
    input               m_axis_aclk    ,
    input               m_axis_aresetn ,
    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    

    );
    
localparam  [7:0]   Diff_Threshold    =   8'd100;   //帧差阈值
localparam  [10:0]  IMG_HDISP         =   11'd1920; //图像分辨率
localparam  [10:0]  IMG_VDISP         =   11'd1080;
    
//*************************************************************************
//将VDMA的图像保存到FIFO中

reg [23:0]  fifo_data   ;
reg         fifo_wr     ;
reg         fifo_wr_en  ;
wire        fifo_full   ;

reg         fifo_rd     ;
reg         fifo_rd_en  ;
wire [23:0] fifo_q      ;

//FIFO不满时,持续从VDMA读取data
assign  s1_axis_tready = ~fifo_full; 

//写状态机
localparam W_IDLE = 2'b01   ;
localparam W_FIFO = 2'b10   ;
reg [1:0] write_state       ;
reg [1:0] next_write_state  ;
reg [3:0]   wcnt            ;

always @ (posedge s1_axis_aclk or negedge s1_axis_aresetn) begin
    if(!s1_axis_aresetn) 
        write_state <=  W_IDLE              ;
    else
        write_state <=  next_write_state    ;
end

always @ (*) begin
    case(write_state)
        W_IDLE: begin
            if(wcnt == 4'd8)
                next_write_state    <=  W_FIFO;
            else
                next_write_state    <=  W_IDLE;
        end
        
        W_FIFO:
            next_write_state    <=  W_FIFO;
        
        default:
            next_write_state    <=  W_IDLE;
     endcase
end

always @ (posedge s1_axis_aclk or negedge s1_axis_aresetn) begin
    if(!s1_axis_aresetn)
        wcnt    <=  4'd0    ;
    else if(write_state ==  W_IDLE)
        wcnt    <=  wcnt + 1'b1;
    else
        wcnt    <=  4'd0    ;
end
   
always @ (posedge s1_axis_aclk or negedge s1_axis_aresetn)begin
    if(!s1_axis_aresetn) begin
        fifo_wr_en  <=  1'b0    ;
        fifo_wr     <=  1'b0    ;
        fifo_data   <=  24'b0   ;      
    end
    else begin
        if(write_state ==  W_FIFO) begin
            //检测到SOF(tuser)信号拉高后才打开写FIFO使能,保证FIFO中写入的第一个数据为SOF
            if(s1_axis_tvalid & s1_axis_tready & s1_axis_tuser) 
                fifo_wr_en  <=  1'b1            ;   //确保前一帧的数据第一个像素点与当前帧的第一个像素点数据对齐
            if(s1_axis_tvalid & s1_axis_tready) begin
                fifo_wr     <=  1'b1            ;
                fifo_data   <=  s1_axis_tdata   ;
            end
            else begin
                fifo_wr     <=  1'b0            ;
                fifo_data   <=  fifo_data       ;
            end
        end
        else begin
            fifo_wr_en  <=  1'b0        ;
            fifo_wr     <=  1'b0        ;
            fifo_data   <=  fifo_data   ;
        end
    end
end

fifo_generator_0 fifo_generator_inst (
  .clk(s1_axis_aclk),                  // input wire clk
  .rst(~s1_axis_aresetn),                  // input wire rst
  .din(fifo_data),                  // input wire [23 : 0] din
  .wr_en(fifo_wr_en & fifo_wr),              // input wire wr_en
  .rd_en(fifo_rd_en & fifo_rd),              // input wire rd_en
  .dout(fifo_q),                // output wire [23 : 0] dout
  .full(),                // output wire full
  .almost_full(fifo_full),  // output wire almost_full
  .empty()              // output wire empty
);

//读状态机
reg [1:0]   read_state          ;
reg [1:0]   next_read_state     ;
reg [3:0]   rcnt                ;
localparam  R_IDLE   =   2'b01  ;
localparam  R_FIFO   =   2'b10  ;

always @ (posedge s0_axis_aclk or negedge s0_axis_aresetn) begin
    if(!s0_axis_aresetn)
        read_state <=  R_IDLE              ;
    else 
        read_state <=  next_read_state    ;
end

always @(*) begin
    case(read_state)
        R_IDLE: begin
            if(rcnt == 8'd8)
                next_read_state    <=  R_FIFO;
            else
                next_read_state    <=  R_IDLE;
        end
        
        R_FIFO: 
            next_read_state    <=  R_FIFO; //一直处于读的状态
        default:  
            next_read_state    <=  R_IDLE;    
    endcase
end

always @ (posedge s0_axis_aclk or negedge s0_axis_aresetn) begin
    if(!s0_axis_aresetn)
        rcnt    <=  4'd0    ;
    else if(read_state == R_IDLE)
        rcnt    <=  rcnt+ 1'b1;
    else
        rcnt    <=  4'd0    ;
end

//************************************************************************
//摄像头输入像素的同时,从FIFO中读出VDMA像素,以进行运算
//需要注意的是FIFO的read mode有两种模式:Standard FIFO,数据会落后于读信号一个周期;还有一种是FWFT模式,预先取出一个数据,读信号有效时,相应的数据也有效
always @ (posedge s0_axis_aclk or negedge s0_axis_aresetn) begin
    if(!s0_axis_aresetn) begin
        fifo_rd_en  <=  1'b0   ;
        fifo_rd     <=  1'b0   ;  
    end
    else begin
        if(read_state == R_FIFO) begin
            //等待FIFO中缓存一定量的数据之后,检测摄像头输入SOF之后才打开读FIFO使能,使得与FIFO中读取的VDMA视频数据同步
            if(s0_axis_tvalid & s0_axis_tready & s0_axis_tuser & fifo_full)
                fifo_rd_en  <=  1'b1    ;   
            //此处FIFO读信号相对于摄像头输入视频延迟了一个时钟周期
            if(s0_axis_tvalid & s0_axis_tready) //当握手信号在上升沿完成时,当前帧的数据开始传输,而FIFO的读使能落后一个时钟,数据读出来又落后读使能一个时钟,所以
                fifo_rd     <=  1'b1    ;       //前一帧的数据落后于当前帧2个时钟周期
            else
                fifo_rd     <=  1'b0    ;
        end
        else begin
            fifo_rd_en  <=  1'b0   ;
            fifo_rd     <=  1'b0   ;
        end
   end
end

//*******************************************消耗掉两个时钟周期
//为了与FIFO钟读出的数据同步,将摄像头输入视频也延迟两个时钟周期(简单通过两个寄存器级联)
reg [23:0]  s0_axis_tdata_delay1;
reg [23:0]  s0_axis_tdata_delay2;

always @ (posedge s0_axis_aclk or negedge s0_axis_aresetn) begin
    if(!s0_axis_aresetn) begin
        s0_axis_tdata_delay1    <=  24'd0   ;
        s0_axis_tdata_delay2    <=  24'd0   ;
    end
    else begin
        s0_axis_tdata_delay1    <=  s0_axis_tdata           ;
        s0_axis_tdata_delay2    <=  s0_axis_tdata_delay1    ;
    end   
end

//*******************************************消耗掉三个时钟周期
//将RGB格式数据转化成YCbCr数据,但是只取Y分量,因此是灰度图像
wire    [7:0]   s0_img_y    ;//来自摄像头
wire    [7:0]   s1_img_y    ;//来自VDMA
rgb_ycbcr   s0_rgb_ycbcr(
     .clk(s0_axis_aclk)                                  ,
     .rst_n(s0_axis_aresetn)                              ,
     .in_img_red(s0_axis_tdata_delay2[23:16])   ,
     .in_img_green(s0_axis_tdata_delay2[15:8])  ,
     .in_img_blue(s0_axis_tdata_delay2[7:0])    ,
     .out_img_Y(s0_img_y)                       ,
     .out_img_Cb()                              ,
     .out_img_Cr()
    );
    
rgb_ycbcr   s1_rgb_ycbcr(
     .clk(s1_axis_aclk)                                  ,
     .rst_n(s1_axis_aresetn)                              ,
     .in_img_red(fifo_q[23:16])                 ,
     .in_img_green(fifo_q[15:8])                ,
     .in_img_blue(fifo_q[7:0])                  ,
     .out_img_Y(s1_img_y)                       ,
     .out_img_Cb()                              ,
     .out_img_Cr()
    );

//******************************************消耗一个时钟周期
//帧差运算
//帧差标志位,为1表示两帧数据差别超过阈值
reg frame_difference_flag   ;

always @ (posedge s0_axis_aclk or negedge s0_axis_aresetn) begin
    if(!s0_axis_aresetn) begin
        frame_difference_flag   <=  1'b0    ;
    end
    else begin
        if(s0_img_y > s1_img_y) begin
            if(s0_img_y - s1_img_y  > Diff_Threshold)   //灰度大于阈值
                frame_difference_flag   <=  1'b1    ;
            else
                frame_difference_flag   <=  1'b0    ;
        end
        else begin
            if(s1_img_y - s0_img_y  > Diff_Threshold)   //灰度大于阈值
                frame_difference_flag   <=  1'b1    ;
            else
                frame_difference_flag   <=  1'b0    ;
        end
    end
end

//*****************************************************延迟六个时钟周期
//将摄像头的输入的同步信号延迟六个时钟周期,与数据同步

wire    s0_axis_tuser_dly       ;
wire    s0_axis_tlast_dly       ;
wire    s0_axis_tvalid_dly      ;

reg [5:0]   s0_axis_tuser_reg   ;
reg [5:0]   s0_axis_tlast_reg   ;
reg [5:0]   s0_axis_tvalid_reg  ;

always @ (posedge s0_axis_aclk or negedge s0_axis_aresetn) begin
    if(!s0_axis_aresetn) begin
        s0_axis_tuser_reg   <=  6'd0    ;
        s0_axis_tlast_reg   <=  6'd0    ;
        s0_axis_tvalid_reg  <=  6'd0    ;    
    end
    else begin
        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};
        s0_axis_tvalid_reg  <=  {s0_axis_tvalid_reg[4:0],s0_axis_tvalid  &   s0_axis_tready}                  ;
    end
end

assign  s0_axis_tuser_dly   =   s0_axis_tuser_reg[5] ;
assign  s0_axis_tlast_dly   =   s0_axis_tlast_reg[5] ;
assign  s0_axis_tvalid_dly  =   s0_axis_tvalid_reg[5];

//*****************************************************************
//计算帧差后的像素坐标
reg [10:0]   x_cnt;
reg [10:0]   y_cnt;

always @ (posedge s0_axis_aclk or negedge s0_axis_aresetn) begin
    if(!s0_axis_aresetn) begin
        x_cnt   <=  11'd0   ;
        y_cnt   <=  11'd0   ;
    end
    else if(s0_axis_tvalid_dly) begin
        if(s0_axis_tlast_dly) begin
            x_cnt   <=  11'd0   ;
            y_cnt   <=  y_cnt+1'b1;
        end
        else if(s0_axis_tuser_dly) begin
            x_cnt   <=  11'd0;
            y_cnt   <=  11'd0;
        end
        else begin
            x_cnt   <=  x_cnt   +   1'b1;
        end
    end
end

//******************************************************
//求出运动目标的最大矩形边框
/**********************
                       down_reg
起始点 -》-----------------------------------
          \                                  \
          \                                  \
          \                                  \
right_reg \                                  \    left_reg
          \                                  \
          \                                  \
          ------------------------------------
                       up_reg
**********************/
reg [10:0]  up_reg  ;
reg [10:0]  down_reg;
reg [10:0]  left_reg;
reg [10:0]  right_reg;
reg         flag_reg;

always @(posedge s0_axis_aclk or negedge s0_axis_aresetn) begin
    if(!s0_axis_aresetn) begin
        up_reg  <=  IMG_VDISP;
        down_reg<=  11'd0    ;
        left_reg<=  IMG_HDISP;
        right_reg<= 11'd0    ;
    end
    else if(s0_axis_tuser_dly) begin
        up_reg  <=  IMG_VDISP;
        down_reg<=  11'd0    ;
        left_reg<=  IMG_HDISP;
        right_reg<= 11'd0    ;
    end
    else if(s0_axis_tvalid_dly & frame_difference_flag) begin
        flag_reg    <=  1'b1        ;
        
        if(x_cnt<left_reg)
            left_reg<=  x_cnt       ;
        else
            left_reg<=  left_reg    ;
            
        if(x_cnt>right_reg)
            right_reg<=  x_cnt      ;
        else
            right_reg<=  right_reg  ;  
            
        if(y_cnt<up_reg)
            up_reg  <=  y_cnt       ;
        else
            up_reg  <=  up_reg      ;
            
        if(y_cnt>down_reg) 
            down_reg <= y_cnt       ;
        else
            down_reg <= down_reg    ;
    end
end

reg [10:0]  rectangular_up      ;
reg [10:0]  rectangular_down    ;
reg [10:0]  rectangular_left    ;
reg [10:0]  rectangular_right   ;
reg         rectangular_flag    ;

always @ (posedge s0_axis_aclk or negedge s0_axis_aresetn) begin
    if(!s0_axis_aresetn) begin
        rectangular_up  <=  11'd0   ;
        rectangular_down<=  11'd0   ;
        rectangular_left<=  11'd0   ;
        rectangular_right<= 11'd0   ;
        rectangular_flag <= 1'b0    ;
    end
    else if((x_cnt == IMG_HDISP-1) && (y_cnt == IMG_VDISP-1)) begin
        rectangular_up  <=  up_reg  ;
        rectangular_down<=  down_reg;
        rectangular_left<=  left_reg;
        rectangular_right<= right_reg;
        rectangular_flag <= flag_reg;
    end
end

//**********************************************************
//绘制矩形框

//计算摄像头输入图像的像素坐标
reg [10:0]  s0_x_cnt;
reg [10:0]  s0_y_cnt;

always @ (posedge s0_axis_aclk or negedge s0_axis_aresetn) begin
    if(!s0_axis_aresetn) begin
        s0_x_cnt    <=  11'd0   ;
        s0_y_cnt    <=  11'd0   ;
    end
    else if(s0_axis_tvalid) begin
        if(s0_axis_tlast) begin
            s0_x_cnt    <=  11'd0       ;
            s0_y_cnt    <=  y_cnt+1'b1  ;
        end
        else if(s0_axis_tuser) begin
            s0_x_cnt    <=  11'd0       ;
            s0_y_cnt    <=  11'd0       ;
        end
        else begin
            s0_x_cnt    <=  s0_x_cnt +   1'b1;
        end    
    end
end


reg boarder_flag;   //标志着像素点位于方框上

always @(posedge s0_axis_aclk or negedge s0_axis_aresetn) begin
    if(!s0_axis_aresetn) begin
        boarder_flag    <=  1'b0   ;    
    end
    else begin
        if(rectangular_flag) begin   //检测到运动目标
            if((s0_x_cnt>rectangular_left) && (s0_x_cnt<rectangular_right) && ((s0_y_cnt == rectangular_up) || (s0_y_cnt == rectangular_down))) begin
                boarder_flag    <=  1'b1   ;
            end
            else if((s0_y_cnt>rectangular_up) && (s0_y_cnt<rectangular_down) && ((s0_x_cnt == rectangular_left) || (s0_x_cnt == rectangular_right))) begin
                boarder_flag    <=  1'b1   ;
            end
            else begin
                boarder_flag    <=  1'b0   ;
            end
        end
        else begin
            boarder_flag    <=  1'b0   ;
        end
     end
end

//输出给摄像头输入的Ready信号
assign s0_axis_tready = m_axis_tready;

//给AXI4_Stream Master接口赋值
always @(posedge s0_axis_aclk or negedge s0_axis_aresetn) begin
    if(!s0_axis_aresetn) begin
        m_axis_tvalid   <=  1'd0    ;
        m_axis_tuser    <=  1'd0    ;
        m_axis_tlast    <=  1'd0    ;
        m_axis_tdata    <=  24'd0   ;
     end
     else begin
        m_axis_tvalid   <=  s0_axis_tvalid    ;
        m_axis_tuser    <=  s0_axis_tuser    ;
        m_axis_tlast    <=  s0_axis_tlast    ;
        m_axis_tdata    <=  boarder_flag ? 24'hff_00_00: s0_axis_tdata   ;
     end
end
endmodule

实验的结果展示

整个实验是参照的B站视频博主大磊FPGA的视频,这里就不放链接了,感兴趣的同学去B站搜一下。 最后我把整个工程打包发给大家,仅供参考。 链接:https://pan.baidu.com/s/1ZI18ad8un_Jua4rZIf_W9g 提取码:cduy
  • 10
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值