FPGA图像处理学习(人脸识别、追踪、转换)

基于FPGA:人脸识别

获取人脸图像——肤色提取(Ycbcr+阈值)——滤波处理(中值、腐蚀膨胀)——人脸框选——显示

肤色提取:顾名思义,将肤色从外界环境中提取出。在肤色识别算法中,常用YCbCr颜色空间(亮度、蓝色、红色分量),因为肤色在 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
View Code

第四级肤色识别:

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
View Code

 

RGB565原图数据,内部进行Ycbcr转换,分别得到8位的Y,Cb,Cr分量,后根据蓝红分量的阈值得到16位的二值化肤色数据face_data,阈值内为白色,阈值外为黑色

检测出肤色后,为提高图像质量,进行中值滤波、腐蚀膨胀处理。

肤色检测出人脸后,我们用行列坐标画框,将人脸框选出来,最终人脸框和图像数据同时输出,原图图像数据是16位,因此前面肤色数据face_data也用的16位。RGB信号:原图数据、使能以及行场有效信号。
face信号:人脸肤色提取后的图像数据、使能以及行场有效信号。

 因为两帧图像差别较小,因此我们将人脸肤色图像分两帧来处理,通过这两帧图像得到人脸框的坐标,这样可防止图像结果偏移的情况出现。其中第一帧得到框的四个顶点坐标,当前帧的输出即可实时的使用人脸框的四个顶点坐标。

always @(posedge clk) begin
    face_vsync_r <= face_vsync;
end
assign pos_vsync =  face_vsync && ~face_vsync_r;
assign neg_vsync = ~face_vsync &&  face_vsync_r;
View Code

 人脸图像纵坐标:

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;
View Code

 

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
View Code
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
View Code

原图行列技术器

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;
View Code

人脸原图输出:

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
View Code

前一帧获得AB

always @(posedge clk) begin
    face_vsync_r <= face_vsync;
end
View Code

图像流经未开始或者结束的时候,最小像素值255,最大像素值0;当图像流过且像素有效时候,开始进行像素比较,得到一帧图像中的最小最大像素值。

always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        max <= 8'd0;
        min <= 8'd255;
    end
    else if(Y_vsync && Y_de) begin     //像素有效时
        max <= (max > Y_data) ? max : Y_data;
        min <= (min < Y_data) ? min : Y_data;
    end
    else if(neg_Y_vsync) begin         //一帧图像结束时
        max <= 8'd0;
        min <= 8'd255;
    end
end
View Code

 

 

基于FPGA:RGB转YCBCR

//RGB分量转Gray灰度图
module RGB_Gray
//========================< 端口 >==========================================
(
input   wire                clk                        ,    //时钟
input   wire                rst_n                       ,    //复位
//原图 ----------------------------------------------
input   wire                RGB_hsync                ,
input   wire                RGB_vsync                ,
input   wire    [23:0]  RGB_data                   ,
input   wire                RGB_de                    ,
//灰度转换图 ----------------------------------------------
output  wire                gray_hsync                  , //这里的行场同步信号也就是最终的VGA行场同步信号。
output  wire                gray_vsync                , //需要根据实际消耗的时钟进行延迟
output  wire    [23:0]    gray_data                ,
output  wire                gray_de
);

wire [7:0]  R0,G0,B0;

reg [15:0]  R1,G1,B1;
reg [15:0]  R2,G2,B2;
reg [15:0]  R3,G3,B3;

reg [16:0] Y1,Cb1,Cr1;
reg [23:0]  Y2,Cb2,Cr2;

reg [7:0]  RGB_de_r     ;  
reg [7:0]  RGB_hsync_r  ;
reg [7:0]  RGB_vsync_r  ;

//将24位RGB分成三分量

assign R0 = RGB_data[23:16];
assign G0 = RGB_data[15:8];
assign B0 = RGB_data[7:0];

//=============根据RGB转Ycbcr的公式进行计算(三级流水线)==================
//---------------------------------------------------
//clk 1 第一级流水线完成所有的乘法计算

