• FPGA图像处理学习——人脸检测


    本文针对咸鱼FPGA的FPGA实现人脸检测以及直方图拉伸进行原理学习。


    一、人脸检测大致流程

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

    肤色提取:顾名思义,将肤色从外界环境中提取出。在肤色识别算法中,常用YCbCr颜色空间(亮度、蓝色、红色分量),因为肤色在 YCbCr 空间受亮度信息的影响较小,从而肤色类聚性好,由此,在Ycbcr空间基础上,我们用人工阈值法将肤色与非肤色区域分开,最终形成二值图像,实现肤色的提取。

    滤波处理:人脸内部可能存在黑点、人脸外的某些地方也可能会被误检测为人脸,这些情况都会造成识别失败,因此加入中值滤波以及腐蚀、膨胀,这些之前都整理过,不展开说了。
    在这里插入图片描述


    1、人脸肤色检测

    原理:先进行Ycbcr空间转换得到亮度、蓝色、红色分量,给cb和cr设置阈值,即可将肤色提取出来。(共采用四级流水线)

    1.1 Ycbcr三级流水线

    首先三级流水线后,可得到三分量如下:

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    1.2 第四级流水线进行肤色检测

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    RTL图如下:
    可看到,输入是RGB565原图数据,内部进行Ycbcr转换,分别得到8位的Y,Cb,Cr分量,后根据蓝红分量的阈值得到16位的二值化肤色数据face_data,阈值内为白色,阈值外为黑色;
    在这里插入图片描述

    2、滤波处理

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

    3、人脸框选(人脸框和原图一起输出)

    通过肤色检测出人脸后,我们用行列坐标画框,将人脸框选出来,最终人脸框和图像数据同时输出,原图图像数据是16位,因此前面肤色数据face_data也用的16位。

    RGB信号:原图数据、使能以及行场有效信号。
    face信号:人脸肤色提取后的图像数据、使能以及行场有效信号。

    在这里插入图片描述

    3.1 人脸框的四个顶点坐标

    如何得到人脸框的四个顶点坐标?

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

    1、既然要用连续的两帧肤色图像,我们就要对图像延迟一拍。

    always @(posedge clk) begin
        face_vsync_r <= face_vsync;
    end
    
    • 1
    • 2
    • 3

    2、通过连续两帧图像的场有效信号得到最终的边沿信号,上升沿为人脸肤色图像开始标志,下降沿为结束标志。

    assign pos_vsync =  face_vsync && ~face_vsync_r;
    assign neg_vsync = ~face_vsync &&  face_vsync_r;
    
    • 1
    • 2

    在这里插入图片描述

    3.2 人脸图像横纵坐标

    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    3.3 人脸框选

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    至此,得到了人脸框,接下来找该人脸框下对应的原图数据。

    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    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
    	[添加链接描述](https://www.cnblogs.com/xianyufpga/p/12531569.html)end
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    本文主要学习:人脸肤色如何提取(为蓝红分量设置阈值),以及人脸框如何得到(延迟一拍得到连续两帧图像,从而实时地使用这四个顶点坐标,根据四个顶点的横纵坐标值赋予颜色,得到方框)。木有想到一个假期竟然只看了这………………


    二、直方图拉伸

    基于人脸识别获取顶点坐标的方法,对直方图拉伸的图像算法进行分析并编写verilg代码。

    1、直方图拉伸的简单介绍

    参考:在视频图像处理中,为了能够实时调节图像对比度,通常进行直方图拉伸,直方图拉伸是指将图像灰度直方图较窄的灰度级区间向两端拉伸,从而增强整幅图像像素的灰度级对比度,达到增强图像的效果。

    2、直方图拉伸公式

    A:Imin,表示最小灰度级
    B:Imax,表示最大灰度级
    在这里插入图片描述
    和人脸框求顶点坐标相同,直方图拉伸也采用两帧图像进行处理。只有一帧图像流过之后,我们才能得到该图像的最大和最小灰度级,才能进行后续直方图拉伸公式的计算,由于连续两帧图像差别小,我们分成两帧图像。

    方法:
    1、帧延迟一拍,获得前一帧和当前帧
    2、前一帧计算最小最大灰度级AB
    3、在前一帧和当前帧的间隙,保存AB的值
    4、在当前帧计算公式——公式用三级流水线来实现(分子分母,商,大括号)

    2.1 帧延迟并获得AB

    always @(posedge clk) begin
        face_vsync_r <= face_vsync;
    end
    
    • 1
    • 2
    • 3

    获得一帧图像的开始结束标志

    assign pos_vsync =  face_vsync && ~face_vsync_r;
    assign neg_vsync = ~face_vsync &&  face_vsync_r;
    
    • 1
    • 2

    前一帧获得AB
    图像流经未开始或者结束的时候,最小像素值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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    2.2 保存AB值

    如何确定间隙?
    nge_vsnyc高电平处为间隙,此时保存AB值即可。
    在这里插入图片描述

    2.3 拉伸公式计算

    三级流水线:

    第一级流水线:计算分子分母

    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            mole <= 'd0; //分子
            deno <= 'd0; //分母
        end
        else begin
            mole <= (Y_data - Y_min) * 255;
            deno <=  Y_max  - Y_min;
        end
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    第二级流水线:分子分母除法计算

    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            quot <= 'd0;
        end
        else begin
            quot <= mole / deno;
        end
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    第三级流水线:大括号的计算

    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            hist_data <= 8'd0;
        end
        else if(Y_data < Y_min) begin
            hist_data <= 8'd0;
        end
        else if(Y_data > Y_max) begin
            hist_data <= 8'd255;
        end
        else begin
            hist_data <= quot[7:0];
        end
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
  • 相关阅读:
    【JAVA】 图书管理系统(javaSE简易版 内含画图分析) | 期末大作业&课程设计
    性能测试从0到1
    windows系统docker中将vue项目网站部署在nginx上
    万宾科技智能井盖传感器特点介绍
    Java @Data注解 的使用
    庆国庆,拟物地图
    若要对多态类进行深拷贝,应使用虚函数的clone,而不是公开的拷贝构造赋值
    漏洞危害之一
    unity webgl怎么获取当前页面网址
    SQL-正则表达式
  • 原文地址:https://blog.csdn.net/H19981118/article/details/125762837