OpenCV Using Python——应用统计肤色模型和相对于块原点能量的肤色分割

来源:互联网 发布:淘宝怎么找学位证办理 编辑:程序博客网 时间:2024/04/25 17:31

应用统计肤色模型和相对于块原点能量的肤色分割

1. 肤色分割简介

(1)统计肤色模型简介

        在前面的文章中,我们利用训练数据已经成功计算出训练数据的后验概率P(Cs|v),即已知像素值判断属于肤色类的概率。同时我们获得后验概率大于不同阈值的肤色掩膜。但之前并没有给出统计肤色模型的肤色检测效果,因为测试效果取决于训练数据对应像素所在的RGB颜色空间分布。本篇将给出统计肤色模型的应用效果,同时改进单一使用模型的缺陷。

(2)块原点能量简介

        图像中被分割出来的肤色块后面统称为块。根据统计肤色模型在一幅图片中可能获得上千个块,那么对应的会有上千个块的中心,块的中心称为块原点。相对于块原点的能量又两部分组成:一部分为静态能量,定义为像素点到块原点的二次距离;另一部分为动态能量,定义为像素值的后验概率与相等能量的像素点的后验概率的差值。相等能量为实验设定值,处于肤色后验概率上界和下界之间,如果某像素点的肤色后验概率大于相等能量值,该像素点的能量饱和,设为1。同时,如果该像素点的肤色后验概率小于肤色后验概率下界,该像素点的能量为空,设为0。如果该像素点的肤色后验概率在肤色后验概率下界和相等能量值的区间内,则计算得到的像素点能量取决于静态能量和动态能量的加权和。

        那么块和块原点能量的存在对统计肤色分割有什么意义呢?统计肤色分割取决于训练数据。如果训练数据过于完善,那么离线学习会很缓慢;同时现实中大多数情况下随光照影响往往不太可能收集到完整的训练数据,并且越完整的数据会造成背景颜色的干扰越剧烈。所以我们需要针对有限的肤色检测场合,收集该场合下充分的肤色训练数据,达到有限场合下完美的肤色检测。这里只是这么一提,笔者从网上的数据库随便找了个数据库就做了,暂时没有考虑那么多问题,如果以后用到还会回头来修改训练数据。大多数复杂环境下,肤色或多或少会受环境背景颜色的影响。所以,这里就有了操纵块的需求:如果统计肤色分割的肤色块中同时存在皮肤块和背景块,那么一定要想办法把背景块消除掉。训练数据库的不完整本来就会使分割出来的肤色块比较琐碎,而背景块的剔除会造成肤色块更加琐碎,最终提取的肤色块效果一定是小块小块并且支离破碎的,这样的肤色分割在后期手势或人脸跟踪中根本没法用,所以就有了块原点能量的需求,相比盲目的形态学计算效果分割得更加完整和精确,因为单一得形态学计算没有考虑肤色和背景噪声的关系。

      那么肤色和背景噪声是什么关系?统计模型分割后的肤色块相对于背景噪声块要更加完整,大多数情况下肤色面积要更大(当然如果存在面积巨大的肤色背景,任何不依赖于肤色前景位置的分割算法都是失效的)。所以在这里提出强假设:假设不存在比肤色前景更大的肤色背景。那么基于块原点能量的肤色分割的应用场合一定是:小面积肤色或大面积类肤色或不带肤色的复杂背景下的肤色前景分割

2. 代码实现

(1)读取csv文件

        读取csv文件中肤色后验概率上界的肤色掩码和18位的RGB颜色空间中的三通道颜色值对应的属于肤色类的后验概率。

################################################################################print 'read skin posterior probability from csv file'csvFile = open('skin_posterior_thresh.csv', "rb")reader = csv.reader(csvFile)# skin mask according to training skin databasemaskSkin = np.zeros(cRange, np.uint8)# skin posterior probability according to training skin databaseprobSkin = np.zeros(cRange, np.double)# prepare an empty image spaceimgSkin = np.zeros(img.shape, np.uint8)imgSkinEnergy = np.zeros(img.shape, np.uint8)# copy original imageimgContour = imgGray.copy()imgThreshBlob = imgGray.copy() imgEnergy = imgGray.copy()# dataType = 0: skinPix (skin mask); dataType = 1: P(Cs|V)dataType = 0rowNum = 0for row in reader:    print len(row)    colNum = 0        for col in row:                 if dataType == 0:                    # restore skin mask                        maskSkin[(rowNum - 1) * cRange + colNum] = col                                    elif dataType == 1:            # restore posterior probability            probSkin[(rowNum - 1) * cRange + colNum] = decimal.Decimal(col)                                                                    colNum += 1                                             rowNum += 1    dataType += 1

(2)获取掩码图像

        读取原始图像在RGB颜色空间中的位置;获得对应肤色后验概率上界的肤色掩码彩色图像(肤色部分为原图,非肤色部分为黑色);获得对应肤色后验概率上界的肤色掩码的初始等高线灰度图像;获得对应肤色后验概率下界的块阈值灰度图像。

(3)检测肤色块

        查找初始等高线灰度图像的等高线,并获得面积最大登高线,同时在肤色掩码彩色图像上用检测到的所有肤色块(红色块,分割的结果包括背景肤色噪声)覆盖肤色块。

################################################################################print 'blob detection'# find the contours in the maskcontour,_ = cv2.findContours(imgContour.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)# print num of contoursprint '%d contours found.' % len(contour)# find maximum areamaxArea = 0.0for cntIdx in range(len(contour)):        # each contour        cnt = contour[cntIdx]    # contour area    area = cv2.contourArea(cnt)        if area > maxArea:        maxContour = contour[cntIdx]        maxArea = areaprint 'maximum area:', maxAreaprint 'contour length before:', len(contour)# loop over the contoursfor c in contour:    # draw all red contours    cv2.drawContours(imgSkin,[c],-1,(251,37,26),cv2.cv.CV_FILLED)

