本文你针对咸鱼FPGA的FPGA实现人脸识别进行学习。
获取人脸图像——肤色提取——滤波处理——人脸框选
肤色提取:顾名思义,将肤色从外界环境中提取出。在肤色识别算法中,常用YCbCr颜色空间(亮度、蓝色、红色分量),因为肤色在 YCbCr 空间受亮度信息的影响较小,从而肤色类聚性好,由此,我们用人工阈值法将肤色与非肤色区域分开,形成二值图像,实现肤色的提取。
此外人脸内部还会有些黑点,包括人脸外的环境可能有些地方也会被误检测为人脸,造成实验失败,因此可以加入形态学处理:腐蚀、膨胀、开运算、闭运算等,这些之前都整理过,不展开说了。
原理:先进行Ycbcr空间转换,给cb和cr设置阈值,将肤色提取出来。(共采用四级流水线)
首先三级流水线后,可得到三分量如下:
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
Cb和Cr设置阈值:Cb:77 ~ 127 ;Cr:133~173
;(前人大量研究得到的经验值),最终输出的结果是二值化结果
,目的是减少运算量!
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
face_data <= 'h0;
end
else if( (Cb2 > 77) && (Cb2 < 127) && (Cr2 > 133) && (Cr2 < 173) ) begin
face_data <= 16'hffff;
end
else begin
face_data <= 'h0;
end
end
RTL图如下:
可看到,输入是RGB565原图数据,内部进行Ycbcr转换,分别得到8位的Y,Cb,Cr分量,后根据蓝红分量的阈值得到16位的二值化肤色数据face_data;
检测出肤色后,为提高图像质量,进行中值滤波、腐蚀膨胀处理,前文已介绍过,这里不再赘述。
通过肤色检测出人脸后,我们用行列坐标画框,将人脸框选出来。这里涉及到侦差图像,前面肤色数据face_data位16位的原因就是便于和原图进行差分。
RGB信号:原图数据、使能以及行场有效信号。
face信号:人脸肤色提取后的图像数据、使能以及行场有效信号。
如何得到人脸框的四个顶点坐标?
因为两帧图像差别较小,因此我们将人脸肤色图像分两帧来处理,第一帧得到框的四个顶点坐标,当前帧的输出即可实时的确定出人脸框的四个顶点坐标。
1、既然要用连续的两帧肤色图像,我们就要对图像延迟一拍。
always @(posedge clk) begin
face_vsync_r <= face_vsync;
end
2、通过人脸肤色侦差图像得到边沿
assign pos_vsync = face_vsync && ~face_vsync_r;
assign neg_vsync = ~face_vsync && face_vsync_r;
3、利用显示驱动生成的行场计数器,得到人脸肤色处的横纵坐标
parameter COL = 11'd640 ; //图片长度
parameter ROW = 11'd480 ; //图片高度
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
face_x <= 10'd0;
else if(add_face_x) begin //人脸肤色数据有效
if(end_face_x) //数据有效且一行640像素计数完成
face_x <= 10'd0;
else
face_x <= face_x + 10'd1; //显示驱动生成的横坐标
end
end
assign add_face_x = face_de;
assign end_face_x = add_face_x && face_x== COL-10'd1;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
face_y <= 10'd0;
else if(add_face_y) begin//一行数据计数完成
if(end_face_y)//一行数据完成且480场计数完成(一帧图像完成)
face_y <= 10'd0;
else
face_y <= face_y + 10'd1;//显示驱动生成的纵坐标
end
end
assign add_face_y = end_face_x;
assign end_face_y = add_face_y && face_y== ROW-10'd1;
4、人脸框选
3中得到了图像的横纵坐标,从而可确定出框的四个顶点坐标,然后利用延迟后的一帧图像来求人脸框的坐标。
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
x_min <= COL;
end
else if(pos_vsync) begin //场有效上升沿
x_min <= COL;
end
else if(face_data==16'hffff && x_min > face_x && face_de) begin //有肤色数据,且框x最小坐标>肤色处x坐标
x_min <= face_x; //当前肤色x坐标就是框的x最小值
end
end
//---------------------------------------------------
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
x_max <= 0;
end
else if(pos_vsync) begin
x_max <= 0;
end
else if(face_data==16'hffff && x_max < face_x && face_de) begin//框x最大坐标<肤色x坐标,那肤色x坐标就是框x最大值
x_max <= face_x;
end
end
//---------------------------------------------------
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
y_min <= ROW;
end
else if(pos_vsync) begin
y_min <= ROW;
end
else if(face_data==16'hffff && y_min > face_y && face_de) begin //同理
y_min <= face_y;
end
end
//---------------------------------------------------
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
y_max <= 0;
end
else if(pos_vsync) begin
y_max <= 0;
end
else if(face_data==16'hffff && y_max < face_y && face_de) begin//同理
y_max <= face_y;
end
end
5、实时顶点坐标值的保存
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
x_min_r <= 0;
x_max_r <= 0;
y_min_r <= 0;
y_max_r <= 0;
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
至此,得到了人脸框,接下来找该人脸框下对应的原图数据。
6、原图行列计数器
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
RGB_x <= 10'd0;
else if(add_RGB_x) begin //原图像数据有效
if(end_RGB_x) //原图像数据有效且一行计数完成
RGB_x <= 10'd0;
else
RGB_x <= RGB_x + 10'd1;
end
end
assign add_RGB_x = RGB_de;
assign end_RGB_x = add_RGB_x && RGB_x== COL-10'd1;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
RGB_y <= 10'd0;
else if(add_RGB_y) begin //一行计数完成
if(end_RGB_y) //一行计数完成且一场计数完成
RGB_y <= 10'd0;
else
RGB_y <= RGB_y + 10'd1;
end
end
assign add_RGB_y = end_RGB_x;
assign end_RGB_y = add_RGB_y && RGB_y== ROW-10'd1;
7、人脸框和原图输出
用按键来控制识别效果,一种是原图的人脸识别,一种是二值化腐蚀膨胀后的人脸识别效果。
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
key_num <= 1'b0;
else if(key_vld)
key_num <= ~key_num;
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
TFT_de <= 1'b0;
TFT_hsync <= 1'b0;
TFT_vsync <= 1'b0;
TFT_data <= 16'b0;
end
else if(key_num==1'b0) begin //按键按下的时候得到白色方框和原图
if((RGB_y >= y_min_r-1 && RGB_y <= y_min_r+1) && RGB_x >= x_min_r && RGB_x <= x_max_r) begin
TFT_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
TFT_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
TFT_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
TFT_data <= 16'b11111_000000_00000;
end
else begin
TFT_de <= RGB_de;
TFT_hsync <= RGB_hsync;
TFT_vsync <= RGB_vsync;
TFT_data <= RGB_data;
end
end
else if(key_num==1'b1) begin //按键释放的时候得到白色方框和二值化腐蚀膨胀后的图像数据
if((face_y >= y_min_r-1 && face_y <= y_min_r+1) && face_x >= x_min_r && face_x <= x_max_r) begin
TFT_data <= 16'b11111_000000_00000;
end
else if((face_y >= y_max_r-1 && face_y <= y_max_r+1) && face_x >= x_min_r && face_x <= x_max_r) begin
TFT_data <= 16'b11111_000000_00000;
end
else if((face_x >= x_min_r-1 && face_x <= x_min_r+1) && face_y >= y_min_r && face_y <= y_max_r) begin
TFT_data <= 16'b11111_000000_00000;
end
else if((face_x >= x_max_r-1 && face_x <= x_max_r+1) && face_y >= y_min_r && face_y <= y_max_r) begin
TFT_data <= 16'b11111_000000_00000;
end
else begin
TFT_de <= face_de;
TFT_hsync <= face_hsync;
TFT_vsync <= face_vsync;
TFT_data <= face_data;
end
end
end
本文主要学习:人脸肤色如何提取(为蓝红分量设置阈值),以及人脸框如何得到(帧差图像得到四个顶点的实时坐标,根据四个顶点的横纵坐标值赋予颜色,得到方框)。木有想到一个假期竟然只看了这………………