Как Mengxin реализует замену лица с помощью Python?

Python алгоритм искусственный интеллект Нейронные сети
Как Mengxin реализует замену лица с помощью Python?

Использованная литература:Мэтью Эрл.GitHub.IO/2015/07/28/…

Перевод: младший брат

Изменить: капитан


ты помнишь? Прошлой зимой в зарубежном кругу ИИ было много неприятностей: на известном форуме Reddit внезапно появился бог по имени дипфейки, и реализовал это с помощью нейросетей.замена лица, пусть некоторые голливудские актрисы "снимаются" в АВ.

谁不喜欢这个技术呢?

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

Беспечный! Кто-то использовал технологию ИИ для создания поддельного AV!

AI решил, что в будущем он будет лучшим актером на каждом «Оскаре».

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


Давайте научим этому методу «изменения лица» Python.

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

Конкретный процесс делится на четыре этапа:

  • обнаружить ориентиры лица;
  • Вращайте, масштабируйте и переводите рисунок 2, чтобы он соответствовал рисунку 1;
  • Отрегулируйте баланс белого изображения 2, чтобы он соответствовал изображению 1;
  • Интегрировать функции рисунка 2 в рисунок 1;

Полный адрес кода этого скрипта находится в конце текста.

Извлечение лицевых ориентиров с помощью dlib

Этот скрипт использует привязки Python dlib для извлечения ориентиров лица:

dlib реализует алгоритм, описанный в статье Вахида Каземи и Жозефины Салливан «Выравнивание лица за одну миллисекунду с помощью ансамбля дерева регрессии». Сам алгоритм очень сложен, но реализовать его через интерфейс dlib очень просто:

PREDICTOR_PATH = "/home/matt/dlib-18.16/shape_predictor_68_face_landmarks.dat"

detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor(PREDICTOR_PATH)

def get_landmarks(im):
   rects = detector(im, 1)

   if len(rects) > 1:
       raise TooManyFaces
   if len(rects) == 0:
       raise NoFaces

return numpy.matrix([[p.x, p.y] for p in predictor(im, rects[0]).parts()])

Функция get_landmarks() получает изображения в виде пустого массива и возвращает матрицу элементов 68x2. Каждая строка матрицы соответствует координатам x,y конкретной характерной точки на входном изображении.

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

Для создания предикторов требуется предварительно обученная модель. Модель доступна для скачивания в репозитории dlib sourceforge.

скачать портал

Выравнивание лица с Procrustes Analysis

Теперь у нас есть две матрицы ориентиров лица, где каждая строка содержит координаты черты лица (например, в строке 30 указаны координаты кончика носа). Нам просто нужно выяснить, как вращать, перемещать и масштабировать все точки в первом векторе, чтобы максимально точно совпадать с точками во втором векторе. Точно так же одно и то же преобразование можно использовать для наложения второго изображения на первое изображение.

Чтобы сделать это более математическим, давайте установим T, s и R и найдем минимум следующего уравнения:

где R — ортогональная матрица 2x2, s — скаляр, T — двумерный вектор, а pi и qi — индексы строки и столбца ранее рассчитанной матрицы подписи лица.

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

def transformation_from_points(points1, points2):
   points1 = points1.astype(numpy.float64)
   points2 = points2.astype(numpy.float64)

   c1 = numpy.mean(points1, axis=0)
   c2 = numpy.mean(points2, axis=0)
   points1 -= c1
   points2 -= c2

   s1 = numpy.std(points1)
   s2 = numpy.std(points2)
   points1 /= s1
   points2 /= s2

   U, S, Vt = numpy.linalg.svd(points1.T * points2)
   R = (U * Vt).T

   return numpy.vstack([numpy.hstack(((s2 / s1) * R,
                                      c2.T - (s2 / s1) * R * c1.T)),
                        numpy.matrix([0., 0., 1.])])

Разберем код пошагово:

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

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

3. Аналогичным образом разделите каждый набор точек на его стандартное отклонение. Это устраняет смещение масштабирования.

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

5. Вернуть весь процесс преобразования в виде матрицы аффинного преобразования.

После этого возвращенный результат можно вставить в функцию OpenCV cv2.warpAffine для сопоставления второго изображения с первым изображением:

def warp_im(im, M, dshape):
   output_im = numpy.zeros(dshape, dtype=im.dtype)
   cv2.warpAffine(im,
                  M[:2],
                  (dshape[1], dshape[0]),
                  dst=output_im,
                  borderMode=cv2.BORDER_TRANSPARENT,
                  flags=cv2.WARP_INVERSE_MAP)
return output_im

Исправить цвет второго изображения

Если мы попытаемся прямо наложить черты лица в этот момент, мы быстро обнаружим проблему:

这样肯定是没法儿看的

这样肯定是没法儿看的

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

