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

Пусть в блоге Яндекса на Хабрахабре эта неделя пройдет под знаком нейронных сетей. Как мы видим, нейросети сейчас начинают использоваться в очень многих областях, включая поиск. Кажется, что «модно» искать для них новые сферы применения, а в тех сферах, где они работают уже какое-то время, процессы не такие интересные.

Однако события в мире синтеза визуальных образов доказывают обратное. Да, компании еще несколько лет назад начали использовать нейросети для операций с изображениями —, но это был не конец пути, а его начало. Недавно руководитель группы компьютерного зрения «Сколтеха» и большой друг Яндекса и ШАДа Виктор Лемпицкий рассказал о нескольких новых способах применения сетей к изображениям. Поскольку сегодняшняя лекция — про картинки, то она очень наглядная.


Под катом — расшифровка и большинство слайдов.
Добрый вечер. Я сегодня буду говорить про сверточные нейросети и буду предполагать, что большинство из вас провели последние четыре года не на необитаемом острове, поэтому что-то про сверточные глубокие нейросети слышали, и знаете примерно, как именно они обрабатывают данные.

fdca53382b3a4541a2996aae7a00407d.jpg

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

b65c56d38b2045dd88896513b915c546.jpg

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

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

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

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

6d60134a729f4321ad71fa06219926bc.jpg

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

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

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

ed71766f32c64cdfbbadee39f08df231.jpg

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

5e6ab727215e4d579e6d9dcf9b303f7a.jpg

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

0c9ce372fa3a445d8798d693423b9a28.jpg

Нейросеть Досовицкого всё делает наоборот — генерирует изображение стула на выходе. Надеюсь, разница понятна.

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

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

5eeaea2cbea847ecac68d3eb139d34ea.jpg

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

Если задать себе вопрос, почему это так хорошо работает, то есть несколько ответов. Меня больше всего убеждают два. Первый: почему должно работать плохо, если хорошо работают прямые нейросети? И прямые, и развернутые сети используют одно и то же свойство натуральных изображений о том, что их статистика локальная и внешний вид кусочков изображений не зависит от того, на какую часть глобального изображения мы смотрим. Такое свойство позволяет нам разделять, переиспользовать параметры внутри сверточных слоев обычных сверточных нейросетей, и это же свойство используют развернутые сети, используют успешно — оно дает им возможность относительно небольшим количеством параметров обрабатывать и заучивать большие объемы данных.

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

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

243f2ae4db9e457e97def6a34301debe.jpg

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

Начинается все с классической задачи текстурного синтеза. Этой задачей люди в компьютерной графике занимались десятилетиями. Совсем коротко она формулируется так: дан образец текстуры и нужно предложить метод, который сможет по данному образцу генерировать новые образцы той же текстуры.

Задача синтеза текстур во многом упирается в задачу сравнения текстур. Как понять, что эти два изображения соответствуют похожим текстурам, а эти два — непохожим? Достаточно очевидно, что какие-то простые подходы — например, сравнить три изображения попиксельно попарно или посмотреть на гистограммы — не приведут нас к успеху, потому что схожесть у этой пары окажется такой же, как и у этой. Многие исследователи ломали голову над тем, как определить меры схожести текстур, как определить некий текстурный дескриптор, чтобы какая-то простая мера в таких продвинутых дескрипторах хорошо бы говорила, являются текстуры похожими или нет.

d69d36cc767f441c886ad05bd686574c.jpg

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

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

Так или иначе, статистика определяется так. Возьмем изображение, пропустим его через первые несколько сверточных слоев нейросети. На этом слое получим набор карт, изображений Fl (t). t — образец текстуры. Его набор карт изображений — на l-слое.

Дальше мы считаем статистику текстур — она должна как-то исключать из себя параметры, связанные с пространственным расположением того или иного элемента. Естественный подход — взять все пары карт в данном представлении. Мы берем все пары карт, считаем все попарные скалярные произведения между этими парами, берем i-тую карту и j-тую карту, дальше k-тый индекс пробегает все возможные пространственные положения, мы считаем подобное скалярное произведение и получаем i, j коэффициент, член матрицы Грама. l-матрица Грама, посчитанная таким образом, описывает нашу текстуру.

