FPGA實現人臉檢測


  之前的博客都是基本的圖像處理,本篇博客整理一下用 FPGA 實現人臉檢測的方法,工程比較有趣。

 

一、膚色提取

  首先我們需要把膚色從外界環境提取出來,在膚色識別算法中,常用的顏色空間為YCbCr,Y 代表亮度,Cb 代表藍色分量,Cr 代表紅色分量。膚色在 YCbCr 空間受亮度信息的影響較小,本算法直接考慮 YCbCr 空間的CbCr 分量,映射為兩維獨立分布的 CbCr 空間。在 CbCr 空間下,膚色類聚性好,利用人工閾值法將膚色與非膚色區域分開,形成二值圖像。

  RGB 轉 YCbCr的實現參照之前博客《FPGA實現圖像灰度轉換(2):RGB轉YCbCr轉Gray》。

  根據經驗,對膚色進行提取的條件場用如下不等式:

  77 < Cb < 127,133 < Cr < 173.

  代碼基於 RGB_YCbCr_Gray,在最后本該輸出灰度數據時,修改為輸出膚色數據:

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

  用一副圖片試試,原圖如下所示:

  膚色提取后結果如下所示:

 

二、濾波處理

  圖片還好,如果是攝像頭數據會有很多噪聲,針對噪聲,我們可以用之前整理過的中值濾波、高斯濾波等處理。

  此外人臉內部還會有些黑點,包括人臉外的環境可能有些地方也會被誤檢測為人臉,造成實驗失敗,因此可以加入形態學處理:腐蝕、膨脹、開運算、閉運算等,這些之前都整理過,不展開說了。

 

三、人臉框選

  現在我們要用一個框將人臉框住,達到人臉檢測的目的。

//==========================================================================
//==    行列划分
//==========================================================================
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,但是移植到攝像頭視頻數據時,不變回初始值就會有問題。這里的時序挺有意思,一開始我以為要打拍什么的,后面發現其實得到的是一個坐標值,坐標值本身在一幀結束到下一幀結束這段時間里是固定的,沒必要打拍,但是要寄存住,和直方圖拉伸一樣。

  最終輸出的結果是一個綠色的矩形框,非矩形框區域則輸出原始視頻數據,效果如下所示:

  板卡壞了哈,本來顏色很好的。

 

四、基於 OV7670 的人臉檢測工程

  算法方面直接移植即可,注意的是,OV7670攝像頭很差勁,噪聲很多,需要進行一定的濾波處理,否則效果很差勁。

  我一開始直接移植寫好的圖像版本的主要代碼,結果基本沒有效果,以為是程序出錯,檢查了半天沒找到毛病。后面將顯示改成“框+二值圖”,終於發現了屏幕上全是椒鹽噪聲,難怪沒法成功,而如果用 OV7725 或 OV5640 等攝像頭,這樣的問題應該沒這么嚴重。設備不給力,算法來使勁,我在膚色提取后連續進行了三次中值濾波去噪,然后用了一次腐蝕算法,最終的效果才勉強成功。

  視頻演示如下所示,不想上鏡,人臉改成手來測試:

 

  由於攝像頭太差,加上板卡出現問題,顏色失真,導致看起來不漂亮,但總的結果還是對的。

 

五、基於OV7725的人臉檢測工程

  回學校修好了板卡,換了 OV7725 攝像頭,效果好多了。

 

參考資料:

[1]OpenS Lee:FPGA開源工作室(公眾號)

[2]NingHechuan:硅農(公眾號)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM