如何使用优化算法寻找最佳参数

7
我会尽力帮忙翻译。以下是需要翻译的内容:

我正在尝试找到一组适合颜色掩码的间隔,以便从图像中提取皮肤。

我有一个包含图像和掩模的数据库,可以从这些图像中提取皮肤。以下是一个示例:

sample image

我正在对每张图片应用掩模,以便获得类似于这样的效果:

masked sample result

我正在从所有蒙版图像中获取所有像素,并删除黑色像素,以保留仅包含皮肤的像素。使用此方法,我能够收集不同人的不同肤色阴影的不同像素。
以下是我用于此的代码:
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)

在从不同的皮肤中提取所有颜色之后,我正在创建一个直方图,以便我可以看到哪些颜色更常见。代码太长了,无法创建直方图,但这是结果:

enter image description here

然后,我为每个颜色空间图选择一个转折点,并选择该颜色空间的距离,比如20。该颜色空间的区间是通过计算 [ 转折点 - 20,转折点 + 20 ] 得到的。

enter image description here

假设我们得到了以下内容:

R :

  • 转折点: 142
  • 距离: 61
  • 间隔: [81, 203]

G :

  • 转折点: 155
  • 距离: 10
  • 间隔: [145, 165]

B :

  • 转折点: 109
  • 距离: 14
  • 间隔: [95, 123]

我会使用这些间隔来创建数据集中彩色图像的蒙版,以提取皮肤(左侧:我的蒙版,右侧:真实蒙版):

enter image description here