always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        {R1,G1,B1} <= {16'd0, 16'd0, 16'd0};
        {R2,G2,B2} <= {16'd0, 16'd0, 16'd0};
        {R3,G3,B3} <= {16'd0, 16'd0, 16'd0};
    end
    else begin
        {R1,G1,B1} <= { {R0 * 16'd77},  {G0 * 16'd150}, {B0 * 16'd29 } };
        {R2,G2,B2} <= { {R0 * 16'd43},  {G0 * 16'd85},  {B0 * 16'd128} };
        {R3,G3,B3} <= { {R0 * 16'd128}, {G0 * 16'd107}, {B0 * 16'd21 } };
    end
end
//---------------------------------------------------
//clk 2 第二级流水线完成所有的加减法计算

always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        Y1  <= 16'd0;
        Cb1 <= 16'd0;
        Cr1 <= 16'd0;
    end
    else begin
        Y1  <= R1 + G1 + B1;
        Cb1 <= B2 - R2 - G2 + 16'd32768; //128扩大256倍
        Cr1 <= R3 - G3 - B3 + 16'd32768; //128扩大256倍
    end
end
//---------------------------------------------------
//clk 3 第三级流水线完成所有的移位计算(缩小256倍) 右移8位

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 >> 8;  
        Cb2 <= Cb1>> 8;
        Cr2 <= Cr1>> 8;
    end
end

// 取YcbCr三分量中的Y分量,将其赋值给RGB888通道即可。
assign gray_data = {Y2[7:0],Y2[7:0],Y2[7:0]}; 

//=================== 信号同步==================================
//为确保图像能正常显示,要保持数据与数据使能和行场有效信号同步
//前面我们花费了三个clk来计算,因此延迟三拍


always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        RGB_de_r    <= 3'b0;
        RGB_hsync_r <= 3'b0;
        RGB_vsync_r <= 3'b0;
    end
    else begin  
        RGB_de_r    <= {RGB_de_r[1:0],    RGB_de};
        RGB_hsync_r <= {RGB_hsync_r[1:0], RGB_hsync};
        RGB_vsync_r <= {RGB_vsync_r[1:0], RGB_vsync};
    end
end


assign gray_de    = RGB_de_r[2];
assign gray_hsync = RGB_hsync_r[2];
assign gray_vsync = RGB_vsync_r[2];

endmodule
View Code

基于FPGA:运动目标检测

由于FPGA不善于处理浮点数;这里完成了浮点数向定点数的转换;各个系数均扩大256倍,最后在计算完之后除以256,如公式

Y = 0.299 R + 0.587 G + 0.114 B
U = - 0.1687 R - 0.3313 G + 0.5 B + 128    (1)
V = 0.5 R - 0.4187 G - 0.0813 B + 128
Y=(77*R+150*G+29*B)»8;
U =(-43R-85G+128B+128*2568    (2)
V=(128R-107G-21B+128*2568
View Code

 

在Verlog里面,使用组合逻辑不能直接按上述公式(2)在计算,不然组合逻辑延时太大,导致时序不收敛,这里就需要添加寄存器来切割流水线;利用FPGA 并行处理的特点加速计算。这里分三级流水线处理:第一级流水线计算所有乘法; 第二级流水线计算所有加法,把正的和负的分开进行加法;第三级流水线计算最终的和,若为负数取0。输入到输岀有三个clock的时延仿真波形如下图所示

 

always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        begin
        {matrix_p11, matrix_p12, matrix_p13} <= 3'b0;
        {matrix_p21, matrix_p22, matrix_p23} <= 3'b0;
        {matrix_p31, matrix_p32, matrix_p33} <= 3'b0;
        end
    else if(read_frame_href)
        begin
        if(read_frame_clken)    //Shift_RAM data read clock enable
            begin
            {matrix_p11, matrix_p12, matrix_p13} <= {matrix_p12, matrix_p13, row1_data};    //1th shift input
            {matrix_p21, matrix_p22, matrix_p23} <= {matrix_p22, matrix_p23, row2_data};    //2th shift input
            {matrix_p31, matrix_p32, matrix_p33} <= {matrix_p32, matrix_p33, row3_data};    //3th shift input
            end
        else
            begin
            {matrix_p11, matrix_p12, matrix_p13} <= {matrix_p11, matrix_p12, matrix_p13};
            {matrix_p21, matrix_p22, matrix_p23} <= {matrix_p21, matrix_p22, matrix_p23};
            {matrix_p31, matrix_p32, matrix_p33} <= {matrix_p31, matrix_p32, matrix_p33};
            end    
        end
View Code

 

通过行场信号,设计行列计数器,从而可以获取图像每个像素点的坐标信息,然后设计4个寄存器分别是edg_up 、 edg_down、edg_left、edg_right目标的上下左右四个点,实时与行列计数器比较,也就是求最大值( edg_down和edg_right)和最小值(edg_up和edg_left),代码如下所示:

always@(posedge clk or negedge rst_n)
begin
    if(!rst_n) begin
      edg_up    <=  10'd479;
      edg_down  <=  10'd0;
      edg_left  <=  10'd639;
      edg_right <=  10'd0;
     end
    else if(vsync_rising) begin
       edg_up    <=  10'd479;
      edg_down  <=  10'd0;
      edg_left  <=  10'd639;
      edg_right <=  10'd0;
    end
   else if(per_frame_clken & per_frame_href)begin
      if(per_img_Y == 1'b1) begin
         if(edg_up > v_cnt)
           edg_up  <=v_cnt ;
         else 
          edg_up  <=edg_up ;    
    
         if(edg_down < v_cnt)
           edg_down  <=v_cnt ;
         else 
          edg_down  <=edg_down ;    
             
          if(edg_left > h_cnt)
           edg_left  <= h_cnt ;
         else 
          edg_left  <=edg_left ;    
    
         if(edg_right < h_cnt)
           edg_right  <=h_cnt ;
         else 
          edg_right  <=edg_right ;             
      end
    end
end 
View Code

 

基于 FPGA 的图像边缘检测系统设计(sobel算法)

设计流程如下:

mif文件的制作→ 调用ip核生成rom以及仿真注意问题→ 灰度处理→ 均值滤波:重点是3*3 像素阵列的生成→ sobel边缘检测→ 图片的显示→ 结果展示 

reg [5:0]p_11,p_12,p_13;  // 3 * 3 卷积核中的像素点
reg [5:0]p_21,p_22,p_23;
reg [5:0]p_31,p_32,p_33;
reg [8:0]mean_value_add1,mean_value_add2,mean_value_add3;//每一行之和

always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        {p_11,p_12,p_13} <= {5'b0,5'b0,5'b0}   ;
        {p_21,p_22,p_23} <= {15'b0,15'b0,15'b0};
        {p_31,p_32,p_33} <= {15'b0,15'b0,15'b0};
    end
    else  begin
     if(per_href_ff0==1&&flag_do==1)begin
        {p_11,p_12,p_13}<={p_12,p_13,row_1};
        {p_21,p_22,p_23}<={p_22,p_23,row_2};
        {p_31,p_32,p_33}<={p_32,p_33,row_3};
     end
     else begin
         {p_11,p_12,p_13}<={5'b0,5'b0,5'b0};
         {p_21,p_22,p_23}<={5'b0,5'b0,5'b0}
         {p_31,p_32,p_33}<={5'b0,5'b0,5'b0}
     end
   end
end

always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        mean_value_add1<=0;
        mean_value_add2<=0;
        mean_value_add3<=0;
    end
    else if(per_href_ff1)begin
        mean_value_add1<=p_11+p_12+p_13;
        mean_value_add2<=p_21+   0   +p_23;
        mean_value_add3<=p_31+p_32+p_33;
    end
end

wire [8:0]mean_value;//8位数之和
wire [5:0]fin_y_data; //平均数,除以8,相当于左移三位。

assign mean_value=mean_value_add1+mean_value_add2+mean_value_add3;
assign fin_y_data=mean_value[8:3]; 
View Code
reg [8:0] p_x_data ,p_y_data ;  // x 和 y 的正值之和
reg [8:0] n_x_data ,n_y_data ; // x 和 y 的负值之和
reg [8:0] gx_data  ,gy_data  ; //最终结果

always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
       p_x_data <=0;
       n_x_data <=0;
       gx_data   <=0;
    end
    else if(per_href_ff1==1) begin 
        p_x_data <= p_13 + (p_23<<1) + p_33 ;
        n_x_data <= p_11 + (p_12<<1 )+ p_13 ;
        gx_data   <= (p_x_data >=n_x_data)? p_x_data - n_x_data : n_x_data - p_x_data ; 
    end
    else begin
         p_x_data<=0;
         n_x_data<=0;
         gx_data <=0;
    end  
end

always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
       p_y_data <=0;
       n_y_data <=0;
       gy_data   <=0;
    end
    else if(per_href_ff1==1) begin
        p_y_data <= p_11 + (p_12<<1) + p_13 ;
        n_y_data <= p_31 + (p_32<<1) + p_33 ;
        gy_data   <= (p_y_data >=n_y_data)? p_y_data - n_y_data : n_y_data - p_y_data ; 
    end
    else begin
        p_y_data <=0;
        n_y_data <=0;
        gy_data   <=0;
   end
end

//求平方和,调用ip核开平方
reg [16:0] gxy; // Gx 与 Gy 的平方和
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        gxy<=0;
    end
    else begin
        gxy<= gy_data* gy_data + gx_data* gx_data ;
    end
end

wire [8:0] squart_out ; 
altsquart  u1_altsquart (     //例化开平方的ip核
    .radical (gxy),
    .q       (squart_out),  //输出的结果
    .remainder()
                       );

//与阈值进行比较
reg [15:0] post_y_data_r;
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        post_y_data_r<=16'h00;
    end
    else if(squart_out>=threshold)
         post_y_data_r<=16'h00  ;
    else
         post_y_data_r<=16'hffff  ;
   
end
View Code

以上算子包含两组 3x3 的矩阵,分别为横向及纵向,将之与图像作平面卷积,即可分别得出横向及纵向的亮度差分近似值。A代表原始图像的 3*3 像素阵列,Gx及Gy分别代表经横向及纵向边缘检测的图像

数据流输出保存txt文件:

integer w_file;  
     initial
     w_file = $fopen("data_out_3.txt");   //保存数据的文件名

     always @(posedge clk or negedge rst_n)  
     begin  
      if(flag_write==1&&post_href==1)//根据自己的需求定义
        $fdisplay(w_file,"%b",post_y_data);   
      end      
View Code

 

Vivado DDS 与 FIR IP核设计 FIR 数字滤波器系统

posted @ 2024-02-08 11:31  有翅膀的大象  阅读(202)  评论(0编辑  收藏  举报