Бывали ли вы когда-нибудь в ситуации, когда вам что-то понравилось в интернет-магазине, но вы не хотели купить это, не примерив? Конечно, некоторые магазины предлагают возможность примерить одежду после заказа перед оплатой.
Однако по статистике с каждым годом доля онлайн-заказов в интернет-магазинах одежды и обуви растёт, но и доля возвратов тоже растёт, она составляет 50–70% — это огромные логистические затраты, которые можно существенно сократить, если воспользовавшись онлайн-примерочной.
Представьте, что вы загружаете свою фотографию, выбираете наряд, и он переносится на ваше изображение.
Виртуальные примерочные уже есть, они работают достаточно успешно.
Некоторое время назад нас заинтересовала эта тема, как обстоят дела с одеждой? Такие работы тоже есть, но успешных гораздо меньше, во многих из них не найти ничего, кроме статьи, о работающем примере можно только мечтать.
Мы решили это исправить и поддержать одну из сетей в библиотеке OpenCV. Что из этого получилось, вы можете увидеть в virtual_try_on.py образец.
Результат не идеален, но в этой области считается неплохим.
Если вы хотите узнать, как работает виртуальная примерочная и с какими трудностями мы столкнулись при интеграции модели в OpenCV — добро пожаловать под кат! Мы выбрали нейросеть 2019 года КП-ВТОН с довольно хорошим визуальным результатом.
По сравнению со своими аналогами CP-VTON интересен тем, что может достаточно точно передавать форму одежды в формируемом изображении, при этом особенности одежды (рисунок, логотип и фактура) не теряются при передаче.
Некоторые подобные сети используют 3D-модели человека, для получения которых требуется 3D-сканер.
Этот факт существенно сужает диапазон применимости модели.
Также большим плюсом является наличие кода на github. А вот авторы КП-ВТОН репозитории Они предоставляют только скрипты для обучения и тестирования, демо-версии, чтобы опробовать сеть на своих фотографиях, нет. Мы сами обучили сеть и сделали ее общедоступной.
Принцип действия
Модель КП-ВТОН состоит из двух субмодулей: ГММ (Geometric Matching Module) — модуль деформации одежды в соответствии с позой человека и ТОМ (Try-On Module) — модуль переноса деформированной одежды на изображение человека.
Обучение GMM требует тройного
, а для ТОМ -
,
- образ человека,
- изображение одежды,
- выход ГММ,
- основная истина (человек в одежде
),
- основная истина (деформированная одежда, полученная путем наложения маски одежды на
).
Но такие тройки собрать достаточно сложно; обычно есть только
И
.
Решение этой проблемы было предложено в одной из предыдущих работ. ВИТОН , авторы статьи вместо этого
на вход сети был подан тензор с описанием человека
.
При создании
Целью было сохранить как можно больше информации о человеке, включая лицо, волосы, форму тела и осанку, при этом минимизировав влияние старой одежды (цвет, форма, текстура).
Такой подход позволяет избежать показа в сети эталонного изображения человека в желаемой одежде.
Таким образом, GMM принимает в качестве входных данных
и генерирует деформированную одежду
, который во время обучения сравнивается с
.
ТОМ принимает ввод
, и сетевой выход
по сравнению с
.
Рассмотрим подробнее формирование тензора
.
Описание человека состоит из маски тела человека, ключевых моментов позы и изображения головы.
Сначала фотография ищется по ключевым точкам с помощью сети.
Для получения бинарной маски тела человека используется семантическая сегментация одежды и частей тела.
Эта задача решается с помощью модели LIP_JPPNet .
В процессе поддержки в OpenCV был добавлен (независимый) образец.
Для маски тела берутся все ненулевые пиксели, для маски головы берутся пиксели, соответствующие классам: лицо, солнцезащитные очки, головной убор и волосы.
Затем маска тела размывается, а маска головы накладывается на входное изображение.
Давайте посмотрим на работу модулей.
ГММ используется для изменения формы желаемой одежды в зависимости от позы человека.
В процессе работы сеть самостоятельно извлекает ключевые признаки из изображения одежды и тензора, описывающего человека, нормализует признаки с помощью
.
Рассчитывается корреляция признаков и прогнозируется вектор параметров пространственного преобразования.
В результате постобработки из него генерируется деформационная сетка исходного изображения одежды.
С помощью билинейной интерполяции одежды по узлам сетки получается форма одежды, соответствующая позе человека.
Сравнить полученную деформацию одежды
С
использовал
.
Модуль ТОМ переносит деформированную одежду в изображение человека.
Сеть основана на архитектуре Unet. Такая архитектура была выбрана потому, что при непосредственном переносе деформированной одежды на изображение человека результат выглядит неестественно на стыках и местах соприкосновения человека с одеждой.
Unet избавляется от этого, делая переходы более плавными.
Однако идеально выровнять одежду по фигуре невозможно, поэтому даже незначительное движение одежды относительно позы может сделать вывод в Unet размытым.
Сеть представляет собой последовательность сверток и объединений, которые уменьшают пространственное разрешение изображения.
А затем разрешение увеличивается с помощью последовательности Upsample и свертки.
Кодировщиком в сети является предварительно обученный ВГГ-19. Чтобы выходное изображение не было слишком размытым, используется следующий подход. Унет предсказывает образ человека
и составная маска
.
Маска затем сочетается с деформированной одеждой.
и накладывается на
, в сочетании с обратной маской
и полученное изображение
.
— признак поэлементного умножения матрицы.
При обучении сети основная цель модуля Try-On — минимизировать несоответствие.
С
.
При сравнении результата сети и изображения из разметки попиксельно малое значение функции потерь будет соответствовать размытым изображениям.
А изображения, похожие с человеческой точки зрения, но с небольшими сдвигами, будут соответствовать большому значению функции потерь.
Чтобы избежать этих недостатков, используйте комбинацию
С потеря восприятия .
Картинка из разметки и предсказание сети пропускаются через несколько слоев сети VGG и сравниваются полученные характеристики нейросети, которые будут инвариантны к небольшому изменению положения в пространстве.
VGG также чувствителен к резким изменениям яркости пикселей — это позволяет устранить размытость изображения за счет увеличения значения функции потерь в таких картинках.
Реализация в OpenCV
Для работы сети помимо изображения человека и одежды также требуется изображение со смысловой сегментацией частей человеческого тела и одежды на нем и файл с ключевыми точками человеческого тела.Авторы статьи использовали json-файлы с сохраненными точками человеческого тела, полученные с помощью сети OpenPose от Caffe, и изображения с сегментацией из разметки набора данных LIP. Чтобы демо было удобно использовать, мы делаем поиск по ключевым точкам и сегментацию на лету, поэтому пользователю для входа достаточно только фото человека и изображения новой одежды.
При портировании сети на OpenCV пришлось убрать всю предобработку через PIL. Вот тут-то и произошли чудеса с сегментацией.python3 virtual_try_on.py -i person_img.jpg -c cloth.jpg
Набор данных LIP представляет изображения в оттенках серого, где интенсивность пикселей соответствует классу объекта.
А при загрузке датасета для CP-VTON те же изображения уже в цвете.
Считывается трехканальное изображение, но затем производится работа с одноканальными изображениями: shape = (segm > 0).
astype(np.float32) head = (segm == 1).
astype(np.float32) + \ (segm == 2).
astype(np.float32) + \ (segm == 4).
astype(np.float32) + \ (segm == 13).
astype(np.float32) cloth = (segm == 5).
astype(np.float32) + \ (segm == 6).
astype(np.float32) + \ (segm == 7).
astype(np.float32)
Оказывается, PIL может хранить цветное изображение и кодировку, с помощью которой оно было получено.
Перевод надписей в цвета находился в human_colormap.mat. Как их оттуда прочитать? Я не хотел устанавливать Matlab. К счастью, в scipy есть загрузчик для таких файлов.
Потратив немного (много) времени на эти махинации, мы смогли извлечь из сегментации маску тела и головы.
Более того.
Чтобы размыть маску, ее сначала уменьшают в 16 раз, затем возвращают к исходным размерам.
mask = mask.resize((width // 16, height // 16), Image.BILINEAR)
mask = mask.resize((width, height), Image.BILINEAR)
Получаем маску в оттенках серого.
Для этого используется билинейная интерполяция.
Кажется, при портировании на OpenCV будет проще.
Мы заменили PIL resize на cv.resize и получили совершенно другой результат.
Слева — маска, полученная посредством изменения размера PIL, справа — cv.resize. Маски оказались совершенно разные, а как насчет результата сети? В общем, смотрите сами.
Слева — результат, полученный при использовании маски с PIL resize, справа — cv.resize. Выглядит немного грустно, не хотелось бы так уродовать людям руки.
Что это за билинейная интерполяция? После долгих поисков я ничего не смог найти.
Оставался последний, но верный способ — посмотреть исходники Си.
Там мы смогли увидеть, что билинейное изменение размера выглядит вовсе не как билинейное, а как площадное.
Размер ядра зависит от масштабного коэффициента, в нашем случае ядро имело размер 33 = 16 * 2 + 1, а в OpenCV ядро фиксированного размера — 3. При этом мы заметили, что пиксель значения различаются только при уменьшении размеров, а при после восстановления результаты достаточно близки.
Но смена типа интерполяции все равно не помогла добиться желаемого результата.
Давайте посмотрим дальше.
Коэффициенты интерполяции рассчитываются очень странным образом; они не симметричны.
Нам никогда не удавалось смоделировать это.
Поэтому мне пришлось написать небольшой класс с таким типом интерполяции: Реализация билинейной интерполяции из PIL class BilinearFilter(object):
"""
PIL bilinear resize implementation
image = image.resize((image_width // 16, image_height // 16), Image.BILINEAR)
"""
def _precompute_coeffs(self, inSize, outSize):
filterscale = max(1.0, inSize / outSize)
ksize = int(np.ceil(filterscale)) * 2 + 1
kk = np.zeros(shape=(outSize * ksize, ), dtype=np.float32)
bounds = np.empty(shape=(outSize * 2, ), dtype=np.int32)
centers = (np.arange(outSize) + 0.5) * filterscale + 0.5
bounds[::2] = np.where(centers - filterscale < 0, 0, centers - filterscale)
bounds[1::2] = np.where(centers + filterscale > inSize, inSize, centers + filterscale) - bounds[::2]
xmins = bounds[::2] - centers + 1
points = np.array([np.arange(row) + xmins[i] for i, row in enumerate(bounds[1::2])]) / filterscale
for xx in range(0, outSize):
point = points[xx]
bilinear = np.where(point < 1.0, 1.0 - abs(point), 0.0)
ww = np.sum(bilinear)
kk[xx * ksize : xx * ksize + bilinear.size] = np.where(ww == 0.0, bilinear, bilinear / ww)
return bounds, kk, ksize
def _resample_horizontal(self, out, img, ksize, bounds, kk):
for yy in range(0, out.shape[0]):
for xx in range(0, out.shape[1]):
xmin = bounds[xx * 2 + 0]
xmax = bounds[xx * 2 + 1]
k = kk[xx * ksize : xx * ksize + xmax]
out[yy, xx] = np.round(np.sum(img[yy, xmin : xmin + xmax] * k))
def _resample_vertical(self, out, img, ksize, bounds, kk):
for yy in range(0, out.shape[0]):
ymin = bounds[yy * 2 + 0]
ymax = bounds[yy * 2 + 1]
k = kk[yy * ksize: yy * ksize + ymax]
out[yy] = np.round(np.sum(img[ymin : ymin + ymax, 0:out.shape[1]] * k[:, np.newaxis], axis=0))
def imaging_resample(self, img, xsize, ysize):
height, width, *args = img.shape
bounds_horiz, kk_horiz, ksize_horiz = self._precompute_coeffs(width, xsize)
bounds_vert, kk_vert, ksize_vert = self._precompute_coeffs(height, ysize)
out_hor = np.empty((img.shape[0], xsize), dtype=np.uint8)
self._resample_horizontal(out_hor, img, ksize_horiz, bounds_horiz, kk_horiz)
out = np.empty((ysize, xsize), dtype=np.uint8)
self._resample_vertical(out, out_hor, ksize_vert, bounds_vert, kk_vert)
return out
Результаты
В целом получился довольно интересный конвейер из 4-х сетей, полученных от разных фреймворков, но работающих вместе в OpenCV. При тестировании сети на реальных фотографиях мы заметили несколько особенностей сети.Набор обучающих данных содержал только изображения девушек, поэтому сеть не всегда хорошо работает с мужчинами.
Их руки становятся более женственными, а переход от головы к телу имеет очень яркую границу.
Также для получения качественного результата необходимо загрузить фото с пропорциональным соотношением сторон 256 на 192, иначе сеть прибавит пару килограммов.
Мы добавили обрезку исходного изображения к образцу, пока не получили желаемое соотношение ширины и высоты.
Лучше, чтобы кроме человека ничего не было, а одежда на изображении была на белом фоне без каких-либо узоров и надписей, иначе эти части тоже будут восприниматься как одежда.
P.S. В мае 2020 года OpenCV отмечает свое 20-летие.
В этом году мы планируем написать больше статей и придумать другие мероприятия.
Следите за новостями! Теги: #Машинное обучение #Интернет вещей #открытый исходный код #искусственный интеллект #глубокое обучение #OpenCV #индустрия моды
-
Хотите Кнопку «Опубликовать В Вконтакте»?
19 Oct, 24 -
X-Wing Развалился В Воздухе
19 Oct, 24