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

How to use an optimization algorithm to find the best possible parameter

我正在尝试为颜色遮罩找到合适的颜色间隔,以便从图像中提取皮肤。

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

我正在为每张图片应用遮罩,以便获得类似这样的效果:

我从所有蒙版图像中获取所有像素并移除黑色像素,以便仅保留包含皮肤的像素。使用这种方法,我能够收集不同像素,这些像素包含来自不同人的不同皮肤的不同颜色。

这是我为此使用的代码:

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)

从不同皮肤中提取所有颜色深浅后,我创建了一个直方图,可以让我看到哪些颜色更常见。代码对于直方图的创建来说太长了,但这是结果:

然后,我使用每个颜色 space 图形的转折点并为该颜色选择一个距离 space,比如 20。该颜色 space 的间隔由做[转折点 - 20,转折点+20]

假设我们得到以下结果:

R :

G :

B :

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

将使用我的间隔提取的掩码与数据集预先存在的掩码进行比较,并计算准确性以查看我得到的间隔的有效性和好坏:

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)

我不断更改间隔,测试和计算准确性,以便找到每种颜色的最佳间隔 space。此更改是通过将距离乘以 0 到 2 之间的数字来完成的。例如:

老 R :

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

新R:

现在,我的问题是:

我想找到每种颜色的最佳间隔 space 使用优化方法而不是手动和随机更改间隔。我应该使用什么优化方法以及如何使用它?

感谢您抽出宝贵时间。感谢您的帮助。

一种快速收敛但可能不会产生全局最优的基本方法是Hillclimbing

爬山是 local search 的一种形式,可以在这种情况下使用。
爬山的工作原理是根据状态的 scoreperformance 从一个状态或解决方案转到下一个状态或解决方案。如果找不到更好的状态,则状态作为解决方案返回。

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

The State:在你的例子中,一个项目包含 MinMax numpy 数组并且 accuracyf-measure 应用在图像上的这些数组创建的蒙版作为 score 属性.

For now I suggest you only take symmetrical ranges to massively reduce the search space.

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

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

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

重复这个过程,直到最好的后继状态比当前状态表现更差,然后你就知道你已经达到了 local 最佳状态。 Return 此状态为解决方案。

问题
正如上一行突出显示的那样,该算法将为起始状态找到局部最优值。这是因为这个算法的贪心。
因此,您可能希望在不同的起始位置重新启动此算法,允许探索更多搜索 space,增加找到 global 最大值的机会。
如果您有多个线程,您可以 运行 多个并行实例,然后最终从每个实例的结果中返回最佳状态。

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

我建议使用遗传优化,它可以很容易地解决像您这样简单的问题。由于问题相对“小”,与@Leander 建议的某些局部优化方法(如 Hillclimb)相比,找到最佳解决方案的时间不会更长。遗传算法是一种元启发式搜索,因此不能保证找到最佳解决方案,但它应该会让您非常接近。事实上,对于这么小的问题,您找到全局最优值的机会非常高。

首先我建议您看一下 DEAP,这样您就不必自己实施任何东西 (https://deap.readthedocs.io/en/master/)。它包含许多遗传算法变体的非常好的实现,并且有带有很好示例的教程。只要稍加努力,您应该能够在一两天内编写出一个简单的优化算法。

为了简单起见,遗传算法从现在起将被表示为GA

从哪里开始的一些提示:

  • 我建议您从 DEAP 中最简单的变体开始eaSimple。当这不能令人满意时,您总是可以转向更复杂的东西,但我认为没有必要。
  • 你的 Individual 在 GA 中将有 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%。您可以设置另一个参数 % of selected population,例如 15%,而不是您使用的固定间隔宽度 40 (-20,20)。这是我们想要的模式周围的总人口(包括模式)。然后,您可以将这 15% 划分为左右人口的比例。

left proportion = 15% x 40% = 6%
right proportion = 15% x 60% = 9%

您应该通过计算 mode % of population 并从每个中取出一半来更正这 6% 和 9%。例如:如果众数占总人口的 5%,则应从 6% 和 9% 中减去 2.5%。这给出调整后的 p_leftp_right 为:

p_left = 6% - 2.5% = 3.5%
p_right = 9% - 2.5% = 6.5%

您不是在均值周围平均划分区间,而是计算需要包括多少左右的 bin 来确定范围。例如:您可能会发现在左侧添加 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,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))

这将节省您的时间,特别是如果您使用我建议的 brute-force 方法,但通常也是一种很好的做法