2024嵌入式FPGA竞赛国特-最佳创意奖-红外瞳孔追踪系统(教学向)

 

致谢

        特此感谢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 

总结(与项目无关)

 下文与本项目无关,是博主的个人感叹(碎碎念),可跳过。

        博客主体写完,心情还是比较不平静的,有很多话想说,但是又因为各种原因删了。

        大一刚入南邮时,班主任(一位非常负责的导师)在班级群问:“大家有什么问题”,

        我:“没有啥问题”,

        班主任:“没问题就是最大的问题”。

        现在想起来当时自己真是愣头青,哈哈。大学不同于高中,我认为大学首要需要学会的能力是信息获取的能力。回首过往三年,虽然不后悔,多少有些遗憾,遗憾在于大多数时间都忙于学习,追求各种指标,而没有时间停下来,用心体会学习的过程。

        对于我来说,成绩更多像是我的保护伞,保护我免受父母的唠叨,保护我作为南邮第一届创新班学委有底气跟学校交流争取自己班的利益,保护我能保留很大一部分的个人自由······现在面对保研选择,才略微有些窥探到自己的追求:心无旁骛的深入追求知识。但是这又牵扯到很多,也很难实现。嗯,不过我依然会追逐理想,我依然问心无愧。

        祝愿各位都有远大前程。

 

FPGA(Field Programmable Gate Array,现场可编程门阵列)是一种集成电路芯片,它可以通过编程来实现不同的逻辑功能。红外小目标是指在红外光谱范围内,物体的尺寸较小,难以直接被肉眼观测到的目标。基于FPGA红外小目标代码,通常是通过编写硬件描述语言(HDL)来实现对红外图像的处理与分析。 首先,基于FPGA红外小目标代码需要用HDL编写对红外图像的采集和处理逻辑。这包括红外传感器与FPGA之间的接口设计,以及对采集到的红外数据进行滤波、增强、分割等处理。这些处理有助于提取出红外图像中的小目标信息,如人体、动物或其他热点物体的位置和形状。 其次,基于FPGA红外小目标代码还需要实现对红外目标的识别和跟踪算法。这些算法可以通过HDL编写并在FPGA上实现,用于识别红外图像中的目标,并跟踪它们的运动轨迹。这有助于在复杂环境下准确地定位和追踪红外小目标,如在夜间或恶劣天气条件下。 最后,基于FPGA红外小目标代码还需要与其他硬件组件(如显示屏、处理器等)进行接口设计,以便将处理后的红外图像信息输出或进行进一步的分析和处理。这可以通过HDL编写对接口协议的设计和实现来实现。 总之,基于FPGA红外小目标代码需要经过对红外图像采集、处理、识别、跟踪和输出等多个方面的设计与实现,以实现对红外小目标的高效处理和应用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值