问答 店铺
热搜: ZYNQ FPGA discuz

QQ登录

只需一步,快速开始

微信登录

微信扫码,快速开始

微信扫一扫 分享朋友圈

已有 27 人浏览分享

开启左侧

FPGA图像处理-基于肤色设别的人脸检测

[复制链接]
27 0
安路-FPGA课程
安路课程: 图像算法 » 图像新手入门实验
安路系列: EG4
本帖最后由 UT发布 于 2025-4-3 10:31 编辑

软件版本:Anlogic -TD5.6.1-64bit
操作系统:WIN10 64bit
硬件平台:适用安路(Anlogic)FPGA
登录米联客”FPGA社区-www.uisrc.com视频课程、答疑解惑!
1概述

本文简述了图像肤色设别的人脸检测的算法,讲解如何进行Verilog的算法实现,并进行上板实验。

2算法原理简介

human skin color clustering for face detection一篇文章中,讲解了如何使用肤色识别进行了人脸检测,算法的推理过程,这里不讲解,感兴趣的可以去看看原文。这里只拿来论文中的结论进行实践,论文中提到了基于RGB颜色空间的肤色识别,判别式如下:

R>95and G>40 and B>20 and R>G and R>B and Max(R,G,B)-Min(R,G,B)>15 and abs(R-G)>15

基于YCbCr颜色空间的肤色识别将图像转换到YCbCr颜色空间,然后按下述计算式判断是否属于皮肤域:

(Cb > 77 and Cb7 < 127)  and (Cr > 133 and Cr < 173)

肤色识别算法计算量小,算法实践简单,缺点也很明显,容易受到外界背景干扰。

具体的计算过程是,将实时的帧图像从RGB转换成YCbCr然后按照上述的肤色识别计算公式,将肤色的区域标记,然后进行图像二值化运算,区分出背景和肤色部分,进行膨胀、腐蚀去除噪点,最后将肤色区域进行框选叠加在实时的视频中。

在框选的算法实现中需要找出两个坐标点,被检测到的肤色形状的左上和右下两个点的坐标,这个两个坐标点的特点是肤色形状的横纵坐标的最大最小值,通过遍历所有点,可以得出这两个坐标点。然后以这两个点得出长方形的框所在行和列,进行颜色的替换。

3工程实现
3.1Verilog代码分析

RGB颜色空间转换Ycbcr的计算过程如下所示:

  1. // ycbcr0(:,:,1)  =  0.2568*image_in_r + 0.5041*image_in_g + 0.0979*image_in_b + 16;
  2. // ycbcr0(:,:,2)  = -0.1482*image_in_r - 0.2910*image_in_g + 0.4392*image_in_b + 128;
  3. // ycbcr0(:,:,3)  =  0.4392*image_in_r - 0.3678*image_in_g - 0.0714*image_in_b + 128;
  4. // ycbcr0(:,:,1)  = 256*( 0.2568*image_in_r + 0.5041*image_in_g + 0.0979*image_in_b + 16 )>>8;
  5. // ycbcr0(:,:,2)  = 256*(-0.1482*image_in_r - 0.2910*image_in_g + 0.4392*image_in_b + 128)>>8;
  6. // ycbcr0(:,:,3)  = 256*( 0.4392*image_in_r - 0.3678*image_in_g - 0.0714*image_in_b + 128)>>8;
  7. // ycbcr0(:,:,1)  = (66*image_in_r + 129*image_in_g + 25*image_in_b + 4096  )>>8;
  8. // ycbcr0(:,:,2)  = (-38*image_in_r - 74*image_in_g + 112*image_in_b + 32768)>>8;
  9. // ycbcr0(:,:,3)  = (112*image_in_r - 94*image_in_g - 18*image_in_b + 32768 )>>8;
  10. 声明变量的名字如下:
  11. reg [15:0] r_d0;
  12. reg [15:0] g_d0;
  13. reg [15:0] b_d0;
  14. reg [15:0] r_d1;
  15. reg [15:0] g_d1;
  16. reg [15:0] b_d1;
  17. reg [15:0] r_d2;
  18. reg [15:0] g_d2;
  19. reg [15:0] b_d2;
  20. reg [15:0] y_d0;
  21. reg [15:0] cb_d0;
  22. reg [15:0] cr_d0;
  23. reg [7:0] y_d1;
  24. reg [7:0] cb_d1;
  25. reg [7:0] cr_d1;
  26. reg [7:0] skin_color_r;
  27. reg [7:0] skin_color_g;
  28. reg [7:0] skin_color_b;
  29. reg [3:0] hsyn;
  30. reg [3:0] vsyn;
  31. reg [3:0] de;
