---
title: FPGA实现人脸检测
url: "https://www.fpgalearn.com/9626.html"
kind: article
id: 9626
excerpt: 一、肤色提取 　　首先我们需要把肤色从外界环境提取出来，在肤色识别算法中，常用的颜色空间为YCbCr，Y 代表亮度，Cb 代表蓝色分量，Cr 代表红色分量。肤色…
published_at: 2025-08-03
author: 二牛学FPGA
tags: FPGA工程, fpga教程, 人脸检测
word_count: 1319
source: "https://www.fpgalearn.com"
---

# FPGA实现人脸检测

> 标签：FPGA工程、fpga教程、人脸检测
> 一、肤色提取 　　首先我们需要把肤色从外界环境提取出来，在肤色识别算法中，常用的颜色空间为YCbCr，Y 代表亮度，Cb 代表蓝色分量，Cr 代表红色分量。肤色…

> 一、肤色提取 　　首先我们需要把肤色从外界环境提取出来，在肤色识别算法中，常用的颜色空间为YCbCr，Y 代表亮度，Cb 代表蓝色分量，Cr 代表红色分量。肤色…

# 一、肤色提取
首先我们需要把肤色从外界环境提取出来，在肤色识别算法中，常用的颜色空间为YCbCr，Y 代表亮度，Cb 代表蓝色分量，Cr 代表红色分量。肤色在 YCbCr 空间受亮度信息的影响较小，本算法直接考虑 YCbCr 空间的CbCr 分量，映射为两维独立分布的 CbCr 空间。在 CbCr 空间下，肤色类聚性好，利用人工阈值法将肤色与非肤色区域分开，形成二值图像。
根据经验，对肤色进行提取的条件场用如下不等式：
77 < Cb < 127，133 < Cr < 173.
代码基于 RGB_YCbCr_Gray，在最后本该输出灰度数据时，修改为输出肤色数据：
```
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        face_data  77) && (Cb2  133) && (Cr2 < 173) ) begin
        face_data <= 16'hffff;
    end
    else begin
        face_data <= 'h0;
    end
end
```
用一副图片试试，原图如下所示：
![图片](https://admin.shaonianxue.cn/wp-content/uploads/2025/08/1536533-20200328181432257-1941552239.jpg)
肤色提取后结果如下所示：
![图片](https://admin.shaonianxue.cn/wp-content/uploads/2025/08/1536533-20200328181455066-725100012.jpg)
# 二、滤波处理
图片还好，如果是摄像头数据会有很多噪声，针对噪声，我们可以用之前整理过的中值滤波、高斯滤波等处理。
此外人脸内部还会有些黑点，包括人脸外的环境可能有些地方也会被误检测为人脸，造成实验失败，因此可以加入形态学处理：腐蚀、膨胀、开运算、闭运算等，这些之前都整理过，不展开说了。
# 三、人脸框选
现在我们要用一个框将人脸框住，达到人脸检测的目的。
```
//==========================================================================
//==    行列划分
//==========================================================================
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        x <= 10'd0;
    else if(add_x) begin
        if(end_x)
            x <= 10'd0;
        else
            x <= x + 10'd1;
    end
end

assign add_x = face_de;
assign end_x = add_x && x== COL-10'd1;

always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        y <= 10'd0;
    else if(add_y) begin
        if(end_y)
            y <= 10'd0;
        else
            y <= y + 10'd1;
    end
end

assign add_y = end_x;
assign end_y = add_y && y== ROW-10'd1;
//==========================================================================
//==    前一帧：人脸框选
//==========================================================================
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 > x) begin
        x_min <= 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 < x) begin
        x_max <= 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 > y) begin
        y_min <= 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 < y) begin
        y_max <= y;
    end
end
//==========================================================================
//==    前一帧结束：保存坐标值
//==========================================================================
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
//==========================================================================
//==    当前帧：数据输出
//==========================================================================
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        TFT_data <= 16'b0;
    end
    else if(TFT_y == y_min_r && TFT_x >= x_min_r && TFT_x <= x_max_r) begin
        TFT_data <= 16'b00000_111111_00000;
    end
    else if(TFT_y == y_max_r && TFT_x >= x_min_r && TFT_x <= x_max_r) begin
        TFT_data <= 16'b00000_111111_00000;
    end
    else if(TFT_x == x_min_r && TFT_y >= y_min_r && TFT_y <= y_max_r) begin
        TFT_data <= 16'b00000_111111_00000;
    end
    else if(TFT_x == x_max_r && TFT_y >= y_min_r && TFT_y <= y_max_r) begin
        TFT_data <= 16'b00000_111111_00000;
    end
    else begin
        TFT_data <= RGB_data;
    end
end
```
x 和 y为图像的实时坐标值，TFT_x 和 TFT_y 为 TFT_driver 生成的坐标值，这两个是不一样的。如果二者一样，最后的图像会有偏移。框的四个顶点坐标代码挺有意思，一开始很难理解，带几个数去看看就明白了，这段代码挺巧妙的，也挺简洁的。总体的思想和直方图拉伸很像，分两帧来处理，第一帧得到顶点坐标，当前帧的输出则实时的使用这个顶点坐标，因为两帧图像的差别很小，所以这么做比较方便。
要注意的是每次扫描一帧后，顶点坐标要变回初始值，否则会出错，这点在图片的处理上体会不到什么，感觉不出bug，但是移植到摄像头视频数据时，不变回初始值就会有问题。这里的时序挺有意思，一开始我以为要打拍什么的，后面发现其实得到的是一个坐标值，坐标值本身在一帧结束到下一帧结束这段时间里是固定的，没必要打拍，但是要寄存住，和直方图拉伸一样。
最终输出的结果是一个绿色的矩形框，非矩形框区域则输出原始视频数据，效果如下所示：
![图片](https://admin.shaonianxue.cn/wp-content/uploads/2025/08/1536533-20200328183144747-1127819260.jpg)
板卡坏了哈，本来颜色很好的。
# 四、基于 OV7670 的人脸检测工程
算法方面直接移植即可，注意的是，OV7670摄像头很差劲，噪声很多，需要进行一定的滤波处理，否则效果很差劲。
我一开始直接移植写好的图像版本的主要代码，结果基本没有效果，以为是程序出错，检查了半天没找到毛病。后面将显示改成“框+二值图”，终于发现了屏幕上全是椒盐噪声，难怪没法成功，而如果用 OV7725 或 OV5640 等摄像头，这样的问题应该没这么严重。设备不给力，算法来使劲，我在肤色提取后连续进行了三次中值滤波去噪，然后用了一次腐蚀算法，最终的效果才勉强成功。
视频演示如下所示，不想上镜，人脸改成手来测试：
由于摄像头太差，加上板卡出现问题，颜色失真，导致看起来不漂亮，但总的结果还是对的。
# 五、基于OV7725的人脸检测工程
回学校修好了板卡，换了 OV7725 摄像头，效果好多了。
