机器学习: Viola-Jones 人脸检测算法解析(一)
在计算机视觉领域中,人脸检测或者物体检测一直是一个非常受关注的领域,而在人脸检测中,Viola-Jones人脸检测算法可以说是非常经典的一个算法,所有从事人脸检测研究的人,都会熟悉了解这个算法,Viola-Jones算法在2001年的CVPR上提出,因为其高效而快速的检测即使到现在也依然被广泛使用,OpenCV 和 Matlab中都将这个算法写进了函数库可以很方便的直接调用。虽然VJ人脸检测算法最初都是用来检测正面的人脸图像,对于侧脸图像的检测不是很稳健,不过这个算法依然有值得研究的价值。
这个算法包含以下几个重要的部分:
1 利用Haar 特征描述人脸的共有属性;
2 建立了一种称为积分图像的特征,并且基于积分图像,可以快速获取几种不同的矩形特征;
3 利用Adaboost 算法进行训练;
4 建立层级分类器。
利用Haar 特征描述人脸的共有属性
一般来说,人脸会有一些基本的共性,比如眼睛区域会比脸颊区域要暗很多,鼻子一般属于脸部的高光区域,鼻子会比周围的脸颊要亮很多,一张正脸图像,眼睛,眉毛,鼻子,嘴巴等的相对位置是有规律可循的。Haar 特征考虑的是某一特定位置相邻的矩形区域,把每个矩形区域的像素相加然后再相减,总得来说,基本对应以下几种情形:
我们以前都是针对单一像素做,现在需要针对矩形区域,相当于是把单个像素变成矩形区域,所以要对每个矩形区域先求和,然后再利用上面的算子做运算,这种特征称之为Haar-like 特征。
积分图像与矩形特征
正如上一节所说,为了计算Haar-like特征,需要对矩形区域的所有像素求和,一个图像所能形成的矩形区域有大有小,如果每个矩形区域都用遍历所有像素再求和的运算方法,无疑这个运算负担将非常巨大,所有VJ人脸检测算法用到了一种非常巧妙的数据结构,称为integral image(积分图像),积分图像的原理非常简单,总得来说,就是对于图像中的任何一点,该点的积分图像值等于位于该点左上角的所有像素之和,表达式如下:
并且积分图像满足如下关系:
其中I<script type="math/tex" id="MathJax-Element-5">I</script> 表示积分图像,
I=intergralImage(img)
如下图所示,左边的图表示人脸图像,右边的图表示归一化之后积分图像。
有了积分图像,就可以很方便的计算图像中任何一个矩形区域的像素和,如下图所示:
为了计算矩形ABCD的像素和,利用积分图像可以表示如下:
这意味着,任何一个矩形区域的像素和,都可以由积分图像 I<script type="math/tex" id="MathJax-Element-55">I</script> 上面的四个点来表示。VJ人脸检测算法用到了三种不同的矩形特征,分别是二邻接,三邻接,四邻接矩形,如下图所示:
很显然一个矩形可以由四个点来表示,二邻接矩形需要六个点表示,三邻接矩形需要八个点,而四邻接矩形需要九个点。
下面我们来看看,给定一张图像,有多少个矩形特征,VJ 算法里用到的是
下面列出每种邻接矩形可能的size大小。
二邻接矩形 (1×2<script type="math/tex" id="MathJax-Element-63">1 \times 2</script>): 1×2<script type="math/tex" id="MathJax-Element-64">1 \times 2</script>, 1×4<script type="math/tex" id="MathJax-Element-65">1 \times 4</script>, 1×6<script type="math/tex" id="MathJax-Element-66">1 \times 6</script>, … 1×24<script type="math/tex" id="MathJax-Element-67">1 \times 24</script> , 2×2<script type="math/tex" id="MathJax-Element-68">2\times 2</script>, 2×4<script type="math/tex" id="MathJax-Element-69">2 \times 4</script>, 2×6<script type="math/tex" id="MathJax-Element-70">2 \times 6</script>, … 2×24<script type="math/tex" id="MathJax-Element-71">2 \times 24</script> … 24×24<script type="math/tex" id="MathJax-Element-72"> 24 \times 24 </script> 矩形的长以2的倍数增加,宽逐渐增加。
三邻接矩形 (1×3<script type="math/tex" id="MathJax-Element-73">1 \times 3</script>): 1×3<script type="math/tex" id="MathJax-Element-74">1 \times 3</script>, 1×6<script type="math/tex" id="MathJax-Element-75">1\times 6</script>, 1×9<script type="math/tex" id="MathJax-Element-76">1 \times 9</script>, … 24×24<script type="math/tex" id="MathJax-Element-77">24 \times 24</script> 矩形的长是以3的倍数增加,宽逐渐增加。
四邻接矩形 (2×2<script type="math/tex" id="MathJax-Element-78">2 \times 2</script>): 2×2<script type="math/tex" id="MathJax-Element-79">2 \times 2</script>, 2×4<script type="math/tex" id="MathJax-Element-80">2 \times 4</script>, 2×8<script type="math/tex" id="MathJax-Element-81">2 \times 8</script>, 2×16<script type="math/tex" id="MathJax-Element-82">2 \times 16 </script>, … 24×24<script type="math/tex" id="MathJax-Element-83">24 \times 24</script> 矩形的长宽都是以2的倍数增加。
根据卷积定理,我们知道一个W×H<script type="math/tex" id="MathJax-Element-84">W \times H</script> 的图像与 m×n<script type="math/tex" id="MathJax-Element-85">m \times n</script> 的filter 做卷积,新生成的图像大小为
(W−m+1)×(H−n+1)<script type="math/tex" id="MathJax-Element-86">(W-m+1) \times (H-n+1)</script>, 新图像的每一个像素其实就是原图一个m×n<script type="math/tex" id="MathJax-Element-87">m \times n</script> 的local patch与 m×n<script type="math/tex" id="MathJax-Element-88">m \times n</script> 的filter 的乘积和。新图像有多少个像素,就对应着原图多少个m×n<script type="math/tex" id="MathJax-Element-89">m \times n</script> 的矩形。
我们用一段代码来求一个24×24<script type="math/tex" id="MathJax-Element-90">24 \times 24</script> 图像会产生多少个矩形特征: 因为 1×2<script type="math/tex" id="MathJax-Element-91">1 \times 2</script> 和 2×1<script type="math/tex" id="MathJax-Element-92">2 \times 1</script> 的情形是一样的,1×3<script type="math/tex" id="MathJax-Element-93">1 \times 3</script> 和 3×1<script type="math/tex" id="MathJax-Element-94">3 \times 1</script> 的情形也是一样,所以我们只要分别计算一种情况,再乘以2就行了。
import numpy as np
a = np.zeros((3, 2), dtype=int)
Count = np.zeros(3, dtype=int)
a[0, :] = [1, 2]
a[1, :] = [1, 3]
a[2, :] = [2, 2]
Img_size = 24
for ii in range(3):
rec_h = a[ii, 0]
rec_w = a[ii, 1]
for xx in range(rec_h, Img_size+1, rec_h):
for yy in range(rec_w, Img_size+1, rec_w):
Count[ii] = Count[ii]+(Img_size-xx+1)*(Img_size-yy+1)
print Count[ii]
Total = Count[0]*2+Count[1]*2+Count[2]
print ("Total: ", Total)
最后可以得到:
1×2<script type="math/tex" id="MathJax-Element-49">1 \times 2 </script>: 43200
1×3<script type="math/tex" id="MathJax-Element-50">1 \times 3 </script>: 27600
2×2<script type="math/tex" id="MathJax-Element-51">2 \times 2</script>: 20736
所以最终总的矩形特征为 43200×2+27600×2+20736=162336<script type="math/tex" id="MathJax-Element-52">43200 \times 2 + 27600 \times 2 + 20736 = 162336</script>
可以看到,一个 24×24<script type="math/tex" id="MathJax-Element-53">24 \times 24</script> 的图像最终会产生162336个矩形特征,这个维度远远高于图像本身的维度。不可能将所有的矩形特征都用,所有需要做特征选择,下一篇,我们将探讨如何利用AdaBoost来做特征选择与训练。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐



所有评论(0)