致谢
特此感谢B站大磊FPGA的多目标帧差法视频,这是我写出这个作品的基础;我的老师刘宁,他给了我最初的灵感;我的队友hqy和zcy,他们是这个项目团队不可缺少的一环;CrazyBingo对于FPGA图像处理的贡献,我参考的很多代码都有他的影子。
前言
本人为南邮大三电科学生,技术有限,能获此殊荣噱头占比更多,算是用简单的逻辑实现了比较花哨的效果。不过FPGA代码都是我一行行敲出来的,除摄像头HDMI输出demo外未用IP核,除大磊FPGA多目标帧差外未参考其他代码。项目完成后成就感还是相当足的。这将近两个月的时间FPGA图像处理方面的编程思维开阔了不少,期望以后能更进一步。
作品部分演示效果已在B站发布视频,期待一键三连加关注(づ ̄3 ̄)づ╭❤️~
一、硬件基础(仅本项目使用,非必须)
易灵思Ti60F100,红外补光灯,VS-SC130GS(特制装载红外窄带滤波片摄像头),云台,眼球模型(开源项目),及部分3D打印支架等杂项。
二、系统框图及基本说明
上图为设计主要模块或功能,本篇文章仅解释说明本人编写的关键模块。
不同的厂家对摄像头基本上都会提供历程,也就是读写视频流这一步最终输出行、场同步,像素时钟,de,以及数据信号(需注意代码中行、场同步信号等是高有效还是低有效),这是基础不过多赘述,小白可看正点原子RGB-LCD彩条显示实验。
该项目通过红外窄带滤波摄像头的得到的图像受自然光,灯光等外界干扰小,基本取决于人为的红外补光灯。由此,眼部因暗瞳效应产生的光斑极为明显,再通过一定的图像处理手段,可通过光斑定位深色瞳孔,从而捕获眼动方向。基本如下:
可控阈值二值化:对采集到的像素进行可控阈值二值化处理,以突出瞳孔等反射的亮斑。
多目标追踪定位:运用多目标追踪算法,定位并筛选出两个瞳孔亮斑的位置。
深色瞳孔提取:在瞳孔亮斑的一定相对位置处提取深色瞳孔,并进行目标追踪。
瞳孔坐标计算:得到瞳孔的坐标后,实时与参考点(眨眼或外部设定)对比,计算出眼球的方向。
实时画框叠加:FPGA 将获取到的相关坐标信息实时画框叠加到原始视频流中,通过 HDMI接口输出实时的视频流。
三、主体模块说明
1.binarization
首先通过二值化,当亮度超过人为设定阈值之后将monoc拉高,作为信号触发目标追踪模块处理:
module binarization
(
input clk ,
input rst_n ,
input ycbcr_vs ,
input ycbcr_hs ,
input ycbcr_de ,
input [7:0] luminance , //pixel_data
output reg post_vs ,
output reg post_hs ,
output reg post_de ,
output reg monoc // monochrome(1=white,0=black)
);
reg ycbcr_vs_d;
reg ycbcr_hs_d;
reg ycbcr_de_d;
wire [7:0] try = 8'd220; //threshold
//binarization
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
monoc <= 1'b0;
else if(ycbcr_vs)begin
if (luminance > try) //threshold
monoc <= 1'b1;
else
monoc <= 1'b0;
end
else
monoc <= 1'b0;
end
//lag 1 clocks signal sync
always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
post_vs <= 1'd0;
post_hs <= 1'd0;
post_de <= 1'd0;
end
else begin
post_vs <= ycbcr_vs;
post_hs <= ycbcr_hs;
post_de <= ycbcr_de;
end
end
endmodule
2.VIP_multi_target_detect(⭐)
此代码是我从大磊视频中的代码修改而来,下面我将简单讲解,想了解代码细节推荐点击之前给的链接看看原视频,大磊老师讲的非常详细形象。
该模块用于获取 二值化得出的亮斑 的边界:target_pos_out,注意:边界由43位组成:{Flag,ymax[41:32],xmax[31:21],ymin[20:11],xmin[10:0]}Flag位为标志位,代表该边界是否有效,后续为下、右、上、左四个边界,所有操作在一帧内完成,也就是一个vs周期内。众所周知,人有两个眼睛,所以在一般情况下,二值化输出的是两个相隔一段距离的亮斑区域——白像素之间间隔在MIN_DIST以内的白像素集,MIN_DIST自由设定。该模块我就直接设定为输出两个亮斑边界。此处祭出本人帅照以示说明,箭头所指即为暗瞳效应产生的亮斑区域:
首先,per_frame_clken 拉高后,每个时钟x_cnt自增,到达一行像素后,x_cnt清零,y_cnt自增,由此即对每个有效像素进行了坐标标记。
再看130行处,通过投票机制判定二值化白像素是新的亮斑区域还是落在之前亮斑区域之内的像素。target_flag用于标记亮斑区域,当target_flag[0] == 0时,证明暂未出现亮斑区域;target_flag[1] == 0时,证明暂未出现第二个亮斑区域。[1:0]new_target_flag是两个投票箱,对应两个投票区域。打个比方,在一片1280*720平米的空地上,将要按照从左到右,从上到下的顺序一颗一颗栽种一种根系范围是方形的树,树占地1平米,根系占地4*MIN_DIST²米,树可能种活也可能死,且当一棵活树在另外一棵活树的根系范围之内时,二者根系结合形成一个种群,当栽种成功两个种群时即完成指标,剩下未在根系范围内的树不再考虑。活树——亮点,死树——暗点,种群——亮斑区域。
此刻,可能有人问了:“你二值化代码这么简单,怎么保证亮点就是暗瞳效应产生的呢?有没有改进方法?”
有的,兄弟,有的。我们先考虑相对常见的问题:摄像头坏点——腐蚀处理;红外光强过弱导致无亮斑——见下文;红外光强过强导致一大片亮斑——见下文;鼻尖反光干扰——模块从左到右,从上到下,抓取到两个亮斑区域即止,一般人鼻子在两个眼睛下方,是抓不到的;眼睛上方背景光源干扰——见下文。
那么就有人要问了:“我有三只眼睛怎么办,你这垃圾代码不废了吗?有没有改进方法?”
没有,兄弟,抱歉。本眼动作品暂不支持多人运动以及外星人。若急需请赞助经费,标注外星人,我将在后续开展外星人专属业务。
module VIP_multi_target_detect
(
input clk,
input rst_n,
input per_frame_vsync,
input per_frame_hs,
input per_frame_clken,
input per_img_Bit, //white
output reg [42:0] target_pos_out1, // {Flag,ymax[41:32],xmax[31:21],ymin[20:11],xmin[10:0]}
output reg [42:0] target_pos_out2,
input [ 9:0] MIN_DIST //min domain
);
//720p
parameter [10:0] IMG_HDISP = 11'd1280;
parameter [9:0] IMG_VDISP = 10'd720 ;
//lag 1 clocks signal sync
reg per_frame_vsync_r;
reg per_frame_hs_r ;
reg per_frame_clken_r;
reg per_img_Bit_r ;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
per_frame_vsync_r <= 0;
per_frame_hs_r <= 0;
per_frame_clken_r <= 0;
per_img_Bit_r <= 0;
end
else begin
per_frame_vsync_r <= per_frame_vsync ;
per_frame_hs_r <= per_frame_hs ;
per_frame_clken_r <= per_frame_clken ;
per_img_Bit_r <= per_img_Bit ;
end
end
wire vsync_pos_flag;//rising edge of vs
wire vsync_neg_flag;//falling edge if vs
assign vsync_pos_flag = per_frame_vsync & (!per_frame_vsync_r);
assign vsync_neg_flag = !(per_frame_vsync) & per_frame_vsync_r;
//
//count the input pixel of v/h to get v/h coordinates
reg [10:0] x_cnt;
reg [9:0] y_cnt;
always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
x_cnt <= 11'd0;
y_cnt <= 10'd0;
end
else if(!per_frame_vsync) begin
x_cnt <= 11'd0;
y_cnt <= 10'd0;
end
else if(per_frame_clken) begin
if(x_cnt < IMG_HDISP - 1) begin
x_cnt <= x_cnt + 1'b1;
y_cnt <= y_cnt;
end
else begin
x_cnt <= 11'd0;
y_cnt <= y_cnt + 1'b1;
end
end
else begin
x_cnt <= 11'd0;
y_cnt <= y_cnt;
end
end
// lag 1 clocks signal sync
reg [10:0] x_cnt_r;
reg [9:0] y_cnt_r;
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
x_cnt_r <= 11'd0;
else
x_cnt_r <= x_cnt;
end
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
y_cnt_r <= 10'd0;
else
y_cnt_r <= y_cnt;
end
// reg the boundaries of each moving object
reg [42:0] target_pos1;
reg [42:0] target_pos2;
// valid mark
wire [1:0]target_flag;
// left/right/up/down fields for each target
wire [10:0] target_left1 ;
wire [10:0] target_right1 ;
wire [9:0] target_top1 ;
wire [9:0] target_bottom1 ;
wire [10:0] target_left2 ;
wire [10:0] target_right2 ;
wire [9:0] target_top2 ;
wire [9:0] target_bottom2 ;
assign target_flag[0] = target_pos1[42];
assign target_bottom1 = (target_pos1[41:32] < IMG_VDISP-1 - MIN_DIST ) ? (target_pos1[41:32] +MIN_DIST) : IMG_VDISP-1;
assign target_right1 = (target_pos1[31:21] < IMG_HDISP-1 - MIN_DIST ) ? (target_pos1[31:21] +MIN_DIST) : IMG_HDISP-1;
assign target_top1 = (target_pos1[20:11] > 10'd0 + MIN_DIST ) ? (target_pos1[20:11] -MIN_DIST) : 10'd0;
assign target_left1 = (target_pos1[10: 0] > 11'd0 + MIN_DIST ) ? (target_pos1[10: 0] -MIN_DIST) : 11'd0;
assign target_flag[1] = target_pos2[42];
assign target_bottom2 = (target_pos2[41:32] < IMG_VDISP-1 - MIN_DIST ) ? (target_pos2[41:32] +MIN_DIST) : IMG_VDISP-1;
assign target_right2 = (target_pos2[31:21] < IMG_HDISP-1 - MIN_DIST ) ? (target_pos2[31:21] +MIN_DIST) : IMG_HDISP-1;
assign target_top2 = (target_pos2[20:11] > 10'd0 + MIN_DIST ) ? (target_pos2[20:11] -MIN_DIST) : 10'd0;
assign target_left2 = (target_pos2[10: 0] > 11'd0 + MIN_DIST ) ? (target_pos2[10: 0] -MIN_DIST) : 11'd0;
reg target_cnt;
reg [1:0] new_target_flag; //ballot box
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
target_pos1 <= {1'b0, 10'd0, 11'd0, 10'd0, 11'd0};
target_pos2 <= {1'b0, 10'd0, 11'd0, 10'd0, 11'd0};
new_target_flag <= 2'd0;
target_cnt <= 1'd0;
end
// initialize
else if(vsync_pos_flag) begin
target_pos1 <= {1'b0, 10'd0, 11'd0, 10'd0, 11'd0};
target_pos2 <= {1'b0, 10'd0, 11'd0, 10'd0, 11'd0};
new_target_flag <= 2'd0;
target_cnt <= 1'd0;
end
else begin
// first clk,judge new target/target in domain
if(per_frame_clken && per_img_Bit ) begin
if(target_flag[0] == 1'b0) // if reg flag0 invalid,vote the bit as a new target
new_target_flag[0] <= 1'b1;
else begin // if reg flag0 valid,judge whether the target is in domain
if((x_cnt < target_left1 || x_cnt > target_right1 || y_cnt < target_top1 || y_cnt > target_bottom1)) //if out of domain,vote the bit as a new target
new_target_flag[0] <= 1'b1;
else
new_target_flag[0] <= 1'b0;
end
if(target_flag[1] == 1'b0) // same as flag0
new_target_flag[1] <= 1'b1;
else begin
if((x_cnt < target_left2 || x_cnt > target_right2 || y_cnt < target_top2 || y_cnt > target_bottom2))
new_target_flag[1] <= 1'b1;
else
new_target_flag[1] <= 1'b0;
end
end
else begin
new_target_flag <= 2'b0;
end
// second clk,according to the vote,update target
if(per_frame_clken_r && per_img_Bit_r) begin
if(new_target_flag == 2'b11) begin // unanimous vote,new target appear
if(target_cnt == 1'b0)begin
target_cnt <=1'd1;
target_pos1 <= {1'b1, y_cnt_r, x_cnt_r, y_cnt_r, x_cnt_r};
end
else if(target_cnt == 1'b1 && target_pos2[42] != 1'b1 && (x_cnt_r < target_left1 || x_cnt_r > target_right1 || y_cnt_r < target_top1 || y_cnt_r > target_bottom1))begin
target_pos2 <= {1'b1, y_cnt_r, x_cnt_r, y_cnt_r, x_cnt_r};
target_cnt <=1'd1;
end
end
else if (new_target_flag > 2'd0 && new_target_flag != 2'b11) begin // target in domain
if(new_target_flag[0] == 1'b0) begin // flag0 == 0, target in domain1
target_pos1[42] <= 1'b1;
if(x_cnt_r < target_pos1[10: 0]) // expand boundary
target_pos1[10: 0] <= x_cnt_r;
if(x_cnt_r > target_pos1[31:21])
target_pos1[31:21] <= x_cnt_r;
if(y_cnt_r < target_pos1[20:11])
target_pos1[20:11] <= y_cnt_r;
if(y_cnt_r > target_pos1[41:32])
target_pos1[41:32] <= y_cnt_r;
end
else if(new_target_flag[1] == 1'b0) begin // flag1 == 0, target in domain2
target_pos2[42] <= 1'b1;
if(x_cnt_r < target_pos2[10: 0])
target_pos2[10: 0] <= x_cnt_r;
if(x_cnt_r > target_pos2[31:21])
target_pos2[31:21] <= x_cnt_r;
if(y_cnt_r < target_pos2[20:11])
target_pos2[20:11] <= y_cnt_r;
if(y_cnt_r > target_pos2[41:32])
target_pos2[41:32] <= y_cnt_r;
end
end
end
end
end
//reg output after a frame
always @(posedge clk or negedge rst_n)
if(!rst_n) begin
target_pos_out1 <= {1'd0,10'd0,11'd0,10'd0,11'd0};
target_pos_out2 <= {1'd0,10'd0,11'd0,10'd0,11'd0};
end
else if(vsync_neg_flag) begin
target_pos_out1 <= target_pos1;
target_pos_out2 <= target_pos2;
end
endmodule
3.VIP_multi_target_detect_black
在已经标定亮斑区域之后,我们便可以进一步在亮斑附近标定瞳孔(红框部分):
相对于上一个多目标追踪的代码,标定瞳孔的单目标追踪代码就简单很多了,只需要例化该模块两次,分别将左右眼亮斑接入target_pos_in即可。不同的是,该模块的per_img_Bit是通过二值化筛选出足够黑的部分(代码类似,自行修改),够黑则拉高。而vsync_neg_flag用于表示一帧的结束,也就代表着模块数据处理完毕,用于各模块协调。
flag_black用于标定当前像素输入否已经在亮斑的某个区域范围之内(根据实际补光灯与人眼的相对位置调节参数),当per_frame_clken(像素时序),per_img_Bit(黑色瞳孔),flag_black(像素范围)同时满足时,当前像素即为组成黑色瞳孔的像素,即可根据当前像素坐标拓展框定范围,并在一帧结束后作为瞳孔坐标信息target_pos_out输出给后续画框模块处理。
这样也就在一定程度上解决了眼睛上方背景光源干扰的问题,因为光干扰一般是一片较亮的区域,二值化后周围小范围内不会存在足够黑到per_img_Bit拉高的程度,干扰也就在该模块被阻断了。
module VIP_multi_target_detect_black
(
input clk,
input rst_n,
input per_frame_vsync,
input per_frame_clken,
input per_img_Bit, //balck
input [42:0] target_pos_in,//{Flag,ymax[41:32],xmax[31:21],ymin[20:11],xmin[10:0]}
output reg [42:0] target_pos_out,//{Flag,ymax[41:32],xmax[31:21],ymin[20:11],xmin[10:0]}
output vsync_neg_flag //mark the ending of a frame
);
parameter [10:0] IMG_HDISP = 11'd1280;
parameter [9:0] IMG_VDISP = 10'd720;
reg per_frame_vsync_r;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
per_frame_vsync_r <= 0;
else
per_frame_vsync_r <= per_frame_vsync ;
end
wire vsync_neg_flag;//falling edge
assign vsync_neg_flag = !(per_frame_vsync) & per_frame_vsync_r;
reg [10:0] x_cnt;
reg [9:0] y_cnt;
reg [9:0] up_reg ;
reg [9:0] down_reg ;
reg [10:0] left_reg ;
reg [10:0] right_reg;
reg flag_reg;
//count the input pixel of v/h to get v/h coordinates
always@(posedge clk or negedge rst_n)
begin
if(!rst_n) begin
x_cnt <= 11'd0;
y_cnt <= 10'd0;
end
else if(!per_frame_vsync) begin
x_cnt <= 11'd0;
y_cnt <= 10'd0;
end
else if(per_frame_clken) begin
if(x_cnt < IMG_HDISP - 1) begin
x_cnt <= x_cnt + 1'b1;
y_cnt <= y_cnt;
end
else begin
x_cnt <= 11'd0;
y_cnt <= y_cnt + 1'b1;
end
end
end
//mark the area from target_pos_in
reg flag_black;
always@(posedge clk or negedge rst_n)
if(!rst_n)
flag_black <= 1'b0;
else if(!per_frame_vsync)
flag_black <= 1'b0;
else if(target_pos_in[42] && per_frame_clken && y_cnt < target_pos_in[41:32] + 5'd20 && x_cnt < target_pos_in[31:21] + 5'd20 && y_cnt > target_pos_in[20:11] - 6'd32 && x_cnt > target_pos_in[10:0] - 5'd20)
flag_black <= 1'b1;
else
flag_black <= 1'b0;
//mark pupil
always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
up_reg <= target_pos_in[41:32] + 5'd20;
down_reg <= 10'd0;
left_reg <= target_pos_in[31:21] + 5'd20;
right_reg <= 10'd0;
flag_reg <= 1'b0;
end
else if(!per_frame_vsync)begin
up_reg <= target_pos_in[41:32] + 5'd20;
down_reg <= 10'd0;
left_reg <= target_pos_in[31:21] + 5'd20;
right_reg <= 11'd0;
flag_reg <= 1'b0;
end
else if (per_frame_clken && per_img_Bit && flag_black) 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
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
target_pos_out <= {1'd0,10'd0,11'd0,10'd0,11'd0};
end
else if (vsync_neg_flag) begin
target_pos_out <= {flag_reg,down_reg,right_reg,up_reg,left_reg};
end
end
endmodule
4.VIP_Video_add_rectangular
该模块为实时画框模块,视频中呈现的瞳孔框,定位框(locater),视线追踪框(sight frame),人员标记(person number)都由该模块在原视频上叠加红框实现。此处代码基本逻辑在大磊的视频里同样解释了,再次推荐观看。各框效果如下图:
模块per_img_red/green/blue为图片实时像素,由于红外窄带滤波灰度摄像头采集到信号为raw8格式,所以本项目中三者都为8位灰度值。此处target_pos_output为前模块输出的两个瞳孔坐标。rx_person为队友做的AI人脸识别,据此信号判断人员编号。flag_rst信号为定位框的重置信号,该信号产生模块见下文,信号用于重置与当前瞳孔位置比较的定位框位置。sit为模块判定人眼移动的方向,作为输出信号用于控制外设舵机、云台等。
模块269行之前都为准备 框的坐标,实际画框部分从269行开始。我首先解释画框部分,以((x_cnt > rectangular_left1) && (x_cnt < rectangular_right1) && ((y_cnt == rectangular_up1) || (y_cnt == rectangular_down1)) && rectangular_flag1)这段用于画上下边框的或条件之一为例,当四个条件同时满足时,输出纯红像素作为上下框边界。rectangular_flag1——框坐标是否有效;x_cnt > rectangular_left1——当前像素在左边界右;x_cnt > rectangular_right1——当前像素在右边界左;(y_cnt == rectangular_up1) || (y_cnt == rectangular_down1)——当前像素坐标等于上/下边界。其余或条件同理。
114行开始,解释了如何重置定位框——外界输入的flag_rst信号(可自由设定触发源,本项目用眨眼和外界按钮,会在后续功能模块说明),当其拉高时,将当前瞳孔坐标作为新的定位框坐标。之后,将定位框x/y方向坐标和rec_x/y_sum分别与瞳孔当前x/y方向坐标和x/y_sum相减取绝对值和正负号,即可进行眼动八角定位 :
如图黑实线为可判定的八个方向,灰虚线为方向区域范围边界,通过比较 x差值与二分之一 y(移位实现)差值,y 差值与二分之一 x 差值,以及差值正负关系,即可划分八个区域。同时使用sit将眼动方向传递至模块外以控制外设;并且根据方向的不同,控制视线追踪框rec_eye的边界定速增加或减少,以实现框随视线移动的直观效果。
module VIP_Video_add_rectangular (
input clk,
input rst_n,
input per_frame_vsync,
input per_frame_hs,
input per_frame_clken,
input [7:0] per_img_red,
input [7:0] per_img_green,
input [7:0] per_img_blue,
input [42:0] target_pos_out1,// {Flag,ymax[41:32],xmax[31:21],ymin[20:11],xmin[10:0]}
input [42:0] target_pos_out2,
input [3:0] rx_person, //person
output reg post_frame_vsync,
output reg post_frame_hs,
output reg post_frame_clken,
output reg [7:0] post_img_red,
output reg [7:0] post_img_green,
output reg [7:0] post_img_blue,
input flag_rst, //rst the locater flag
output [3:0] sit //eye direction
);
//1280*720 resolution
parameter [10:0] IMG_HDISP = 11'd1280;
parameter [9:0] IMG_VDISP = 10'd720;
wire [9:0] rectangular_up1;
wire [9:0] rectangular_up2;
wire [9:0] rectangular_down1;
wire [9:0] rectangular_down2;
wire [10:0] rectangular_left1;
wire [10:0] rectangular_left2;
wire [10:0] rectangular_right1;
wire [10:0] rectangular_right2;
wire rectangular_flag1; //flag whether there is a moving target
wire rectangular_flag2;
assign rectangular_flag1 = target_pos_out1[42];
assign rectangular_down1 = target_pos_out1[41:32];
assign rectangular_right1 = target_pos_out1[31:21];
assign rectangular_up1 = target_pos_out1[20:11];
assign rectangular_left1 = target_pos_out1[10:0];
assign rectangular_flag2 = target_pos_out2[42];
assign rectangular_down2 = target_pos_out2[41:32];
assign rectangular_right2 = target_pos_out2[31:21];
assign rectangular_up2 = target_pos_out2[20:11];
assign rectangular_left2 = target_pos_out2[10:0];
reg [10:0] x_cnt;
reg [9:0] y_cnt;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
x_cnt <= 11'd0;
y_cnt <= 10'd0;
end
else begin
if(!per_frame_vsync)begin
x_cnt <= 11'd0;
y_cnt <= 10'd0;
end
else if(per_frame_clken) begin
if(x_cnt < IMG_HDISP - 1) begin
x_cnt <= x_cnt + 1'b1;
y_cnt <= y_cnt;
end
else begin
x_cnt <= 11'd0;
y_cnt <= y_cnt + 1'b1;
end
end
else begin
x_cnt <= 11'd0;
y_cnt <= y_cnt;
end
end
end
//sum of x/y
wire [12:0]x_sum;
wire [11:0]y_sum;
assign x_sum = (rectangular_flag1 && rectangular_flag2) ? (rectangular_right1 + rectangular_right2 + rectangular_left1 + rectangular_left2):1'b0;
assign y_sum = (rectangular_flag1 && rectangular_flag2) ? (rectangular_up1 + rectangular_up2 + rectangular_down1 + rectangular_down2):1'b0;
//locater
wire [9:0] rectangular_up3;
wire [9:0] rectangular_up4;
wire [9:0] rectangular_down3;
wire [9:0] rectangular_down4;
wire [10:0] rectangular_left3;
wire [10:0] rectangular_left4;
wire [10:0] rectangular_right3;
wire [10:0] rectangular_right4;
reg [41:0]rec_left = 42'b0101000000_00111011011_0100110000_00111000110;// {Flag,ymax[41:32],xmax[31:21],ymin[20:11],xmin[10:0]}
reg [41:0]rec_right = 42'b0101000000_01011011011_0100110000_01011000110;// {Flag,ymax[41:32],xmax[31:21],ymin[20:11],xmin[10:0]}
//rst locater
always @(posedge clk or negedge rst_n)
if(!rst_n)begin
rec_left = 42'b0101000000_00111011011_0100110000_00111000110;
rec_right = 42'b0101000000_01011011011_0100110000_01011000110;
end
else if(flag_rst && rectangular_flag1 && rectangular_flag2)begin
rec_left = target_pos_out1[41:0];
rec_right = target_pos_out2[41:0];
end
wire [11:0]rec_y_sum;
wire [12:0]rec_x_sum;
assign rec_x_sum = (rectangular_flag1 && rectangular_flag2) ? (rectangular_right3 + rectangular_right4 + rectangular_left3 + rectangular_left4):1'b0;
assign rec_y_sum = (rectangular_flag1 && rectangular_flag2) ? (rectangular_up3 + rectangular_up4 + rectangular_down3 + rectangular_down4):1'b0;
assign rectangular_down3 = rec_left[41:32];
assign rectangular_right3 = rec_left[31:21];
assign rectangular_up3 = rec_left[20:11];
assign rectangular_left3 = rec_left[10:0];
assign rectangular_down4 = rec_right[41:32];
assign rectangular_right4 = rec_right[31:21];
assign rectangular_up4 = rec_right[20:11];
assign rectangular_left4 = rec_right[10:0];
//output eye direction
reg right;
reg left;
reg down;
reg up;
wire [3:0] sit;
//judge sight direction(now-locater)
wire sign_x;
wire sign_y;
wire [10:0]sub_x;
wire [9:0]sub_y;
assign sub_x = (x_sum > rec_x_sum) ? (x_sum - rec_x_sum) : (rec_x_sum - x_sum);
assign sub_y = (y_sum > rec_y_sum) ? (y_sum - rec_y_sum) : (rec_y_sum - y_sum);
assign sign_x = (x_sum > rec_x_sum) ? 1'b1 : 1'b0;
assign sign_y = (y_sum > rec_y_sum) ? 1'b1 : 1'b0;
wire h;
wire v;
wire h_v;
assign h = ((sub_x >> 1) >= sub_y) ? 1'b1 : 1'b0;
assign v = ((sub_y >> 1) >= sub_x) ? 1'b1 : 1'b0;
assign h_v = (((sub_y >> 1) < sub_x) && ((sub_x >> 1) < sub_y)) ? 1'b1 : 1'b0;
//sight frame
reg [41:0]rec_eye = 41'b0101100000_01011011011_0100010000_00111000110;
always @(posedge clk or negedge rst_n)
if(!rst_n)
rec_eye = 41'b0101100000_01011011011_0100010000_00111000110;
else if((x_cnt == IMG_HDISP - 2'b10) && (y_cnt == IMG_VDISP - 2'b10) && rectangular_flag1 && rectangular_flag2)begin
if(h && sign_x && rec_eye[31:21] < IMG_HDISP)begin //right
rec_eye[31:21] <= rec_eye[31:21] + 2'b10;
rec_eye[10:0] <= rec_eye[10:0] + 2'b10;
right <= 1'b1;
left <= 1'b0;
down <= 1'b0;
up <= 1'b0;
end
else if(h && !sign_x && rec_eye[10:0] >= 1'b1)begin //left
rec_eye[31:21] <= rec_eye[31:21] - 2'b10;
rec_eye[10:0] <= rec_eye[10:0] - 2'b10;
right <= 1'b0;
left <= 1'b1;
down <= 1'b0;
up <= 1'b0;
end
else if(v && sign_y && rec_eye[41:32] < IMG_VDISP)begin //down
rec_eye[41:32] <= rec_eye[41:32] + 2'b10;
rec_eye[20:11] <= rec_eye[20:11] + 2'b10;
right <= 1'b0;
left <= 1'b0;
down <= 1'b1;
up <= 1'b0;
end
else if(v && !sign_y && rec_eye[20:11] >= 1'b1)begin //up
rec_eye[41:32] <= rec_eye[41:32] - 2'b10;
rec_eye[20:11] <= rec_eye[20:11] - 2'b10;
right <= 1'b0;
left <= 1'b0;
down <= 1'b0;
up <= 1'b1;
end
else if(h_v && sign_x && sign_y && rec_eye[31:21] < IMG_HDISP && rec_eye[41:32] < IMG_VDISP)begin //right_down
rec_eye[31:21] <= rec_eye[31:21] + 2'b10;
rec_eye[10:0] <= rec_eye[10:0] + 2'b10;
rec_eye[41:32] <= rec_eye[41:32] + 2'b10;
rec_eye[20:11] <= rec_eye[20:11] + 2'b10;
right <= 1'b1;
left <= 1'b0;
down <= 1'b1;
up <= 1'b0;
end
else if(h_v && !sign_x && sign_y && rec_eye[10:0] >= 1'b1 && rec_eye[41:32] < IMG_VDISP)begin //left_down
rec_eye[31:21] <= rec_eye[31:21] - 2'b10;
rec_eye[10:0] <= rec_eye[10:0] - 2'b10;
rec_eye[41:32] <= rec_eye[41:32] + 2'b10;
rec_eye[20:11] <= rec_eye[20:11] + 2'b10;
right <= 1'b0;
left <= 1'b1;
down <= 1'b1;
up <= 1'b0;
end
else if(h_v && !sign_y && sign_x && rec_eye[31:21] < IMG_HDISP && rec_eye[20:11] >= 1'b1)begin //right_up
rec_eye[31:21] <= rec_eye[31:21] + 2'b10;
rec_eye[10:0] <= rec_eye[10:0] + 2'b10;
rec_eye[41:32] <= rec_eye[41:32] - 2'b10;
rec_eye[20:11] <= rec_eye[20:11] - 2'b10;
right <= 1'b1;
left <= 1'b0;
down <= 1'b0;
up <= 1'b1;
end
else if(h_v && !sign_y && !sign_x && rec_eye[10:0] >= 1'b1 && rec_eye[20:11] >= 1'b1)begin //left_up
rec_eye[31:21] <= rec_eye[31:21] - 2'b10;
rec_eye[10:0] <= rec_eye[10:0] - 2'b10;
rec_eye[41:32] <= rec_eye[41:32] - 2'b10;
rec_eye[20:11] <= rec_eye[20:11] - 2'b10;
right <= 1'b0;
left <= 1'b1;
down <= 1'b0;
up <= 1'b1;
end
end
assign sit = {up, down, left, right};
wire [9:0] rectangular_up5;
wire [9:0] rectangular_down5;
wire [10:0] rectangular_left5;
wire [10:0] rectangular_right5;
assign rectangular_down5 = rec_eye[41:32];
assign rectangular_right5 = rec_eye[31:21];
assign rectangular_up5 = rec_eye[20:11];
assign rectangular_left5 = rec_eye[10:0];
//draw
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
post_frame_vsync <= 1'd0;
post_frame_hs <= 1'd0;
post_frame_clken <= 1'd0;
post_img_red <= 8'd0;
post_img_green <= 8'd0;
post_img_blue <= 8'd0;
end
else begin
post_frame_vsync <= per_frame_vsync;
post_frame_hs <= per_frame_hs;
post_frame_clken <= per_frame_clken;
if(post_frame_clken) begin
if(((x_cnt > rectangular_left1) && (x_cnt < rectangular_right1) && ((y_cnt == rectangular_up1) || (y_cnt == rectangular_down1)) && rectangular_flag1) ||
((x_cnt > rectangular_left2) && (x_cnt < rectangular_right2) && ((y_cnt == rectangular_up2) || (y_cnt == rectangular_down2)) && rectangular_flag2) ||
((x_cnt > rectangular_left3) && (x_cnt < rectangular_right3) && ((y_cnt == rectangular_up3) || (y_cnt == rectangular_down3))) ||
((x_cnt > rectangular_left4) && (x_cnt < rectangular_right4) && ((y_cnt == rectangular_up4) || (y_cnt == rectangular_down4))) ||
((x_cnt > rectangular_left5) && (x_cnt < rectangular_right5) && ((y_cnt == rectangular_up5) || (y_cnt == rectangular_down5))) ||
((x_cnt > rectangular_left3) && (x_cnt < rectangular_left3 + 4'd10) && ((y_cnt == rectangular_up3 - 4'd4) || (y_cnt == rectangular_up3 - 5'd8) || (y_cnt == rectangular_up3 - 5'd1))
&& rx_person == 4'b0010)||
((x_cnt > rectangular_left3) && (x_cnt < rectangular_left3 + 4'd10) && ((y_cnt == rectangular_up3 - 4'd4) || (y_cnt == rectangular_up3 - 5'd8))
&& rx_person == 4'b0100)||
((x_cnt > rectangular_left3) && (x_cnt < rectangular_left3 + 4'd10) && ((y_cnt == rectangular_up3 - 4'd4))
&& rx_person == 4'b1000))begin
//draw up and down
post_img_red <= 8'd255;
post_img_green <= 8'd0;
post_img_blue <= 8'd0;
end
else if(((y_cnt > rectangular_up1) && (y_cnt < rectangular_down1) && ((x_cnt == rectangular_left1) || (x_cnt == rectangular_right1)) && rectangular_flag1)||
((y_cnt > rectangular_up2) && (y_cnt < rectangular_down2) && ((x_cnt == rectangular_left2) || (x_cnt == rectangular_right2)) && rectangular_flag2) ||
((y_cnt > rectangular_up3) && (y_cnt < rectangular_down3) && ((x_cnt == rectangular_left3) || (x_cnt == rectangular_right3)))||
((y_cnt > rectangular_up4) && (y_cnt < rectangular_down4) && ((x_cnt == rectangular_left4) || (x_cnt == rectangular_right4)))||
((y_cnt > rectangular_up5) && (y_cnt < rectangular_down5) && ((x_cnt == rectangular_left5) || (x_cnt == rectangular_right5))))begin
//draw left and right
post_img_red <= 8'd255;
post_img_green <= 8'd0;
post_img_blue <= 8'd0;
end
else begin
post_img_red <= per_img_red ;
post_img_green <= per_img_green;
post_img_blue <= per_img_blue ;
end
end
else begin
post_img_red <= per_img_red ;
post_img_green <= per_img_green;
post_img_blue <= per_img_blue ;
end
end
end
endmodule
四、功能模块说明
1、rec_rst
该模块利用双目信号的捕获情况,判定是否刻意眨眼,以控制flag_rst信号重置定位框,flag12为模块VIP_multi_target_detect_black输出的target_pos_out[42],若flag1&&flag2 == 1,代表两眼信号都稳定。此处我编写了状态机以判断人是否刻意眨眼。概括来说就是稳定睁眼——稳定闭眼——稳定睁眼的过程,具体时间修改参数可调。
IDLE:初始态,用于flag_rst信号的置零。
S1: 双目都捕获时每帧对cnt_neg_p计数。视频是60Hz,也就是说当双目都捕获持续大约两秒后,若双目都丢失,则跳转至S2,同时cnt_neg_p清零。这是一个从睁眼到闭眼的过程。
S2: 注意,该状态使用cnt_neg_n在双目都丢失时计数。为避免信号波动或自然眨眼影响,设定为双目都丢失1秒左右后,若再次捕获到任意信号,判定为睁眼,跳转到S3。大于半秒,小于1秒的时间内捕获信号,则判定为干扰,返回S1态。
S3: 当双目再次被持续捕捉一秒左右,跳转至S4转态。
S4: 回归IDLE,同时拉高flag_rst代表刻意眨眼过程完成,重置定位框。
module rec_rst
(
input clk,
input rst_n,
input flag1,
input flag2,
input v_neg, //falling edge
output reg flag_rst
);
parameter IDLE = 5'b00001;
parameter S1 = 5'b00010; //visibility
parameter S2 = 5'b00100; //lost
parameter S3 = 5'b01000; //appear
parameter S4 = 5'b10000; //output rst
reg [4:0] curr_st ;
reg [4:0] next_st ;
//Excitation signal generation
reg [7:0] cnt_neg_p;
reg [7:0] cnt_neg_n;
always @(posedge clk or negedge rst_n)
if(!rst_n || curr_st == IDLE || curr_st == S2 || (curr_st == S3 && !flag1 && !flag2))
cnt_neg_p <= 1'b0;
else if(cnt_neg_p >= 8'd128)
cnt_neg_p <= cnt_neg_p;
else if(flag1 && flag2 && v_neg)
cnt_neg_p <= cnt_neg_p + 1'b1;
always @(posedge clk or negedge rst_n)
if(!rst_n || curr_st == IDLE || curr_st == S1 || flag1 || flag2)
cnt_neg_n <= 1'b0;
else if(cnt_neg_n >= 8'd128)
cnt_neg_n <= cnt_neg_n;
else if(!flag1 && !flag2 && v_neg && curr_st == S2)
cnt_neg_n <= cnt_neg_n + 1'b1;
always @(posedge clk or negedge rst_n)
if(!rst_n)
curr_st <= IDLE;
else
curr_st <= next_st;
always @(curr_st)begin
case(curr_st)
IDLE: begin
next_st <= S1;
flag_rst <= 1'b0;
end
S1 : if(cnt_neg_p >= 8'd128 && !flag1 && !flag2)
next_st <= S2;
else
next_st <= S1;
S2 : if(cnt_neg_n >= 8'd64 && (flag1 || flag2))
next_st <= S3;
else if((flag1 || flag2) && cnt_neg_n >= 8'd32)
next_st <= S1;
else
next_st <= S2;
S3 : if(cnt_neg_p >= 7'd64 && flag1 && flag2)
next_st <= S4;
else
next_st <= S3;
S4 : if(flag1 && flag2)begin
next_st <= IDLE;
flag_rst <= 1'b1;
end
else
next_st <= S4;
default : next_st <= IDLE;
endcase
end
endmodule
2、cnt_all
此模块通过对一帧内所有有效像素的灰度值求和,与设定阈值对比,判断需要调高还是调低亮度,通过FPGA调控输出PWM波至外部硬件电路控制红外灯的亮度。模块相当简单,但是效果很不错,解决了很多问题。
luminance为八位灰度图像数据,cmp为比较结果,再输入PWM模块调节PWM占空比。请根据实际环境调节代码末尾阈值上下限。
module cnt_all
(
input clk ,
input rst_n ,
input per_frame_vsync ,
input ycbcr_de , // en
input [7:0] luminance ,
output reg [1:0] cmp // 00 IDLE;10 exceed;01 below;11 ok
);
reg per_frame_vsync_r;
wire vsync_pos_flag;//rising edge
wire vsync_neg_flag;//falling edge
assign vsync_pos_flag = per_frame_vsync & (!per_frame_vsync_r);
assign vsync_neg_flag = !(per_frame_vsync) & per_frame_vsync_r;
reg [27:0] cnt_fin;
reg [27:0] cnt;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
cnt <= 1'b0;
else if(ycbcr_de)begin
cnt <= cnt + luminance;
end
else if(vsync_pos_flag)
cnt <= 1'b0;
end
//delay
always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
per_frame_vsync_r <= 1'd0;
end
else begin
per_frame_vsync_r <= per_frame_vsync;
end
end
always @(posedge clk or negedge rst_n)
if(!rst_n) begin
cnt_fin <= 1'b0;
end
else if(vsync_neg_flag) begin //reg cnt after a frame
cnt_fin <= cnt;
end
always @(posedge clk or negedge rst_n)
if(!rst_n) begin
cmp <= 2'b0;
end
else if(cnt_fin > 28'h2f00000) begin
cmp <= 2'b10;
end
else if(cnt_fin < 28'h2800000) begin
cmp <= 2'b01;
end
else if(cnt_fin > 28'h2800000 && cnt_fin < 28'h2f00000) begin
cmp <= 2'b11;
end
endmodule
总结(与项目无关)
下文与本项目无关,是博主的个人感叹(碎碎念),可跳过。
博客主体写完,心情还是比较不平静的,有很多话想说,但是又因为各种原因删了。
大一刚入南邮时,班主任(一位非常负责的导师)在班级群问:“大家有什么问题”,
我:“没有啥问题”,
班主任:“没问题就是最大的问题”。
现在想起来当时自己真是愣头青,哈哈。大学不同于高中,我认为大学首要需要学会的能力是信息获取的能力。回首过往三年,虽然不后悔,多少有些遗憾,遗憾在于大多数时间都忙于学习,追求各种指标,而没有时间停下来,用心体会学习的过程。
对于我来说,成绩更多像是我的保护伞,保护我免受父母的唠叨,保护我作为南邮第一届创新班学委有底气跟学校交流争取自己班的利益,保护我能保留很大一部分的个人自由······现在面对保研选择,才略微有些窥探到自己的追求:心无旁骛的深入追求知识。但是这又牵扯到很多,也很难实现。嗯,不过我依然会追逐理想,我依然问心无愧。
祝愿各位都有远大前程。