Как использовать алгоритм оптимизации для поиска наилучшего возможного параметра

#python #optimization #mask

#python #оптимизация #маска

Вопрос:

Я пытаюсь найти хороший интервал цветов для маскировки цвета, чтобы извлечь обложку из изображений.

У меня есть база данных с изображениями и масками для извлечения обложек из этих изображений. вот пример примера :

пример изображения

Я применяю маску для каждого изображения, чтобы получить что-то вроде этого :

пример результата в маске

Я получаю все пиксели из всех замаскированных изображений и удаляю черные пиксели, чтобы сохранить только пиксели, содержащие скин. Используя этот метод, я могу собирать разные пиксели, содержащие разные оттенки цвета разных скинов разных людей.

Это код, который я использую для этого :

 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,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[

Теперь, к моему вопросу:

Я хотел бы найти наилучший возможный интервал для каждого цветового пространства, используя метод оптимизации вместо ручного и случайного изменения интервалов. Какой метод оптимизации я должен использовать и как я мог бы его использовать?

Спасибо, что нашли время. Мы ценим вашу помощь.

Комментарии:

1. Из ваших случайных тестов вы заметили какие-либо закономерности? Для начала я бы реализовал какой-нибудь жадный способ восхождения на холм, если у вас несколько потоков, вы можете даже начать в нескольких местах / использовать beam search. У вас уже есть несколько показателей производительности, которые вы можете использовать. Если вы хотите, я могу подробнее рассказать о реализации.

2. Если вам действительно нужно найти оптимальную маску, вам также необходимо рассмотреть маску с асимметричным диапазоном, поскольку ваши гистограммы показывают слегка искаженные нормальные распределения.

3. @Leander Я не знаком с этими концепциями.. не могли бы вы поделиться каким-нибудь материалом, чтобы я мог лучше понять? и, пожалуйста, да, поделитесь как можно большим количеством информации о реализации

4. @Леандер, я могу найти закономерность, да. Я начинаю с одного интервала и двигаюсь вверх и вниз, пока не достигну хорошего интервала .. но мне нужен код или метод, который делал бы это автоматически..

5. Я обеспокоен тем, что в маске, которую вы создали (ваша «маска интервалов» в клипе), у вас нет незамаскированных областей за пределами исходной маски. Применяете ли вы свою интервальную маску только внутри исходной маски, что было бы проблематично? Я бы подумал, что вы получите небольшое количество «ложных срабатываний» из-за изначально замаскированного фона.

Ответ №1:

Я бы предложил использовать генетическую оптимизацию, которая может быть легко реализована для такой простой задачи, как ваша. Поскольку проблема относительно «небольшая», поиск оптимального решения не должен занять намного больше времени по сравнению с некоторым локальным методом оптимизации, таким как Hillclimb, предложенным @Leander. Генетический алгоритм — это метаэвристический поиск, поэтому он не гарантирует нахождения оптимального решения, но он должен подвести вас очень близко. На самом деле для такой небольшой задачи вероятность того, что вы найдете глобальный оптимум, очень высока.

Для начала я бы рекомендовал взглянуть на DEAP, чтобы вам не пришлось ничего внедрять самостоятельно (https://deap.readthedocs.io/en/master /). Он содержит очень хорошие реализации многих вариантов генетического алгоритма, и есть учебные пособия с хорошими примерами. Приложив немного усилий, вы сможете составить простой алгоритм оптимизации за день или два.

Генетический алгоритм отныне будет обозначаться как GA для простоты

Несколько советов, с чего начать:

  • Я предлагаю вам начать с простейшего варианта eaSimple в DEAP. Когда это не будет удовлетворительным, вы всегда можете перейти к чему-то немного более сложному, но я думаю, что в этом не будет необходимости.
  • ваш 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 возможных значений. Это предотвратит использование большинства методов оптимизации, поскольку они требуют, чтобы задача была непрерывной.

Ответ №2:

Один из основных подходов, который быстро сходится, но может не привести к глобальному оптимуму, — это восхождение на холм.

Восхождение на холм — это форма локального поиска, которая может быть использована в этом случае.
Восхождение на холм работает путем перехода от одного состояния или решения к следующему в зависимости от оценки или производительности состояния. Если не удается найти лучшего состояния, это состояние возвращается как решение.

Существует несколько способов реализации восхождения на холм, в вашем случае я бы сделал что-то вроде этого:

Состояние: в вашем случае элемент, содержащий массивы numpy с минимальными и максимальными значениями точности или f-мерой маски, созданной с использованием этих массивов, примененных к изображению в качестве свойства score.

На данный момент я предлагаю вам использовать только симметричные диапазоны, чтобы значительно сократить пространство поиска.

Начальное состояние
Вы можете создать начальное состояние случайным образом, взяв случайный интервал для каждого канала (красный, зеленый, синий). Это особенно полезно, если вы запускаете этот алгоритм несколько раз. Определите максимум и минимум для каждого интервала на основе ваших гистограмм.

Итерационный процесс (здесь выполняется поиск)
Вы хотите создать бесконечный цикл, в котором вы создаете последующие состояния для текущего состояния. Увеличение или уменьшение интервала каждого канала, скажем, 10 текущего состояния, и затем каждая комбинация этих новых интервалов может быть последующим состоянием.
Другим способом могло бы быть переключение канала на каждой итерации. Итак, на первой итерации вы создаете состояние-преемник, у которого красный канал текущего состояния уменьшен на 10, и состояние-преемник, у которого красный канал текущего состояния увеличен на 10. На второй итерации вы меняете зеленый канал, на третьей итерации — синий канал и т.д.

Затем вы создаете маску на основе каждого последующего состояния и применяете их к изображению, таким образом определяя производительность каждого последующего состояния.
Выберите наиболее эффективное последующее состояние и примите его за текущее состояние, если его производительность выше.

Повторяйте этот процесс до тех пор, пока наилучшее последующее состояние не будет работать хуже текущего состояния, тогда вы будете знать, что достигли локального оптимума. Верните это состояние в качестве решения.

Проблемы
Как указано в строке выше, этот алгоритм найдет локальный оптимум для начального состояния. Это из-за жадности этого алгоритма.
Поэтому вы можете захотеть перезапустить этот алгоритм в разных начальных местоположениях, что позволит исследовать больше пространства поиска и повысит вероятность нахождения глобального максимума.
Если у вас несколько потоков, вы можете запускать несколько экземпляров параллельно, а затем, наконец, возвращать наилучшее состояние из результатов каждого экземпляра.

Восхождение на холм — не самый лучший алгоритм оптимизации, но он очень быстрый и простой в реализации.

Ответ №3:

В вашем текущем алгоритме вы находите режим (т. Е. пик) данных цветового пространства, а затем берете ячейки (значения цвета) симметрично вокруг режима.

Для кривой нормального распределения у вас будет процент населения, основанный на количестве стандартных отклонений от среднего, как указано ниже:

Кривая нормального распределения

При нормальном распределении среднее значение, медиана и режим будут одинаковыми. Однако, если ваше распределение искажено, совокупность в левой части среднего значения не будет такой же, как совокупность в правой части среднего значения. Итак, простая настройка, которую вы можете внести, заключается в следующем:

Пусть p_left будет % населения слева от пика и p_right будет% населения справа от пика. Например: пусть p_left = 40% и p_right = 60% . Вместо используемой вами фиксированной ширины интервала 40 (-20,20) , вы можете установить другой параметр, который составляет % of selected population , скажем, 15%. Это общая совокупность, которую мы хотим получить для режима (включая режим). Затем вы можете разделить эти 15% на пропорции между левой и правой совокупностью.

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

Вы должны исправить эти 6% и 9%, вычислив mode % of population и вычеркнув половину из каждого. Например: если режим составляет 5% от общей численности, вы должны вычесть 2,5% из 6% и 9%. Это дает скорректированный 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% в примере выше), вы можете вычислить гистограммы для стандартного набора ваших замаскированных изображений и использовать это для определения хорошей начальной оценки. По сути, подсчитайте немаскированные пиксели в ваших замаскированных изображениях и разделите его на общее количество пикселей

Комментарии:

1. Я понимаю, что p_left p_right p_mode не должно быть> 100%, вот почему я добавил корректировку. Прочитайте определение p_left и p_right выше как left / (левый правый) [исключая режим]. Вы также можете вычислить p_left, p_right и p_mode непосредственно для общей совокупности, и вам не понадобится корректировка.

Ответ №4:

На самом деле, найти глобальный оптимум для данного набора данных не слишком сложно. Для простоты давайте сначала предположим, что у вас есть изображения в оттенках серого, поскольку каждый из цветов обрабатывается независимо (я полагаю). Было бы немного сложнее, если бы вы оценивали пиксель на основе всех 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))
  

Это сэкономит вам время, особенно если вы используете подход грубой силы, подобный тому, который я предложил, но также является хорошей практикой в целом