Дальше, когда надо сравнить, похожи ли два изображения как текстуры, мы просто берем некоторый набор слоев: это может быть один слой — тогда данная сумма включает в себя один член. Или можем взять несколько сверточных слоев. Сравниваем матрицы Грама, посчитанные таким образом для этих двух изображений. Сравнивать их мы можем просто поэлементно.

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

8acf3ab650934ec48272d3d6f5b69a4a.jpg

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

3ba6c8780f6c4adfa8c358fa3da6ba6c.jpg

Работает это очень хорошо. Вот пример из их статьи. Справа пример, который мы хотим повторить, а слева синтезированная текстура. Главная проблема — время работы. Для небольших изображений оно составляет много секунд.

Идея данного подхода — радикально ускорить процесс генерации текстур через использование перевернутой нейросети.

14b2442206eb468e81e81faf70d68e4d.jpg

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

Таким образом х — который в предыдущем подходе был некоторой независимой переменной и мы ей манипулировали, пытаясь создать текстуры, — теперь становится зависимой переменной, выходом новой нейросети. У нее свои параметры θ. Идея в том, чтобы перенести обучение в отдельную стадию. Теперь мы берем и учим нейросеть, настраивая ее параметры так, чтобы для произвольных векторов шума получающиеся изображения имели матрицы Грама, соответствующие нашему образцу.

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

Оптимизация нейросети тоже производится методом стохастического градиентного спуска, а наше обучение происходит так: мы синтезируем вектор шума, пропускаем его через нейросеть, считаем матрицы Грама, смотрим расхождения и обратным распространением через весь этот путь понимаем, как именно надо поменять параметры нейросети.

Здесь некоторые детали архитектуры, я их пропущу. Архитектура полностью сверточная, в ней нет полносвязных слоев, и количество параметров невелико. Такая схема, в частности, не позволяет сети просто заучить предоставленный ей пример текстуры так, чтобы выдавать его снова и снова.

05403435d030449187f028da2900bef4.jpg

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

e73b01e944de4c1fa6a509167a03afcb.jpg

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

ed04543d77be4d44964e997337bfadf2.jpg

e3bf092bf6564d029c95c6428df30750.jpg

Посередине приведен образец текстуры, полученной с помощью метода Готисса (неразборчиво — прим. ред.), оптимизации, а справа примеры текстур, выдаваемых нейросетью просто путем преобразования вектора шума.

74b58871f72647648c6fff82c8978a18.jpg

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

0832b0196988471a92d727cc70c73858.jpg

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

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

672061d5868c4474b52b5bef1a296000.jpg

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

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

98ac58b14472449d9f185fcf9131a521.jpg

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

8785db1520e644289368d3b2c436ea7a.jpg

Здесь, наверное, скорее правый.

6bfb9c638d26455399cf0509361e6381.jpg

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

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

d28f86f53abc4ec4a4fa4e03c1cd7954.jpg

Зачем нам понадобилось решать такую экзотическую задачу? Оказывается, есть по крайней мере два приложения, где она актуальна. Главное для нас — задача улучшения зрительного контакта при видеоконференциях. Наверное, многие замечали, что когда вы разговариваете по Skype или другому видеоконференционному сервису, то не можете смотреть с собеседником друг другу в глаза, потому что камера и лицо собеседника разнесены по местоположению.

Еще одно применение — постобработка кинофильмов. У вас есть какая-то суперзвезда, у которой минута съемки стоит миллион долларов. Вы отсняли дубль, а эта суперзвезда посмотрела не туда. И теперь надо или переснять дубль, или — подредактировать направление взгляда.

fccb5902f86d44948c3650ea1c233a8d.jpg

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

e3af6851476a4509897d34b5cad505b9.jpg

Это позволило нам извлекать из подобного набора данных пары примеров, для которых мы знаем: единственное отличие между изображениями слева и справа — направление взгляда. Например, здесь в каждом случае отличие составляет 15 градусов в вертикальном направлении.