使用我的间隔提取的掩码与数据集预先存在的掩码进行比较,计算准确性,以了解我得到的间隔的有效性和好坏。
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,j,0] == 0:
                TN = TN+1
            if mask[i,j] == 255 and img[i,j,0] == 0:
                FP = FP+1
            if mask[i,j] == 0 and img[i,j,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之间的数字来完成的。例如:

旧R:

  • 转折点:142
  • 距离:61
  • 间隔:[81, 203]

新距离=旧距离*0.7=61*0.7=43

新R:

  • 转折点:142
  • 距离:43
  • 间隔:[99, 185]
  • 为了获得更高的间隔,我会乘以1到2之间的数字
  • 为了获得较小的间隔,我会乘以0到1之间的数字

现在,我的问题是:

我想使用优化方法而不是手动和随机更改间隔来找到每个颜色空间的最佳可能间隔。我应该使用什么优化方法,如何使用它?

感谢您花费时间。感谢您的帮助。


从您的随机测试中,您是否注意到任何模式?作为起点,我会实现某种贪婪爬山算法,如果您有多个线程,甚至可以从多个位置开始/使用束搜索。您已经有了多个可用的性能度量标准。如果需要,我可以详细阐述实现细节。 - Leander
如果你真的需要找到最佳的掩膜,你还需要考虑一个具有不对称范围的掩膜,因为你的直方图显示略微偏斜的正态分布。 - Leander
@Leander,我对这些概念不熟悉...你能分享一些材料让我更好地理解吗?并且请尽可能多地分享有关实现的信息。 - Mohamed Benkedadra
@Leander 我可以找到一个模式。我从一个单一的区间开始,上下移动直到找到一个好的区间.. 但是我想要一个能够自动完成这个过程的代码或方法.. - Mohamed Benkedadra
我担心你在创建掩模(即剪辑中的‘区间掩模’)时,在原始掩模之外没有没有未遮挡的区域。你是仅将你的区间掩模应用于原始掩模内部,这会带来问题吗?我认为你可能会从最初遮盖的背景中得到一定数量的“误报”。 - AirSquid
@AirSquid 我没有使用原始掩码来应用从我的间隔创建的新掩码.. 目标是创建可以直接作用于彩色图像的间隔.. 就像问题中所看到的那样。 - Mohamed Benkedadra
4个回答

1
我建议使用遗传优化,可以很容易地实现您这样简单的问题。由于问题相对“小”,与@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个可能的值。这将防止使用大多数优化方法,因为它们要求问题连续。

1

一种快速收敛但可能无法获得全局最优解的基本方法是爬山算法

爬山算法是一种局部搜索,可以在这种情况下使用。
爬山算法的工作原理是根据状态的分数性能从一个状态或解决方案转移到下一个状态或解决方案。如果找不到更好的状态,则返回该状态作为解决方案。

有多种实现爬山算法的方法,在您的情况下,我会这样做:

状态: 在您的情况下,该项包含MinMax numpy数组以及应用这些数组创建的掩膜的准确度f-measure作为分数属性的图像。

目前我建议您只采取对称范围,以大幅减少搜索空间。

起始状态
您可以随机创建起始状态,为每个通道(红色、绿色、蓝色)取一个随机区间。如果多次运行此算法,则特别有用。根据您的直方图确定每个区间的最大值和最小值。

迭代过程(这是搜索过程)
您需要创建一个无限循环,在其中为当前状态创建后继状态。增加或减少每个通道的间隔,例如当前状态下的 10,然后这些新间隔的每种组合都可以成为后继状态。
另一种方法是在每次迭代中切换通道。因此,在第一次迭代中,您将创建一个后继状态,该状态具有当前状态的红色通道减小 10,以及一个后继状态,该状态具有当前状态的红色通道增加 10。第二次迭代更改绿色通道,第三次迭代更改蓝色通道,依此类推。

然后,您基于每个后继状态创建一个掩码,并将它们应用到图像上,从而确定每个后继状态的性能。
选择表现最佳的后继状态,并将其作为当前状态,如果其性能更好。

重复此过程,直到最佳的后继状态的性能比当前状态差,然后您就知道已经达到了局部最优解。将此状态作为解返回。

问题
如上所述,该算法将找到起始状态的局部最优解。这是由于该算法的贪婪性。
因此,您可能希望在不同的起始位置重新启动此算法,以允许更多的搜索空间被探索,增加找到全局最大值的机会。
如果您有多个线程,可以并行运行多个实例,然后从每个实例的结果中返回最佳状态。

爬山算法并不是最佳的优化算法,但它非常快速和易于实现。


1

在您当前的算法中,您正在查找色彩空间数据的众数(即峰值),然后取周围对称的条形(颜色值)。

对于正态分布曲线,您可以根据平均值周围标准偏差数量来确定人口百分比,如下所示:

Normal Distribution Curve

在正态分布中,平均数、中位数和众数将相同。然而,如果您的分布是偏斜的,则均值左侧的人口与均值右侧的人口不同。因此,您可以进行以下简单的调整:
p_left成为峰值左侧人口的百分比,p_right成为峰值右侧人口的百分比。例如:让p_left = 40%p_right = 60%。而不是使用(-20,20)的固定间隔宽度40,您可以设置另一个参数,即% of selected population,例如15%。这是我们想要围绕众数(包括众数)的总人口。然后,您可以按左侧人口与右侧人口的比例将这15%分配。
left proportion = 15% x 40% = 6%
right proportion = 15% x 60% = 9%

你应该通过计算人口的众数%来纠正这6%和9%,并从中各取一半。例如:如果众数是人口的5%,则应该从6%和9%中分别减去2.5%。这将得到调整后的p_leftp_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%)的正确%,您可以在一组标准的掩膜图像上计算直方图,并使用它来确定一个良好的初始估计。基本上,计算掩膜图像中未遮盖像素的数量并将其除以总像素数。


我意识到 p_left + p_right + p_mode 不应该大于100%,这就是为什么我添加了调整的原因。请阅读上面关于 p_left 和 p_right 的定义,它们表示左侧 /(左侧+右侧)[不包括模式]。您也可以直接根据总人口计算 p_left、p_right 和 p_mode,这样就不需要进行调整了。 - vvg

1
实际上,对于给定的数据集找到全局最优解并不太复杂。为简单起见,让我们首先假设您拥有灰度图像,因为每种颜色都是独立处理的(我相信)。如果您根据所需间隔评分像素,则如果每个像素仅采用[0,255]中的整数值,则会更加复杂,但似乎您没有这样做。
因此,您可以仅针对每个图像检查每个间隔,并根据数据集的大小进行检查。例如,如果每个像素仅采用[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,j,0] == 0:
            TN = TN+1
        if mask[i,j] == 255 and img[i,j,0] == 0:
            FP = FP+1
        if mask[i,j] == 0 and img[i,j,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,ones,zeros))
FP = sum(np.where(diff == -1,ones,zeros))
FN = sum(np.where(diff == 1,ones,zeros))

这会节省你的时间,特别是如果你使用类似我建议的暴力破解方法,但一般来说也是好的实践。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接