复制代码
执行乘法运算如下:
  1. always@(posedge i_clk or negedge i_rst_n)
  2. begin
  3.     if(!i_rst_n)
  4.         begin
  5.         r_d0 <= 16'd0;
  6.         g_d0 <= 16'd0;
  7.         b_d0 <= 16'd0;
  8.                 r_d1 <= 16'd0;
  9.                 g_d1 <= 16'd0;
  10.                 b_d1 <= 16'd0;
  11.                 r_d2 <= 16'd0;
  12.                 g_d2 <= 16'd0;
  13.                 b_d2 <= 16'd0;        
  14.     end
  15.     else
  16.         begin
  17.         r_d0 <= 66  * i_r;
  18.         g_d0 <= 129 * i_g;
  19.         b_d0 <= 25  * i_b;
  20.                 r_d1 <= 38  * i_r;
  21.                 g_d1 <= 74  * i_g;
  22.                 b_d1 <= 112 * i_b;
  23.                 r_d2 <= 112 * i_r;
  24.                 g_d2 <= 94  * i_g;
  25.                 b_d2 <= 18  * i_b;                        
  26.     end
  27. end
复制代码
执行加法如下:
  1. always@(posedge i_clk or negedge i_rst_n)
  2. begin
  3.     if(!i_rst_n)
  4.         begin
  5.                 y_d0  <= 16'd0;
  6.                 cb_d0 <= 16'd0;
  7.                 cr_d0 <= 16'd0;
  8.     end
  9.     else
  10.         begin
  11.                 y_d0  <= r_d0 + g_d0 + b_d0 + 4096 ;
  12.                 cb_d0 <= b_d1 - r_d1 - g_d1 + 32768;
  13.                 cr_d0 <= r_d2 - g_d2 - b_d2 + 32768;              
  14.     end
  15. end
  16. 求得Ycbcr的值如下:
  17. always@(posedge i_clk or negedge i_rst_n)
  18. begin
  19.     if(!i_rst_n)
  20.         begin
  21.                 y_d1  <= 16'd0;
  22.                 cb_d1 <= 16'd0;
  23.                 cr_d1 <= 16'd0;
  24.     end
  25.     else
  26.         begin
  27.                 y_d1  <= y_d0[15:8];
  28.                 cb_d1 <= cb_d0[15:8];
  29.                 cr_d1 <= cr_d0[15:8];              
  30.     end
  31. end
复制代码
进行肤色的识别计算,如下:
  1. always@(posedge i_clk or negedge i_rst_n)
  2. begin
  3.     if(!i_rst_n)
  4.         begin
  5.                 skin_color_r <= 8'd0;
  6.                 skin_color_g <= 8'd0;
  7.                 skin_color_b <= 8'd0;
  8.     end
  9.     else if(cb_d1 > 75 && cb_d1 < 125 && cr_d1 > 131 && cr_d1 < 185)
  10.         begin
  11.                 skin_color_r <= 8'd255;
  12.                 skin_color_g <= 8'd255;
  13.                 skin_color_b <= 8'd255;            
  14.     end
  15.     else
  16.         begin
  17.                 skin_color_r <= 8'd0;
  18.                 skin_color_g <= 8'd0;
  19.                 skin_color_b <= 8'd0;            
  20.     end        
  21. end
复制代码
进行时序的同步设计:
  1. always@(posedge i_clk )
  2. begin
  3.         hsyn        <= {hsyn[2:0],i_hsyn};
  4.         vsyn        <= {vsyn[2:0],i_vsyn};
  5.         de                <= {de[2:0],i_en};
  6. end
