问题描述
我正在尝试找到合适的颜色间隔以进行颜色遮罩,以便从图像中提取皮肤。
我有一个包含图像和遮罩的数据库,可以从这些图像中提取皮肤。这是一个示例示例:
我正在为每个图像应用蒙版,以获得类似这样的效果:
我要从所有蒙版图像中获取所有像素,并删除黑色像素,以便仅保留包含皮肤的像素。使用这种方法,我可以从不同的人那里收集不同的像素,其中包含不同肤色的不同阴影。
这是我正在使用的代码:
for i,(img_color,img_mask) in enumerate ( zip(COLORED_IMAGES,MASKS) ) :
# masking
img_masked = cv2.bitwise_and(img_color,img_mask)
# transforming into pixels array
img_masked_pixels = img_masked.reshape(len(img_masked) * len(img_masked[0]),len(img_masked[0][0]))
# merging all pixels from all samples
if i == 0:
all_pixels = img_masked_pixels
else:
all_pixels = np.concatenate((all_pixels,img_masked_pixels),axis = 0)
# removing black
all_pixels = all_pixels[ ~ (all_pixels == 0).all(axis = 1) ]
# sorting pixels
all_pixels = np.sort(all_pixels)
# reshape into 1 NB_PIXELSx1 image in order to create histogram
all_pixels = all_pixels.reshape(len(all_pixels),1,3)
# creating image NB_PIXELSx1 image containing all skin colors from dataset samples
all_pixels = cv2.cvtColor(all_pixels,cv2.COLOR_BGR2YCR_CB)
从不同的皮肤中提取所有颜色的阴影之后,我正在创建一个直方图,使我可以查看哪种颜色更常见。该代码对于创建直方图来说太长了,但这是结果:
然后,我为每个颜色空间图使用转折点,并为该颜色空间选择一个距离,例如20。通过执行[转折点-20,转折点+20]获得该颜色空间的间隔>
所以我们说以下内容:
R:
- 转折点:142
- 距离:61
- 时间间隔:[81,203]
G:
- 转折点:155
- 距离:10
- 时间间隔:[145,165]
B:
- 转折点:109
- 距离:14
- 时间间隔:[95,123]
我将使用这些间隔来从数据集中创建彩色图像的蒙版以提取皮肤(左:我的间隔蒙版,右:地面真相蒙版):
将使用我的间隔提取的蒙版与数据集先前存在的蒙版进行比较,并计算准确性,以查看我获得的间隔的有效性和良好程度:
precision_moy = 0
accuracy_moy = 0
for i,(image,img) in enumerate ( zip(COLORED,GROUND_TRUTH) ) :
Min = np.array([81,145,95],np.uint8)
Max = np.array([203,165,123],np.uint8)
mask = cv2.inRange (image,Min,Max)
TP = 0 # True Positive
TN = 0 # True Negative
FP = 0 # False Positive
FN = 0 # False Negative
for i in range(mask.shape[0]) :
for j in range(mask.shape[1]) :
if mask[i,j] == 255 and img[i,j,0] == 255:
TP = TP + 1
if mask[i,j] == 0 and img[i,0] == 0:
TN = TN+1
if mask[i,0] == 0:
FP = FP+1
if mask[i,0] == 255:
FN = FN+1
precision = TP/(TP+FP)
accuracy = (TP+TN)/(TP+TN+FP+FN)
precision_moy = precision_moy + precision
accuracy_moy = accuracy_moy + accuracy
precision_moy = precision_moy / len(COLORED)
accuracy_moy = accuracy_moy / len(COLORED)
我不断更改间隔,进行测试并计算准确性,以便为每个色彩空间找到最佳的间隔。通过将距离乘以0到2之间的数字来完成此更改。例如:
OLD R:
- 转折点:142
- 距离:61
- 时间间隔:[81,203]
新距离=旧距离* 0.7 = 61 * 0.7 = 43
NEW R:
- 转折点:142
- 距离:43
- 时间间隔:[99,185]
- 要获得更大的间隔,我需要将[1、2]中的数字相乘
- 要获得较小的间隔,我将乘以[0,1]中的数字[
现在,我的问题:
我想使用一种优化方法为每个颜色空间找到最佳的间隔,而不是手动和随机地更改间隔。我应该使用哪种优化方法以及如何使用它?
感谢您抽出宝贵的时间。感谢您的帮助。
解决方法
一种快速收敛但可能无法产生全局最优值的基本方法是Hillclimbing。
爬山是local search的一种形式,可以在这种情况下使用。
Hillclimbing的工作方式是根据状态的得分或性能从一个状态或解决方案进入下一个状态。如果找不到更好的状态,那么该状态将作为解决方案返回。
有多种实现Hillclimbing的方法,在您的情况下,我会这样做:
状态:在您的情况下,该项目包含 Min 和 Max numpy数组以及 accuracy 或使用这些数组创建的蒙版的 f-measure 作为 score 属性应用于图像。
目前,我建议您仅采用对称范围以大幅减少搜索空间。
开始状态
您可以随机创建一个开始状态,并为每个通道(红色,绿色,蓝色)采取随机间隔。如果您多次运行此算法,这将特别有用。根据直方图确定每个间隔的最大值和最小值。
迭代过程(在此处进行搜索)
您想创建一个无限循环,在其中为当前状态创建后继状态。用当前状态的10
增大或减小每个通道的间隔,然后这些新间隔的每种组合都可以是后继状态。
另一种方法是在每次迭代时切换通道。因此,在第一个迭代中,您将创建一个继承状态,该继承状态的当前状态的红色通道减少了,为10,并且创建了一个继承状态的当前状态的红色通道,其增加了10。第二次迭代更改绿色通道,第三次迭代更改蓝色通道,等等。
然后,您将基于每个后继状态创建一个蒙版并将其应用于图像,从而确定每个后继状态的性能。
选择性能最佳的后继状态,如果性能更好,则将其作为当前状态。
重复此过程,直到最佳后继状态的状态比当前状态差,然后您便知道达到了本地最佳状态。返回此状态作为解决方案。
问题
如上一行中突出显示的那样,该算法将为起始状态找到局部最优值。这是因为该算法的贪婪性。
因此,您可能希望在不同的起始位置重新启动该算法,从而允许探索更多的搜索空间,从而增加了找到 global 最大值的机会。
如果您有多个线程,则可以并行运行多个实例,然后最终从每个实例的结果中返回最佳状态。
Hillclimbing不是最佳的优化算法,但是它非常快速且易于实现。
,我建议您使用遗传优化方法,该优化方法可以轻松解决与您一样简单的问题。由于问题相对较小,与某些局部优化方法(如@Leander建议的Hillclimb)相比,找到最佳解决方案的时间应该不会更长。遗传算法是一种元启发式搜索,因此不能保证找到最佳解决方案,但它可以使您非常接近。实际上,对于如此小的问题,您找到全局最优值的机会非常高。
首先,我建议您看一下DEAP,这样您就不必自己实现任何内容(https://deap.readthedocs.io/en/master/)。它包含许多遗传算法变体的非常好的实现,并且有一些带有精美示例的教程。稍加努力,您就应该能够在一两天内组成一个简单的优化算法。
为简便起见,从现在开始,遗传算法将标记为GA
一些从哪里开始的提示:
- 我建议您从DEAP中最简单的版本
eaSimple
开始。如果这不能令人满意,您可以随时使用更复杂的方法,但是我认为这不是必需的。 - 您在GA中的
Individual
将具有6个组成部分-> [blue_low,blue_high,green_low,green_high,red_low,red_high]这还将解决@Leander在评论中提到的不对称间隔的问题 -
mutations
将通过随机更改个人元素来完成 - 对于
fittness
函数,您可以在计算时使用其准确性
基本上,这是为您的问题构建GA所需要的。 https://deap.readthedocs.io/en/master/examples/ga_onemax.html此处的示例应使您开始工作。您只需要定义自己的个人,操作员和适应性评估功能即可,
关于使用任何常规优化方法的最后说明。据我了解,这是6个维度的离散问题,因为您有6个分量:blue_low,blue_high,green_low,green_high,red_low,red_high,并且每个分量只有255个可能的值。这将阻止使用大多数优化方法,因为它们要求问题是连续的。
,在当前算法中,您要找到色彩空间数据的模式(即峰值),然后围绕该模式对称地获取分箱(颜色值)。
对于正态分布曲线,您将基于平均值附近的标准偏差数得出总体百分比,如下所示:
在正态分布中,均值,中位数和众数将相同。但是,如果您的分布偏斜,则均值左侧的总体将不会与均值右侧的总体相同。因此,您可以进行以下简单调整:
让p_left
为峰左侧的人口百分比,p_right
为峰右侧的人口百分比。例如:让p_left = 40%
和p_right = 60%
。您可以设置另一个参数(-20,20)
,例如15%,而不是使用% of selected population
的固定间隔宽度40。这是我们希望围绕该模式(包括该模式)的总人口。然后,您可以将这15%划分为左与右人口的比例。
left proportion = 15% x 40% = 6%
right proportion = 15% x 60% = 9%
您应该通过计算mode % of population
并从其中各取一半来更正这6%和9%的值。例如:如果该模式是总人口的5%,则应从6%和9%中减去2.5%。调整后的p_left
和p_right
为:
p_left = 6% - 2.5% = 3.5%
p_right = 9% - 2.5% = 6.5%
您无需计算均值周围的间隔,而是计算需要包含左右两边的区间以确定范围。例如:您可能会发现,左侧包含5个垃圾箱,总计占总人口的3.5%,右侧添加3个垃圾箱,大约占总人口的6.5%。
因此,您的范围变为(x - 5,x + 3)
,其中x是模式的x坐标。
参数估计::要确定人口的众数百分比的正确百分比(上例中为15%),您可以在标准的蒙版图像集上计算直方图,并使用确定一个好的初始估算。本质上是计算蒙版图像中的未蒙版像素,然后将其除以总像素
,实际上,找到给定数据集的全局最优并不是太复杂。为简单起见,我们首先假设您具有灰度图像,因为每种颜色都是独立对待的(我相信)。如果您要根据所需间隔内的所有3种颜色对像素进行评分,则可能会有些复杂,但似乎并非如此。
因此,无论如何,您可以根据数据集的大小彻底检查每个图像的每个间隔。例如,如果每个像素仅采用[0,255]中的整数值,则您甚至只需考虑100个间隔大小。因此,您可以计算每个候选间隔大小和每个图像的准确性,并简单地采用产生最高平均准确性的间隔。重复所有颜色。当然,这是蛮力的方法,但是除非您的数据集很大,否则使用优化的矩阵运算在计算上不会昂贵。如果您的数据集很大,那么使用该技术的足够大的随机图像样本将产生一个近似值(尽管不是全局最优解)。
顺便说一句,当前计算蒙版和地面真相之间的精度的方法效率很低。根据经验,几乎总是使用numpy矩阵运算,因为它们效率更高(有一些很酷的算法技巧可以节省矩阵运算的时间,并且它们是用C编写的,因此由于好吧。
您可以替换为:
for i in range(mask.shape[0]) :
for j in range(mask.shape[1]) :
if mask[i,j] == 255 and img[i,j,0] == 255:
TP = TP + 1
if mask[i,j] == 0 and img[i,0] == 0:
TN = TN+1
if mask[i,0] == 0:
FP = FP+1
if mask[i,0] == 255:
FN = FN+1
使用等效矩阵运算:
ones = np.ones(img.shape)
zeros = np.zeros(img.shape)
diff = mask - img
TP = sum(np.where(np.multiply(diff,img) == 1,ones,zeros))
TN = sum(np.where(np.multiply(diff,1-img) == 1,zeros))
FP = sum(np.where(diff == -1,zeros))
FN = sum(np.where(diff == 1,zeros))
这将为您节省时间,特别是如果您使用像我建议的那种蛮力方法,但通常也是一种好习惯