Мы практически перешли от такой сложной задачи к классической задаче обучения с учителем. У нас есть пример слева, и нам нужно сгенерировать пример справа. К сожалению, просто решать эту задачу с помощью черного ящика, как говорил Сергей, не получается. Мы много раз пробовали черный ящик, даже темно-серый ящик. Поскольку у нас вход и выход имеют довольно сложную структуру, а обучающих данных довольно много, мы долго мучили людей. Решение с помощью черного ящика не работает. Нужно использовать некую специфику задачи, а именно — то, что правое изображение может быть достаточно неплохо приближено левым изображением после некоторой нежесткой деформации.

b52c6b988bda47ed99c65ecdf2d4497e.jpg

Была построена нейросеть, которая принимает на вход изображение слева, а также берет положение особых точек глаза на данном изображении. При этом изображение кодируется тремя картамиЕ красной, зеленой и синей. Каждая особая точка тоже кодируется картой. Дальше она принимает на вход угол, на который мы хотим перенаправить взгляд на картинке, и строит поле искажения, деформации. Потом такое поле применяется к входному изображению дифференцируемым образом с помощью spatial transformer layer. Он сейчас есть, например, в torch и многих других библиотеках.

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

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

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

7302cb2ff9ad4c26a1bde29a379d17ae.jpg

Вот еще статические примеры. Такая нейросеть прибавляет плюс-минус 15 градусов в вертикальном направлении. Ни один из этих людей не представлен в тренировочном множестве.

d54cf20799864b63af30dee7c3643a9c.jpg

Пример для горизонтального перенаправления.

e6036bc0689b4903bb6128a76c353a7f.jpg

Еще набор примеров. Мы рады сказать, что готовится к выпуску веб-демо, куда каждый сможет подгрузить картинку и посмотреть на результаты.

Наконец, третья система. Это совсем свежая статья, первый раз про нее рассказываю. Она посвящена нейросетям, которые учат визуальные маркеры.

Визуальные маркеры нас окружают и становятся всё более заметными. Речь идёт об изображениях, в которых компьютером — и для компьютера же — зашита некая информация. Такие изображения возникают в реальном человеческом мире и обладают некоторыми нежелательными свойствами. Они, например, эстетически неидеальны. Я говорю о таких примерах, как QR-коды, бар-коды.

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

Наша идея — объединить процесс создания маркера и процесс создания распознавателя маркера в единый оптимизационный процесс и параллельно добиться некоторых эстетических свойств получаемых маркеров.

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

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

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

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

Мы можем посмотреть, какие именно маркеры нашей системы выучатся. Главная идея, что синтезатор будет адаптирован под распознаватель, а распознаватель — под синтезатор.

0f30b340b5df477cb7cb9c6fc5310d7e.jpg

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

В целом, они имеют интересную регулярную структуру — какую-то гармоническую, синусоидальную.

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

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

b125faba2dfc4f88886f5fed04689143.jpg

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

f5077ddd741b45b4be4a6b42ddfdfd3a.jpg

Все это происходит в рамках единого оптимизационного процесса и позволяет нам синтезировать маркеры, подобные тем, что на слайде. Слева задан образец текстуры, справа — образцы маркеров для случайных последовательностей. Интересный факт: глядя на эти маркеры, можно увидеть, что некоторые камушки не меняются от маркера к маркеру, а некоторые части маркера меняются в зависимости от бита. Такая ситуация в точности соответствует тому, что происходит в QR-коде, где у вас есть по углам четыре якоря, которые не меняются и используются для локализации, и есть переменная часть кода. Эта нейросеть выучила такой эффект — он не зашит в ее архитектуру.

e4734ba3a6e946b4acc98f96c23ab07e.jpg

Еще два примера. Нейросеть учится письменности.

Нейросеть учится на фотографии храма Василия Блаженного. Нам показалось, что было бы здорово, если бы огромные QR-коды, налепленные на стенах в Москве, заменились бы чем-то более интересным.

48125a37d9a94f2da07ba1f214448bbb.jpg

Я представил три статьи. Хотел бы поблагодарить своих соавторов. Это их результаты, и в данной области есть много чего нерешенного. Главная нерешенная задача, на мой взгляд, — построение нейросетей, способных синтезировать большие новые изображения. Сейчас многие умеют синтезировать по фотографии версию Ван Гога, но я не знаю нейросетей, которые могут по изображению Ван Гога синтезировать исходную фотографию. Это интересная нерешенная задача. Спасибо.

Комментарии (0)

© Habrahabr.ru