复制代码
进行输出的赋值:
  1. assign         o_hs        = hsyn[3];
  2. assign         o_vs        = vsyn[3];
  3. assign         o_r                = skin_color_r;
  4. assign         o_g                = skin_color_g;
  5. assign         o_b                = skin_color_b;
  6. assign         o_en        = de[3];
复制代码
以下是求取识别区域框选的算法实现。
定义显示区域的大小与选框的颜色。
  1. parameter  H_ACTIVE                 = 1024; //显示区域宽度                              
  2. parameter  V_ACTIVE                 = 600;  //显示区域高度
  3. parameter  R_VALUE                        = 0;//显示选框颜色
  4. parameter  G_VALUE                        = 0;//显示选框颜色
  5. parameter  B_VALUE                        = 0;//显示选框颜色
复制代码
变量声明如下:
  1. reg  [10:0]             h_cnt;
  2. reg  [10:0]             v_cnt;
  3. reg                                 i_vsyn_d;
  4. reg  [10:0]             skin_h_min;
  5. reg  [10:0]             skin_h_max;
  6. reg  [10:0]             skin_v_min;
  7. reg  [10:0]             skin_v_max;
  8. reg  [10:0]             skin_h_min_d;
  9. reg  [10:0]             skin_h_max_d;
  10. reg  [10:0]             skin_v_min_d;
  11. reg  [10:0]             skin_v_max_d;
