- Регистрация
- 27.12.2016
- Сообщения
- 289
- Благодарностей
- 404
- Баллы
- 63
Всем добра!
В статье я предлагаю воспользоваться одним из web-сервисов, который возвращает сгенерированные лица никогда не существовавших людей. Правда максимальный размер бесплатного изображения составляет всего 512х512 пикселей (вот тут уточнение — пока писал шаблон, с¥ки заграничные стали отдавать картинки размером 256х256px), с учетом новых обстоятельств, конечно так себе, но при увеличении до 512px получается в принципе не сильно хуже, чем у многих юзеров соцсетей:
И хотя, по большей части, генерированные лица получаются неплохо, ложка дегтя имеется — нейросеть (она же искусственный интеллект) на самом деле все же тупая железяка (по-крайней мере пока), и среди фейков отличного качества можно встретить красивых телок с вполне себе четкой щетиной или косоглазых, странных неопределимых оно, ну и прочие приколы, вроде прозрачных зубов . Да и качество генерации волос тоже так себе — в локонах присутствует грязь и следы каких-то фонов, а сами волосы иногда выглядят столь же нелепо, как выглядел бы учебник по C# в порнофильме:
Предлагаемый шаблон делает следующее:
Шаб, за исключением стандартного Zenno-свитча, написан полностью на C#, не многопоточный, сбор ссылок и на лица, и на фоны выполняется через web. Имеется возможность указать проксю — я пользую для этих целей свистки и 3proxy, так что формат такой — protocol://ip:port (например http://localhost:3099). К использованию подходят только http прокси, т.к. я не нашел простого способа подружить Webclient (о нем ниже) с SOCKS проксями (стандартная реализация базового класса их не поддерживает). Штатным кубиком загрузить фото не удалось, через геты пару раз ткнулся и бросил — времени не было на эксперименты. Фильтровать орков с женским лицом и щетиной придется вручную, но тут уж ничего не попишешь.
Структура шаблона
В папке проекта находится сам шаблон, список для промежуточных операций и три папки — img, bg, ready. В папку img будут сохраняться фейки, в папку bg — фоны, а в папку ready готовые аватары.
В директории img имеется подпапка resize, в которую по-умолчанию сохраняются фото фейк-лиц с измененными размерами, а в директории bg подпапка croped (для сохранения обрезанных фоновых изображений ). В папке ready, при генерации создаются подпапки с именем файла фейк-фото,внутри которых текущее фото авы накладывается на все доступные фоны. Сделал так для того, чтобы можно было сразу выбрать то, что получилось более-менее адекватно (с учетом артефактов на волосах, прозрачных зубов и т.д.) и удалить всю лишнюю шнягу.
Сам шаблон состоит из трех веток — сбор фейк-фото, сбор фонов, создание аватар:
1. Сбор фейк-фото
Страница ресурса, с которой будем брать фейковые лица https://generated.photos/faces.
На странице можно выбирать опции — положение головы, пол, возраст, расу цвет волос и т.д. А еще выбрать фон фейка — и это очень кстати, т.к. по-умолчанию страница отдает ссылки на .jpg, а вот если выбрать любой из предложенных вариантов, вместо жипегов подтягиваются ссылки на .png без фона. А это же просто песня!
Немного поковыряв страницу, выяснил, что чекбоксы отвечающие за выбор красивых молодых телок (почти все, за исключением выбора фона) всего лишь добавляют параметры к ссылке. Например, если мы выбираем пол female, то к ссылке добавляется параметр /female, если выбираем белую расу (кстати — у создателей сайта никакого уважения к современным тенденциям политкорректности: тут тебе и черные, и латиносы, и азиаты), то к ссылке добавится /white-race ну и т.д. Конечно параметры добавляются в определенном порядке, но это же дело техники!
После изучения страницы нарисовался алгоритм действий:
1.1. Формирование ссылки на основании опций, выбираемых во входных настройках
1.1.1. Выбор типа лица (All, Natural, Beautefied)
1.1.2. Выбор позиции головы (front, left, right)
1.1.3. Выбор пола (male, female)
1.1.4. Выбор возраста ()
1.1.5. Выбор расы ()
Описывать все возможные опции не стал — сложность формирования ссылки возрастает, а профит от всего этого довльно сомнительный.
1.2. Переход по сформированной ссылке, проверка загрузки страницы
1.3. Выбор параметра Transparent в секции выбора фона (по умолчанию фото отдаются в jpg, чтобы брать фото в png нужно выбрать параметр transparent, какой-то фон из предложенных или указать свой цвет)
1.4. Выбор количества кликов на кнопку «showmore». По умолчанию страница возвращает 30 изображений, при нажатии на кнопке showmore подгружается еще 30 картинок и т.д.
1.5. Сбор коллекции ссылок в список проекта, сохранение фото в папку img, с unixtime в качестве уникальноого имени файла.
1.6. Ресайз сохраненных фейков на указанное количество процентов, сохранение в папке img/resize
Выбор и изменение параметров фото(пол, возраст, раса и т.п.), количество кликов на «show more» доступно из входных настроек.
2. Сбор фоновых изображений
В качестве донора выбрал https://www.pexels.com/, точнее их страницу поиска https://www.pexels.com/search/background/ — первое что попалось из бесплатных стоков (вру — первым был pixabay, но там оригиналы картинок лучше спрятаны, не стал усложнять шаб). Можно выбрать другую страницу этого же ресурса (например https://www.pexels.com/search/abstract/). Можно и другой ресурс, но придется переписывать xpath и имя аттрибутов, содержащих ссылки на фото. Из личных наблюдений — лучше всего подходят светлые фоны, при наложении на которые, сгенерированный мусор в волосах незаметен. Но выстреливают и другие.
Алгоритм сбора фонов получился такой:
1.1. По умолчанию страница возвращает 60 изображений. Чтобы увеличить их количество, добавил цикл из 10 итераций с использованием метода
1.2. Сбор ссылок на фоны по xpath
1.3. Сохранение фонов в папку bg
1.4. Кроп изображений до размера 512х512 px
1.5. Сохранение в папку bg/croped
3. Наложение собранных лиц на собранные фоны
Т.к. качество генерации аватар не всегда на высоте (я об этом выше писал), а следовательно на некоторых фонах будут видны артефакты нейросетевых косяков, я решил накладывать каждую фейк-морду на все фоны и сохранять результат в отдельную папку (например лицо 1 накладываем на фоны 11,12,13 — в папке 1 у нас будут фото с именами 11.jpg,12.jpg, 13.jpg), чтобы можно было сразу удалить ненужное г.
Ну вот, шаблон работает, клепает нам фейковые аватары пачками, и вроде все хорошо, но... Мне не очень нравится, что в нашем проекте смешаны мухи и котлеты, точнее логика сбора изображений (как фейк-лиц, так и фонов), их обработки и сохранения, с обработкой и проверкой промежуточных шагов, таких как проверки загрузки страницы, наличия элемента на ней и т.п. Предлагаю немного отредактировать шаблон, и вынести в общий код все, что не имеет отношения к логике, а заодно подробнее познакомиться с кодом шаблона.
4. Вынос в общий код всех операций которые не имеют отношения к логике шаблона
Открываем окно «Директивы using и общий код», и после всех указанных using и закрывающей фигурной скобки namespace ZennoLab.OwnCode добавляем свое пространство имен FakeGen, в котором и будем записывать наши доработки, в нем создаем класс ZHelper. Чтобы можно было работать с экземплярами (объектами) этого класса, создаем приватные поля и конструктор класса (который при создании экземпляра передает в него объекты ZP project и instance):
Теперь вернемся к первому кубику проекта, где выполняется очистка кэша и куков, закрытие всех вкладок, проверка и установка прокси (я понимаю что похожие реализации для общего кода уже имеются на форуме):
Вроде все понятно, но можно сделать еще чуть понятнее, и свернуть в один метод очистку кэша и куков, закрытие вкладок, а во второй метод проверку переменной и установку прокси. Добавляем методы очистки инстанса и установки прокси в класс ZHelper сразу после конструктора:
Так как это служебные методы шаблона, я указал их как статические, а так как они не возвращают никакх значений в кубик, вместо типа возвращаемого значения указываем ключевое слово void. Теперь кубик проекта можно переписать так:
И глаз уже не отвлекается на почти одинаковые действия с инстансом и проверку переменной проекта на пустоту.
4.1. Ветка сбора фейк-лиц — вынос в общий код операций, не имеющих отношения к логике
Первым действием мы собираем ссылку — здесь куча if-ов, которые режут глаза, и их хочется свернуть, чтобы получить одну красивую строчку. Но, это логика работы именно шаблона, поэтому я решил оставить все как есть.
Следующий кубик — переход на страницу и поиск контрольного элемента на странице — т.к. частенько окончательно страница формируется скриптами, мне кажется что наиболее верный способ убелиться в ее загрузке — попытиться найти html-элемент, с которым планируется работать. Вынесем эти действия в метод общего кода (я больше не буду упоминать, что все методы мы пишем в классе ZHelper пространства имен FakeGen), добавив к ним функционал, на автомате пробующий повторно загрузить страницу, если контрольный элемент не найден:
До этого кубик перехода на страницу выглядел так:
После выноса в общий код всех проверок и нескольких попыток загрузить страницу, кубик стал выглядеть так:
Теперь нам нужно прокликать по кнопке «show more» чтобы увеличить количество возвращаемых страницей фотографий:
Выносим все это в общий код:
И переделываем кубик:
В следующем шаге нам нужно выбрать прозрачный фон фейков — в таком случае вместо jpg-картинок возвращаемых по умолчанию, подтянутся ссылки на .png без фона:
Пользуясь написанным ранее методом FindElementByXpathAndCheck, а так же стандартным методом ZP Click(), переписываем кубик выбора прозрачного фона:
Кубик «Проверка и установка путей для сохранения», проверяет на пустоту переменные, заданные во входных настройках пути к папкам, и устанавливает значения по умолчанию, если они пусты. Это логика шаблона, и я оставляю ее как есть.
Кубик получения в список ссылок на фейк-фото. Здесь мы получаем коллекцию html-элементов, получаем из каждого элемента значение аттрибута со ссылкой на фото и сохраняем эти ссылки в список:
Выносим обработку коллекции и сохранение в список в общий код:
Кубик сохранения фейковых фото в указанную директорию:
Опять перерабатываем его в метод общего кода:
И переписываем кубик:
Изначально я не планировал использовать WebClient, но стандартным кубиком загрузки изображений, так же как и повтором Get-запроса из траффик-инспектора скачать фото у меня не получилось, с разбегу не разобрался, а тратить время на продолжительные поиски желания не было. А WebClient, невзирая на отсутствие поддержки SOCKS-проксей, прекрасно загрузил фото.
В следующем кубике мы проверяем, нужно ли ресайзить фото, и если да, собираем в список проекта пути ко всем файлам директории с фонами (/bg по-умолчанию):
Тут нечего переписывать, поэтому не стал трогать.
А в последнем кубике ветки производится изменение размера изображения на указанное количество процентов. Для работы с графикой в C# имеется пространство имен System.Drawing. Используя классы Image, Bitmap, Graphics и структуру Rectangle этого пространства, мы и будем обрабатывать изображения в этом проекте. В кубике ниже, в цикле выполняются следующие операции:
Его стоит вынести в общий код, чтобы свернуть все вспомогательные опрации:
Теперь перепишем кубик:
На этом ветка сбора фейков полностью переписана. Я не буду так же подробно описывать ветку получения фонов (и версия с общим кодом, и версия без него есть в шаблоне — можно открыть и потрогать), остановлюсь лишь на кубиках обработки графики.
Последний кубик ветки сбора фонов обрезает все фоны до размера фейк-изображения. Это нужно, чтобы избежать ситуаций, как на изображении ниже:
Код кубика:
Распишу подробнее — здесь мы в цикле выполняем следующие действия:
И переписываем кубик:
Теперь не нужно скрипеть мозгом, чтобы понять что происходит в кубике
Ветка наложения фейков на фон состоит из 1 кубика, и здесь тоже используются методы простанства имен System.Drawing:
Здесь создаются два временных списка List<string> в которые сложены пути к файлам источникам феков и фонов (директории заданные переменными проекта project.Variables["CREATE_avaDirPath"].Value и project.Variables["CREATE_bgDirPath"].Value) А далее, используя вложенные циклы мы:
А теперь выносим в метод операции по наложению изображений:
И переписываем кубик:
Конечно, на таком небольшом проекте можно не понять прелесть выноса в общий код всех не относящихся к логике действий, но, все же, в этом имеются очень заметные плюсы:
1. Когда в проекте присутствует большое количество одинаковых действий (например парсинг атрибутов html-элементов товаров в магазине), вынос в общий код метода получения этих атрибутов дает возможность изменить метод в одном месте, а не переписывать его в множестве кубиков, со все увеличивающейся от правки к правке вероятностью появления косяка.
2. При необходимости сменить логику работы шаблона — нам не придется пречитывать простыни кода, выискивая в них именно логику, а не дополнительные действия, призванные эту логику обеспечить.
3. Поддерживать и дебажить такой код проще и приятнее — ведь и лада и тойота способны доставить нас к месту назначения, но с¥ка, добираться на япомобиле почему-то ощутимо кайфовее.
4. Освоение общего кода как инструмента, открывает новые возможности. А новые возможности это всегда круто!
Еще немного идей по нейросетевым технологиям:
1. Уникализация фото-контента — уже сейчас в сети есть web-интерфейсы, позволяющие:
а) удалять с изображений фоны (иногда довольно сносно, иногда совсем ущербно),
б) накладывать на изображения различные стили живописи,
в) встречал в телеге бота, который переделывает расу человека на фото в китайца-негра (набрать фоток порнозвезд, сменить им расу — вот и перспективные девочки для дейтинга)
2. Генерация фейк-новостей, фейк-резюме, и других текстовых прогонов (тестил русифицированную GPT-2 — иногда получается забавно, хотя обойтись без ручной фильтрации пока невозможно), ну и стандартно — чатботы всех мастей и расцветок.
В заключении пара ссылок на статьи из прошлых конкурсов (с большим респектом авторам), которые будут полезны для освоения инструментария общего кода:
https://zennolab.com/discussion/threads/king-of-parsing-nachalo-chast-1.79341/ от автора Маломальский
https://zennolab.com/discussion/threads/nestandartnye-podxody-k-razrabotke-shablonov.48583/ от автора shtift
https://zennolab.com/discussion/threads/bystryj-start-proektov-gotovoe-reshenie-uskorjaem-razrabotku-s-pomoschju-owncode-i-metodov-rasshirenija.79162/ от RoyalBank
Литература (субъективно):
Роберт Мартин «Чистый код: создание, анализ и рефакторинг»
Герберт Шилдт C# 4.0. Полное руководство
Албахари Джозеф, Албахари Бен, С# 6.0. Справочник. Полное описание языка, 6-е изд.
P.S. Вполне возможно что методы которые я здесь привел не оптимальны или не совсем правильны, а может быть вынесено что-то, что должно было остаться в кубиках — я не программист, и опыта работы с общим кодом мне не хватает, так что респект вем, кто укажет ошибки!
С каждым днем область применения нейросетей растет — на сегодняшний день они способны генерировать вполне связные, последовательные тексты, отличить которые от человеческих иногда достаточно проблематично (привет копирайтерам), писать несложный код (это пипец, товарищи программеры), озвучвать тексты (неплохо, с интонациями), разукрашивать и ретушировать черно-белые исторические фото, добавлять на фото различные эффекты (или удалять лишние детали — фоны, бывших баб , пьяных дятлов и т.д.)(и фотошоперам тоже привет), да и просто генерировать различные медиаобъекты (аудио, фото, видео), в том числе человеческие лица. Единственный (но очень жирный) недостаток нейросети — требования к железу для ее развертывания (особенно это касается GPU). И хотя это очень удручает (по крайней мере лично меня), в сети все чаще можно найти web-интерфейсы для получения платного и (как правило с ограничениями) бесплатного контента от мощных нейросетей.
В статье я предлагаю воспользоваться одним из web-сервисов, который возвращает сгенерированные лица никогда не существовавших людей. Правда максимальный размер бесплатного изображения составляет всего 512х512 пикселей (вот тут уточнение — пока писал шаблон, с¥ки заграничные стали отдавать картинки размером 256х256px), с учетом новых обстоятельств, конечно так себе, но при увеличении до 512px получается в принципе не сильно хуже, чем у многих юзеров соцсетей:
И хотя, по большей части, генерированные лица получаются неплохо, ложка дегтя имеется — нейросеть (она же искусственный интеллект) на самом деле все же тупая железяка (по-крайней мере пока), и среди фейков отличного качества можно встретить красивых телок с вполне себе четкой щетиной или косоглазых, странных неопределимых оно, ну и прочие приколы, вроде прозрачных зубов . Да и качество генерации волос тоже так себе — в локонах присутствует грязь и следы каких-то фонов, а сами волосы иногда выглядят столь же нелепо, как выглядел бы учебник по C# в порнофильме:
Предлагаемый шаблон делает следующее:
- Собирает и сохраняет фейк-лица
- Ресайзит их и сохраняет в указанную папку (опционально)
- Собирает фоны, обрезает их под размер изображений несуществующих людей
- Накладывает фейки на фоны для уникализации изображений
Шаб, за исключением стандартного Zenno-свитча, написан полностью на C#, не многопоточный, сбор ссылок и на лица, и на фоны выполняется через web. Имеется возможность указать проксю — я пользую для этих целей свистки и 3proxy, так что формат такой — protocol://ip:port (например http://localhost:3099). К использованию подходят только http прокси, т.к. я не нашел простого способа подружить Webclient (о нем ниже) с SOCKS проксями (стандартная реализация базового класса их не поддерживает). Штатным кубиком загрузить фото не удалось, через геты пару раз ткнулся и бросил — времени не было на эксперименты. Фильтровать орков с женским лицом и щетиной придется вручную, но тут уж ничего не попишешь.
Структура шаблона
В папке проекта находится сам шаблон, список для промежуточных операций и три папки — img, bg, ready. В папку img будут сохраняться фейки, в папку bg — фоны, а в папку ready готовые аватары.
В директории img имеется подпапка resize, в которую по-умолчанию сохраняются фото фейк-лиц с измененными размерами, а в директории bg подпапка croped (для сохранения обрезанных фоновых изображений ). В папке ready, при генерации создаются подпапки с именем файла фейк-фото,внутри которых текущее фото авы накладывается на все доступные фоны. Сделал так для того, чтобы можно было сразу выбрать то, что получилось более-менее адекватно (с учетом артефактов на волосах, прозрачных зубов и т.д.) и удалить всю лишнюю шнягу.
Сам шаблон состоит из трех веток — сбор фейк-фото, сбор фонов, создание аватар:
1. Сбор фейк-фото
Страница ресурса, с которой будем брать фейковые лица https://generated.photos/faces.
На странице можно выбирать опции — положение головы, пол, возраст, расу цвет волос и т.д. А еще выбрать фон фейка — и это очень кстати, т.к. по-умолчанию страница отдает ссылки на .jpg, а вот если выбрать любой из предложенных вариантов, вместо жипегов подтягиваются ссылки на .png без фона. А это же просто песня!
Немного поковыряв страницу, выяснил, что чекбоксы отвечающие за выбор красивых молодых телок (почти все, за исключением выбора фона) всего лишь добавляют параметры к ссылке. Например, если мы выбираем пол female, то к ссылке добавляется параметр /female, если выбираем белую расу (кстати — у создателей сайта никакого уважения к современным тенденциям политкорректности: тут тебе и черные, и латиносы, и азиаты), то к ссылке добавится /white-race ну и т.д. Конечно параметры добавляются в определенном порядке, но это же дело техники!
После изучения страницы нарисовался алгоритм действий:
1.1. Формирование ссылки на основании опций, выбираемых во входных настройках
1.1.1. Выбор типа лица (All, Natural, Beautefied)
1.1.2. Выбор позиции головы (front, left, right)
1.1.3. Выбор пола (male, female)
1.1.4. Выбор возраста ()
1.1.5. Выбор расы ()
Описывать все возможные опции не стал — сложность формирования ссылки возрастает, а профит от всего этого довльно сомнительный.
1.2. Переход по сформированной ссылке, проверка загрузки страницы
1.3. Выбор параметра Transparent в секции выбора фона (по умолчанию фото отдаются в jpg, чтобы брать фото в png нужно выбрать параметр transparent, какой-то фон из предложенных или указать свой цвет)
1.4. Выбор количества кликов на кнопку «showmore». По умолчанию страница возвращает 30 изображений, при нажатии на кнопке showmore подгружается еще 30 картинок и т.д.
1.5. Сбор коллекции ссылок в список проекта, сохранение фото в папку img, с unixtime в качестве уникальноого имени файла.
1.6. Ресайз сохраненных фейков на указанное количество процентов, сохранение в папке img/resize
Выбор и изменение параметров фото(пол, возраст, раса и т.п.), количество кликов на «show more» доступно из входных настроек.
2. Сбор фоновых изображений
В качестве донора выбрал https://www.pexels.com/, точнее их страницу поиска https://www.pexels.com/search/background/ — первое что попалось из бесплатных стоков (вру — первым был pixabay, но там оригиналы картинок лучше спрятаны, не стал усложнять шаб). Можно выбрать другую страницу этого же ресурса (например https://www.pexels.com/search/abstract/). Можно и другой ресурс, но придется переписывать xpath и имя аттрибутов, содержащих ссылки на фото. Из личных наблюдений — лучше всего подходят светлые фоны, при наложении на которые, сгенерированный мусор в волосах незаметен. Но выстреливают и другие.
Алгоритм сбора фонов получился такой:
1.1. По умолчанию страница возвращает 60 изображений. Чтобы увеличить их количество, добавил цикл из 10 итераций с использованием метода
FullEmulationMouseMoveToHtmlElement.
1.2. Сбор ссылок на фоны по xpath
1.3. Сохранение фонов в папку bg
1.4. Кроп изображений до размера 512х512 px
1.5. Сохранение в папку bg/croped
3. Наложение собранных лиц на собранные фоны
Т.к. качество генерации аватар не всегда на высоте (я об этом выше писал), а следовательно на некоторых фонах будут видны артефакты нейросетевых косяков, я решил накладывать каждую фейк-морду на все фоны и сохранять результат в отдельную папку (например лицо 1 накладываем на фоны 11,12,13 — в папке 1 у нас будут фото с именами 11.jpg,12.jpg, 13.jpg), чтобы можно было сразу удалить ненужное г.
Ну вот, шаблон работает, клепает нам фейковые аватары пачками, и вроде все хорошо, но... Мне не очень нравится, что в нашем проекте смешаны мухи и котлеты, точнее логика сбора изображений (как фейк-лиц, так и фонов), их обработки и сохранения, с обработкой и проверкой промежуточных шагов, таких как проверки загрузки страницы, наличия элемента на ней и т.п. Предлагаю немного отредактировать шаблон, и вынести в общий код все, что не имеет отношения к логике, а заодно подробнее познакомиться с кодом шаблона.
4. Вынос в общий код всех операций которые не имеют отношения к логике шаблона
Открываем окно «Директивы using и общий код», и после всех указанных using и закрывающей фигурной скобки namespace ZennoLab.OwnCode добавляем свое пространство имен FakeGen, в котором и будем записывать наши доработки, в нем создаем класс ZHelper. Чтобы можно было работать с экземплярами (объектами) этого класса, создаем приватные поля и конструктор класса (который при создании экземпляра передает в него объекты ZP project и instance):
Создаем пространство имен и класс, в который вынесем все лишнее:
namespace FakeGen
{
public class ZHelper
{
private Instance instance;
private IZennoPosterProjectModel project;
private Tab tab;
private HtmlElement he;
private HtmlElementCollection hecol;
private Random rnd;
#region Конструктор класса
/// <summary>
/// Конструктор класса — служит для создания экземпляра класса
/// </summary>
/// <param name="project">текущий проект</param>
/// <param name="instance">текущий instance</param>
public ZHelper(IZennoPosterProjectModel project, Instance instance)
{
//при создании экземпляра присваиваем закрытым полям класса
this.instance = instance;
this.project = project;
tab = instance.ActiveTab;
he = null;
hecol = null;
rnd = new Random();
}
#endregion
}
}
C#:
//Чистим куки, кэш, закрываем вкладки
instance.ClearCache();
instance.ClearCookie();
instance.CloseAllTabs();
//проверяем переменную проекта и устанавливаем прокси если она не пуста
if(!string.IsNullOrEmpty(project.Variables["proxy"].Value))
{
instance.SetProxy(project.Variables["proxy"].Value,false,true,true,true);
}
Общий код:
/// <summary>
/// Метод чистит куки, кэш и закрывает все вкладки
/// </summary>
/// <param name="instance">текущий инстанс проекта (instance)</param>
public static void InstanceClearAndClose(this Instance instance)
{
instance.ClearCache();
instance.ClearCookie();
instance.CloseAllTabs();
}
/// <summary>
/// Метод проверяет переменную проекта proxy, если она не пуста, устанавливает прокси проекта
/// </summary>
/// <param name="project">текущий проект</param>
/// <param name="instance">текущий инстанс проекта (instance)</param>
public static void CheckAndSetProxy(IZennoPosterProjectModel project, Instance instance)
{
string proxy = project.Variables["proxy"].Value;
if(!string.IsNullOrEmpty(proxy))
{
instance.SetProxy(proxy,false,true,true,true);
project.SendInfoToLog(string.Format("Установили прокси {0}", proxy), true);
}
else project.SendInfoToLog("Прокси не указан", true);
}
C#:
//Очистили куки и кэш, закрыли все вкладки
FakeGen.ZHelper.InstanceClearAndClose(instance);
//Если в переменной проекта proxy не пусто, устанавливаем прокси
FakeGen.ZHelper.CheckAndSetProxy(project, instance);
4.1. Ветка сбора фейк-лиц — вынос в общий код операций, не имеющих отношения к логике
Первым действием мы собираем ссылку — здесь куча if-ов, которые режут глаза, и их хочется свернуть, чтобы получить одну красивую строчку. Но, это логика работы именно шаблона, поэтому я решил оставить все как есть.
Следующий кубик — переход на страницу и поиск контрольного элемента на странице — т.к. частенько окончательно страница формируется скриптами, мне кажется что наиболее верный способ убелиться в ее загрузке — попытиться найти html-элемент, с которым планируется работать. Вынесем эти действия в метод общего кода (я больше не буду упоминать, что все методы мы пишем в классе ZHelper пространства имен FakeGen), добавив к ним функционал, на автомате пробующий повторно загрузить страницу, если контрольный элемент не найден:
Общий код:
/// <summary>
/// Расширение стандартного Zenno-метода Navigate — Проверяет существование таба, переходит на страницу, ждет ее загрузку,
/// выдерживает указанную паузу(по умолчанию 10с) и проверяет по xpath наличие на стр. контрольного элемента.
/// Если элемент не найден, пробует загрузить еще раз
/// </summary>
/// <param name="url">Url страницы, на которую нужно перейти</param>
/// <param name="xpath">Xpath элемента, наличие которого будет подтверждением загрузки страницы</param>
/// <param name="refer">Страница с которой переходим, необязательный параметр</param>
/// <param name="counter">Количество попыток загрузить страницу, по умолчанию 3</param>
public Tab NavigateWaitAndCheckElement(string url, string xpath, int pause = 10, string refer = "", int counter = 3)
{
if ((tab.IsVoid) || (tab.IsNull)) throw new Exception("Не найден Tab!");
if (tab.IsBusy) tab.WaitDownloading();
for(int i=0; i<counter; i++)
{
tab.Navigate(url, refer);
if (tab.IsBusy) tab.WaitDownloading();
//Пауза перед проверкой элемента подбирается индивидуально в зависимости от качества прокси и наличия-отсутствия скриптов подгужающих элементы страницы
Thread.Sleep(pause*1000);
if(tab.URL == url && tab.URL != "about:blank" && tab.URL != "about:startpage")
{
he = tab.FindElementByXPath(xpath, 0);
if(!he.IsNull)
{
project.SendInfoToLog("На странице найден контрольный html-элемент", false);
break;
}
}
if(counter == i+1) throw new Exception(string.Format("Ошибка загрузки страницы {0}", url));
}
return tab;
}
C#:
Tab tab = instance.ActiveTab;
//ссылка которую собрали в предыдущем кубике
string url = project.Variables["AVA_link"].Value;
//xpath элемента для проверки загрузки страницы
string xpath = @"//div[@class='card-image']//img";
if ((tab.IsVoid) || (tab.IsNull)) return -1;
if (tab.IsBusy) tab.WaitDownloading();
tab.Navigate(project.Variables["AVA_link"].Value, "");
if (tab.IsBusy) tab.WaitDownloading();
var he = instance.ActiveTab.FindElementByXPath(xpath, 0);
if(he.IsNull) throw new Exception("Не найден элемент!");
C#:
//ссылка которую собрали в предыдущем кубике
string url = project.Variables["AVA_link"].Value;
//xpath элемента для проверки загрузки страницы
string xpath = @"//div[@class='card-image']//img";
//инициализируем экземпляр созданного нами класса ZHelper
ZHelper gotopage = new ZHelper(project, instance);
//Переходим на страницу — работая с экземпляром класса
gotopage.NavigateWaitAndCheckElement(url, xpath, 7);
C#:
//количество кликов по кнопке из вх настроек
int counter = Int32.Parse(project.Variables["AVA_clickCount"].Value);
//если не указано — выходим из кубика
if(counter == 0) return "Количество кликов на кнопку «show more» не указано, по кнопке не кликаем";
//Кнопка показать еще
var he = instance.ActiveTab.FindElementByXPath(@"//button[@class='loadmore-btn']", 0);
if(he.IsNull || he.IsVoid) throw new Exception("Не найдена кнопка Show more");
//кликаем
for(int i=0; i<counter; i++)
{
//рандом для паузы подбирал экспериментально. Чувствителен к качеству прокси
he.Click();
//пауза
Thread.Sleep(rnd);
}
Общий код:
/// <summary>
/// Метод выполняет заданное количество кликов (минимум 1) по указанному html-элементу
/// </summary>
/// <param name="he">html-элемент по которому нужно кликать</param>
/// <param name="count">Количество кликов по элементу. По умолчанию = 0, поэтому указывать не обязательно</param>
public void FindElementAndCycleClick(HtmlElement he, int count = 0)
{
for(int i=0; i<count; i++)
{
int rnd = new Random().Next(1533, 2711);
he.Click();
Thread.Sleep(rnd);
}
}
C#:
//xpath кнопка показать еще
string xpath = @"//button[@class='loadmore-btn']";
//Количество кликов
int count = Int32.Parse(project.Variables["AVA_clickCount"].Value);
if(count == 0) return "Количество кликов на кнопку «show more» не указано, по кнопке не кликаем";
//новый экземпляр класса ZHelper
ZHelper shmore = new ZHelper(project, instance);
//Кнопка показать еще
var he = shmore.FindElementByXpathAndCheck(xpath);
//Кликаем по кнопке «Показать еще» столько, сколько указали во вх настройках или хотя бы 1 раз
shmore.FindElementAndCycleClick(he, count);
C#:
//Xpath дропдаун-кнопки «Background color»
string xddown = @"//div[@class='background-details']//button[@class='summary']";
//Xpath кнопки выбора прозрачного фона
string xselector = @"//div[@class='colors']/div[contains(@class,'transparent')]";
//ищем и разворачиваем дропдаун
var he = instance.ActiveTab.FindElementByXPath(xddown, 0);
if(he.IsNull || he.IsVoid) throw new Exception("Не найден html-элемент");
he.Click();
//pause
int rnd = new Random().Next(2357,3471);
Thread.Sleep(rnd);
//ищем элемент и выбираем прозрачный фон
he = instance.ActiveTab.FindElementByXPath(xselector,0);
if(he.IsNull || he.IsVoid) throw new Exception("Не найден html-элемент");
he.Click();
C#:
//Xpath дропдаун-кнопки «Background color»
string xddown = @"//div[@class='background-details']//button[@class='summary']";
//Xpath кнопки выбора прозрачного фона
string xselector = @"//div[@class='colors']/div[contains(@class,'transparent')]";
//новый экземпляр класса ZHelper
ZHelper trselect = new ZHelper(project, instance);
//ищем и разворачиваем дропдаун
trselect.FindElementByXpathAndCheck(xddown,"button").Click();
//pause
int rnd = new Random().Next(2357,3471);
Thread.Sleep(rnd);
//ищем элемент и выбираем прозрачный фон
trselect.FindElementByXpathAndCheck(xselector, "div class contans transparent").Click();
Кубик получения в список ссылок на фейк-фото. Здесь мы получаем коллекцию html-элементов, получаем из каждого элемента значение аттрибута со ссылкой на фото и сохраняем эти ссылки в список:
C#:
//список проекта
IZennoList links = project.Lists["links"];
//очистили его перед испрользованием
links.Clear();
//xpath элементов коллекции
string xpath = @"//div[@class='card-image']//img";
//получили коллекцию
HtmlElementCollection hecol = instance.ActiveTab.FindElementsByXPath(xpath);
//проверяем, что коллекция не пуста
if(hecol.Count >0)
{
project.SendInfoToLog(hecol.Count.ToString());
//элементы коллекции в список
foreach(HtmlElement he in hecol)
{
string link = he.GetAttribute("src");
links.Add(link);
}
}
Общий код:
/// <summary>
/// Метод выполняет заданное количество кликов (минимум 1) по указанному html-элементу
/// </summary>
/// <param name="he">html-элемент по которому нужно кликать</param>
/// <param name="count">Количество кликов по элементу. По умолчанию = 0, поэтому указывать не обязательно</param>
public void FindElementAndCycleClick(HtmlElement he, int count = 0)
{
for(int i=0; i<count; i++)
{
int rnd = new Random().Next(1533, 2711);
he.Click();
Thread.Sleep(rnd);
}
}
C#:
//список проекта
IZennoList links = project.Lists["links"];
//Рандом
Random r = new Random();
//папка для сохранения
string dir = project.Variables["AVA_savePath"].Value;
//в цикле получаем ссылки и сохраняем фото
foreach(string url in links)
{
//путь для сохранения и имя изображения
string path = dir + DateTimeOffset.Now.ToUnixTimeSeconds().ToString()+".png";
//сохраняем изображение из указанного урла в указанный путь используя стандартный класс C# WebClient
System.Net.WebClient wc = new System.Net.WebClient();
//прокси
System.Net.WebProxy wp = new System.Net.WebProxy(project.Variables["proxy"].Value);//("http://127.0.0.1:3097");//
wc.Proxy = wp;
wc.DownloadFile(url,path);
//случайная пауза
int rnd = r.Next(977,1789);
Thread.Sleep(rnd);
}
Общий код:
/// <summary>
/// Метод получает из списка проекта строку с url фото и загружает фото в указанную директорию. Имя файла — текущее значение unixtime, расширение - png
/// </summary>
/// <param name="list">Список проекта, в котором собраны ссылки на фото</param>
/// <param name="dir">путь к директории в которую будем сохранять картинки</param>
/// <param name="ext">формат сохраняемого изображения - .jpg, .png</param>
public void GetListItemSaveImg(IZennoList list, string dir, string ext)
{
//Проверяем что список не пуст
if(list.Count == 0) throw new Exception("Методу был передан пустой список!");
if(string.IsNullOrEmpty(ext)) throw new Exception("Не указан формат сохранения изображения");
//сохраняем изображение из указанного урла в указанный путь используя стандартный класс C# WebClient
System.Net.WebClient wc = new System.Net.WebClient();
System.Net.WebProxy wp = new System.Net.WebProxy(project.Variables["proxy"].Value);//("http://127.0.0.1:3097");//
wc.Proxy = wp;
//в цикле получаем ссылки и сохраняем фото
foreach(string url in list)
{
string path = dir+DateTimeOffset.Now.ToUnixTimeSeconds().ToString()+ext;
//project.SendInfoToLog(url, false);
wc.DownloadFile(url,path);
//случайная пауза
int pause = rnd.Next(977,1789);
Thread.Sleep(pause);
}
}
C#:
//список с прямыми ссылками на фото
IZennoList links = project.Lists["links"];
//путь для сохранения фото
string dir = project.Variables["AVA_savePath"].Value;
//экземпляр класса
ZHelper photosaver = new ZHelper(project, instance);
//Сохранили изображения из списка links в папку dir
photosaver.GetListItemSaveImg(links, dir, ".png");
В следующем кубике мы проверяем, нужно ли ресайзить фото, и если да, собираем в список проекта пути ко всем файлам директории с фонами (/bg по-умолчанию):
C#:
//если не выбрана галка, выходим из кубика
if(project.Variables["AVA_resizeImg"].Value == "false") return "Ресайз фейк-лиц не нужен";
//путь для сохранения фото
string dir = project.Variables["AVA_savePath"].Value;
IZennoList links = project.Lists["links"];
links.Clear();
links.AddRange(Directory.GetFiles(dir));
А в последнем кубике ветки производится изменение размера изображения на указанное количество процентов. Для работы с графикой в C# имеется пространство имен System.Drawing. Используя классы Image, Bitmap, Graphics и структуру Rectangle этого пространства, мы и будем обрабатывать изображения в этом проекте. В кубике ниже, в цикле выполняются следующие операции:
- Получаем из списка проекта путь к файлу изображения igetpath,
- Создаем экземпляр класса Image srcimage загружая в него изображение из файла,
- Вычисляем размер в пикселах высоты w и ширины h нового изображения, используя свойства srcimage.Width, srcimage.Height, и указанное количество процентов pct,
- Создаем прямоугольник destRect, с координатами левого верхнего угла по оси x=0 и оси y=0? а так же размером по ширине x и по высоте y,
- Создаем объект Bitmap dest, из изображения srcimage, с размерами x, y
- Используя метод DrawImage класса Graphics рисуем на объекте dest изображение srcimage с размером и координатами начала рисования, заданными прямоугольником destRect,
- Сохраняем объект Bitmap в объект Image dst,
- Сохраняем объект dst как изображение в указанную директорию dirsavepath с именем iname
C#:
IZennoList list = project.Lists["links"];
//процент изменения размера. Если увеличиваем — положит число, если уменьшаем — отриц. число
int pct = 100;
//Папка для сохранения
string dirsavepath = project.Variables["AVA_saveResizePath"].Value;
foreach(string igetpath in list)
{
//получили имя текущего файла
string iname = Path.GetFileNameWithoutExtension(igetpath);
//создали экземпляр Image из файла, чтобы получить его ширину-высоту
Image srcimage = new Bitmap(igetpath);
//процент от ширины
double wplus = ((double) srcimage.Width/100)*pct;
//процент от высоты
double hplus = ((double) srcimage.Height/100)*pct;
//новые ширина и высота
int w = Convert.ToInt32(srcimage.Width + wplus);
int h = Convert.ToInt32(srcimage.Height + hplus);
//Создали экз. прямоугольника с новым размером
Rectangle destRect = new Rectangle(0, 0, w, h);
//создали экз. Bitmap с новыми размерами
Bitmap dest = new Bitmap(srcimage, w, h);
//Отрисовали в Bitmap картинку srcimage с размерами destRect
using(Graphics g = Graphics.FromImage(dest))
{
g.DrawImage(srcimage, destRect);
}
//сохраняем объект Bitmap в объект Image
Image dst = (Image)dest;
//сохраняем в файл
dst.Save(dirsavepath+iname+".png", System.Drawing.Imaging.ImageFormat.Png);
}
Общий код:
/// <summary>
/// Метод в цикле получает из списка имя-путь изображения, изменяет размеры изображения на указанное количество процентов и сохраняет его в указанную папку
/// </summary>
/// <param name="list">Список проекта с путями к изображениям</param>
/// <param name="dirsavepath">Путь к папке для сохранения обрезанных картинок</param>
/// <param name="pct">Процент, на который нужно изменить размер (для уменьшения указывается со знаком "-"), по умолчанию 100%</param>
public string ResizeAndSaveImageFromList(IZennoList list, string dirsavepath,int pct = 100)
{
foreach(string igetpath in list)
{
if(string.IsNullOrEmpty(igetpath)) throw new Exception("в списке с изображениями была пустая строка");
//создали экземпляр Image из файла, чтобы получить его ширину-высоту
Image srcimage = new Bitmap(igetpath);
//получили имя текущего файла
string iname = Path.GetFileNameWithoutExtension(igetpath);
//процент от ширины
double wplus = ((double) srcimage.Width/100)*pct;
//процент от высоты
double hplus = ((double) srcimage.Height/100)*pct;
//новые ширина и высота
int w = Convert.ToInt32(srcimage.Width + wplus);
int h = Convert.ToInt32(srcimage.Height + hplus);
//ЗАписали новые размеры в структуру хранящую размеры для создания прямоугольника
Size size = new Size(w, h);
//Создали экз. прямоугольника с новым размером
Rectangle destRect = new Rectangle(0, 0, w, h);
//создали экз. Bitmap с новыми размерами
Bitmap dest = new Bitmap(srcimage,size);
//Отрисовали в Bitmap картинку srcimage с размерами destRect
using(Graphics g = Graphics.FromImage(dest))
{
g.DrawImage(srcimage, destRect);
}
//сохраняем объект Bitmap в объект Image
Image dst = (Image)dest;
//сохраняем в файл
dst.Save(dirsavepath+iname+".png", System.Drawing.Imaging.ImageFormat.Png);
}
//метод CropAndSaveImageFromList был с типом возвращаемого значения void (т.е. не возвращает значений),
//здесь, по завершению, метод возвращает строку со значением "Ok" (исключительно в демо целях)
return "Ок";
}
C#:
IZennoList list = project.Lists["links"];
//процент изменения размера. Если увеличиваем — положит число, если уменьшаем — отриц. число
int pct = Int32.Parse(project.Variables["AVA_percentResizing"].Value);
//Папка для сохранения
string dirsavepath = project.Variables["AVA_saveResizePath"].Value;
//экз. класса ZHelper
ZHelper zh = new ZHelper(project, instance);
//Изменили размер, сохранили файлы
zh.ResizeAndSaveImageFromList(list, dirsavepath);
Последний кубик ветки сбора фонов обрезает все фоны до размера фейк-изображения. Это нужно, чтобы избежать ситуаций, как на изображении ниже:
Код кубика:
C#:
IZennoList links = project.Lists["links"];
//Папка для сохранения
string dirsavepath = project.Variables["BG_saveResizePath"].Value;
foreach(string file in links)
{
string name = Path.GetFileNameWithoutExtension(file);
Image srcimage = Image.FromFile(file);
//если размер картинки меньше 512px по любой стороне => берем следующую
if(srcimage.Width < 512 || srcimage.Height < 512) continue;
//обрезаем края изображения, поэтому для начала вычисляем начальные координаты нашего прямоугольника
//левый угол x = (центр = ширина/2) - половина будущей ширины
int x = srcimage.Width/2 - 256;
//левый угол y
int y = srcimage.Height/2 - 256;
//прямоугольник с координатами начала обрезки и размером нового изображения
Rectangle crop = new Rectangle(x, y, 512, 512);
//новый экземпляр класса Bitmap из изображения-источника
Bitmap src = new Bitmap(srcimage);
//новый экземпляр класса Bitmap для обрезанного изображения
Bitmap dest = new Bitmap(crop.Width, crop.Height);
//рисуем новое изображение
using(Graphics g = Graphics.FromImage(dest))
{
g.DrawImage(src, new Rectangle(0, 0, dest.Width, dest.Height),
crop,
GraphicsUnit.Pixel);
}
//сохраняем объект Bitmap в объект Image
Image dst = (Image)dest;
//сохраняем файл
dst.Save(dirsavepath + name + "_512.jpg", System.Drawing.Imaging.ImageFormat.Jpeg);
}
- Получаем строку с путем к файлу,
- Создаем объект Image srcimage из файла, указанного в пути предыдущего шага,
- Проверяем, что размеры изображения любой из сторон srcimage не меньше, чем требуемый размер нового изображения,
- Вычисляем координаты начала и конца обрезки (обрезаем по-краям, поэтому отсчет от центра), используя свойства srcimage.Width и srcimage.Height,
- Создаем прямоугольник crop с координатами начала и конца обрезки и размерами нового изображения,
- Из srcimage создаем объект Bitmap src,
- Создаем объект Bitmap dest с размерами crop.Width, crop.Height,
- Используя объект g класса Graphics, рисуем в объект dest изображение из объекта src с координатами начала рисования и размерами new Rectangle(0, 0, dest.Width, dest.Height) и координатами обрезки и размерами, заданными прямоугольником crop и единицами измерения GraphicsUnit.Pixel (т.е. мы оперируем пикселями),
- Сохраняем объект dest с новым изображением в объект Image dst,
- Сохраняем объект dst в файл
Общий код:
/// <summary>
/// Метод в цикле получает из списка имя-путь изображения, проверяет, что его размер соответствует указанным размерам,
/// если да, обрезает картинку до указанных размеров (от центра) и сохраняет ее в указанное место
/// </summary>
/// <param name="list">Список проекта с путями к изображениям</param>
/// <param name="dirpath">Путь к папке для сохранения обрезанных картинок</param>
/// <param name="isizew">Ширина, до которой нужно обрезать изображение, по умолчанию 512px</param>
/// <param name="isizeh">Высота, до которой нужно обрезать изображение, по умолчанию 512px</param>
public void CropAndSaveImageFromList(IZennoList list, string dirpath, int isizew = 512, int isizeh = 512)
{
foreach(string file in list)
{
//Получаем имя файла без расширения
string name = Path.GetFileNameWithoutExtension(file);
//Создаем новый объект Image
Image srcimage = Image.FromFile(file);
//если размер картинки меньше 512px по любой стороне => берем следующую
if(srcimage.Width < isizew || srcimage.Height < isizeh) continue;
//обрезаем края изображения, поэтому для начала вычисляем начальные координаты нашего прямоугольника
//левый угол x = (центр = ширина/2) - половина будущей ширины
int x = srcimage.Width/2 - isizew/2;
//левый угол y
int y = srcimage.Height/2 - isizeh/2;
//прямоугольник с координатами начала обрезки и размером нового изображения
Rectangle crop = new Rectangle(x, y, isizew, isizeh);
//новый экземпляр класса Bitmap из изображения-источника
Bitmap src = new Bitmap(srcimage);
//новый экземпляр класса Bitmap для обрезанного изображения
Bitmap dest = new Bitmap(crop.Width, crop.Height);
//рисуем новое изображение
using(Graphics g = Graphics.FromImage(dest))
{
g.DrawImage(src, new Rectangle(0, 0, dest.Width, dest.Height), crop, GraphicsUnit.Pixel);
}
//сохраняем объект Bitmap в объект Image
Image dst = (Image)dest;
//сохраняем файл
dst.Save(dirpath+name+"_w"+isizew+".jpg", System.Drawing.Imaging.ImageFormat.Jpeg);
}
}
C#:
IZennoList links = project.Lists["links"];
//директория для сохранения
string dir = project.Variables["BG_saveResizePath"].Value;
//экземпляр класса
ZHelper zh = new ZHelper(project, instance);
//Получение фона, проверка размера, кроп и сохранение изображений из списка
zh.CropAndSaveImageFromList(links, dir);
Ветка наложения фейков на фон состоит из 1 кубика, и здесь тоже используются методы простанства имен System.Drawing:
C#:
//путь к папке с фонами
string bgpath = project.Variables["CREATE_bgDirPath"].Value;
if(string.IsNullOrEmpty(bgpath)) throw new Exception("Укажите путь к папке с фонами");
//путь к папке с авами
string avapath = project.Variables["CREATE_avaDirPath"].Value;
if(string.IsNullOrEmpty(avapath)) throw new Exception("Укажите путь к папке с фейк-лицами");
//Путь к папке для сохранения готовых ав
string savepath = project.Variables["CREATE_saveDirPath"].Value;
if(string.IsNullOrEmpty(savepath))
{
savepath = project.Directory+@"\ready\";
}
//Создаем временный список для фоновых изображений
List<string> bglist = new List<string>();
//Если директории не существует — ошибка
if(!Directory.Exists(bgpath)) throw new Exception("Указанная директория для фонов НЕ СУЩЕСТВУЕТ!");
//закидываем все файлы фонов вместе путями к ним в список
bglist.AddRange(Directory.GetFiles(bgpath));
//если количество строк = 0 => ошибка
if(bglist.Count == 0) throw new Exception("В директории с фонами нет файлов");
//Создаем временный список для автар
List<string> avalist = new List<string>();
//Если директории не существует — ошибка
if(!Directory.Exists(avapath)) throw new Exception("Указанная директория для аватар НЕ СУЩЕСТВУЕТ!");
//закидываем все файлы ав вместе путями к ним в список
avalist.AddRange(Directory.GetFiles(avapath));
// если количество строк = 0 => ошибка
if(avalist.Count == 0) throw new Exception("В директории с аватарами нет файлов");
for(int i=0; i<avalist.Count; i++)
{
//берем строку из списка
string avafile = avalist.ElementAt(i);
//получаем имя текущего файла
string avaname = Path.GetFileNameWithoutExtension(avafile);
//Создаем из файла новый объект Image
Image iava = Image.FromFile(avafile);
//в цикле берем все фоны, накладываем на них текущую аву и сохраняем в папку с ее именем
for(int j=0; j<bglist.Count; j++)
{
//берем файл из списка
string bgfile = bglist.ElementAt(j);
string bgname = Path.GetFileNameWithoutExtension(bgfile);
//Создаем из файла новый объект Image
Image ibg = Image.FromFile(bgfile);
//Новый Bitmap-объект в котором будем рисовать фон и аву поверх него, с размером как у фонового изображения
Bitmap dest = new Bitmap(ibg.Width, ibg.Height);
//рисуем
using(Graphics g = Graphics.FromImage(dest))
{
//Рисуем фон(фоновое изображение, левый верх край x, левый верх край y, ширина,высота, цветовое пространство)
g.DrawImage(ibg, 0, 0);
//Рисуем аву поверх фона(ава, левый верх край x, левый верх край y, ширина,высота, цветовое пространство)
g.DrawImage(iava, 0, 0);
}
//создаем директорию только если ее нет
string savedirpath = savepath+@"\"+avaname;
if(!Directory.Exists(savedirpath))
{
Directory.CreateDirectory(savedirpath);
}
//путь и иммя сохраняемого файла
string saveimgpath = savedirpath + @"\" + bgname + ".jpg";
//сохраняем объект Bitmap в объект Image
Image dst = (Image)dest;
//сохраняем файл
dst.Save(saveimgpath, System.Drawing.Imaging.ImageFormat.Jpeg);
}
}
- Получаем строку avafile с путем к файлу фейка,
- Создаем из файла avafile объект Image iava,
- Получаем строку bgfile с путем к фону,
- Создаем обект Image ibg из файла bgfile,
- Создаем объект Bitmap dest, в котором будем выполнять наложение изображений,
- Используя объект g класса Graphics , рисуем на объекте dest изображение ibg с координатами начала рисунка 0,0,
- А после, поверх первого рисуем изображение фейка
- Сохраняем все фоновые изображения с наложенным на них текущим фейком в папку с именем файла-фейка, проверяя при этом что папка с таким именем существует, и создавая ее, если нет.
Общий код:
/// <summary>
/// Метод создает список List<string> и сохраняет в него пути ко всем файлам укеазанной директории
/// </summary>
/// <param name="path">Путь к директории, файлы из которой нужно поместить в список</param>
public List<string> GetDirFilesToList(string path)
{
//Создаем список
List<string> list = new List<string>();
//Если директории не существует — ошибка
if(!Directory.Exists(path)) throw new Exception("Указанная директория НЕ СУЩЕСТВУЕТ!");
//закидываем все файлы фонов вместе путями к ним в список
list.AddRange(Directory.GetFiles(path));
if(list.Count == 0) throw new Exception("Список файлов пуст! Возможно в директории нет файлов");
return list;
}
Общий код:
/// <summary>
/// Метод в цикле получает из списков имя-путь изображения фейк-лица и фонов, накладывает на каждый фон лицо и сохраняет в подпапках с именем файла фейк-лица в папке назначения
/// </summary>
/// <param name="avalist">Список с путями к изображениям фейк-лиц</param>
/// <param name="bglist">Список с путями к изображениям фонов</param>
/// <param name="savepath">Путь к папке для сохранения готовых аватар</param>
public void OverlayAndSaveImages(List<string> avalist, List<string> bglist, string savepath)
{
if(string.IsNullOrEmpty(savepath))
{
savepath = project.Directory + @"\ready\";
}
for(int i=0; i<avalist.Count; i++)
{
//берем строку из списка
string avafile = avalist.ElementAt(i);
//получаем имя текущего файла
string avaname = Path.GetFileNameWithoutExtension(avafile);
//Создаем из файла новый объект Image
Image iava = Image.FromFile(avafile);
//в цикле берем все фоны, накладываем на них текущую аву и сохраняем в папку с ее именем
for(int j=0; j<bglist.Count; j++)
{
//берем файл из списка
string bgfile = bglist.ElementAt(j);
string bgname = Path.GetFileNameWithoutExtension(bgfile);
//Создаем из файла новый объект Image
Image ibg = Image.FromFile(bgfile);
//Новый Bitmap-объект в котором будем рисовать фон и аву поверх него, с размером как у фонового изображения
Bitmap dest = new Bitmap(ibg.Width, ibg.Height);
//рисуем
using(Graphics g = Graphics.FromImage(dest))
{
//Рисуем фон(фоновое изображение, левый верх край x, левый верх край y, ширина,высота, цветовое пространство)
g.DrawImage(ibg, 0, 0);
//Рисуем аву поверх фона(ава, левый верх край x, левый верх край y, ширина,высота, цветовое пространство)
g.DrawImage(iava, 0, 0);
}
//создаем директорию только если ее нет
string savedirpath = savepath+@"\"+ avaname;
if(!Directory.Exists(savedirpath))
{
Directory.CreateDirectory(savedirpath);
}
//путь и иммя сохраняемого файла
string saveimgpath = savedirpath + @"\" + bgname + ".jpg";
//сохраняем объект Bitmap в объект Image
Image dst = (Image)dest;
//сохраняем файл
dst.Save(saveimgpath, System.Drawing.Imaging.ImageFormat.Jpeg);
}
}
}
C#:
//путь к папке с фонами
string bgpath = project.Variables["CREATE_bgDirPath"].Value;
if(string.IsNullOrEmpty(bgpath)) throw new Exception("Укажите путь к папке с фонами");
//путь к папке с авами
string avapath = project.Variables["CREATE_avaDirPath"].Value;
if(string.IsNullOrEmpty(avapath)) throw new Exception("Укажите путь к папке с фейк-лицами");
//Путь к папке для сохранения готовых ав, если не указан, задаем по-умолчанию
string savepath = project.Variables["CREATE_saveDirPath"].Value;
//Новый экз. класса ZHelper
ZHelper zh = new ZHelper(project, instance);
//Получаем в список файлы из директории bgpath
List<string> bglist = zh.GetDirFilesToList(bgpath);
//Получаем в список файлы из директории avapath
List<string> avalist = zh.GetDirFilesToList(avapath);
//В цикле берем фейк-фото, накладываем на все имеющиеся фоны, создаем в указанной папке директорию с именем
//фейк-фото и сохраняем в нее все получившиеся аватары
zh.OverlayAndSaveImages(avalist, bglist, savepath);
1. Когда в проекте присутствует большое количество одинаковых действий (например парсинг атрибутов html-элементов товаров в магазине), вынос в общий код метода получения этих атрибутов дает возможность изменить метод в одном месте, а не переписывать его в множестве кубиков, со все увеличивающейся от правки к правке вероятностью появления косяка.
2. При необходимости сменить логику работы шаблона — нам не придется пречитывать простыни кода, выискивая в них именно логику, а не дополнительные действия, призванные эту логику обеспечить.
3. Поддерживать и дебажить такой код проще и приятнее — ведь и лада и тойота способны доставить нас к месту назначения, но с¥ка, добираться на япомобиле почему-то ощутимо кайфовее.
4. Освоение общего кода как инструмента, открывает новые возможности. А новые возможности это всегда круто!
Еще немного идей по нейросетевым технологиям:
1. Уникализация фото-контента — уже сейчас в сети есть web-интерфейсы, позволяющие:
а) удалять с изображений фоны (иногда довольно сносно, иногда совсем ущербно),
б) накладывать на изображения различные стили живописи,
в) встречал в телеге бота, который переделывает расу человека на фото в китайца-негра (набрать фоток порнозвезд, сменить им расу — вот и перспективные девочки для дейтинга)
2. Генерация фейк-новостей, фейк-резюме, и других текстовых прогонов (тестил русифицированную GPT-2 — иногда получается забавно, хотя обойтись без ручной фильтрации пока невозможно), ну и стандартно — чатботы всех мастей и расцветок.
В заключении пара ссылок на статьи из прошлых конкурсов (с большим респектом авторам), которые будут полезны для освоения инструментария общего кода:
https://zennolab.com/discussion/threads/king-of-parsing-nachalo-chast-1.79341/ от автора Маломальский
https://zennolab.com/discussion/threads/nestandartnye-podxody-k-razrabotke-shablonov.48583/ от автора shtift
https://zennolab.com/discussion/threads/bystryj-start-proektov-gotovoe-reshenie-uskorjaem-razrabotku-s-pomoschju-owncode-i-metodov-rasshirenija.79162/ от RoyalBank
Литература (субъективно):
Роберт Мартин «Чистый код: создание, анализ и рефакторинг»
Герберт Шилдт C# 4.0. Полное руководство
Албахари Джозеф, Албахари Бен, С# 6.0. Справочник. Полное описание языка, 6-е изд.
P.S. Вполне возможно что методы которые я здесь привел не оптимальны или не совсем правильны, а может быть вынесено что-то, что должно было остаться в кубиках — я не программист, и опыта работы с общим кодом мне не хватает, так что респект вем, кто укажет ошибки!
- Тема статьи
- Другое
- Номер конкурса статей
- Четырнадцатый конкурс статей
Вложения
-
36,1 КБ Просмотры: 372
Для запуска проектов требуется программа ZennoPoster или ZennoDroid.
Это основное приложение, предназначенное для выполнения автоматизированных шаблонов действий (ботов).
Подробнее...
Для того чтобы запустить шаблон, откройте нужную программу. Нажмите кнопку «Добавить», и выберите файл проекта, который хотите запустить.
Подробнее о том, где и как выполняется проект.
Последнее редактирование модератором: