Задумался, как можно оживить логотип, который накладываю на видеоролики неких спортивных мероприятий. Возникла идея сделать поворачивающийся вокруг своей вертикальной оси каждые секунд 20 логотип. В результате хотелось получить нечто подобное:
Для выполнения задачи понадобится система с установленными imagemagick и ffmpeg. Для формирования команд утилите imagemagick будет использоваться bash. Что повлечет за собой использование утилиты bc, т.к. математика в bash довольно простая и тригонометрические функции вычислить затруднительно.
С помощью python, например, тоже самое сделать еще проще, в нем-то синус и косинус вычисляются.
Для создания видео создадим N кадров, за которые логотип сделает полный оборот, затем превратим их в видеофайл.
По сути, вращение — это последовательное сжатие и расширение изображения по одному из измерений. Если мы хотим получить эффект равномерного вращения вокруг вертикальной оси — ширина изображения будет изменяться кратно значению функций синус или косинус.
Начальное состояние для нас — это видимый в полный размер логотип, поэтому для изменения ширины изображения будем умножать его ширину на косинус (его значение в нуле как раз равно единице).
Вращение изображения для данных целей можно поделить на 4 этапа:
— изображение сужается;
— расширяется, но к зрителю повернуто обратной стороной;
— сужается обратной стороной к зрителю;
— расширяется до исходных размеров.
Сформируем базовый набор кадров построим для вращения на 0-90 градусов, а кадры для остальных 270 получим немного их преобразовывая. Цикл, соответственно, будет не от 0 до 360 градусов, а от 0 до 90.
Пропишем путь к нашему логотипу, чтобы потом обращаться к нему коротким $logo:
logo=../../logo/Moto_Gymkhana_transparent.png
Для удобства введем переменные равные ширине и высоте логотипа, так будет проще подправить скрипт под другую картинку:
width=842
height=595
Заведем переменную, равную количеству кадров, за которые логотип совершит четверть оборота. Она понадобится один раз — для вычисления шага, с которым должен вычисляться угол поворота.
Frames=15
В цикле понадобится:
— вычислить, какова будет ширина изображения при его повороте на заданный переменной «n» угол;
— создать пустой холст размером, скажем 850x600;
— добавить в его центр сжатое по ширине изображение.
for (( n=0; n<=90; n+=$(expr $(( 90/$Frames )))))
do
oWidth=$(printf %.$2f $(echo "$width*(c($n*4*a(1)/180))+1" | bc -l))
convert -size 850x600 xc:transparent -background none \
\( -alpha set -channel A -evaluate add -60% $logo -geometry $oWidth\ x$height\! \)\
-gravity center -composite ./tmp/$n.png
done
Разберем код по частям.
Вычислить ширину изображения под данным углом:
oWidth=$(printf %.$2f $(echo "$width*(c($n*4*a(1)/180))+1" | bc -l))
Удобнее будет разбирать этот кусок справа налево:
"| bc -l" указывает, что вычисляться это будет утилитой bc.
Т.к. косинус в утилите «bc» принимает на вход значения в радианах, а числа пи в bash нет — используем следующее выражение:
c($n*4*a(1)/180) — где
«c» — косинус,
$n — угол в градусах узменяющийся в цикле;
a(1) — арктангенс единицы, равный пи/4. Поэтому 4*a(1) это способ записать число пи. На 180 делим чтобы перейти от радиан к градусам.
Таким образом, "$width*(c($n*4*a(1)/180))" — ширина умноженная на косинус угла «n».
"+1" после этого выражения для того, чтобы ширина изображения не принимала значения 0. Попытку сделать изображение нулевой ширины не поймет ImageMagick.
$(printf %.$2f $(echo "$width*(c($n*4*a(1)/180))+1" | bc -l)) — вычислить значение ширины для данного угла и вывести, округлив до целого.
convert -size 850x600 xc:transparent -background none \
\( -alpha set -channel A -evaluate add -60% $logo -geometry $oWidth\ x$height\! \)\
-gravity center -composite ./tmp/$n.png
convert -size 850x600 xc:transparent -background none — создать прозрачный холст размером 640x360 без заднего фона.
Последующая часть команды взята в экранированные "\" скобки чтобы она выполнялась изолированно. Это важно, иначе изменение размера коснется и холста.
-alpha set — включить канал прозрачности.
-channel A -evaluate add -60% — добавить к значению канала А всех пикселей изображения "-60%".
$logo- путь к изображению.
-geometry $oWidth\ x$height\! — изменить размер изображения.
"-geometry AxB" изменит размеры изображения, но сохранит соотношение сторон. Т.е. если мы попытаемся убавить ширину — высота изображения тоже уменьшится чтобы сохранить соотношение «ширина х высота», а выше и ниже появятся пустые области.
"-geometry AxB\!" не бедут пытаться сохранить соотношение «ширина х высота», а изменит их независимо друг от друга.
-gravity center — разместить посередине.
-composite — объединить изображения.
./tmp/$n.png — сохранить файл в папку «tmp», назвав текущим значением переменной «n».
Вид с угла в 54 градуса:
Из этого эталонного набора изображений создадим 4 кадра будущей анимации.
Пусть логотип вращается по часовой стрелке если смотреть сверху. Для придания объема изображению добавим «тень» сдвинутую вправо или влево в зависимости от того, куда на кадре вращается изображение. И отразим изображение слева-направо на кадрах, где логотип повернут к зрителю задней стороной. «Тень» на этих же кадрах будет добавлена не под исходное изображение, а поверх него, чтобы создать эффект оборотной стороны логотипа.
Рассмотрим подробно выражение для первой четверти поворота. Остальные будут понятны по аналогии. Лишь акцентирую внимание на отличиях от первого:
convert tmp/$n.png \
\( +clone -background '#cccf' -shadow 100x8+$shadowShift-3 \)\
-background none -compose Src_Over -layers merge \
-gravity center tmp/logo$(expr $(( 1000+n ))).png
convert tmp/$n.png — взять за основу исходное изображение.
\( +clone -background '#cccf' -shadow 100x8+$shadowShift-3 \) — склонировать и создать тень изображения.
Цвет заданный опцией background задаст цвет тени.
Насколько тень непрозрачна (в процентах) и размыта задается параметром «100x5».
Сдвиг тени относительно оригинального изображения задается как +x+y. В нашем случае сдвиг по вертикали постоянен (3 пикселя вверх), а сдвиг по горизонтали задается переменной shadowShift.
Значение этой переменной вычислим аналогично сжатию исходного кадра по вертикали. Только возьмем синус угла, а не косинус, т.к. сдвиг при нулевом угле должен быть ноль.
shadowShift=$(printf %.$2f $(echo «15*(s($n*4*a(1)/180))+1» | bc -l))
Для первой и второй четверти тень должна быть сдвинута вправо, поэтому значение shadowShift берем со знаком «плюс», для третьей и четвертой — со знаком «минус».
-background none -compose Dst_Over -layers merge -gravity center -depth 8 tmp/logo$(expr $(( 1000+n ))).png
Тут важная опция это -compose. Она задает какое из изображений будет расположено сверху.
Для первой четверти поворота, сверху располагается сам логотип, поэтому Dst_Over. Для второй и третьей четверти будет прописано Src_Over (тень сверху).
-layers merge -gravity center — совместить слои, расположить посередине.
tmp/logo$(expr $(( 1000+n ))).png — сохранить под именем logo + «1000+значение n» в папку tmp.
имя такое чтобы имена кадров сразу отсортировались в алфавитном порядке и их было удобно отдать на обработку ffmpeg'у.
Для кадров второй четверти поворота будет «1800-n», для третьей «2000+n», для четвертой «2800-n».
Для второй и третьей четвертей появится опция -flop — отразить изображение слева-направо. Из-за этого, кстати, понадобится изменить знак смещения тени на противоположный.
Код для всех четырех четвертей поворота:
convert tmp/$n.png \( +clone -background '#cccf' -shadow 100x8+$shadowShift-3 \) \
-background none -compose Dst_Over -layers merge -gravity center -depth 8 tmp/logo$(expr $(( 1000+n ))).png
convert tmp/$n.png \( +clone -background '#cccf' -shadow 100x8-$shadowShift-3 \) \
-background none -compose Src_Over -layers merge -gravity center -flop tmp/logo$(expr $(( 1800-n ))).png
convert tmp/$n.png \( +clone -background '#cccf' -shadow 100x8+$shadowShift-3 \) \
-background none -compose Src_Over -layers merge -gravity center -flop tmp/logo$(expr $(( 2000+n ))).png
convert tmp/$n.png \( +clone -background '#cccf' -shadow 100x8-$shadowShift-3 \) \
-background none -compose Dst_Over -layers merge -gravity center tmp/logo$(expr $(( 2800-n ))).png
В результате из исходного кадра получены 4 с небольшими отличиями:
Теперь дело за малым, собрать из этих изображений видео:
ffmpeg -pattern_type glob -i 'tmp/logo*.png' -pix_fmt argb -vcodec qtrle -r 30 rotatingLogo.mov
-pattern_type glob — позволяет задать маску имени кадра в привычном по консоли виде
-i 'tmp/logo*.png' — взять как исходные данные изображения из папки tmp чье имя начинается с «logo»
-pix_fmt argb — задать формат изображения с прозрачностью. «a» в ARGB — это как раз альфа-канал
-vcodec qtrle — задать кодек для видео поддерживающий прозрачность. Из известных мне это «qtrle» и «png»
-r 30 — задать частоту итогового видео 30 кадров в секунду.
rotating_logo.mov — сохранить под именем «rotatingLogo.mov»
Теперь можем выполнить команду
ffplay rotatingLogo.mov
и посмотреть на результат:
Получившееся видео длинной всего в пару секунд, т.ч. совместим множество таких кусков вместе.
Для этого используем тот же ffmpeg.
Чтобы логотип вращался не постоянно, а эпизодически создадим кусок видео с неподвижным логотипом. Для статьи пусть будет длительностью в 3 секунды, чтобы не приходилось долго ждать вращения при просмотре:
ffmpeg -loop 1 -pattern_type glob -i 'tmp/logo1000.png' -pix_fmt argb -vcodec qtrle -r 30 -t 3 stillLogo.mov
В этой команде появилась опция "-loop 1" — указывающая ffmpeg повторять входную последовательность изображений (в данном случае одно единственное), и опция "-t 3" указывающая, что продолжительность итогового видео 3 секунды.
Чтобы склеить видео с неподвижным и вращающимся логотипом, создадим файл со списком файлов, которые нужно склеить. В данном случае 20 экземпляров наших файлов:
for (( i=0; i<20; i+=1 ))
do
echo file 'stillLogo.mov' >> list.txt
echo file 'rotatingLogo.mov' >> list.txt
done
Подадим этот файл на вход фильтру concat с указанием копировать кодеки, а не перекодировать (-c copy).
ffmpeg -f concat -i list.txt -c copy logoWithRotation.mov
Осталось наложить получившийся логотип на видео.
Логотип довольно большой, т.ч. при наложении отмасштабируем его, уменьшив в три раза.
ffmpeg -i video.mov -i logoWithRotation.mov \
-filter_complex "[1:0]scale=iw/2:ih/2[logo];[0:0][logo]overlay=shortest=1:x=20:y=500" \
-vcodec libx264 -crf 18 overlay.mov
ffmpeg -i video.mov -i logoWithRotation.mov — взять на вход два видео.
-filter_complex — использовать filter_complex которому мы указываем, следующее:
[1:0]scale=iw/3:ih/3[logo] — взять поток 0 из второго видео (1 отсчет идет с нуля) и уменьшить значения ширины (iw — input width) и высоты (ih — input height) втрое. Передать дальше под именем «logo».
[0:0][logo]overlay=shortest=1:x=20:y=0 — взять поток 0 из первого видео и поток «logo» и наложить их друг на друга.
«shortest=1» — говорит, прекратить по достижении конца любого из потоков.
x=20:y=500 — указывает где располагать левый верхний угол накладываемого видео.
-vcodec libx264 -crf 18 — использовать кодек H264. Значение после crf указывает насколько сильно сжимать. 0 — вообще без сжатия.
overlay.mov — сохранить под этим именем.
Посмотреть результат:
ffplay overlay.mov
Рисунки и анимация для статьи созданы с помощью тех же инструментов. В порядке появления в статье:
ffmpeg -i rotatingLogo.mov -filter_complex "[0:0]scale=iw/3:ih/3" rotatingLogo.gif
montage tmp/54.png -geometry 300x200 result.png
montage -mode concatenate -tile 2x2 tmp/logo1054.png tmp/logo1746.png tmp/logo2054.png tmp/logo2746.png -geometry 300x200 result.png
ffmpeg -i logoWithRotation.mov -t 5.5 -filter_complex "[0:0]scale=iw/3:ih/3" logoWithRotation.gif
Итоговый скрипт для создания вращающегося логотипа:
logo=../../logo/Moto_Gymkhana_transparent.png
Frames=15
width=842
height=595
mkdir tmp
for (( n=0; n<=90; n+=$(expr $(( 90/$Frames )))))
do
oWidth=$(printf %.$2f $(echo "$width*(c($n*4*a(1)/180))+1" | bc -l))
shadowShift=$(printf %.$2f $(echo "15*(s($n*4*a(1)/180))+1" | bc -l))
convert -size 850x600 xc:transparent -background none \( -alpha set -channel A -evaluate add -50% $logo -geometry $oWidth\ x$height\! \) -gravity center -composite -depth 8 ./tmp/$n.png
convert tmp/$n.png \( +clone -background '#cccf' -shadow 100x8+$shadowShift-3 \) -background none -compose Dst_Over -layers merge -gravity center -depth 8 tmp/logo$(expr $(( 1000+n ))).png
convert tmp/$n.png \( +clone -background '#cccf' -shadow 100x8-$shadowShift-3 \) -background none -compose Src_Over -layers merge -gravity center -flop tmp/logo$(expr $(( 1800-n ))).png
convert tmp/$n.png \( +clone -background '#cccf' -shadow 100x8+$shadowShift-3 \) -background none -compose Src_Over -layers merge -gravity center -flop tmp/logo$(expr $(( 2000+n ))).png
convert tmp/$n.png \( +clone -background '#cccf' -shadow 100x8-$shadowShift-3 \) -background none -compose Dst_Over -layers merge -gravity center tmp/logo$(expr $(( 2800-n ))).png
done
ffmpeg -pattern_type glob -i 'tmp/logo*.png' -pix_fmt argb -vcodec qtrle -r 30 rotatingLogo.mov
ffmpeg -loop 1 -pattern_type glob -i 'tmp/logo1000.png' -pix_fmt argb -vcodec qtrle -r 30 -t 3 stillLogo.mov
rm list.txt
for (( i=0; i<20; i+=1 ))
do
echo file 'stillLogo.mov' >> list.txt
echo file 'rotatingLogo.mov' >> list.txt
done
ffmpeg -f concat -i list.txt -c copy logoWithRotation.mov
Наложение на файл video.mov с уменьшением логотипа в три раза:
ffmpeg -i video.mov -i logoWithRotation.mov -filter_complex "[1:0]scale=iw/3:ih/3[logo];[0:0][logo]overlay=shortest=1:x=20:y=500" -vcodec libx264 -crf 18 overlay.mov
Recommended article: Chomsky: We Are All – Fill in the Blank.
This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.
Комментариев нет:
Отправить комментарий