RGB颜色空间中的统计肤色模型
1. 统计肤色模型简介
前几篇文章提出的参数肤色模型,由于参数值固定,所以来自测试图像集的分割误差有可能很大(不过在光照足够的情况下已经够用了)。对于光线比较昏暗的场合,一般人们不太会训练这样的数据来提取参数,同时光照不足时颜色容易跟背景混在一起,所以做出来效果也不好。在光照昏暗的场合,直接在自然光和复杂背景下分割肤色到现在为止依然是比较困难的事。这里讨论的仅仅是单个像素是否为肤色,如果要求在大面积肤色的背景下分割出手或脸来,这种不依赖上下文的方法就完蛋了。
那么统计肤色模型相比参数肤色模型到底有什么改进呢?参数肤色模型的参数是不变的,而统计肤色模型的参数是可变的(根据训练的数据集计算出来),并且引入了可能性的判断。
Jone MJ等人提出统计肤色模型,参数计算步骤如下:
(1)使用图片训练集建立肤色类和非肤色类的颜色直方图;
(2)计算先验概率:属于肤色类的概率P(Cs)和属于非肤色类的概率P(Cns);
(3)计算条件概率:在肤色类中属于该颜色值的概率P(v|Cs)和在非肤色类中属于该颜色值的概率P(v|Cns);
(4)贝叶斯法则计算后验概率:该颜色块属于肤色类的概率P(Cs|v)。
2. 统计肤色模型实现问题
图片训练集合几十到上千张图片不等,到底用多大分辨率的图片呢?我尝试过RGB三通道颜色分别为8bit的图片(即单通道颜色用8位表示),颜色直方图的自变量大小为256*256*256个。笔记本跑起来很慢,我忍了。终于跑出来数据了,存放在2个数组里面,好高兴~
接着是读取数据,考虑到训练数据量远大于测试数据量,所以计算训练数据的代码和计算测试数据的代码我分开写。执行测试数据时需要从之前跑出来的训练数据中获取,所以我在训练数据程序的后面添加了写入csv文件的片段。写入完成后尝试打开csv文件,令我惊呆的是,csv文件125MB,表格打开显示不完全,文本文档根本打不开!好吧,我不打开你了。直接读取数据总是可以的吧,在另一边添加读取csv文件,出现MemoryError。问题在于为Python运行分配的内存空间比较小,超长的数据即使显示,中间的数据也是用“...”来略过。还有后面的显示部分运行起来也比较耗时。
最后决定用6*6*6位颜色值来作颜色直方图。
3. 代码实现
(1)训练代码
训练的数据写入skin_nonskin.csv文件。由于训练数据较大,所以如果没有特殊要求,暂时不提供训练数据。
import cv2 import numpy as np import os import csv ################################################################################ print 'Set Image Directory' imgFile = 'images/skin_color_database/skin_color_database_1/' ################################################################################ # define variables for histogram colorRange = 64 # skin color counts countSkin = np.zeros(colorRange ** 3, np.double) # non-skin color counts countNonSkin = np.zeros(colorRange ** 3, np.double) # label sets true if it's a label image, otherwise sets false if it's an original image label = True ################################################################################ print 'Skin Color Histogram' for fileName in os.listdir(imgFile): if label == True: # load a label image print 'Label File Name:',fileName imgLabel = cv2.imread(imgFile + fileName) # convert color space from bgr to gray imgLabel = cv2.cvtColor(imgLabel, cv2.COLOR_BGR2GRAY) else: # load an original image print 'Original File Name:',fileName img = cv2.imread(imgFile + fileName) ############################################################################ # count pixel color values if label == False: # get image shape rows,cols,channels = img.shape for r in range(rows): for c in range(cols): # get values from rgb color space B = img.item(r,c,0) / 4 G = img.item(r,c,1) / 4 R = img.item(r,c,2) / 4 color = B * 64 * 64 + G * 64 + R if imgLabel.item(r,c) == 255: countSkin[color] += 1 else: countNonSkin[color] += 1 label = not label ################################################################################ # data combination # ---------countSkin------------ # ---------countNonSkin--------- data = [countSkin, countNonSkin] ################################################################################ # write csv file csvFile = open('skin_nonskin.csv','wb') writer = csv.writer(csvFile) for row in data: writer.writerow([row[col] for col in range(colorRange ** 3)]) ################################################################################ print 'Goodbye!'
(2)测试代码
import os import cv2 import csv import numpy as np from matplotlib import pyplot as plt from mpl_toolkits.mplot3d.axes3d import Axes3D ################################################################################ print 'read csv file' VRange = 64 * 64 * 64 print VRange countSkin = np.zeros(VRange, np.double) countNonSkin = np.zeros(VRange, np.double) csvFile = open('skin_nonskin.csv', "rb") reader = csv.reader(csvFile) rowNum = 0 for row in reader: colNum = 0 for col in row: # restore countSkin and countNonSkin modNum = rowNum - rowNum / 2 * 2 if modNum == 1: countNonSkin[((rowNum - 1) * VRange + colNum)] = float(col) if modNum == 0: countSkin[((rowNum - 1) * VRange + colNum)] = float(col) colNum += 1 rowNum += 1 ################################################################################ print 'Skin Color Database Result' # count print 'count of skin pixels:',countSkin print 'count of non-skin pixels:',countNonSkin ################################################################################ print 'prior probability' skinPix = sum(countSkin) nskinPix = sum(countNonSkin) PCs = skinPix / (skinPix + nskinPix) # P(Cs) PCns = 1 - PCs # P(Cns) print 'probability of skin class:',PCs print 'probability of non-skin class',PCns ################################################################################ print 'conditional probability variables' PVCs = np.zeros(VRange, np.double) # P(V|Cs) PVCns = np.zeros(VRange, np.double) # P(V|Cns) for i in range(VRange): # pixel color probability given skin color PVCs[i] = countSkin[i] / skinPix # pixel color probability given non-skin color PVCns[i] = countNonSkin[i] / nskinPix ################################################################################ print 'posterior probability' PCsV = np.zeros(VRange, np.double) # P(Cs|V) for i in range(VRange): # skin probability given pixel color PCsV[i] = (PVCs[i] * PCs) / (PVCs[i] * PCs + PVCns[i] * PCns + 0.00000001) ################################################################################ print 'determine skin distribution' # pixel color range v = range(VRange) # skin class threshold theta = [0.6,0.7,0.8,0.9] # color of a single class rgbColor = np.zeros(3, np.uint8) # rgb color value skinPix = np.zeros(VRange, np.uint8) # prepare for 3d plot fig = plt.figure() ax = fig.add_subplot(111, projection = '3d') for i in v: # split 3 channels(64b * 64b * 64b) B = i / (64 * 64) G = (i - B * 64 * 64) / 64 R = i - B * 64 * 64 - G * 64 # given probability as threshold if PCsV[i] > theta[3]: skinPix[i] = 4 ax.scatter(R, G, B, c='r', marker='o') else: skinPix[i] = 0 ################################################################################ print 'display distribution of skin color' ax.set_xlabel('Red') ax.set_ylabel('Green') ax.set_zlabel('Blue') plt.show() ################################################################################ print 'Goodbye!'<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>
这部分也不能算作测试代码吧,因为没用测试图片去测试效果。为什么不测试呢?因为该模型的泛化误差取决于训练数据,所以该模型的完美形态为基于完美数据库的带缺陷模型。同时,看到肤色在RGB颜色空间中的形状,要比用图片测试肤色来得更加直观。下图是我找到的训练数据集的实验结果:按照从左到右,从上到下的顺序依次为有90%,80%,70%和60%的可能性确定的肤色空间。
结语
这部分的实验结果为训练数据库得到的肤色在RGB颜色空间中的分布。所以如果给出1个像素点,就可以通过上图来判断这个像素点的颜色值有大可能性为肤色。相比之前参数肤色模型的绝对性判断会有更好的鲁棒性。