使用芯片为ZYNQ—7020,基于野火FPGA ZYNQ开发板
肤色模型简介
YCrCb也称为YUV,主要用于优化彩色视频信号的传输。与RGB视频信号传输相比,它最大的优点在于只需占用极少的频宽(RGB要求三个独立的视频信号同时传输)。其中“Y”表示明亮度(Luminance或Luma),也就是灰阶值;而“U”和“V” 表示的则是色度(Chrominance或Chroma),作用是描述影像色彩及饱和度,用于指定像素的颜色。其中,Cr反映了RGB输入信号红色部分与RGB信号亮度值之间的差异。而Cb反映的是RGB输入信号蓝色部分与RGB信号亮度值之间的差异。
肤色检测 77<=Cb<=127 133<=Cr<=173 可以认为为肤色
MATLAB实现
clear all; close all; clc;
% -------------------------------------------------------------------------
% Read PC image to Matlab
IMG1 = imread('../doc/1920_1080.jpg'); % 读取jpg图像
h = size(IMG1,1); % 读取图像高度
w = size(IMG1,2); % 读取图像宽度
subplot(121);imshow(IMG1);title('识别前');
IMG2=rgb2ycbcr(IMG1);
% -------------------------------------------------------------------------
% Relized by user logic
% Y = ( R*76 + G*150 + B*29) >>8
% Cb = (-R*43 - G*84 + B*128 + 32768) >>8
% Cr = ( R*128 - G*107 - B*20 + 32768) >>8
IMG1 = double(IMG1);
IMG_YCbCr = zeros(h,w,3);
Face = zeros(h,w,3);
for i = 1 : h
for j = 1 : w
IMG_YCbCr(i,j, 1) = bitshift(( IMG1(i,j,1)*76 + IMG1(i,j,2)*150 + IMG1(i,j,3)*29),-8);
IMG_YCbCr(i,j,2) = bitshift((-IMG1(i,j,1)*43 - IMG1(i,j,2)*84 + IMG1(i,j,3)*128 + 32768),-8);
IMG_YCbCr(i,j,3) = bitshift(( IMG1(i,j,1)*128 - IMG1(i,j,2)*107 - IMG1(i,j,3)*20 + 32768),-8);
end
end
% 77<=Cb<=127 133<=Cr<=173 可以认为为肤色
for i = 1 : h
for j = 1 : w
if((77<=IMG_YCbCr(i,j,2))&&(IMG_YCbCr(i,j,2)<=127)&&(133<=IMG_YCbCr(i,j,3)&&(IMG_YCbCr(i,j,2)<=173)))
Face(i,j,:) = IMG1(i,j,:);
else
Face(i,j,:) = 0;
end
end
end
% -------------------------------------------------------------------------
% Display Y Cb Cr Channel
IMG_YCbCr = uint8(IMG_YCbCr);
Face =uint8(Face);
subplot(122); imshow(Face); title('识别后人脸');
识别效果
FPGA实现
基本流程:从pl端 OV5640摄像头,通过AXI HP总线将数据缓存至ps端的ddr3之中,pl端读取ps端ddr3中的摄像头数据,转换为YCbCr数据,判断肤色,输出(肤色数据输出为24'hFFFFFF,其余为0),经过两次中值滤波(买的摄像头噪点太多),一次腐蚀(只能对二值化数据操作),一次均值滤波,之后判断人脸数据位置,在原图上绘制绿色边框。(部分代码参考FPGA实现人脸检测 - 咸鱼IC - 博客园)
OV5640摄像头配置
参考小梅哥《基于HDL的FPGA逻辑设计与验证教程V3.5.2》-基于 DDR3 的串口传图帧缓存系统设计实现 配置好的或者使用其他FPGA芯片的可以跳过本章节
PLL设置
50MHz用于LCD屏幕的显示,200MHz用于DDR3数据的读取
ZYNQ核添加
与小梅哥中,** Block Design 与 ZYNQ 核 ** 小章节一致,唯一不同的是,
在此界面处,需要同时使能ps端串口,使得ps端工作,不然只使用ps端的ddr3时,ps端并不会不工作,我们添加一个串口程序,使得ps端运行。在vitis中,选择默认模板时,选择 Hello world 模板就可以 操作如下:
在此界面选中uart0,点击变绿即可。(需要根据自己开发板串口管脚选择)
在此界面可以配置串口参数(波特率啥的)
之后按照小梅哥的操作,生成system代码即可
pl端读取ps端ddr3 FIFO封装
参考 小梅哥FIFO 转 AXI4 接口模块设计 也可以使用我的代码,经过修改,支持乒乓操作。
摄像头寄存器配置
跳过 ** 可以使用我的代码**
肤色识别
首先将摄像头读取的数据转换成YCbCr数据,并且判断人脸数据,并输出
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
//例化程序示例
// RGB888_YCbCr_Face inst_RGB888_YCbCr_Face
// (
// .sys_clk (sys_clk ),
// .sys_rst_n (sys_rst_n),
// //input------------------------------------------
// .rgb888 (rgb888 ), //输入的RGB888数据 24bit
// .rgb_de (rgb_de ), //de使能信号
// .rgb_hsync (rgb_hsync), //行同步信号
// .rgb_vsync (rgb_vsync), //场同步信号
// //output-----------------------------------------
// .face_data (face_data), //输出的人脸数据 人脸为黑色,其余为白色 24bit
// .face_de (face_de ), //延时4个时钟周期
// .face_hsync (face_hsync),
// .face_vsync (face_vsync),
// );
// Create Date: 2024/03/26 20:42:44
// Design Name: FRZ
// Module Name: RGB888_YCbCr_Face
// Description:
// 将RGB888数据转换成YCbCr数据,并且检测人脸 输入为RGB888数据,输出为处理后的人脸数据RGB888 人脸为黑色,其余为白色
// Dependencies:
// [email protected]
// https://www.cnblogs.com/fangrunze
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//////////////////////////////////////////////////////////////////////////////////
module RGB888_YCbCr_Face(
input wire sys_clk ,
input wire sys_rst_n ,
//input----------------------------------
input wire [23:0] rgb888 ,
input wire rgb_de , //de使能信号
input wire rgb_hsync , //行同步信号
input wire rgb_vsync , //场同步信号
//output---------------------------------
output reg [23:0] face_data , //肤色数据 1 像素位置为人脸 0不是
output wire face_de , //延时4个时钟周期
output wire face_hsync ,
output wire face_vsync
);
//wire define
wire [7:0] R; //R分量
wire [7:0] G; //G分量
wire [7:0] B; //B分量
//reg defien
reg [15:0] R1 ,R2 , R3 ;
reg [15:0] G1 ,G2 , G3 ;
reg [15:0] B1 ,B2 , B3 ;
reg [15:0] Y1 , Cb1 , Cr1 ;
reg [7:0] Y , Cb , Cr ; //转换出的YCbCr数据
reg [3:0] rgb_de_r ; //rgb信号延时4个时钟周期
reg [3:0] rgb_hsync_r ;
reg [3:0] rgb_vsync_r ;
//R G B 分量赋值
assign R = rgb888[23:16];
assign G = rgb888[15:8];
assign B = rgb888[7:0];
/*********************************************
//Refer to full/pc range YCbCr format
Y = R*0.299 + G*0.587 + B*0.114
Cb = -R*0.169 - G*0.331 + B*0.5 + 128
Cr = R*0.5 - G*0.419 - B*0.081 + 128
--->
Y = (76 *R + 150*G + 29 *B)>>8
Cb = (-43*R - 84 *G + 128*B + 32768)>>8
Cr = (128*R - 107*G - 20 *B + 32768)>>8
**********************************************/
//clk1 乘法
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_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} <= { {R * 16'd76}, {G * 16'd150}, {B * 16'd29 } };
{R2,G2,B2} <= { {R * 16'd43}, {G * 16'd84}, {B * 16'd128} };
{R3,G3,B3} <= { {R * 16'd128}, {G * 16'd107}, {B * 16'd20 } };
end
end
//clk2 相加
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)begin
{Y1,Cb1,Cr1} <= {1'b0, 1'b0, 1'b0};
end
else begin
Y1 <= R1 + G1 + B1 ;
Cb1 <= B2 - R2 - G2 + 16'd32768;
Cr1 <= R3 - G3 - B3 + 16'd32768;
end
end
//clk3 右移8位,取高8位即可
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
{Y,Cb,Cr} <= {1'b0, 1'b0, 1'b0};
else begin
Y <= Y1[15:8];
Cb <= Cb1[15:8];
Cr <= Cr1[15:8];
end
end
//clk4 肤色检测 77<=Cb<=127 133<=Cr<=173 可以认为为肤色
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
face_data <= 1'd0;
else if((Cb >= 77) && (Cb <= 127) && (Cr >= 133) && (Cr <= 173) )
face_data <= 24'hFFFFFF;
else
face_data <= 1'b0;
end
//rgb信号同步 延时4个周期
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)begin
rgb_de_r <= 1'b0;
rgb_hsync_r <= 1'b0;
rgb_vsync_r <= 1'b0;
end
else begin
rgb_de_r <= {rgb_de_r[2:0], rgb_de};
rgb_hsync_r <= {rgb_hsync_r[2:0], rgb_hsync};
rgb_vsync_r <= {rgb_vsync_r[2:0], rgb_vsync};
end
end
assign face_de = rgb_de_r[3];
assign face_hsync = rgb_hsync_r[3];
assign face_vsync = rgb_vsync_r[3];
endmodule
之后由于我买的摄像头噪声太大,使用了两次中值滤波。
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// 例化程序示例
// median_filter inst_median_filter
// (
// .sys_clk (sys_clk ), //LCD驱动时钟
// .sys_rst_n (sys_rst_n ), //系统复位
// //input----------------------------------
// .data_in (data_in ), //输入数据
// .data_in_valid(data_in_valid ), //数据有效信号
// .data_in_hs (data_in_hs ), //行同步信号
// .data_in_vs (data_in_vs ), //场同步信号
// //output---------------------------------
// .data_out (data_out ), //输出的中值数据
// .data_out_valid(data_out_valid ), //数据有效信号
// .data_out_hs(data_out_hs ), //行同步信号
// .data_out_vs(data_out_vs ) //场同步信号
// );
//
// Create Date: 2024/06/13 15:06:59
// Design Name:
// Module Name: median filter
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
// 中值滤波器
// Dependencies:
// [email protected]
// https://www.cnblogs.com/fangrunze
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//////////////////////////////////////////////////////////////////////////////////
module median_filter(
input wire sys_clk ,
input wire sys_rst_n ,
//input----------------------------------
input wire [7:0] data_in , //输入的数据
input wire data_in_valid, //数据有效信号
input wire data_in_hs , //行同步信号
input wire data_in_vs , //场同步信号
//output---------------------------------
output wire [7:0] data_out , //输出的中值数据
output reg data_out_valid, //数据有效信号
output reg data_out_hs , //行同步信号
output reg data_out_vs
);
//wire define
//line data
wire [7:0] line0_data;
wire [7:0] line1_data;
wire [7:0] line2_data;
wire [7:0] line0_max;
wire [7:0] line0_mid;
wire [7:0] line0_min;
wire [7:0] line1_max;
wire [7:0] line1_mid;
wire [7:0] line1_min;
wire [7:0] line2_max;
wire [7:0] line2_mid;
wire [7:0] line2_min;
wire [7:0] max_max;
wire [7:0] max_mid;
wire [7:0] max_min;
wire [7:0] mid_max;
wire [7:0] mid_mid;
wire [7:0] mid_min;
wire [7:0] min_max;
wire [7:0] min_mid;
wire [7:0] min_min;
wire [7:0] matrix_mid;
//reg define
//matrix 3x3 data
reg [7:0] row0_col0;
reg [7:0] row0_col1;
reg [7:0] row0_col2;
reg [7:0] row1_col0;
reg [7:0] row1_col1;
reg [7:0] row1_col2;
reg [7:0] row2_col0;
reg [7:0] row2_col1;
reg [7:0] row2_col2;
reg data_in_valid_dly1;
reg data_in_valid_dly2;
reg data_in_valid_dly3;
reg data_in_hs_dly1;
reg data_in_hs_dly2;
reg data_in_hs_dly3;
reg data_in_vs_dly1;
reg data_in_vs_dly2;
reg data_in_vs_dly3;
//----------------------------------------------------
// matrix 3x3 data
// row0_col0 row0_col1 row0_col2
// row1_col0 row1_col1 row1_col2
// row2_col0 row2_col1 row2_col2
//----------------------------------------------------
assign line2_data = data_in;
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)begin
row0_col0 <= 'd0;
row0_col1 <= 'd0;
row0_col2 <= 'd0;
row1_col0 <= 'd0;
row1_col1 <= 'd0;
row1_col2 <= 'd0;
row2_col0 <= 'd0;
row2_col1 <= 'd0;
row2_col2 <= 'd0;
end
else if((!data_in_hs) && (!data_in_vs))begin
if(data_in_valid) begin
row0_col2 <= line0_data;
row0_col1 <= row0_col2;
row0_col0 <= row0_col1;
row1_col2 <= line1_data;
row1_col1 <= row1_col2;
row1_col0 <= row1_col1;
row2_col2 <= line2_data;
row2_col1 <= row2_col2;
row2_col0 <= row2_col1;
end
else begin
row0_col2 <= row0_col2;
row0_col1 <= row0_col1;
row0_col0 <= row0_col0;
row1_col2 <= row1_col2;
row1_col1 <= row1_col1;
row1_col0 <= row1_col0;
row2_col2 <= row2_col2;
row2_col1 <= row2_col1;
row2_col0 <= row2_col0;
end
end
else begin
row0_col0 <= 'd0;
row0_col1 <= 'd0;
row0_col2 <= 'd0;
row1_col0 <= 'd0;
row1_col1 <= 'd0;
row1_col2 <= 'd0;
row2_col0 <= 'd0;
row2_col1 <= 'd0;
row2_col2 <= 'd0;
end
end
//----------------------------------------------------
//延时三个时钟
//----------------------------------------------------
always @(posedge sys_clk)
begin
data_in_valid_dly1 <= data_in_valid;
data_in_valid_dly2 <= data_in_valid_dly1;
data_in_valid_dly3 <= data_in_valid_dly2;
data_in_hs_dly1 <= data_in_hs;
data_in_hs_dly2 <= data_in_hs_dly1;
data_in_hs_dly3 <= data_in_hs_dly2;
data_in_vs_dly1 <= data_in_vs;
data_in_vs_dly2 <= data_in_vs_dly1;
data_in_vs_dly3 <= data_in_vs_dly2;
end
//----------------------------------------------------
//获得maxtrix 3x3 data
//----------------------------------------------------
shift_register_2taps shift_register_2taps(
.sys_clk (sys_clk ), //输入时钟
.shiftin (data_in ), //输入数据
.shiftin_valid (data_in_valid ), //输入数据有效信号
.shiftout ( ),
.taps0x (line0_data ), //输出数据
.taps1x (line1_data ) //输出数据
);
//----------------------------------------------------
// 计算3x3矩阵的中值 第0行
//----------------------------------------------------
sort sort_line0
(
.sys_clk (sys_clk ), //pixel clk
.sys_rst_n (sys_rst_n ), //复位信号
//input----------------------------------
.data0_in (row0_col0 ), //输入的第0行数据
.data1_in (row0_col1 ), //输入的第1行数据
.data2_in (row0_col2 ), //输入的第2行数据
.data_in_valid (data_in_valid_dly1 ), //数据有效信号
//output---------------------------------
.data_max_out (line0_max ), //最大值
.data_mid_out (line0_mid ), //最小值
.data_min_out (line0_min ), //中值
.data_out_valid ( ) //输出的数据有效信
);
//----------------------------------------------------
// 计算3x3矩阵的中值 第1行
//----------------------------------------------------
sort sort_line1
(
.sys_clk (sys_clk ), //pixel clk
.sys_rst_n (sys_rst_n ), //复位信号
//input----------------------------------
.data0_in (row1_col0 ), //输入的第0行数据
.data1_in (row1_col1 ), //输入的第1行数据
.data2_in (row1_col2 ), //输入的第2行数据
.data_in_valid (data_in_valid_dly1 ), //数据有效信号
//output---------------------------------
.data_max_out (line1_max ), //最大值
.data_mid_out (line1_mid ), //最小值
.data_min_out (line1_min ), //中值
.data_out_valid ( ) //输出的数据有效信
);
//----------------------------------------------------
// 计算3x3矩阵的中值 第2行
//----------------------------------------------------
sort sort_line2
(
.sys_clk (sys_clk ), //pixel clk
.sys_rst_n (sys_rst_n ), //复位信号
//input----------------------------------
.data0_in (row2_col0 ), //输入的第0行数据
.data1_in (row2_col1 ), //输入的第1行数据
.data2_in (row2_col2 ), //输入的第2行数据
.data_in_valid (data_in_valid_dly1 ), //数据有效信号
//output---------------------------------
.data_max_out (line2_max ), //最大值
.data_mid_out (line2_mid ), //最小值
.data_min_out (line2_min ), //中值
.data_out_valid ( ) //输出的数据有效信
);
//----------------------------------------------------
//line0_max line1_max line2_max
//----------------------------------------------------
sort sort_max
(
.sys_clk (sys_clk ), //pixel clk
.sys_rst_n (sys_rst_n ), //复位信号
//input----------------------------------
.data0_in (line0_max ), //输入的第0行数据
.data1_in (line1_max ), //输入的第1行数据
.data2_in (line2_max ), //输入的第2行数据
.data_in_valid (data_in_valid_dly2 ), //数据有效信号
//output---------------------------------
.data_max_out (max_max ), //最大值
.data_mid_out (max_mid ), //最小值
.data_min_out (max_min ), //中值
.data_out_valid ( ) //输出的数据有效信
);
//----------------------------------------------------
//line0_mid line1_mid line2_mid
//----------------------------------------------------
sort sort_mid
(
.sys_clk (sys_clk ), //pixel clk
.sys_rst_n (sys_rst_n ), //复位信号
//input----------------------------------
.data0_in (line0_mid ), //输入的第0行数据
.data1_in (line1_mid ), //输入的第1行数据
.data2_in (line2_mid ), //输入的第2行数据
.data_in_valid (data_in_valid_dly2 ), //数据有效信号
//output---------------------------------
.data_max_out (mid_max ), //最大值
.data_mid_out (mid_mid ), //最小值
.data_min_out (mid_min ), //中值
.data_out_valid ( ) //输出的数据有效信
);
//----------------------------------------------------
//line0_min line1_min line2_min
//----------------------------------------------------
sort sort_min
(
.sys_clk (sys_clk ), //pixel clk
.sys_rst_n (sys_rst_n ), //复位信号
//input----------------------------------
.data0_in (line0_min ), //输入的第0行数据
.data1_in (line1_min ), //输入的第1行数据
.data2_in (line2_min ), //输入的第2行数据
.data_in_valid (data_in_valid_dly2 ), //数据有效信号
//output---------------------------------
.data_max_out (min_max ), //最大值
.data_mid_out (min_mid ), //最小值
.data_min_out (min_min ), //中值
.data_out_valid ( ) //输出的数据有效信
);
//----------------------------------------------------
//
//----------------------------------------------------
sort sort_all
(
.sys_clk (sys_clk ), //pixel clk
.sys_rst_n (sys_rst_n ), //复位信号
//input----------------------------------
.data0_in (max_min ), //输入的第0行数据
.data1_in (mid_mid ), //输入的第1行数据
.data2_in (min_max ), //输入的第2行数据
.data_in_valid (data_in_valid_dly3 ), //数据有效信号
//output---------------------------------
.data_max_out ( ), //最大值
.data_mid_out (matrix_mid ), //最小值
.data_min_out ( ), //中值
.data_out_valid ( ) //输出的数据有效信
);
assign data_out = matrix_mid;
//----------------------------------------------------
//对齐输出信号
//----------------------------------------------------
always @(posedge sys_clk)
begin
data_out_valid <= data_in_valid_dly3;
data_out_hs <= data_in_hs_dly3;
data_out_vs <= data_in_vs_dly3;
end
endmodule
其中构建3*3像素矩阵则使用的为 移位寄存器 IP核 具体可以查看 小梅哥 灰度图像中值滤波设计实现(HDMI 和 TFT 显示) **
这里有一个bug:移位寄存器在每一帧的第一行开始,会输出前一帧的最后两行数据(移位寄存器特性),这会导致后边肤色边界判断异常。具体现象参考:生成3x3矩阵(3):shift ip核 - 咸鱼IC - 博客园 换帧数据的问题。解决办法:最后肤色边界判断时,屏蔽前两行数据。**
腐蚀操作:
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// 例化程序示例
// Erode inst_Erode(
//.sys_clk (sys_clk ) ,
//.sys_rst_n (sys_rst_n ) ,
////input----------------------------------
//.data_in (data_in ) , //输入的RGB数据
//.data_in_valid (data_in_valid ) , //数据有效信号
//.data_in_hs (data_in_hs ) , //行同步信号
//.data_in_vs (data_in_vs ) , //场同步信号
////output---------------------------------
//.erode_out (erode_out ) , //输出的数据
//.erode_out_valid (erode_out_valid) ,
//.erode_out_hs (erode_out_hs ) , //行同步信号
//.erode_out_vs (erode_out_vs ) //场同步信号
// );
// Create Date: 2024/06/13 17:06:23
// Design Name:
// Module Name: Erode
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
// 腐蚀处理,输入必须为二值图像
// Dependencies:
// [email protected]
// https://www.cnblogs.com/fangrunze
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module Erode(
input wire sys_clk ,
input wire sys_rst_n ,
//input----------------------------------
input wire [7:0] data_in , //输入的RGB数据
input wire data_in_valid , //数据有效信号
input wire data_in_hs , //行同步信号
input wire data_in_vs , //场同步信号
//output---------------------------------
output wire [7:0] erode_out , //输出的数据
output reg erode_out_valid ,
output reg erode_out_hs , //行同步信号
output reg erode_out_vs //场同步信号
);
wire [7:0] line0_data;
wire [7:0] line1_data;
wire [7:0] line2_data;
//reg define
//matrix 3x3 data
reg [7:0] row0_col0;
reg [7:0] row0_col1;
reg [7:0] row0_col2;
reg [7:0] row1_col0;
reg [7:0] row1_col1;
reg [7:0] row1_col2;
reg [7:0] row2_col0;
reg [7:0] row2_col1;
reg [7:0] row2_col2;
reg erode_out_valid_dly1 ;
reg erode_out_valid_dly2 ;
reg erode_out_hs_dly1 ;
reg erode_out_hs_dly2 ;
reg erode_out_vs_dly1 ;
reg erode_out_vs_dly2 ;
reg [7:0] erode ;
reg [7:0] erode_1;
reg [7:0] erode_2;
reg [7:0] erode_3;
//----------------------------------------------------
// matrix 3x3 data
// row0_col0 row0_col1 row0_col2
// row1_col0 row1_col1 row1_col2
// row2_col0 row2_col1 row2_col2
//----------------------------------------------------
assign line2_data = data_in;
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)begin
row0_col0 <= 'd0;
row0_col1 <= 'd0;
row0_col2 <= 'd0;
row1_col0 <= 'd0;
row1_col1 <= 'd0;
row1_col2 <= 'd0;
row2_col0 <= 'd0;
row2_col1 <= 'd0;
row2_col2 <= 'd0;
end
else if((!data_in_hs) && (!data_in_vs))begin
if(data_in_valid) begin
row0_col2 <= line0_data;
row0_col1 <= row0_col2;
row0_col0 <= row0_col1;
row1_col2 <= line1_data;
row1_col1 <= row1_col2;
row1_col0 <= row1_col1;
row2_col2 <= line2_data;
row2_col1 <= row2_col2;
row2_col0 <= row2_col1;
end
else begin
row0_col2 <= row0_col2;
row0_col1 <= row0_col1;
row0_col0 <= row0_col0;
row1_col2 <= row1_col2;
row1_col1 <= row1_col1;
row1_col0 <= row1_col0;
row2_col2 <= row2_col2;
row2_col1 <= row2_col1;
row2_col0 <= row2_col0;
end
end
else begin
row0_col0 <= 'd0;
row0_col1 <= 'd0;
row0_col2 <= 'd0;
row1_col0 <= 'd0;
row1_col1 <= 'd0;
row1_col2 <= 'd0;
row2_col0 <= 'd0;
row2_col1 <= 'd0;
row2_col2 <= 'd0;
end
end
//----------------------------------------------------
//获得maxtrix 3x3 data
//----------------------------------------------------
shift_register_2taps erode_shift(
.sys_clk (sys_clk ), //输入时钟
.shiftin (data_in ), //输入数据
.shiftin_valid (data_in_valid ), //输入数据有效信号
.shiftout ( ),
.taps0x (line0_data ), //输出数据
.taps1x (line1_data ) //输出数据
);
//----------------------------------------------------
//腐蚀
//----------------------------------------------------
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)begin
erode_1 <= 'd0;
erode_2 <= 'd0;
erode_3 <= 'd0;
end
else if(data_in_valid) begin
erode_1 <= row0_col0 && row0_col1 && row0_col2;
erode_2 <= row1_col0 && row1_col1 && row1_col2;
erode_3 <= row2_col0 && row2_col1 && row2_col2;
end
else begin
erode_1 <= 'd0;
erode_2 <= 'd0;
erode_3 <= 'd0;
end
end
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)begin
erode <= 'd0;
end
else
erode <= erode_1 && erode_2 && erode_3;
end
assign erode_out = erode ? 8'hff : 8'h00;
//----------------------------------------------------
//对齐时序
//----------------------------------------------------
always @(posedge sys_clk) begin
erode_out_valid_dly1 <= data_in_valid;
erode_out_valid_dly2 <= erode_out_valid_dly1;
erode_out_valid <= erode_out_valid_dly2;
erode_out_hs_dly1 <= data_in_hs;
erode_out_hs_dly2 <= erode_out_hs_dly1;
erode_out_hs <= erode_out_hs_dly2;
erode_out_vs_dly1 <= data_in_vs;
erode_out_vs_dly2 <= erode_out_vs_dly1;
erode_out_vs <= erode_out_vs_dly2;
end
endmodule
也是为了进一步的降噪 腐蚀操作针对二值化数据,这也是为什么最后才做均值滤波,均值滤波之后,就不是二值化数据了
均值滤波:
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2024/05/11 19:32:21
// Design Name:
// Module Name: mean_filter
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module mean_filter
(
input wire sys_clk , //LCD驱动时钟
input wire sys_rst_n , //系统复位
input wire data_in_valid , //输入数据有效信号
input wire [7:0] data_in , //输入数据
input wire data_in_hs , //输入数据水平同步信号
input wire data_in_vs , //输入数据垂直同步信号
// input wire data_in_de , //输入数据使能信号
// output reg data_out_de , //输出数据使能信号
output reg data_out_valid , //输出数据有效信号
output wire [7:0] data_out , //输出数据
output reg data_out_hs , //输出数据水平同步信号
output reg data_out_vs //输出数据垂直同步信号
);
//wire define
//line data
wire [7:0] line0_data;
wire [7:0] line1_data;
wire [7:0] line2_data;
//reg define
//matrix 3x3 data
reg [7:0] row0_col0;
reg [7:0] row0_col1;
reg [7:0] row0_col2;
reg [7:0] row1_col0;
reg [7:0] row1_col1;
reg [7:0] row1_col2;
reg [7:0] row2_col0;
reg [7:0] row2_col1;
reg [7:0] row2_col2;
reg [10:0] sum_data ;
reg data_in_valid_dly1 ;
reg data_in_hs_dly1 ;
reg data_in_vs_dly1 ;
//reg data_in_de_dly1 ;
assign line2_data = data_in;
//----------------------------------------------------
// matrix 3x3 data
// row0_col0 row0_col1 row0_col2
// row1_col0 row1_col1 row1_col2
// row2_col0 row2_col1 row2_col2
//----------------------------------------------------
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)begin
row0_col0 <= 'd0;
row0_col1 <= 'd0;
row0_col2 <= 'd0;
row1_col0 <= 'd0;
row1_col1 <= 'd0;
row1_col2 <= 'd0;
row2_col0 <= 'd0;
row2_col1 <= 'd0;
row2_col2 <= 'd0;
end
else if((!data_in_hs) && (!data_in_vs))begin
if(data_in_valid) begin
row0_col2 <= line0_data;
row0_col1 <= row0_col2;
row0_col0 <= row0_col1;
row1_col2 <= line1_data;
row1_col1 <= row1_col2;
row1_col0 <= row1_col1;
row2_col2 <= line2_data;
row2_col1 <= row2_col2;
row2_col0 <= row2_col1;
end
else begin
row0_col2 <= row0_col2;
row0_col1 <= row0_col1;
row0_col0 <= row0_col0;
row1_col2 <= row1_col2;
row1_col1 <= row1_col1;
row1_col0 <= row1_col0;
row2_col2 <= row2_col2;
row2_col1 <= row2_col1;
row2_col0 <= row2_col0;
end
end
else begin
row0_col0 <= 'd0;
row0_col1 <= 'd0;
row0_col2 <= 'd0;
row1_col0 <= 'd0;
row1_col1 <= 'd0;
row1_col2 <= 'd0;
row2_col0 <= 'd0;
row2_col1 <= 'd0;
row2_col2 <= 'd0;
end
end
//----------------------------------------------------
//赋值需要一个周期 则需要将输入的信号延迟一个周期
//----------------------------------------------------
always @(posedge sys_clk) begin
data_in_valid_dly1 <= data_in_valid;
data_in_hs_dly1 <= data_in_hs;
data_in_vs_dly1 <= data_in_vs;
// data_in_de_dly1 <= data_in_de;
end
//----------------------------------------------------
// 计算3x3矩阵的和 乘以3641
//----------------------------------------------------
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)begin
sum_data <= 'd0;
end
else if(data_in_valid_dly1) begin
sum_data <= (row0_col0 + row0_col1 + row0_col2
+ row1_col0 + row1_col2
+ row2_col0 + row2_col1 + row2_col2);
end
else begin
sum_data <= sum_data;
end
end
assign data_out = sum_data>>3; //对data_sum右移15位,并且进行四舍五入
//----------------------------------------------------
//输出信号赋值
//----------------------------------------------------
always @(posedge sys_clk) begin
data_out_valid <= data_in_valid_dly1;
data_out_hs <= data_in_hs_dly1;
data_out_vs <= data_in_vs_dly1;
//data_out_de <= data_in_de_dly1;
end
//----------------------------------------------------
//获得maxtrix 3x3 data
//----------------------------------------------------
shift_register_2taps shift_register_mean(
.sys_clk (sys_clk ), //输入时钟
.shiftin (data_in ), //输入数据
.shiftin_valid (data_in_valid ), //输入数据有效信号
.shiftout ( ),
.taps0x (line0_data ), //输出数据
.taps1x (line1_data ) //输出数据
);
endmodule
边界判断 参考:
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
//例化程序示例
// face_box_select#(
// .IMG_WIDTH (IMG_WIDTH ), //图片宽度
// .IMG_HEIGHT (IMG_HEIGHT) //图片长度
// )
// inst_face_box_select
// (
// .sys_clk (sys_clk ),
// .sys_rst_n (sys_rst_n ),
// //input------------------------------------------
// .rgb888 (rgb888 ), //原始数据 24bit
// .face_de (face_de ), //人脸数据de使能信号
// .face_hsync (face_hsync), //人脸数据行同步信号
// .face_vsync (face_vsync), //人脸数据场同步信号
// .face_data_1 (face_data_1 ), //人脸数据 24bit
// //output-----------------------------------------
// .box_de (box_de ),
// .box_hsync (box_hsync ), //行同步信号
// .box_vsync (box_vsync ), //场同步信号
// .box_data (box_data ) //24bit
// );
//
// Create Date: 2024/04/02 12:39:46
// Design Name: FRZ
// Module Name: face_box_select
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
// 输入原始数据以及处理过的人脸数据,输出被框选住人脸的原始数据
// Dependencies:
// [email protected]
// https://www.cnblogs.com/fangrunze
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module face_box_select#(
parameter IMG_WIDTH = 'd480 , //图片宽度
parameter IMG_HEIGHT = 'd272 //图片长度
)
(
input wire sys_clk ,
input wire sys_rst_n ,
//input----------------------------------
input wire [23:0] rgb888 , //原始数据
input wire rgb_de ,
input wire rgb_hsync , //行同步信号
input wire rgb_vsync , //场同步信号
input wire face_de , //人脸数据de使能信号
input wire face_hsync , //人脸数据行同步信号 低电平有效
input wire face_vsync , //人脸数据场同步信号 低电平有效
input wire [7:0] face_data , //人脸数据
//output---------------------------------
output reg box_de ,
output reg box_hsync , //行同步信号 低电平有效
output reg box_vsync , //场同步信号 低电平有效
output reg [23:0] box_data //24bit
);
//reg define
reg face_vsync_r ; //场同步信号延时一拍
reg [11:0] face_x ; //人脸数据坐标
reg [11:0] face_y ;
reg [11:0] face_x_min ; //人脸范围
reg [11:0] face_x_max ;
reg [11:0] face_y_min ;
reg [11:0] face_y_max ;
reg [11:0] reg_face_x_min ;
reg [11:0] reg_face_x_max ;
reg [11:0] reg_face_y_min ;
reg [11:0] reg_face_y_max ;
reg [23:0] rgb888_r1 ;
reg [23:0] rgb888_r2 ;
reg [23:0] rgb888_r3 ;
reg [23:0] rgb888_r4 ;
//wire define
wire pose_face_vsync ; //场同步信号上升沿
wire nege_face_vsync ; //场同步信号下降沿
//=========================================================================
//==场同步信号上升、下降沿检测
//=========================================================================
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
face_vsync_r <= 1'b0 ;
else
face_vsync_r <= face_vsync ;
end
assign pose_face_vsync = face_vsync && (~face_vsync_r) ; //场同步信号上升沿
assign nege_face_vsync = (~face_vsync) && face_vsync_r ; //场同步信号下降沿
//=========================================================================
//==输入人脸像素数据坐标
//=========================================================================
//x坐标
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
face_x <= 1'b0;
else if(face_de) begin
if(face_x == IMG_WIDTH - 1'b1)
face_x <= 1'b0;
else
face_x <= face_x + 1'b1;
end
else
face_x <= face_x;
end
//y坐标
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
face_y <= 1'b0;
else if(face_x == IMG_WIDTH - 1'b1 && face_de)begin
if(face_y == IMG_HEIGHT - 1'b1)
face_y <= 1'b0;
else
face_y <= face_y + 1'b1;
end
else
face_y <= face_y;
end
//=========================================================================
//==确定人脸的x、y坐标范围
//=========================================================================
wire [7:0] face_data_1;
assign face_data_1 = (face_y > 1)? face_data : 8'h00; //人脸数据第两行无效 移位寄存器特性
//face_x_min
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
face_x_min <= IMG_WIDTH;
else if(nege_face_vsync)
face_x_min <= IMG_WIDTH;
else if(face_data_1 == 8'hFF && face_x_min > face_x && face_de)
face_x_min <= face_x;
end
//face_x_max
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
face_x_max <= 1'b0;
else if(nege_face_vsync)
face_x_max <= 1'b0;
else if(face_data_1 == 8'hFF && face_x_max < face_x && face_de)
face_x_max <= face_x;
end
//face_y_min
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
face_y_min <= IMG_HEIGHT;
else if(nege_face_vsync)
face_y_min <= IMG_HEIGHT;
else if(face_data_1 == 8'hFF && face_y_min > face_y && face_de)
face_y_min <= face_y;
end
//face_y_max
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
face_y_max <= 1'b0;
else if(nege_face_vsync)
face_y_max <= 1'b0;
else if(face_data_1 == 8'hFF && face_y_max < face_y && face_de)
face_y_max <= face_y;
end
//=========================================================================
//==保存坐标
//=========================================================================
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)begin
reg_face_x_min <= 1'b0;
reg_face_x_max <= 1'b0;
reg_face_y_min <= 1'b0;
reg_face_y_max <= 1'b0;
end
else if(pose_face_vsync)begin
reg_face_x_min <= face_x_min;
reg_face_x_max <= face_x_max;
reg_face_y_min <= face_y_min;
reg_face_y_max <= face_y_max;
end
else begin
reg_face_x_min <= reg_face_x_min;
reg_face_x_max <= reg_face_x_max;
reg_face_y_min <= reg_face_y_min;
reg_face_y_max <= reg_face_y_max;
end
end
////=========================================================================
////==原始数据数据延时4拍 rgb转YCbCr算法延时4拍
////=========================================================================
//always @(posedge sys_clk or negedge sys_rst_n) begin
// if(!sys_rst_n)begin
// rgb888_r1 <= 1'b0;
// rgb888_r2 <= 1'b0;
// rgb888_r3 <= 1'b0;
// rgb888_r4 <= 1'b0;
// end
// else begin
// rgb888_r1 <= rgb888 ;
// rgb888_r2 <= rgb888_r1;
// rgb888_r3 <= rgb888_r2;
// rgb888_r4 <= rgb888_r3;
// end
//end
//
//=========================================================================
//==处理后数据输出
//=========================================================================
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)begin
box_de <= 1'b0;
box_hsync <= 1'b0;
box_vsync <= 1'b0;
box_data <= 1'b0;
end
else if(face_x == reg_face_x_min
&& face_y>=reg_face_y_min && face_y<=reg_face_y_max )begin
box_de <= rgb_de ;
box_hsync <= rgb_hsync ;
box_vsync <= rgb_vsync ;
box_data <= 24'h00FF00 ; //绿色边框
end
else if(face_x == reg_face_x_max
&& face_y>=reg_face_y_min && face_y<=reg_face_y_max )begin
box_de <= rgb_de ;
box_hsync <= rgb_hsync ;
box_vsync <= rgb_vsync ;
box_data <= 24'h00FF00 ; //绿色边框
end
else if(face_y == reg_face_y_min
&& face_x>=reg_face_x_min && face_x<=reg_face_x_max )begin
box_de <= rgb_de ;
box_hsync <= rgb_hsync ;
box_vsync <= rgb_vsync ;
box_data <= 24'h00FF00 ; //绿色边框
end
else if(face_y == reg_face_y_max
&& face_x>=reg_face_x_min && face_x<=reg_face_x_max )begin
box_de <= rgb_de ;
box_hsync <= rgb_hsync ;
box_vsync <= rgb_vsync ;
box_data <= 24'h00FF00 ; //绿色边框
end
else begin
box_de <= rgb_de ;
box_hsync <= rgb_hsync ;
box_vsync <= rgb_vsync ;
box_data <= rgb888 ; //绿色边框
end
end
endmodule
FPGA实现人脸检测 - 咸鱼IC - 博客园
注意:边界判断时需要注意输入的hs、vs信号为低电平有效,还是高电平有效 上边说的移位寄存器的问题使用:assign face_data_1 = (face_y > 1)? face_data : 8'h00; //人脸数据第两行无效 移位寄存器特性 解决
工程源码:
链接: https://pan.baidu.com/s/1iHwRqPcZJTe4-9pRzRU0-w?pwd=fdt6 提取码: fdt6 复制这段内容后打开百度网盘手机App,操作更方便哦
所有的代码 均在这里,大家自取。
上文中涉及的小梅哥的教材
链接: https://pan.baidu.com/s/1o9pmkgHDI3qkS16UKT9soQ?pwd=9kec 提取码: 9kec 复制这段内容后打开百度网盘手机App,操作更方便哦