COLOUR_CORRECT_BLUR_FRAC = 0.6
LEFT_EYE_POINTS = list(range(42, 48))
RIGHT_EYE_POINTS = list(range(36, 42))

def correct_colours(im1, im2, landmarks1):
   blur_amount = COLOUR_CORRECT_BLUR_FRAC * numpy.linalg.norm(
                             numpy.mean(landmarks1[LEFT_EYE_POINTS], axis=0) -
                             numpy.mean(landmarks1[RIGHT_EYE_POINTS], axis=0))
   blur_amount = int(blur_amount)
   if blur_amount % 2 == 0:
       blur_amount += 1
   im1_blur = cv2.GaussianBlur(im1, (blur_amount, blur_amount), 0)
   im2_blur = cv2.GaussianBlur(im2, (blur_amount, blur_amount), 0)

   # Avoid divide-by-zero errors.
   im2_blur += 128 * (im2_blur <= 1.0)

   return (im2.astype(numpy.float64) * im1_blur.astype(numpy.float64) /
                                               im2_blur.astype(numpy.float64))

Как сейчас эффект? Давайте взглянем:

这不是更奇怪了吗...

enter_image_description_here

Эта функция пытается изменить цвет изображения 2, чтобы он соответствовал изображению 1, то есть разделить im2 на размытие по Гауссу im2 и умножить на размытие по Гауссу im1. Здесь мы используем цветовой баланс (коррекция цвета с масштабированием RGB), но вместо прямого использования постоянного коэффициента масштабирования для всего изображения мы используем локальный коэффициент масштабирования для каждого пикселя.

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

Это,Это довольно грубое решение, а ключ представляет собой ядро ​​Гаусса соответствующего размера. Если он слишком мал, черты лица с рисунка 1 появятся на рисунке 2. Если он слишком большой, ядро ​​будет выходить за пределы области лица, закрытой пикселями, и менять цвет. Размер ядра здесь в 0,6 раза больше межзрачкового расстояния.

Слияние особенностей рисунка 2 с рисунком 1

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

Значение 1 (белый) — это область, в которой должен отображаться рисунок 2, а значение 0 (черный) — это область, в которой должен отображаться рисунок 1. Если значение находится между 0 и 1, это смешанная область рисунков 1 и рисунков 2.

Вот код, который генерирует вышеуказанное:

LEFT_EYE_POINTS = list(range(42, 48))
RIGHT_EYE_POINTS = list(range(36, 42))
LEFT_BROW_POINTS = list(range(22, 27))
RIGHT_BROW_POINTS = list(range(17, 22))
NOSE_POINTS = list(range(27, 35))
MOUTH_POINTS = list(range(48, 61))
OVERLAY_POINTS = [
   LEFT_EYE_POINTS + RIGHT_EYE_POINTS + LEFT_BROW_POINTS + RIGHT_BROW_POINTS,
   NOSE_POINTS + MOUTH_POINTS,
]
FEATHER_AMOUNT = 11

def draw_convex_hull(im, points, color):
   points = cv2.convexHull(points)
   cv2.fillConvexPoly(im, points, color=color)

def get_face_mask(im, landmarks):
   im = numpy.zeros(im.shape[:2], dtype=numpy.float64)

   for group in OVERLAY_POINTS:
       draw_convex_hull(im,
                        landmarks[group],
                        color=1)

   im = numpy.array([im, im, im]).transpose((1, 2, 0))

   im = (cv2.GaussianBlur(im, (FEATHER_AMOUNT, FEATHER_AMOUNT), 0) > 0) * 1.0
   im = cv2.GaussianBlur(im, (FEATHER_AMOUNT, FEATHER_AMOUNT), 0)

   return im

mask = get_face_mask(im2, landmarks2)
warped_mask = warp_im(mask, M, im1.shape)
combined_mask = numpy.max([get_face_mask(im1, landmarks1), warped_mask],
                         axis=0)

Давайте проанализируем:

  • Обычное определение функции get_face_mask(): генерировать маску для изображения и матрицу флагов. Маска рисует два белых выпуклых многоугольника: один вокруг глаз и один вокруг носа и рта. После этого краевые области маски растушевываются на 11 пикселей, что может помочь удалить оставшиеся разрывы.
  • Создайте маски для лица для рисунка 1, рисунка 2. Используя то же преобразование, что и на шаге 2, маску на рисунке 2 можно преобразовать в координатное пространство на рисунке 1.
  • После этого возьмите максимальное значение всех элементов и объедините две маски в одну. Это сделано для того, чтобы гарантировать, что функции, показанные на рисунке 1, также охвачены при отображении функций, изображенных на рисунке 2.

Наконец, примените маску к конечному изображению:

output_im = im1 * (1.0 - combined_mask) + warped_corrected_im2 * combined_mask

哈!换脸成功!

Ха, смена лица прошла успешно!

Приложение: Кодовый адрес этого проекта:Github

Категории