DeepFake своими руками [часть 1]

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

    Меня данная технология заинтересовала недавно. Впервые о ней я узнал из доклада одного из спикеров на “AI Conference 2018”. Там демонстрировалось видео, в котором по аудиозаписи алгоритм сгенерировал видео с обращением Барака Обамы. Ссылка на подборку видео созданных с помощью этой технологии. Результаты меня сильно вдохновили, и мною было принято решение лучше разобраться с данной технологией, чтобы в будущем противодействовать ей. Для этого я решил написать DeepFake на языке C#. В итоге получил такой результат.

    image

    Приятного чтения!

    Общие принципы

    Отправной точкой стал этот проект. Из него я узнал как именно работает замена лица на видео.

    1. Загрузка картинки с которой мы будем брать лицо
    2. Извлечение лица
    3. Создание 3D маски
    4. Видео разбивается на кадры
    5. Вычисляется область локализации лица в кадре
    6. Вычисляется ракурс и выражение лица
    7. Перенос поворота и выражения лица на 3D модель
    8. Рендеринг
    9. Замена реального лица на кадре результатом рендеринга

    Видео с демонстрацией работы проекта «FaceSwap»:


    Работу я решил разбить на 3 части:

    1-я) Замена лица на одном фото лицом с другого, без использования 3D маски
    2-я) Доработка замены с применением 3D маски
    3-я) Обработка видео

    Замену лица на фото можно разложить на следующие пункты:

    1. Загрузка картинки с которой мы будем брать лицо
    2. Загрузка картинки на которую будем проецировать лицо
    3. Извлечение лиц
    4. Масштабирование лица взятого с изображения 2 к пропорциям в изображении 1
    5. Замена лица в картинке 1 на лицо в картинке 2

    Встраивание одного изображения в другое

    Первое с чего я начал работу — это встраивание одного изображения в другое. Для демонстрации встраивания в оригинальном проекте используется скрипт zad1.py.
    В результате создается файл «eyeHandBlend.jpg», где глаз встраивается в руку.

    eyeHandBlend.jpg

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

    Первую часть я полностью перенес из оригинального проекта.

    Код на питоне
        def colorTransfer(src, dst, mask):
        transferredDst = np.copy(dst)
        #indeksy nie czarnych pikseli maski
        maskIndices = np.where(mask != 0)
        #src[maskIndices[0], maskIndices[1]] zwraca piksele w nie czarnym obszarze maski
    
        maskedSrc = src[maskIndices[0], maskIndices[1]].astype(np.int32)
        maskedDst = dst[maskIndices[0], maskIndices[1]].astype(np.int32)
    
        meanSrc = np.mean(maskedSrc, axis=0)
        meanDst = np.mean(maskedDst, axis=0)
    
        maskedDst = maskedDst - meanDst
        maskedDst = maskedDst + meanSrc
        maskedDst = np.clip(maskedDst, 0, 255)
    
        transferredDst[maskIndices[0], maskIndices[1]] = maskedDst
    
        return transferredDst


    Код перенесенный на C#
     static public Bitmap NewColor(Bitmap src, Bitmap ins, Rectangle r)
            {
    
                List<Vector> srV = new List<Vector>();
                List<Vector> inV = new List<Vector>(); ;
    
    
                for (int i = r.X; i < r.X + r.Width-2; i+=3)
                {
                    for (int j = r.Y; j < r.Y + r.Height-3; j+=4)
                    {
                        Color color = src.GetPixel(i, j);
                        Color color2 = ins.GetPixel(i, j);
                        srV.Add(new double[] { color.R, color.G, color.B }.ToVector());
                        inV.Add(new double[] { color2.R, color2.G, color2.B }.ToVector());
                    }
                }
    
                Vector meanSrc = Vector.Mean(srV.ToArray()) / 255;
                Vector meanInk = Vector.Mean(inV.ToArray()) / 255;
    
                Tensor tensor = ImgConverter.BmpToTensor
                (ins.Clone(r, PixelFormat.Format32bppArgb));
    
    
                tensor = tensor.DivD(meanInk);
                tensor = tensor.PlusD(meanSrc);
    
                tensor = tensor.TransformTensor(x =>
                {
                    if (x < 0) x = 0;
                    if (x > 1) x = 1;
                    return x;
                });
    
                return ImgConverter.TensorToBitmap(tensor);
            }
    


    Чтобы края были более прозрачными, чем центральная часть изображения, для расчета альфа канала, была введена радиально-базисная функция следующего вида:
    $\\k = 190 \\n = 3 \\r= (\frac{i-C_w}{1.2 \cdot W})^2+(\frac{j-C_h}{H})^2 \\ \alpha = 255 \cdot exp({-k\cdot r}^n)$

    k и n были подобраны эмпирически.
    i — индекс пикселя по оси OX
    j — индекс пикселя по оси OY
    $C_w$ — компонента x центра изображения
    $C_h$ — компонента y центра изображения

    В итоге я получил следующий результат:

    eyeHandBlend.jpg

    Поиск лица

    Для поиска лица на фото существует множество алгоритмов:

    • Алгоритм Виолы-Джонса(каскады Хаара)
    • Hog+SVM
    • R-CNN
    • Fast R-CNN
    • Faster R-CNN
    • Yolo

    Изначально использовался алгоритм Виолы-Джонса, но он оказался не достаточно точным, т.к. выделял лица не точно. Область выделения одного лица не совпадала с областью выделения второго, из-за чего замена происходила с дефектами, пример выделения лиц с помощь данного алгоритма показан ниже. Лица могут быть смещены, т.е. в на одном изображении оно захватывает оба уха, на другом только одно. Такие дефекты довольно плохо сказываются на конечном результате (на фото работа с DLib, предыдущая библиотека не всегда находила лицо, но к сожалению скриншоты не сохранились).



    Далее я решил использовать Landmarks из библиотеки Dlib. Нашел DlibDotNet, который написан на .Net Core. Для использования в .Net Framework был создан промежуточный проект на .Net Standard 2.0 с основными функциями, поиска лица и выделения Landmarks.

    Код на C#
    public int[] Face(byte[] bts, int row, int col, int st)
    {
           var img = Dlib.LoadImageData<RgbPixel>
           (ImagePixelFormat.Bgr, bts, (uint)row, (uint)col, (uint)st );
           var face = faceDetector.Operator(img)[0];
           int[] rect = { face.Left, face.Top, (int)face.Width, (int)face.Height};
           return rect;
    }
    
    public List<int[]> FacePoints(byte[] bts, int row, int col, int st)
     {
                List<int[]> points = new List<int[]>();
                var img = Dlib.LoadImageData<RgbPixel>
                (ImagePixelFormat.Bgr, bts, (uint)row, (uint)col, (uint)st);
                var face = faceDetector.Operator(img)[0];
                var shape = shapePredictor.Detect(img, face);
                for (var i = 0; i < shape.Parts; i++)
                {
                    var point = shape.GetPart((uint)i);
                    points.Add(new int[] { point.X, point.Y });
                }
                return points;
    }
    


    После чего написал библиотеку на .Net Framework 4.6.1, в которой реализовал всю логику.

    Пример получения Langmarks:



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



    Потом лицо вырезалось из картинки в правом нижнем углу и вставлялось, с помощью описанного выше алгоритма, в картину: «Caballero de la mano en el pecho».

    Был получен следующий результат.

    image


    В следующей статье я планирую рассмотреть создание 3D маски по фотографии.
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 16

      +10

      Разрешите допоинтересоваться, в итоге цикл статей прийдёт к использованию нейросетей?


      Потому что в самом названии DeepFake подразумевается использование нейросети и методов глубокого обучения, а здесь пока что обычный face swap реализован

        +3

        Да, Вы правы, сейчас это FaceSwap. В первых 3х статьях я планирую повторить на языке C# этот проект.
        Первая нейронная сеть появится в 4-й части, там будет замена лица конкретного человека на видео. Нейросеть будет использоваться для распознавания лиц, до этого момента буду работать с одним лицом.
        В 5-й части планирую сделать "оживление" портрета. Т.е. синтезировать мимику по голосу, думаю использовать LSTM, на вход принимать спектрограмму голоса за короткий промежуток времени, на выходе выдавать изменения Landmarks.

          +1

          Не знаю, кто влепил минус на коммент, но буду ждать, звучит интересно

            0
            поясните а зачем вообще что-то повторять на C#?
            я сам фанат и C# и питона, но область дипфейков постоянно развивается, и если вы хотите развивать проект, то его нужно писать на питоне, потому что только на питоне есть обилие самых разных готовых библиотек с которыми легко быстро воплощать любую задумку, чего не скажешь про другие языки.
              0
              Теперь Вы мой кумир! Главное чтобы всё это воплотилось в жизнь. Побольше Вам свободного времени, чтобы эти запланированные статьи увидели свет!
                0

                Спасибо!)

                +1
                Для этого я решил написать DeepFake на языке C#

                Да, Вы правы, сейчас это FaceSwap.
                  –1

                  Да, на данный момент это Face Swap. Но конечная цель — полноценный DeepFake.

              +1
              Спасибо автору. Жду продолжений с Neural Network на C#. Гуглил несколько дней назад примеры NN+GPU+C#, что-то не густо было. С интересом посмотрю, что выйдет.
                0

                Кстати, что касается использования NN+GPU+C#, есть такие проекты:


                  +1
                  Спасибо! Посмотрю по ссылкам, что интересного.
                  +1

                  Попиарюсь тоже: https://GitHub.com/losttech/Gradient-Samples


                  Бесплатно для некоммерческого.

                    0

                    Спасибо, изучу ее!
                    Но тут стоит лицензия MIT, которая подразумевает бесплатное использование и в коммерческих проектах тоже, а единственным требованием является указание авторства и копия лицензии, но не обязывает использовать ту же лицензию, в отличии от GPL. Или публиковать производные библиотеки под той же лицензией, как в LGPL. Я тут делал небольшой обзор лицензий, если интересно, можете посмотреть: https://vk.com/@ponimashzahar-licenzii-na-open-source-proekty

                      0
                      Только открытой лицензии недостаточно, чтобы сделать хороший проект. CNTK, например, тоже открыт, но не взлетел. Потому что нужны фичи, хорошая документация и сообщество.
                  +1
                  Мне кажется, что надо еще и фильтр для кожи использовать, типа что-нибудь вроде YUCIHighPassSkinSmoothing (https://github.com/YuAo/YUCIHighPassSkinSmoothing).
                    0

                    Спасибо большое!))
                    Только сейчас высвободилось время, посмотрел, очень крутая технология.

                  Only users with full accounts can post comments. Log in, please.