之前的博客都是基本的圖像處理,本篇博客整理一下用 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:硅農(公眾號)