FPGA实现移动目标检测
上一篇整理了人脸检测,这篇讲一下移动目标检测。
目前逐渐形成三种运动目标的检测算法:
1)帧间差分法是采用视频序列中的相邻两帧图像做差的方法,来检测视频序列中 的移动目标。但是受运动目标和背景变化的影响,检测过程中有可能出现伪目标或者目标中 出现“空洞”,在目标运动不是太快时可以有效的检测到目标。
2)背景减除法首先在没有目标的场景中获取背景图像,然后利用实时视频序列和背 景图像做差,来实现地移动目标的检测。如何获得背景是背景减除法的关键。
3)光流法是通过给图像中每个像素点赋予一个速度矢量的方法建立光流场,利用光 流场中矢量运动的连续性来检测移动目标。该方法的计算量通常很大,难以实现实时性的检测。
其中帧差法比较简单,可操作性较强。
一、帧差法原理
帧差法是通过两帧相邻图像间做差,并选取合适的阈值对图像进行二值化,从而选取出运动的物体。设 f(x,y)为灰度差分图像,gk(x,y)、gk-1(x,y) 为相邻的两帧灰度图像,D(x,y)为侦差图像,T为差分阈值。

1、缓存两帧灰度图像。
2、两帧灰度图像做差,将结果和设置的阈值进行比较后转二值化输出。
3、对二值化结果进行框选,确定移动目标,类似人脸检测。
本设计的难点是如何能缓存两帧图像,以 SDRAM 为例,常用的方法有两种:掩码法和非掩码法,下面分别介绍一下。
二、移动目标检测——掩码法
1、结构框图

如图所示:摄像头采集数据后,再SDRAM通道0中缓存后输出到 VGA_driver,正常的摄像头显示工程到这就结束了。而为了后续处理,我将 VGA_driver 的输出数据先不输出到VGA引脚,而是对其进行图像处理:先进行 RGB转YCbCr处理,得到 8bit 的灰度数据 Y 分量,然后将 Y 分量输入到 SDRAM的通道 1 中,利用 SDRAM 的掩码,通道 1 的读出数据包含了 2 帧的灰度数据,将这两帧数据进行帧差计算,然后进行一些图像处理。最后和人脸检测的方法类似,得到 4 个极值坐标,利用极值坐标绘制框框,将移动目标框住,并一起写入到原图中,最后一起在显示器上显示,实现移动目标的检测。
本框图只是其中一种数据流走向,也可以改为:摄像头数据直接进行图像处理,得出极值坐标后加入到摄像头数据中,一起写入到另一个SDRAM通道中,最后直接输出。
2、SDRAM 掩码过程
(1)摄像头数据写入通道 0,然后通过VGA_driver读出通道0的数据,再将该数据转换为 8bit 的灰度数据。
(2)SDRAM 的数据位宽为 16bit,灰度数据为 8bit,设置通道 1 的写使能为灰度数据的数据指示信号,通道 1 的写数据为 ch1_wr_data = {Y_data[7:0],Y_data[7:0]},即高8位和低8位都存储一样的数据。有些人这一步就开始帧划分,用 8'bz 来填充丢弃的高8位或低8位,经过我实际测试发现直接写就行,因为后续SDRAM会采用掩码,在进SDRAM之前不需要高低位处理。同时设置通道 1 的读使能等于通道1的写使能。
(3)通道 1 的SDRAM写掩码最开始设置为 2‘b10(遮掩高8位),当SDRAM写完一帧图像后则SDRAM写掩码翻转变成 01(遮掩低位),如此循环反复。通道 1 的SDRAM读过程中掩码一直为2'b00(不遮掩)。写不断切换高低掩码,读不遮掩,因此读出的 16 bit 数据里包含了两帧的 8bit 灰度数据。示意图如下:

(4)SDRAM 通道 1 的读使能可以设置和通道 1 的写使能一样,能把数据读出来就行,读出的数据进行高低位分离,二者相减实现侦差法。
3、掩码法重点
(1)首先要有双通道的 4 口 SDRAM 控制器,自己写的可以,移植别人的也行。
(2)写掩码 wr_dqm 和读掩码 rd_dqm 最终都赋值给SDRAM引脚的掩码信号 sdram_dqm,我们先自行定义写掩码 wr_dqm 和读掩码 wr_dqm。
- 写掩码 wr_dqm 初始为 2'b10(遮掩高位),当写完一帧时(找对这个点),写掩码 wr_dqm 翻转;
- 读掩码 rd_dqm 始终为 2'b00(不遮掩);
- 当SDRAM进行写操作时,写掩码 wr_dqm 赋值给端口掩码sdram_dqm;
- 当SDRAM进行读操作时,读掩码 rd_dqm 赋值给端口掩码sdram_dqm。
(3)需要掩码的是通道1,正常读写的通道0的掩码必须始终为 2'b00。
(4)必须关闭乒乓操作,否则写入的数据无法拼接。
三、移动目标检测——非掩码法
1、结构框图

非掩码法的通道1直接写入 8 bit的数据即可,非掩码的关键在于通道1的读使能:通道1的读使能等于通道1的写使能,但是比通道1的写使能落后一帧时间,所以最后通道1的读写数据刚好就差了一帧,可以进行帧差计算,其他的和人脸检测相似。上面框图的数据流只是其中一种方式,实际上也可以在摄像头之后,进入SDRAM之前完成图像处理。
2、非掩码法重点
(1)非掩码法的帧差计算实际是第一帧和第二帧,第三帧和第四帧......理论上的连贯性没有掩码法好;
(2)可以用乒乓操作,因此成像效果比掩码法好。
关于非掩码法,要感谢一下公众号《FPGA自习室》的 Beyond 前辈,他提供了一个结构框图给我,如下所示:

因为想有些改变,所以我上面的框图做的和这个有点不一样,这两种数据流都试过,效果都很好。
四、开发过程中的难点
1、帧差法的计算不能直接二者相减,而是要判断大小,取绝对值。我一开始忘了绝对值,二者直接相减,结果效果非常差,图像全是闪动。于是开始怀疑摄像头和SDRAM控制器是不是出了问题,弄了一天,最后发现是没有取绝对值,改过来后效果立马变好了,后续的加框一气呵成。
//侦差计算 //--------------------------------------------------- always @(posedge clk_10m or negedge rst_n) begin if(!rst_n) diff_data <= 16'b0; else if(Y_data_1 >= Y_data_2 && Y_de_2) diff_data <= (Y_data_1 - Y_data_2 > 80) ? 16'hffff : 16'h0000; else if(Y_data_1 < Y_data_2 && Y_de_2) diff_data <= (Y_data_2 - Y_data_1 > 80) ? 16'hffff : 16'h0000; end
2、阈值的设置要合理。移动目标检测效果和当时的光照、物体运动速度、物体和背景相似度等都有关,可根据实际情况进行阈值调整。可以引入两个外部按键,一个按键用于增加阈值,同时数码管显示当前阈值;另一个按键用于切换原图和侦差图,结果不合理时方便调试。
//按键设置阈值 0-120 //--------------------------------------------------- always @(posedge clk_10m or negedge rst_n) begin if(!rst_n) value <= 32'd0; else if(value > 32'd100) //100以上阈值的侦差法效果很差 value <= 32'd0; else if(key_vld[0]) value <= value + 32'd10; end SEG_driver u_SEG_driver ( .clk (clk ), .rst_n (rst_n ), .en (1 ), .value (value ), //侦差阈值 .SH_CP (SH_CP ), .ST_CP (ST_CP ), .DS (DS ) ); //侦差计算 //--------------------------------------------------- always @(posedge clk_10m or negedge rst_n) begin if(!rst_n) diff_data <= 8'b0; else if(Y_data_1 >= Y_data_2 && Y_de_2) diff_data <= (Y_data_1 - Y_data_2 > value) ? 8'hff : 8'h00; else if(Y_data_1 < Y_data_2 && Y_de_2) diff_data <= (Y_data_2 - Y_data_1 > value) ? 8'hff : 8'h00; end
3、特别注意信号的同步。例如通道1的读写使能相同,我的 SDRAM 控制器的内部读 FIFO 是 normal 模式,即读数据会落后读使能一拍。后续帧差计算要使用到这个读数据,不能用通道1的写数据和通道1的读数据直接进行帧差计算,必须将写数据打一拍后才是和读数据对齐的,才能进行帧差计算。而且我这里帧差计算用的时序逻辑,因此数据过来是:读使能和写使能相同,读数据落后一个时钟周期,帧差计算又消耗一个时钟周期,总的消耗了两个周期。因此同步信号要特别注意对齐。
//侦差法消耗一拍,故打拍对齐 //--------------------------------------------------- always @(posedge clk_10m) begin diff_de <= Y_de_2; diff_hsync <= Y_hsync_2; diff_vsync <= Y_vsync_2; end
五、侦差法缺点和改进方法
侦差法的核心思想是移动目标和背景做差,不同点即移动目标本身。如果移动目标和背景的颜色相似,检测精度将大大降低。
侦差法的缺点难以逾越,但是也可以通过一些技术手段提高检测精度:
1、对结果进行平滑滤波、边缘检测、形态学滤波等,提高检测精度。
2、两帧图像做差改为三帧图像做差或四帧图像做差,提高检测精度。具体实现为增加缓存器件,或者上述的两种方法结合,实现缓存三帧、四帧图像的要求。
六、效果展示
之前做图像处理时,板子上TFT屏接口的引脚击穿了,导致图像偏红色。后来去了修,修好后用了没多久,又坏了,这次变成偏蓝色了,唉。
图像失真偏蓝是我板子的问题,只要关注结果就好了。可以看到阈值为 0 时,无法检测到移动目标;按键 1 可以调节阈值,阈值为 10 时,非常灵敏,甚至灵敏过头了,随着阈值的增加,检测精度也在不断变化,阈值为 80 的效果还不错,阈值为110以上后,基本就没法检测了。另外按键 2 可以切换显示帧差图像,方便和原图对比。
7、核心代码展示