复制代码
进行数据的打拍:
  1. always@(posedge i_clk )
  2. begin
  3.         i_vsyn_d <= i_vsyn;
  4. end
  5. //显示区域行计数
  6. always@(posedge i_clk or negedge i_rst_n)
  7. begin
  8.     if(!i_rst_n)
  9.         begin
  10.         h_cnt <= 11'd0;
  11.     end
  12.     else if(i_de)
  13.         begin
  14.                 if(h_cnt == H_ACTIVE - 1'b1)
  15.                         h_cnt <= 11'd0;
  16.                 else
  17.                         h_cnt <= h_cnt + 11'd1;
  18.     end
  19. end
  20. //显示区域场计数
  21. always@(posedge i_clk or negedge i_rst_n)
  22. begin
  23.     if(!i_rst_n)
  24.         begin
  25.         v_cnt <= 11'd0;
  26.     end
  27.     else if(h_cnt == H_ACTIVE - 1'b1)
  28.         begin
  29.                 if(v_cnt == V_ACTIVE - 1'b1)
  30.                         v_cnt <= 11'd0;
  31.                 else
  32.                         v_cnt <= v_cnt + 11'd1;
  33.     end
  34. end
  35. //查找face H最小值
  36. always@(posedge i_clk or negedge i_rst_n)
  37. begin
  38.     if(!i_rst_n)
  39.         begin
  40.                 skin_h_min  <= H_ACTIVE;
  41.     end
  42.     else if(~i_vsyn_d & i_vsyn)
  43.         begin
  44.             skin_h_min  <= H_ACTIVE;
  45.     end        
  46.     else if(i_r == 8'hff && i_g == 8'hff && i_b == 8'hff && skin_h_min > h_cnt)
  47.         begin
  48.             skin_h_min  <= h_cnt;
  49.     end
  50. end
  51. //查找face H最大值
  52. always@(posedge i_clk or negedge i_rst_n)
  53. begin
  54.     if(!i_rst_n)
  55.         begin
  56.                 skin_h_max  <= 8'h00;
  57.     end
  58.     else if(~i_vsyn_d & i_vsyn)
  59.         begin
  60.             skin_h_max  <= 8'h00;
  61.     end        
  62.     else if(i_r == 8'hff && i_g == 8'hff && i_b == 8'hff && skin_h_max < h_cnt)
  63.         begin
  64.             skin_h_max  <= h_cnt;
  65.     end
  66. end
  67. //查找face V最小值
  68. always@(posedge i_clk or negedge i_rst_n)
  69. begin
  70.     if(!i_rst_n)
  71.         begin
  72.                 skin_v_min  <= V_ACTIVE;
  73.     end
  74.     else if(~i_vsyn_d & i_vsyn)
  75.         begin
  76.             skin_v_min  <= V_ACTIVE;
  77.     end        
  78.     else if(i_r == 8'hff && i_g == 8'hff && i_b == 8'hff && skin_v_min > v_cnt)
  79.         begin
  80.             skin_v_min  <= v_cnt;
  81.     end
  82. end
  83. //查找face V最大值
  84. always@(posedge i_clk or negedge i_rst_n)
  85. begin
  86.     if(!i_rst_n)
  87.         begin
  88.                 skin_v_max  <= 8'h00;
  89.     end
  90.     else if(~i_vsyn_d & i_vsyn)
  91.         begin
  92.             skin_v_max  <= 8'h00;
  93.     end        
  94.     else if(i_r == 8'hff && i_g == 8'hff && i_b == 8'hff && skin_v_max < v_cnt)
  95.         begin
  96.             skin_v_max  <= v_cnt;
  97.     end
  98. end
复制代码
将得到的坐标值进行锁存,供后面使用,每帧数据更新一次。
  1. always@(posedge i_clk or negedge i_rst_n)
  2. begin
  3.     if(!i_rst_n)
  4.         begin
  5.                 skin_h_min_d <= 'd0;
  6.                 skin_h_max_d <= 'd0;
  7.                 skin_v_min_d <= 'd0;
  8.                 skin_v_max_d <= 'd0;
  9.     end
  10.     else if(~i_vsyn & i_vsyn_d)
  11.         begin
  12.                 skin_h_min_d <= skin_h_min;
  13.                 skin_h_max_d <= skin_h_max;
  14.                 skin_v_min_d <= skin_v_min;
  15.                 skin_v_max_d <= skin_v_max;           
  16.     end
  17. end
复制代码
计算框选的四边的位置进行框的替换写入:
  1. always@(posedge i_clk or negedge i_rst_n)
  2. begin
  3.     if(!i_rst_n)
  4.         begin
  5.                 o_r <= 'd0;
  6.                 o_g <= 'd0;
  7.                 o_b <= 'd0;
  8.     end
  9.     // else if((i_x_pos == skin_h_min_d && i_y_pos >= skin_v_min_d && i_y_pos <= skin_v_max_d)||
  10.                         // (i_x_pos == skin_h_max_d && i_y_pos >= skin_v_min_d && i_y_pos <= skin_v_max_d)||
  11.                         // (i_y_pos == skin_v_min_d && i_x_pos >= skin_h_min_d && i_x_pos <= skin_h_max_d)||
  12.                         // (i_y_pos == skin_v_max_d && i_x_pos >= skin_h_min_d && i_x_pos <= skin_h_max_d))
  13.     else if(((i_x_pos == skin_h_min_d || i_x_pos == skin_h_max_d ) && i_y_pos >= skin_v_min_d && i_y_pos <= skin_v_max_d)||
  14.                         ((i_y_pos == skin_v_min_d || i_y_pos == skin_v_max_d ) && i_x_pos >= skin_h_min_d && i_x_pos <= skin_h_max_d))                        
  15.         begin
  16.                 o_r <= R_VALUE;
  17.                 o_g <= G_VALUE;
  18.                 o_b <= B_VALUE;        
  19.     end
  20.     else
  21.         begin
  22.                 o_r <= i_r_original;
  23.                 o_g <= i_g_original;
  24.                 o_b <= i_b_original;        
  25.     end        
  26. end
复制代码
3.2工程结构
工程结构如图所示:
image.jpg

图像数据通过摄像头采集进来,先缓存在fifo中,然后通过写状态机,将图像数据送进DDR进行缓存,缓存后的图像数据从DDR中取出,通过读状态机送出到fifo中,然后算法处理模块在fifo中取出数据,取出的数据分别送入肤色识别的算法和款选的算法,进行肤色识别后需要进行腐蚀和膨胀算法去除噪点,最后完成数据处理后送到LCD进行显示输出。

4上板实验
image.jpg

点击下载后,可以看到正常的输出如下所示,摄像头的分辨率为640x480,以画中画的形式与LCD一起进行显示。

在背景不复杂的情况下,可以实时识别肤色相关的物体,如图所示:

image.jpg
image.jpg











































您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

0

关注

0

粉丝

272

主题
精彩推荐
热门资讯
网友晒图
图文推荐

  • 微信公众平台

  • 扫描访问手机版