颜色空间人脸检测


实验目的

使用颜色空间的方法检测人脸,并用二值化的方法进行显示。


实验内容

选定图像色彩模型

如果使用RGB颜色模型,当光照、肤色稍微变化时,对RGB各分量的灰度值有较大的影响。所以通过查阅资料,最终选定了YCbCr颜色空间作为人脸检测的颜色模型,并选定Cr分量的灰度作为判定标准。

YCbCr颜色模型

YCbCr空间是一种常见的色彩模型,其中Y表示亮度信息,Cb和Cr表示色度信息。其中Cb表示蓝色的浓度偏移成分,Cr表示红色的浓度偏移成分。而人脸主要是红色成分,所以主要考虑Cr分量的灰度值。

RGB颜色模型转YCbCr颜色模型公式如下:

rgb2ycbcr

确定人脸灰度区间

我找了两张黄种人的图片,将其中的人脸区域截出,观察Cr分量的灰度直方图如下:

face show

根据观察,然后结合查阅的资料,黄种人的肤色对应的Cr分量在140-160范围内。

根据上面两张图的对比,光照对于Cr分量影响不大,所以图片几乎可以不用预处理就可以直接提取Cr分量判断。

人脸检测

人脸检测主要分为以下三个步骤:

  1. 提取图片的Cr分量,并二值化
  2. 使用5*5的中值滤波器去出噪声
  3. 对连通区域标号,并统计每个连通区域的面积,面积小于图片总面积的0.25%时认为是噪声

其中,第3步我们曾经使用闭操作,尝试修补孔洞。但是有些对于背景复杂的照片,有些空洞是较大的,这就需要提高结构元素的大小。但是,当结构元素过大的时候,执行闭操作之后人脸的边界形状会被严重破坏。所以,我们最终使用了现在的统计连通块面积占比的方法。


结果与反思

实验结果

  • 实验中,对6张图片进行颜色空间人脸检测结果如下。
  • 每张图片包含4张图片依次为:
    • 原始图像
    • 提取原始图像Cr分量,并二值化后的结果
    • 对二值化图像使用5*5中值滤波器平滑后的结果
    • 对平滑后的图像进行连通块标号,并填补孔洞后的结果

result1

result2

result5

result6

result4

result3

结果反思

  • 在一些背景较为复杂的照片中,有一定的概率会存在某个较大区域的Cr分量和人脸的Cr分量相符,都在140到160之间。这种情况暂时还没有想出办法解决。
  • 程序中需要确定一个连通块面积占总图片总面积的占比,这个占比根据不同的图片会有一些差别。以人为照片主体的时候,0.25%的占比较为合适;但如果人很小,图片主要是背景的时候,则人脸会被当做噪声处理掉。
  • 人脸检测主要是提取人脸部分,但是由于很多照片有人体其他部位裸露,且裸露区域较大,也会被当做人脸区域。我们曾经考虑利用形状判断——人脸更偏向于正方形或长方形,但是很多时候,手或小臂的形状也是正方向或长方形,因此仍然无法排除这一部分。

程序实现

  • 统计人脸Cr灰度区间:show.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# -*- coding:utf-8 -*-
from PIL import Image
from pylab import *
import numpy as np

img1 = Image.open('./ss1.png')

figure(1)
subplot(221)
title('image1')
axis('off')
imshow(img1)

subplot(222)
title('Cr_hist1')

img1_cr = np.array(img1.convert('YCbCr'))[:, :, 2]
hist(img1_cr.ravel(), 50, normed=1, facecolor='g')


img2 = Image.open('./ss2.png')

subplot(223)
title('image2')
axis('off')
imshow(img2)

subplot(224)
title('Cr_hist2')

img2_cr = np.array(img2.convert('YCbCr'))[:, :, 2]
hist(img2_cr.ravel(), 50, normed=1, facecolor='g')

show()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# -*- coding:utf-8 -*-
from PIL import Image
from pylab import *
import numpy as np
import math
from collections import defaultdict, deque

im = Image.open('./s6.jpg')

subplot(221)
title('original')
axis('off')
imshow(im)

ycbcr = np.array(im.convert('YCbCr'))

n, m = ycbcr.shape[0], ycbcr.shape[1]

cb = ycbcr[:, :, 1]
cr = ycbcr[:, :, 2]

for i in range(n):
for j in range(m):
if cr[i, j] > 140 and cr[i, j] < 160:
cr[i, j] = 255
else:
cr[i, j] = 0

subplot(222)
title('Cr binarization')
axis('off')
gray()
imshow(cr)

# 平滑,中值滤波器
for i in range(2, n - 2):
for j in range(2, m - 2):
z = cr[i - 2: i + 3, j - 2: j + 3].reshape(25)
z = sort(z)
cr[i, j] = z[13]


subplot(223)
title('median filter, smooth')
axis('off')
gray()
imshow(cr)
'''
# 闭操作,补孔洞
## 腐蚀
sz = 1
cr1 = np.zeros((n, m))
def erode(x, y):
for ii in range(-sz, sz + 1):
for jj in range(-sz, sz + 1):
if cr[x + ii, y + jj] != 255:
return 0
return 255

for i in range(sz, n - sz):
for j in range(sz, m - sz):
if cr[i, j] == 255:
cr1[i, j] = erode(i, j)

## 膨胀
cr2 = np.zeros((n, m))
def dilate(x, y):
for ii in range(-sz, sz + 1):
for jj in range(-sz, sz + 1):
if cr1[x + ii, y + jj] == 255:
return 255
return 0

for i in range(sz, n - sz):
for j in range(sz, m - sz):
if cr1[i, j] == 255:
cr2[i, j] = erode(i, j)
'''

# 连通区域标号
label = np.zeros((n, m))
cr2 = np.zeros((n, m))
num = [0, ]

def bfs(x, y, col):
label[x, y] = col
num[col] += 1
q = deque([(x, y), ])
while len(q) != 0:
z = q.popleft()
x, y = z[0], z[1]
# print(x, y)

for i in [-1, 0, 1]:
for j in [-1, 0, 1]:
if x + i < 0 or x + i >= n or y + j < 0 or y + j >= m:
continue
if cr[x + i, y + j] == 255 and label[x + i, y + j] == 0:
label[x + i, y + j] = col
num[col] += 1
q.append((x + i, y + j))

cnt = 0

for i in range(n):
for j in range(m):
if label[i, j] == 0 and cr[i, j] == 255:
cnt += 1
num.append(0)
bfs(i, j, cnt)

# print(cnt)

mx = 400 # 不同图像占比不同

for i in range(n):
for j in range(m):
if num[int(label[i, j])] * mx < n * m:
cr2[i, j] = 0
else:
cr2[i, j] = 255


subplot(224)
title('without small component')
axis('off')
gray()
imshow(cr2)

show()