最近帮助师兄做毕业设计,课题为手部姿态估计,需要提取RGB图像中手部区域的前景。 考虑到手部肤色比较统一,我首先想到用之前另一个同学写的dominant color方法来做前景提取。 随后到网上搜索了一下,发现已经有许多基于肤色的检测方法,遂直接拿来用了。 以下主要参考的是这篇博客, 这位兄弟介绍的比较全面, 我主要挑选自己在实验中效果最好的一种方法来介绍。最后的效果如下图所示。
将图像转至YCrCb颜色空间下
YCrCb即YUV,其Y通道表示明亮度(Luminance或Luma),即灰阶值; Cr,Cb通道则表示色度(Chrominance或Chroma),用于描述色彩及饱和度。 其中Cr反映了图像信号红色部分与亮度值之间的差异,而Cb反应信号中蓝色部分与亮度值之间的差异。 下面三幅图分别为原始手部图像转至YCrCb颜色空间下后,Y、Cr、Cb通道的图像。
可以明显看到在Cr通道下,手部区域与其他区域有较大差别,可利用该特性来对手部区域进行分割。
Otsu阈值分割
Otsu算法又称大津法,由日本科学家大津展与1979年提出。 它是一种常用的灰度图像二值化算法。 该算法会选取出一个阈值将灰度图像中的像素分为两类,这两类像素点之间具有最大的最大类间方差(ICV,Intra-Class Variance)。 Ostu给出的类间方差定义如下:
ICV = Pa x (Ma - M)^2 + Pb x (Mb - a)^2
其中Pa,Pb为两类像素所占的比例,Ma、Mb为类内均值,M总均值。 根据图像的直方图,我们可以很容易的计算出使得类间方差最大的阈值。

上图所示为之前的手图片的Cr通道图像的分布直方图。 可以看到,直方图明显呈双峰状。 橙色曲线即取不同阈值下的ICV值,在两个波峰间的谷底处ICV达到最大。 取改点作为阈值,我们即可大致的将手的前景区域提取出来,结果如下图所示。

去除噪声
通过Otsu阈值分割之后,手部的前景基本上是提取出来了,但是还是存在一些噪音。 通过先腐蚀再膨胀操作,我们可以去除其中的一些毛边。 去除毛边后我们再从中挑选出最大的contour,基本上就是比较好的前景mask图像了。

代码
def YCrCb_Otsu_detect(img):
# 转至YCrCb颜色空间
ycrcb_img = cv2.cvtColor(img, cv2.COLOR_BGR2YCrCb)
cr = ycrcb_img[:, :, 1]
# 通过OTSU算法从Cr通道提取手部前景区域
_, mask = cv2.threshold(cr, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
# 腐蚀膨胀去除噪声
kernel_size = min(img.shape[0], img.shape[1]) // 50
element = cv2.getStructuringElement(cv2.MORPH_RECT, (kernel_size, kernel_size))
mask = cv2.erode(mask, element)
mask = cv2.dilate(mask, element)
# 保留最大轮廓
_, contours, _ = cv2.findContours(mask, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
max_index = 0
max_val = -1
for idx, c in enumerate(contours):
if c.shape[0] > max_val:
max_val = c.shape[0]
max_index = idx
canvas = mask * 0
mask = cv2.drawContours(canvas, contours, max_index, 1, -1)
mask = np.expand_dims(mask, axis=2)
return mask*img