sdram_4port 的代码可以查看博客《SDRAM(6):sdram_4port》
(1)top
top
//**************************************************************************
// *** 名称 : top.v
// *** 作者 : xianyu_FPGA
// *** 博客 : https://www.cnblogs.com/xianyufpga/
// *** 日期 : 2019-10-10
// *** 描述 : 工程的顶层模块
//**************************************************************************
module top
//========================< 参数 >==========================================
#(
parameter H_DISP = 480 , //图像宽度
parameter V_DISP = 272 //图像高度
)
//========================< 端口 >==========================================
(
input sys_clk , //系统时钟,50Mhz
input sys_rst_n , //系统复位,低电平有效
//key -----------------------------------------------
input [ 1:0] key , //按键输入
//CMOS ----------------------------------------------
output cmos_xclk , //CMOS 驱动时钟
output cmos_rst_n , //CMOS 复位信号
output cmos_pwdn , //CMOS 休眠模式
input cmos_pclk , //CMOS 数据时钟
input cmos_href , //CMOS 行同步
input cmos_vsync , //CMOS 场同步
input [ 7:0] cmos_data , //CMOS 像素数据
output cmos_scl , //CMOS SCCB_SCL
inout cmos_sda , //CMOS SCCB_SDA
//SDRAM ---------------------------------------------
output sdram_clk , //SDRAM 时钟
output sdram_cke , //SDRAM 时钟有效
output sdram_cs_n , //SDRAM 片选
output sdram_ras_n , //SDRAM 行有效
output sdram_cas_n , //SDRAM 列有效
output sdram_we_n , //SDRAM 写有效
output [ 1:0] sdram_ba , //SDRAM Bank地址
output [ 1:0] sdram_dqm , //SDRAM 数据掩码
output [12:0] sdram_addr , //SDRAM 地址
inout [15:0] sdram_dq , //SDRAM 数据
//TFT -----------------------------------------------
output TFT_clk , //TFT 像素时钟
output TFT_rst , //TFT 复位信号
output TFT_blank , //TFT 背光控制
output TFT_hsync , //TFT 行同步
output TFT_vsync , //TFT 场同步
output [15:0] TFT_data , //TFT 像素输出
output TFT_de , //TFT 数据使能
//Segment -------------------------------------------
output SH_CP , //数码管 存储寄存器时钟
output ST_CP , //数码管 移位寄存器时钟
output DS //数码管 串行数据
);
//========================< 信号 >==========================================
wire rst_n ; //稳定后的复位信号
//PLL -----------------------------------------------
wire clk_24m ; //50Mhz时钟
wire clk_100m ; //100Mhz时钟
wire clk_100m_shift ; //100Mhz时钟
wire clk_10m ; //10Mhz时钟
wire locked ; //PLL稳定信号
//SDRAM ---------------------------------------------
wire ch0_wr_en ; //SDRAM通道0 写使能
wire [15:0] ch0_wr_data ; //SDRAM通道0 写数据
wire ch0_rd_en ; //SDRAM通道0 读使能
wire [15:0] ch0_rd_data ; //SDRAM通道0 读数据
//--------------------------
wire ch1_wr_en ; //SDRAM通道1 写使能
wire [15:0] ch1_wr_data ; //SDRAM通道1 写数据
wire ch1_rd_de ; //SDRAM通道1 读使能
wire [15:0] ch1_rd_data ; //SDRAM通道1 读数据
wire sdram_init_done ; //SDRAM初始化完成
//RGB_0 ---------------------------------------------
wire RGB_0_hsync ; //RGB行同步
wire RGB_0_vsync ; //RGB场同步
wire [15:0] RGB_0_data ; //RGB数据
wire RGB_0_de ; //RGB数据使能
//RGB_1 ---------------------------------------------
wire RGB_1_hsync ; //RGB行同步
wire RGB_1_vsync ; //RGB场同步
wire [15:0] RGB_1_data ; //RGB数据
wire RGB_1_de ; //RGB数据使能
//key -----------------------------------------------
wire [ 1:0] key_vld ; //消抖后的按键值
wire [ 7:0] value ; //阈值
//==========================================================================
//== PLL
//==========================================================================
pll_clk u_pll_clk
(
.inclk0 (sys_clk ), //输入系统时钟50Mhz
.areset (~sys_rst_n ), //输入复位信号
//-----------------------------------------------
.c0 (clk_24m ), //输出时钟24Mhz
.c1 (clk_100m ), //输出时钟100mhz
.c2 (clk_100m_shift ), //输出时钟100Mhz,-75°偏移
.c3 (clk_10m ), //输出时钟10Mhz
.locked (locked ) //输出时钟稳定指示
);
//复位信号 = 系统复位 + PLL稳定 + SDRAM初始化完成
assign rst_n = sys_rst_n & locked & sdram_init_done;
//==========================================================================
//== ov7670
//==========================================================================
ov7670_top
#(
.H_DISP (H_DISP ), //图像宽度
.V_DISP (V_DISP ) //图像高度
)
u_ov7670_top
(
.clk_24m (clk_24m ), //时钟
.rst_n (rst_n ), //复位
//cmos ------------------------------------------
.cmos_xclk (cmos_xclk ), //CMOS 驱动时钟
.cmos_rst_n (cmos_rst_n ), //CMOS 复位信号
.cmos_pwdn (cmos_pwdn ), //CMOS 休眠模式
.cmos_pclk (cmos_pclk ), //CMOS 数据时钟
.cmos_href (cmos_href ), //CMOS 行同步信号
.cmos_vsync (cmos_vsync ), //CMOS 场同步信号
.cmos_data (cmos_data ), //CMOS 像素数据
.cmos_scl (cmos_scl ), //CMOS SCCB_SCL
.cmos_sda (cmos_sda ), //CMOS SCCB_SDA
//RGB -------------------------------------------
.RGB_vld (ch0_wr_en ), //RGB数据使能
.RGB_data (ch0_wr_data ), //RGB数据
.FPS_rate ( ) //FPS帧率
);
//==========================================================================
//== SDRAM
//==========================================================================
sdram_4port_top u_sdram
(
.ref_clk (clk_100m ), //SDRAM 控制器参考时钟
.out_clk (clk_100m_shift ), //用于输出的相位偏移时钟
.rst_n (sys_rst_n ), //系统复位
//通道0 -----------------------------------------
.ch0_min_addr (24'd0 ), //通道0 写SDRAM的起始地址
.ch0_max_addr (H_DISP * V_DISP ), //通道0 写SDRAM的结束地址
.ch0_wr_clk (cmos_pclk ), //通道0 写端口FIFO: 写时钟
.ch0_wr_req (ch0_wr_en ), //通道0 写端口FIFO: 写使能
.ch0_wr_data (ch0_wr_data ), //通道0 写端口FIFO: 写数据
.ch0_rd_clk (clk_10m ), //通道0 读端口FIFO: 读时钟
.ch0_rd_req (ch0_rd_en ), //通道0 读端口FIFO: 读使能
.ch0_rd_data (ch0_rd_data ), //通道0 读端口FIFO: 读数据
//通道1 -----------------------------------------
.ch1_min_addr (24'd0 ), //通道1 写SDRAM的起始地址
.ch1_max_addr (H_DISP * V_DISP ), //通道1 写SDRAM的结束地址
.ch1_wr_clk (clk_10m ), //通道1 写端口FIFO: 写时钟
.ch1_wr_req (ch1_wr_en ), //通道1 写端口FIFO: 写使能
.ch1_wr_data (ch1_wr_data ), //通道1 写端口FIFO: 写数据
.ch1_rd_clk (clk_10m ), //通道1 读端口FIFO: 读时钟
.ch1_rd_req (ch1_rd_en ), //通道1 读端口FIFO: 读使能
.ch1_rd_data (ch1_rd_data ), //通道1 读端口FIFO: 读数据
//读写设置 --------------------------------------
.wr_length (10'd512 ), //写SDRAM时的数据突发长度
.wr_load (~rst_n ), //写端口复位: 复位写地址,清空写FIFO
.rd_length (10'd512 ), //写SDRAM时的数据突发长度
.rd_load (~rst_n ), //读端口复位: 复位读地址,清空读FIFO
//用户控制端口 ----------------------------------
.sdram_init_done (sdram_init_done ), //SDRAM 初始化完成标志
.sdram_pingpang_en (1'b1 ), //SDRAM 乒乓操作使能,图片0视频1
//SDRAM 芯片端口 --------------------------------
.sdram_clk (sdram_clk ), //SDRAM 芯片时钟
.sdram_cke (sdram_cke ), //SDRAM 时钟有效
.sdram_cs_n (sdram_cs_n ), //SDRAM 片选
.sdram_ras_n (sdram_ras_n ), //SDRAM 行有效
.sdram_cas_n (sdram_cas_n ), //SDRAM 列有效
.sdram_we_n (sdram_we_n ), //SDRAM 写有效
.sdram_ba (sdram_ba ), //SDRAM Bank地址
.sdram_addr (sdram_addr ), //SDRAM 行/列地址
.sdram_dq (sdram_dq ), //SDRAM 数据
.sdram_dqm (sdram_dqm ) //SDRAM 数据掩码
);
//==========================================================================
//== TFT
//==========================================================================
//读取SDRAM通道0
TFT_driver u_TFT_driver_0
(
.clk (clk_10m ), //时钟
.rst_n (rst_n ), //复位
//-----------------------------------------------
.TFT_req (ch0_rd_en ), //输出数据请求
.TFT_din (ch0_rd_data ), //得到图像数据
//-----------------------------------------------
.TFT_clk (TFT_clk ), //TFT数据时钟
.TFT_rst (TFT_rst ), //TFT复位信号
.TFT_blank (TFT_blank ), //TFT背光控制
.TFT_hsync (RGB_0_hsync ), //TFT行同步
.TFT_vsync (RGB_0_vsync ), //TFT场同步
.TFT_data (RGB_0_data ), //TFT数据输出
.TFT_de (RGB_0_de ) //TFT数据使能
);
//输出图像再传入SDRAM通道1
assign ch1_wr_en = RGB_0_de;
assign ch1_wr_data = RGB_0_data;
//读取SDRAM通道1
TFT_driver u_TFT_driver_1
(
.clk (clk_10m ), //时钟
.rst_n (rst_n ), //复位
//-----------------------------------------------
.TFT_req (ch1_rd_en ), //输出数据请求
.TFT_din (ch1_rd_data ), //得到图像数据
//-----------------------------------------------
.TFT_clk ( ), //TFT数据时钟
.TFT_rst ( ), //TFT复位信号
.TFT_blank ( ), //TFT背光控制
.TFT_hsync (RGB_1_hsync ), //TFT行同步
.TFT_vsync (RGB_1_vsync ), //TFT场同步
.TFT_data (RGB_1_data ), //TFT数据输出
.TFT_de (RGB_1_de ) //TFT数据使能
);
//==========================================================================
//== 图像处理模块
//==========================================================================
ISP_top
#(
.H_DISP (H_DISP ), //图像宽度
.V_DISP (V_DISP ) //图像高度
)
u_ISP_top
(
.clk (clk_10m ), //时钟
.rst_n (rst_n ), //复位
//RGB_0 -----------------------------------------
.RGB_0_hsync (RGB_0_hsync ), //RGB_0行同步
.RGB_0_vsync (RGB_0_vsync ), //RGB_0场同步
.RGB_0_data (RGB_0_data ), //RGB_0数据
.RGB_0_de (RGB_0_de ), //RGB_0数据使能
//value -----------------------------------------
.value (value ), //阈值
//RGB_1 -----------------------------------------
.RGB_1_hsync (RGB_1_hsync ), //RGB_1行同步
.RGB_1_vsync (RGB_1_vsync ), //RGB_1场同步
.RGB_1_data (RGB_1_data ), //RGB_1数据
.RGB_1_de (RGB_1_de ), //RGB_1数据使能
//key_vld ---------------------------------------
.key_vld (key_vld[1] ), //消抖后的按键值
//DISP ------------------------------------------
.DISP_hsync (TFT_hsync ), //最终显示的行同步
.DISP_vsync (TFT_vsync ), //最终显示的场同步
.DISP_data (TFT_data ), //最终显示的数据
.DISP_de (TFT_de ) //最终显示的数据使能
);
//==========================================================================
//== 按键消抖
//==========================================================================
key_filter
#(
.TIME_20MS (200_000 ), //20ms时间
.KEY_W (2 ) //按键个数
)
u_key_filter
(
.clk (clk_10m ), //时钟
.rst_n (rst_n ), //复位
.key (key ), //按键输入
//-----------------------------------------------
.key_vld (key_vld ) //消抖后的按键值
);
//==========================================================================
//== 按键控制阈值
//==========================================================================
key_value u_key_value
(
.clk (clk_10m ), //时钟
.rst_n (rst_n ), //复位
.key_vld (key_vld[0] ), //消抖后的按键值
.value (value ) //阈值
);
//==========================================================================
//== 数码管
//==========================================================================
SEG_driver u_SEG_driver
(
.clk (sys_clk ), //时钟
.rst_n (rst_n ), //复位
//-----------------------------------------------
.value (value ), //数码管显示的数值
//-----------------------------------------------
.SH_CP (SH_CP ), //存储寄存器时钟
.ST_CP (ST_CP ), //移位寄存器时钟
.DS (DS ) //串行数据
);
endmodule
(2)key_value
key_value
//**************************************************************************
// *** 名称 : key_value.v
// *** 作者 : xianyu_FPGA
// *** 博客 : https://www.cnblogs.com/xianyufpga/
// *** 日期 : 2019-08-10
// *** 描述 : 按键获得阈值
//**************************************************************************
module key_value
//========================< 端口 >==========================================
(
input wire clk ,
input wire rst_n ,
input wire key_vld ,
output reg [ 7:0] value
);
//==========================================================================
//== 代码
//==========================================================================
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
value <= 8'd50;
end
else if(value > 8'd100) begin //100以上的效果很差
value <= 8'd0;
end
else if(key_vld) begin
value <= value + 8'd10;
end
end
endmodule
(3)ISP_top
ISP_top
//**************************************************************************
// *** 名称 : ISP_top.v
// *** 作者 : xianyu_FPGA
// *** 博客 : https://www.cnblogs.com/xianyufpga/
// *** 日期 : 2020年3月
// *** 描述 : 图像处理模块的顶层文件
//**************************************************************************
module ISP_top
//========================< 参数 >==========================================
#(
parameter H_DISP = 12'd480 , //图像宽度
parameter V_DISP = 12'd272 //图像高度
)
//========================< 端口 >==========================================
(
input wire clk , //时钟
input wire rst_n , //复位
//RGB_0 ---------------------------------------------
input wire RGB_0_hsync , //RGB_0行同步
input wire RGB_0_vsync , //RGB_0场同步
input wire [15:0] RGB_0_data , //RGB_0数据
input wire RGB_0_de , //RGB_0数据使能
//value ---------------------------------------------
input wire [ 7:0] value , //阈值
//RGB_1 ---------------------------------------------
input wire RGB_1_hsync , //RGB_1行同步
input wire RGB_1_vsync , //RGB_1场同步
input wire [15:0] RGB_1_data , //RGB_1数据
input wire RGB_1_de , //RGB_1数据使能
//key -----------------------------------------------
input wire key_vld , //消抖后的按键值
//DISP ----------------------------------------------
output wire DISP_hsync , //最终显示的行同步
output wire DISP_vsync , //最终显示的场同步
output wire [15:0] DISP_data , //最终显示的数据
output wire DISP_de //最终显示的数据使能
);
//========================< 连线 >==========================================
//Y_0分量 -------------------------------------------
wire Y_0_hsync ; //Y分量行同步
wire Y_0_vsync ; //Y分量场同步
wire [ 7:0] Y_0_data ; //Y分量数据
wire Y_0_de ; //Y分量数据使能
//Y_1分量 -------------------------------------------
wire Y_1_hsync ; //Y分量行同步
wire Y_1_vsync ; //Y分量场同步
wire [ 7:0] Y_1_data ; //Y分量数据
wire Y_1_de ; //Y分量数据使能
//diff ----------------------------------------------
reg diff_hsync ; //diff行同步
reg diff_vsync ; //diff场同步
reg [ 7:0] diff_data ; //diff数据
reg diff_de ; //diff数据使能
//==========================================================================
//== RGB565转YCbCr444,取Y分量
//==========================================================================
RGB565_Y u_RGB565_Y_0
(
.clk (clk ), //时钟
.rst_n (rst_n ), //复位
//RGB -------------------------------------------
.RGB_hsync (RGB_0_hsync ), //RGB行同步
.RGB_vsync (RGB_0_vsync ), //RGB场同步
.RGB_data (RGB_0_data ), //RGB数据
.RGB_de (RGB_0_de ), //RGB数据使能
//Y分量 -----------------------------------------
.Y_hsync (Y_0_hsync ), //Y分量行同步
.Y_vsync (Y_0_vsync ), //Y分量场同步
.Y_data (Y_0_data ), //Y分量数据
.Y_de (Y_0_de ) //Y分量数据使能
);
RGB565_Y u_RGB565_Y_1
(
.clk (clk ), //时钟
.rst_n (rst_n ), //复位
//RGB -------------------------------------------
.RGB_hsync (RGB_1_hsync ), //RGB行同步
.RGB_vsync (RGB_1_vsync ), //RGB场同步
.RGB_data (RGB_1_data ), //RGB数据
.RGB_de (RGB_1_de ), //RGB数据使能
//Y分量 -----------------------------------------
.Y_hsync (Y_1_hsync ), //Y分量行同步
.Y_vsync (Y_1_vsync ), //Y分量场同步
.Y_data (Y_1_data ), //Y分量数据
.Y_de (Y_1_de ) //Y分量数据使能
);
//==========================================================================
//== 侦差计算
//==========================================================================
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
diff_de <= 1'b0;
diff_hsync <= 1'b0;
diff_vsync <= 1'b0;
diff_data <= 8'b0;
end
else if(Y_0_data >= Y_1_data) begin
diff_de <= Y_0_de;
diff_hsync <= Y_0_hsync;
diff_vsync <= Y_0_vsync;
diff_data <= (Y_0_data - Y_1_data > value) ? 8'hff : 8'h0;
end
else if(Y_0_data < Y_1_data) begin
diff_de <= Y_0_de;
diff_hsync <= Y_0_hsync;
diff_vsync <= Y_0_vsync;
diff_data <= (Y_1_data - Y_0_data > value) ? 8'hff : 8'h0;
end
end
//==========================================================================
//== 添加包围盒后输出
//==========================================================================
img_box
#(
.H_DISP (H_DISP ), //图像宽度
.V_DISP (V_DISP ) //图像高度
)
u_img_box
(
.clk (clk ), //时钟
.rst_n (rst_n ), //复位
//RGB -------------------------------------------
.RGB_hsync (RGB_0_hsync ), //RGB行同步
.RGB_vsync (RGB_0_vsync ), //RGB场同步
.RGB_data (RGB_0_data ), //RGB数据
.RGB_de (RGB_0_de ), //RGB数据使能
//diff ------------------------------------------
.diff_hsync (diff_hsync ), //diff行同步
.diff_vsync (diff_vsync ), //diff场同步
.diff_data (diff_data ), //diff数据
.diff_de (diff_de ), //diff数据使能
//key -------------------------------------------
.key_vld (key_vld ), //消抖后的按键值
//DISP ------------------------------------------
.DISP_hsync (DISP_hsync ), //最终显示的行同步
.DISP_data (DISP_data ), //最终显示的场同步
.DISP_de (DISP_de ), //最终显示的数据
.DISP_vsync (DISP_vsync ) //最终显示的数据使能
);
endmodule
(4)RGB565_Y
RGB565_Y
//**************************************************************************
// *** 名称 : RGB565_Y.v
// *** 作者 : xianyu_FPGA
// *** 博客 : https://www.cnblogs.com/xianyufpga/
// *** 日期 : 2020年3月
// *** 描述 : RGB565转RGB888再转YCbCr444,再取Y分量作为灰度数据
//--------------------------------------------------------------------------
// Y = 0.299*R + 0.587*G + 0.114*B
// Cb = 0.586*(B-Y) + 128 = -0.172*R - 0.339*G + 0.511*B + 128
// Cr = 0.713*(R-Y) + 128 = 0.511*R - 0.428*G - 0.083*B + 128
// --->
// Y = ( 77*R + 150*G + 29*B) >> 8
// Cb = (-43*R - 85*G + 128*B) >> 8 + 128
// Cr = (128*R - 107*G - 21*B) >> 8 + 128
// --->
// Y = ( 77*R + 150*G + 29*B) >> 8
// Cb = (-43*R - 85*G + 128*B + 32768) >> 8
// Cr = (128*R - 107*G - 21*B + 32768) >> 8
//**************************************************************************
module RGB565_Y
//========================< 端口 >==========================================
(
input wire clk , //时钟
input wire rst_n , //复位
//input ---------------------------------------------
input wire RGB_hsync , //RGB行同步
input wire RGB_vsync , //RGB场同步
input wire [15:0] RGB_data , //RGB数据
input wire RGB_de , //RGB数据使能
//output --------------------------------------------
output wire Y_hsync , //Y分量行同步
output wire Y_vsync , //Y分量场同步
output wire [ 7:0] Y_data , //Y分量数据
output wire Y_de //Y分量数据使能
);
//========================< 信号 >==========================================
wire [ 7:0] R0, G0, B0 ;
reg [15:0] R1, G1, B1 ;
reg [15:0] R2, G2, B2 ;
reg [15:0] R3, G3, B3 ;
reg [15:0] Y1, Cb1, Cr1 ;
reg [ 7:0] Y2, Cb2, Cr2 ;
//---------------------------------------------------
reg [ 2:0] RGB_de_r ;
reg [ 2:0] RGB_hsync_r ;
reg [ 2:0] RGB_vsync_r ;
//==========================================================================
//== RGB565转RGB888
//==========================================================================
assign R0 = {RGB_data[15:11],RGB_data[13:11]};
assign G0 = {RGB_data[10: 5],RGB_data[ 6: 5]};
assign B0 = {RGB_data[ 4: 0],RGB_data[ 2: 0]};
//==========================================================================
//== RGB888转YCbCr
//==========================================================================
//clk 1
//---------------------------------------------------
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
{R1,G1,B1} <= {16'd0, 16'd0, 16'd0};
{R2,G2,B2} <= {16'd0, 16'd0, 16'd0};
{R3,G3,B3} <= {16'd0, 16'd0, 16'd0};
end
else begin
{R1,G1,B1} <= { {R0 * 16'd77}, {G0 * 16'd150}, {B0 * 16'd29 } };
{R2,G2,B2} <= { {R0 * 16'd43}, {G0 * 16'd85}, {B0 * 16'd128} };
{R3,G3,B3} <= { {R0 * 16'd128}, {G0 * 16'd107}, {B0 * 16'd21 } };
end
end
//clk 2
//---------------------------------------------------
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
Y1 <= 16'd0;
Cb1 <= 16'd0;
Cr1 <= 16'd0;
end
else begin
Y1 <= R1 + G1 + B1;
Cb1 <= B2 - R2 - G2 + 16'd32768; //128扩大256倍
Cr1 <= R3 - G3 - B3 + 16'd32768; //128扩大256倍
end
end
//clk 3,除以256即右移8位,即取高8位
//---------------------------------------------------
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
Y2 <= 8'd0;
Cb2 <= 8'd0;
Cr2 <= 8'd0;
end
else begin
Y2 <= Y1[15:8];
Cb2 <= Cb1[15:8];
Cr2 <= Cr1[15:8];
end
end
assign Y_data = Y2; //只取Y分量输出
//==========================================================================
//== 信号同步
//==========================================================================
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
RGB_de_r <= 3'b0;
RGB_hsync_r <= 3'b0;
RGB_vsync_r <= 3'b0;
end
else begin
RGB_de_r <= {RGB_de_r[1:0], RGB_de};
RGB_hsync_r <= {RGB_hsync_r[1:0], RGB_hsync};
RGB_vsync_r <= {RGB_vsync_r[1:0], RGB_vsync};
end
end
assign Y_de = RGB_de_r[2];
assign Y_hsync = RGB_hsync_r[2];
assign Y_vsync = RGB_vsync_r[2];
endmodule
(5)img_box
img_box
//**************************************************************************
// *** 名称 : img_box.v
// *** 作者 : xianyu_FPGA
// *** 博客 : https://www.cnblogs.com/xianyufpga/
// *** 日期 : 2020年3月
// *** 描述 : 将目标框住后输出
//**************************************************************************
module img_box
//========================< 参数 >==========================================
#(
parameter H_DISP = 12'd480 , //图像宽度
parameter V_DISP = 12'd272 //图像高度
)
//========================< 端口 >==========================================
(
input wire clk , //时钟
input wire rst_n , //复位
//RGB -----------------------------------------------
input wire RGB_hsync , //RGB行同步
input wire RGB_vsync , //RGB场同步
input wire [15:0] RGB_data , //RGB数据
input wire RGB_de , //RGB数据使能
//diff ----------------------------------------------
input wire diff_hsync , //diff行同步
input wire diff_vsync , //diff场同步
input wire [ 7:0] diff_data , //diff数据
input wire diff_de , //diff数据使能
//key -----------------------------------------------
input wire key_vld , //消抖后的按键值
//DISP ----------------------------------------------
output reg DISP_hsync , //最终显示的行同步
output reg DISP_vsync , //最终显示的场同步
output reg [15:0] DISP_data , //最终显示的数据
output reg DISP_de //最终显示的数据使能
);
//========================< 信号 >==========================================
reg diff_vsync_r ;
wire pos_vsync ;
wire neg_vsync ;
//---------------------------------------------------
reg [11:0] diff_x ;
wire add_diff_x ;
wire end_diff_x ;
reg [11:0] diff_y ;
wire add_diff_y ;
wire end_diff_y ;
//---------------------------------------------------
reg [11:0] x_min ;
reg [11:0] x_max ;
reg [11:0] y_min ;
reg [11:0] y_max ;
reg [11:0] x_min_r ;
reg [11:0] x_max_r ;
reg [11:0] y_min_r ;
reg [11:0] y_max_r ;
//---------------------------------------------------
reg [11:0] RGB_x ;
wire add_RGB_x ;
wire end_RGB_x ;
reg [11:0] RGB_y ;
wire add_RGB_y ;
wire end_RGB_y ;
//---------------------------------------------------
reg mode ;
//==========================================================================
//== 帧开始和结束标志
//==========================================================================
always @(posedge clk) begin
diff_vsync_r <= diff_vsync;
end
assign pos_vsync = diff_vsync && ~diff_vsync_r;
assign neg_vsync = ~diff_vsync && diff_vsync_r;
//==========================================================================
//== 肤色图像的行列划分
//==========================================================================
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
diff_x <= 12'd0;
else if(add_diff_x) begin
if(end_diff_x)
diff_x <= 12'd0;
else
diff_x <= diff_x + 12'd1;
end
end
assign add_diff_x = diff_de;
assign end_diff_x = add_diff_x && diff_x== H_DISP-12'd1;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
diff_y <= 12'd0;
else if(add_diff_y) begin
if(end_diff_y)
diff_y <= 12'd0;
else
diff_y <= diff_y + 12'd1;
end
end
assign add_diff_y = end_diff_x;
assign end_diff_y = add_diff_y && diff_y== V_DISP-12'd1;
//==========================================================================
//== 帧运行:人脸框选
//==========================================================================
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
x_min <= H_DISP;
end
else if(pos_vsync) begin
x_min <= H_DISP;
end
else if(diff_data==8'hff && x_min > diff_x && diff_de) begin
x_min <= diff_x;
end
end
//---------------------------------------------------
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
x_max <= 'd0;
end
else if(pos_vsync) begin
x_max <= 'd0;
end
else if(diff_data==8'hff && x_max < diff_x && diff_de) begin
x_max <= diff_x;
end
end
//---------------------------------------------------
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
y_min <= V_DISP;
end
else if(pos_vsync) begin
y_min <= V_DISP;
end
else if(diff_data==8'hff && y_min > diff_y && diff_de) begin
y_min <= diff_y;
end
end
//---------------------------------------------------
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
y_max <= 'd0;
end
else if(pos_vsync) begin
y_max <= 'd0;
end
else if(diff_data==8'hff && y_max < diff_y && diff_de) begin
y_max <= diff_y;
end
end
//==========================================================================
//== 帧结束:保存坐标值
//==========================================================================
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
x_min_r <= 'd0;
x_max_r <= 'd0;
y_min_r <= 'd0;
y_max_r <= 'd0;
end
else if(neg_vsync) begin
x_min_r <= x_min;
x_max_r <= x_max;
y_min_r <= y_min;
y_max_r <= y_max;
end
end
//==========================================================================
//== 原图的行列划分
//==========================================================================
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
RGB_x <= 12'd0;
else if(add_RGB_x) begin
if(end_RGB_x)
RGB_x <= 12'd0;
else
RGB_x <= RGB_x + 12'd1;
end
end
assign add_RGB_x = RGB_de;
assign end_RGB_x = add_RGB_x && RGB_x== H_DISP-12'd1;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
RGB_y <= 12'd0;
else if(add_RGB_y) begin
if(end_RGB_y)
RGB_y <= 12'd0;
else
RGB_y <= RGB_y + 12'd1;
end
end
assign add_RGB_y = end_RGB_x;
assign end_RGB_y = add_RGB_y && RGB_y== V_DISP-12'd1;
//==========================================================================
//== 按键切换不同显示效果
//==========================================================================
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
mode <= 1'b0;
end
else if(key_vld) begin
mode <= ~mode;
end
end
//==========================================================================
//== 最终数据输出:包围盒+图像
//==========================================================================
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
DISP_hsync <= 1'b0;
DISP_vsync <= 1'b0;
DISP_data <= 16'b0;
DISP_de <= 1'b0;
end
//--------------------------------------------------- 输出包围盒+原图
else if(mode==1'b0) begin
DISP_hsync <= RGB_hsync;
DISP_vsync <= RGB_vsync;
DISP_de <= RGB_de;
if((RGB_y >= y_min_r-1 && RGB_y <= y_min_r+1) && RGB_x >= x_min_r && RGB_x <= x_max_r) begin
DISP_data <= 16'b11111_000000_00000;
end
else if((RGB_y >= y_max_r-1 && RGB_y <= y_max_r+1) && RGB_x >= x_min_r && RGB_x <= x_max_r) begin
DISP_data <= 16'b11111_000000_00000;
end
else if((RGB_x >= x_min_r-1 && RGB_x <= x_min_r+1) && RGB_y >= y_min_r && RGB_y <= y_max_r) begin
DISP_data <= 16'b11111_000000_00000;
end
else if((RGB_x >= x_max_r-1 && RGB_x <= x_max_r+1) && RGB_y >= y_min_r && RGB_y <= y_max_r) begin
DISP_data <= 16'b11111_000000_00000;
end
else begin
DISP_data <= RGB_data;
end
end
//--------------------------------------------------- 输出包围盒+侦差
else if(mode==1'b1) begin
DISP_vsync <= diff_vsync;
DISP_de <= diff_de;
DISP_hsync <= diff_hsync;
if((diff_y >= y_min_r-1 && diff_y <= y_min_r+1) && diff_x >= x_min_r && diff_x <= x_max_r) begin
DISP_data <= 16'b11111_000000_00000;
end
else if((diff_y >= y_max_r-1 && diff_y <= y_max_r+1) && diff_x >= x_min_r && diff_x <= x_max_r) begin
DISP_data <= 16'b11111_000000_00000;
end
else if((diff_x >= x_min_r-1 && diff_x <= x_min_r+1) && diff_y >= y_min_r && diff_y <= y_max_r) begin
DISP_data <= 16'b11111_000000_00000;
end
else if((diff_x >= x_max_r-1 && diff_x <= x_max_r+1) && diff_y >= y_min_r && diff_y <= y_max_r) begin
DISP_data <= 16'b11111_000000_00000;
end
else begin
DISP_data <= {diff_data[7:3],diff_data[7:2],diff_data[7:3]};
end
end
end
endmodule
参考资料:
[1]OpenS Lee:FPGA开源工作室(公众号)
[2]NingHechuan:硅农(公众号)
[3]Beyond:FPGA自习室(公众号)

浙公网安备 33010602011771号