(4)删除背景肤色块

        删除小于最大肤色块面积的1%的背景颜色块(背景颜色块是指包含大量背景区域的肤色或类肤色块,同时有可能包含少量琐碎的皮肤块)。删除过小的肤色块后琐碎的肤色块少了,但本来属于同一肤色单元的肤色区域会更加琐碎。删除的背景肤色块依然为红色块,未删除的肤色块覆盖为蓝色块。

# blob origins eliminationprint 'eliminate false positive area'cntIdx = 0while cntIdx < len(contour):            cnt = contour[cntIdx]    # contour area    area = cv2.contourArea(cnt)        if area < 0.01 * maxArea:        del contour[cntIdx]        # cntIdx decrease 1 after elimination    else:        # print area        cntIdx += 1print 'contour length after:', len(contour)                      # loop over the contoursfor c in contour:    # draw relative big blue contours    cv2.drawContours(imgSkin,[c],-1,(41,207,190),cv2.cv.CV_FILLED)# draw biggest green contour cv2.drawContours(imgSkin,[maxContour],-1,(120,236,64),cv2.cv.CV_FILLED)

(5)块原点扩张

        在块原点扩张之前需要保存删除背景肤色块后的块对应的中心(即块原点)和计算像素点到块原点之间的距离。然后,才能根据能量函数规则计算每个满足肤色后验概率下界的像素点的能量,映射到灰度区间后产生能量图像。满足肤色后验概率下界的像素点满足能量阈值要求则扩张为肤色块上的像素点。

################################################################################print 'blob spread'    for r in range(rows):    for c in range(cols):                energy = 0        # v(p) > Pth_blob (blob acceptance threshold)        if imgThreshBlob.item(r,c) == 255:                             # get values from rgb color space            R = img.item(r,c,0) / 4            G = img.item(r,c,1) / 4            B = img.item(r,c,2) / 4                        color = B * 64 * 64 + G * 64 + R                                    vp = probSkin[color]                        # distance between v(p) and blob origin            E0 = 1.0 / (distance[r * cols + c] + + 0.0000001)                        energy = E0 + K * (vp - Peq)                         energy = min(1, max(energy,0))                    imgEnergy.itemset((r,c),energy * 255)

(6)检测肤色块和删除背景颜色块

        检测能量图像中的肤色块,同时删除小于最大等高线区域面积3%的对应背景颜色块。其目的是为了删除潜在的被扩张的背景颜色块。绿色块为扩张后的等高线面积最大的肤色块。

3. 实验结果

        实验同时采用前几篇文章的所有图像,力图避免理想图像造成的完美假象,同时暴露不完美图像经过上述步骤到底改进在什么地方。

        第一幅原始图像肤色前景面积小,肤色背景面积巨大同时噪声干扰严重。所以实验结果有可能存在的问题:肤色背景面积大于肤色前景面积会造成在强假设下得到的背景和前景互换。实际上,结果没有想象得严重。

        先从头开始看图分析处理后的效果:统计肤色模型得到的等高线图像是这一些列文章中肤色前景最琐碎的结果,但肤色背景噪声要比参数肤色模型小一些。肤色图像中发现最大面积等高线区域为爱丽丝的右手臂,暂时结果可以接受,因为至少不是背景肤色对应的等高线区域。阈值块图像为满足统计肤色模型后验概率下界时对应的肤色掩码图像,因为是概率下界,还会包含处于概率上下界之间的肤色块,所以相比等高线图像,肤色前景和背景块懂更加饱满。能量图像为能量在图像中的映射(越白表示能量越高,灰色区域较少不容易看清,说明概率处于上下界之间的肤色块较少,即不确定性大的肤色块较少)。去噪的皮肤图像相比去噪前的皮肤图像,肤色前景和背景区域都更加连通完整,最大面积等高线区域为爱丽丝的左右手臂,分割结果更加完整。

        回答上一个问题:为什么该幅图像中的肤色前景和背景互换问题没有想象中严重?该幅图像中背景和前景距离较远,背景颜色的饱和度被冲淡,所以与平均肤色的距离要比真实颜色更加远一些;同时加上远处物体接收多角度的光照使得物体表面的颜色分布更加复杂,从而分割时肤色背景会变得琐碎,最后计算等高线区域面积时会偏小。

        第二幅原始图像肤色前景更多,但爱丽丝身上的肤色琐碎又紧密。所以在块原点扩张时因为像素点距离块原点距离过近,造成计算得到的静态能量权重过大,以至于最后非肤色区域也被扩张出来了。适当减小静态能量的权重系数即可获得满意的效果,但如果过度减小该权重系数会无法扩张出完整的脸部。考虑到强假设的存在和本人偷懒的欲望,所以并没有对权重系数的取值作实验。

        第三幅原始图像肤色前景和背景都很完整。但统计肤色模型不是同一训练数据,所以统计肤色模型分割的结果也不好,不过最后挺完整。加上人脸的肤色也没有变色龙那样变化,即颜色空间中区域的范围有限,所以对不同的训练数据鲁棒性还是有的。

        肤色分割可视化时,后两幅图片的脸部(最大面积的肤色前景区域)中的非肤色区域的黑色块被绿色块挡住。因为填充颜色用的是自带的floodfill()函数,并且没有作填充函数间的配合。好吧~支线内容先放放。



结语

        除了上述方法,还可以用参数肤色模型做。直观地说是在颜色空间中划分区域,然后给出从颜色空间到肤色可能性的映射。不过这个映射直接找非常不容易。不过统计肤色模型可以不用朴素贝叶斯,还可以试试其它的统计方法。

0 0