Masktools.

- Дорогой, а почему ты весь потный, а футболка сухая и совсем не пахнет?
- Всё просто, я пользуюсь masktools!

Предисловие

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

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

Часть материала, конечно, взята из мана самого Manao, однако, ман это почему-то оказывается непонятен большей части читателей. Поэтому постараюсь рассказать всё, начиная с самых основ.

В общем о masktools

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

Фильтр работает исключительно с планарными изображениями, из которых самым распространенным является YV12. Особенность в том, что вся информация в кадре хранится в 3х плоскостях - плоскость яркости (она же Y, PLANAR_Y, luma) и двух плоскостях цветности (U и V, PLANAR_U и PLANAR_V, chroma). Необходимо помнить, что разрешение каждой из плоскостей цветности в 2 раза меньше яркости, так называемая модель 4:2:0.

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

Кадр, в общем виде, можно представить как трёхмерный массив, где 2 плоскости представляют собой координаты точек x и y в кадре, а последняя, состоящая из трёх элементов - значения плоскостей Y, U и V для этой точки. В AvsP посмотреть значения для кадой точки довольно просто. Идём в опции, Видео 1, "Настроить полосу статуса", и там устанавливаем отображения позиции и цвета пикселя. Потом просто стоит навести мышь на пиксель, чтобы увидеть значения.

overlap overlap overlap

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

Базовые параметры

Фильтры

mt_square, mt_circle, mt_diamond , mt_rectangle, mt_ellipse, mt_losange, mt_freerectangle, mt_freeellipse, mt_freelosange

mt_square : int radius(1), bool zero(true)
mt_circle : int radius(1), bool zero(true)
mt_diamond : int radius(1), bool zero(true)
mt_rectangle : int hor_radius(1), int ver_radius(1), bool zero(true)
mt_ellipse : int hor_radius(1), int ver_radius(1), bool zero(true)
mt_losange : int hor_radius(1), int ver_radius(1), bool zero(true)
mt_freerectangle : int top_x(-1), int top_y(-1), int bottom_x(1), int bottom_y(1), bool zero(true)
mt_freeellipse : int top_x(-1), int top_y(-1), int bottom_x(1), int bottom_y(1), bool zero(true)
mt_freelosange : int top_x(-1), int top_y(-1), int bottom_x(1), int bottom_y(1), bool zero(true)

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

mt_lut

mt_lut : string expr("x"), string yexpr("x"), string uexpr("x"), string vexpr("x")

Пожалуй, самый просто для понимания фильтр. Просто применяет определенное выражение, записанное в строках expr, yexpr, uexpr, vexpr для каждой точки данного кадра. Выражение записываться полькой, т.е. сначала перечисляются аргументы, а затем выражение, которое к ним применяется. Например: Фильтр довольно полезен для обрабатывания одного кадра, особенно с использованием условного оператора. Значение x, записываемое в выражении - значение пикселя по определенной плоскости. Т.е. если фильтр обрабатывает яркость, для каждого пикселя x будет представлять значения яркости в этой точке. Если не определены выражения yexpr, uexpr или vexpr, для из таких плоскостей используется выражение expr. Покажу лишь пару примеров.
mt_lut("x 150 > 255 0 ?",chroma="128")
В данном примере мы берем все точки, значения яркости которых больше 150, и устанавливаем их в 255, остальные в 0. Это полезно, к примеру, для отлова титров на сложных паттернах ivtc.

overlap overlap

Более сложный пример: обработка маски границ, реализованная в скрипте LimitedSharpen, немного изменённая мной в учебных целях.
mt_lut("x 128 / 0.7 ^ 128 *")
Здесь уже над вспомнить немного математики. Значения каждого пикселя по яркости могут быть от 0 до 255. Сначала, разделим его на 128. Теперь, значение каждой точки лежит в промежутке от 0 до 1 для точек, чья яркость изначально была меньше 128, в значении 1 для равных 128 и от 1 до 2 для ярких точек, больших 128 на входном клипе. Затем, расчитаем степень, меньшую единицы, для каждого полученного после деления значения. Степени меньше 1 обладают очень интересным свойством - они увеличивают числа меньшие единицы, и уменьшают числа, большие 1 (степерь 0.5, фактически, равна корню второй степени, и т.п.). Полученные значения умножим на 128 для возвращения их к прежним рамками. А что на выходе? На выходе та же маска, но с усиленными слабыми линиями, и ослабленными сильными.

overlap overlap

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

mt_lutxy, mt_lutxyz

mt_lutxy : clip clip1, clip clip2, string expr("x"), string yexpr("x"), string uexpr("x"), string vexpr("x")

mt_lutxyz : clip clip1, clip clip2, clip clip3, string expr("x"), string yexpr("x"), string uexpr("x"), string vexpr("x")

Очень похожи на mt_lut, только предназначены для обрабатывания двух или трёх кадров соответственно. В формулах теперь присутствует не только x, но и y - аналогичен x, но представляет значение второго входного клипа. В mt_lutxyz так же можно использовать z - значение точки третьего клипа.

Опять же, приведу пару примеров.
mt_lutxy(last,last.trim(1,0),"x x y - 0.4 * +",u=2,v=2)
Типичная формула для создания блендов - a = b + (a - b) * alpha. Одно изображение накладывается на другое. Довольно частоиспользуемый прием при обработке видео, который можно реализовать и в ависинте, достаточно лишь привязать коэффициент alpha к номеру кадра по определенной пропорции.

overlap overlap overlap

Конечно, это довольно простой и бесполезный для "рипания" пример, но, надеюсь, алгоритм работы ф-ии стал понятен. Во избежание окончательного захламления мана скриншотами, не буду приводить пример для mt_lutxyz, тем более, это довольно редко используемая ф-ия. Тем не менее, на её основе я пытался побороть динамичные лесенки в одном месте ROD, где использовал обычный временной усреднитель.
mt_lutxyz(last,last.trim(1,0),last.trim(0,-1)+last,"x y + z + 3 /",u=2,v=2)

mt_lutf, mt_luts, mt_lutsx

mt_lutf : clip clip1, clip clip2, string mode("avg"), string expr("y"), string yexpr("y", string uexpr("y"), string vexpr("y")

mt_luts : clip clip1, clip clip2, string mode("avg"), string pixels(""), string expr("x"), string yexpr("x"), string uexpr("x"), string vexpr("x")

mt_lutsx : clip clip, clip clip1, clip clip2, string mode("avg"), string mode2("none"), string pixels(""), string expr("x"), string yexpr("x"), string uexpr("x"), string vexpr("x")

Пожалуй, самые наркоманские функции masktools. Работают довольно сложно, может быть поэтому используются очень редко. Первая принимает на вход 2 клипа, расчитывает одно единственное значение для всего клипа, согласно параметру mode, а затем применяет формулу, определенную expr, ко всем значениям второго клипа. X в этом выражении будет константой, в то время как y - значение каждого пикселя clip2.

Вторая еще более сурова! Параметром pixels определяется область, с которой будут использоваться значения y. Т.е. на вход выражению expr последовательно будут поданы значения из clip2, определенные этим параметром.

Значение параметра pixels может быть обычной строкой, к примеру "-1 -1 0 -1 1 -1 0 0". Если мысленно разбить числа на пары, то получим набор координат (-1;-1), (0;-1), (1;-1), (0;0). В данном случае 0;0 - сам пиксель, для которого ведётся расчет, координаты остальных расчитываются относительно него. Т.е. (0;-1) - пиксель, находящийся на одну позицию выше центрального.

Затем для каждого пикселя из этой области расчитывается выражение expr, и результат этого выражения (всё тот же набор значений) передается на обработку, согласно параметру mode.

mt_luts( clip1, clip2, mode = "avg", pixels = mt_square( 1 ), expr = "y" )

В это примере для каждой точки clip2 сначала берутся 9 значений, представляющих собой матрицу 3х3, где исходная точка - центр матрицы. Затем, к ним применяется выражение expr, которое просто возвращает эти значения нетронутыми. Впоследствии, согласно mode = "avg" расчитывается среднее между этими значениями. На выходе получаем самый настоящий блюр, или проще говоро "пространственный усреднитель".

Если вы поняли, что написано выше, то последняя функция уже не станет проблемой. Аналогичным образом, для набора пикселей, представленных переменной pixels, расчитывается одно определенное значение, указанное в mode и mode2 соответственно. Затем к ним применяется выражение, определённое переменной expr, где x - значение входного клипа, y - значения, полученные после расчета mode, z- после расчета mode2.

"Уходи, тролль, здесь не дадут тебе еды!". Примеров не будет. Как я и сказал изначально, применения этим ф-ия найти довольно сложно, и я буду очень рад если кто-то кроме парней типа Didee это сможет. Единственный пример, который приходит на ум, это GradFun2dbmod, который с помощью mt_luts строит маску границ (да, он накладывает на границы меньше зерна, чем на плоскости).
GFmask = radius==1 ? input.mt_edge(mode="min/max",thY1=0,thY2=255,u=1,v=1).mt_lut(expr="255 x 1 "+string(range)+" / * 2 ^ /",u=1,v=1).removegrain(19,-1)
\      :             mt_luts(input,input,mode="range",pixels=mt_square(radius),expr="y",u=1,v=1).mt_lut(expr="255 x 1 "+string(range)+" / * 2 ^ /",u=1,v=1).removegrain(19,-1)
А теперь представьте, сколько времени требуется на расчет такого выражения для radius > 1. Еще остались вопросы, почему градфан такой медленный? ;)

mt_lutspa

mt_lutspa : clip clip, bool("relative"), string expr("x"), string yexpr("x"), string uexpr("x"), string vexpr("x")

А это уже значительно проще. Расчитывает значение для каждого пикселя согласно мат. выражению, записанному в expr, где x - координата точки по x, y - координата по y. Отсчет координат начинается с верхнего левого угла. Параметр relative определяет, будут ли координаты относительными, или абсолютными. Относительные представляют собой значение от 0 до 1, в то время как абсолютное значение расчитывается, к примеру, abs_x = width * rel_x. Абсолютные же значения определяют непосредственно координату.

Фильтру абсолютно всё равно, что вы дадите на вход - черный клип или реальный кадр, ибо значения пикселей клипа в расчетах не участвуют. Вообще, с фильтром можно строить сложные градиентные маски и т.п., но самое банальное применение - фильтрация определенной части кадра, которую нельзя (либо очень сложно) реализовать через offx и ему подобные параметры.

mt_lutspa(relative = false, expr="y 200 - x 15 / sin 50 * round == 255 0 ?") #ничего не мешает вам фильтровать по синусоиде. ;) 

overlap

function periodicMask(clip clp,int "lc",int "rc", int "bc", int "tc")  #или, к примеру, реализовывать постоянно вращающийся по окружности объект (создать маску вращающегося логотипа канала)
{
lc = Default( lc , 0 )
rc = Default( rc , 0 )
bc = Default( bc , 0 )
tc = Default( tc , 0 )
clp.crop(lc,tc,-rc,-bc)
scriptclip("""t = string(current_frame)
rad = string(30)
period=string(50)
xoff = string(200)
yoff=string(150)
mt_lutspa(last,relative=false,expr="x "+xoff+" - "+rad+" 2 "+t+" * "+period+" / pi * "+t+" "+period+" / round pi * 2 * - cos * round == y "+yoff+" - "+rad+" 2 "+t+" * "+period+" / pi * "+t+" "+period+" / round pi * 2 * - sin * round == & 255 0 ?",chroma="128",y=3)
""")
mt_expand(mode=mt_rectangle(20,10),u=2,v=2).addborders(lc,tc,rc,bc)
return ( last )
} 
mt_lutspa(relative= false, expr= "x 16 % 0 == y 16 % 0 == | 255 0 ?",chroma="128") #а можно и создать карту блоков 16х16, чтобы обработать деблоком только их.

overlap

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

mt_average, mt_makediff, mt_adddiff, mt_invert, mt_binarize

mt_average : clip clip1, clip clip2 = mt_lutxy("x y + 2 /")

mt_makediff : clip clip1, clip clip2 = mt_lutxy("x y - 128 +")

mt_adddiff : clip clip1, clip clip2 = mt_lutxy("x y + 128 -")

mt_invert : clip c = mt_lut("255 x -")

mt_binarize : clip c, int threshold(128), bool upper(false) = mt_lut("x threshold > 0 255 ?") либо mt_lut("x threshold > 255 0 ?") если upper=false

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

Третья практически не используется без второй, но их совместное использование довольно интересно. К примеру, если какой-либо фильтр (тот же шарп) работает лучше на чистых сорцах, нежели шумных, то можно сначала выфильтровать весь шум, потом фукнцией mt_makediff забрать разницу между сорцем и отфильтрованным изображением (тот самый шум) в отдельный клип, а после обработки изображения шарпом добавить шум обратно с помощью mt_adddiff.

Четвертая инвертирует клип, полезность чего я покажу в дальнейшем, при рассмотрении маски границ. Пятая бинаризирует клип, приводя его значения к 255, если они больше порога, и к 0, если меньше. Параметр upper = true полезен для инвертирования, т.е. если в скрипте стояло mt_binarize(128).mt_invert(), то это можно заменить на простое mt_binarize(128,upper=true), что быстрее.

overlap overlap overlap overlap

mt_inpand, mt_expand, mt_inflate, mt_deflate

mt_xxpand : int thY(255), int thC(255), string mode("square")

mt_xxflate : int thY(255), int thC(255)

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

Первая пара заменяет значение пикселя на максимальное (expand)/минимальное (inpand) из области, ограниченной mode. В переменную mode можно передать как области, полученные с помощью впомогательных функций masktools, так и области, определенные координатами вручную. Есть так же и внутренние типы, такие как "square" (матрица 3х3), "horizontal" (3х1, горизонтальная строка), "vertical" (1х3, вертикальный столбец), "both" (крест из строки 3х1 и столбца 1х3).

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

overlap overlap overlap overlap

Но могут так же применяться, к примеру, для создания маски границ.

overlap overlap

mt_logic

mt_logic : clip clip1, clip clip2, string mode("and")

Применяет определенную битовую операцию к каждому пикселю, согласно параметру mode. Алгоритм работы можно посмотреть в мануале самого Manao. Хотя, я обычно использую эту ф-ию на уже бинаризированных масках, поэтому правила для таких масок довольно просты, и понимаются, если просто их произнести. Только следует помнить, что для mode="andn", выражение расчитывается относительно второго клипа. Т.е. "второй клип и не первый".

mt_hysteresis

mt_hysteresis : clip small_mask, clip big_mask

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

overlap overlap overlap

mt_motion

mt_motion : int thY1(10), int thY2(10), int thC1(10), int thC2(10), int thT(10)

Практически бесполезная функция. Имея в арсенале кучу, mt_lut-ов и mvtools Физика, пользоваться ей довольно бессмысленно. Результат mt_motion(0,255) эквивалентен mt_lutxy(last,last.trim(0,-1)+last,"x y - abs"). Итого имеем простой модуль разности каждого пикселя в текущем и предыдущем кадрах. thT - порог детекта смены сцены, остальные пороги эквиваленты параметрам mt_edge, о которых я расскажу ниже.

mt_convolution

mt_convolution : clip c, string horizontal("1 1 1"), string vertical("1 1 1"), bool saturate(true), float total(1.0f)

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

overlap

mt_mappedblur

mt_mappedblur : clip c, clip map, string kernel("1 1 1 1 1 1 1 1 1"), string mode("replace")

Очень похож на convolution, так же позволяет указывать кастомные матрицы, однако имеет параметр map и mode. Первый, принимая на вход клип, определяет порог разницы между значением пикселя в точке и центром матрицы преобразования (обрабатываемого пикселя). Второй - что делать с теми пикселями, которые не прошли порог. Их можно заменять на центральный пиксель матрицы ("replace"), a можно просто выбрасывать из матрицы ("dump"). Особого практического применения я пока не нашел.

mt_clamp

mt_clamp : clip c, clip bright_limit, clip dark_limit, int overshoot(0), int undershoot(0)

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

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

overlap overlap

mt_edge

mt_edge : string mode("sobel"), int thY1(10), int thY2(10), int thC1(10), int thC2(10)

Как ясно из названия, используется для построения маски границ. Согласно строке mode для каждого пикселя расчитывается матрица преобразования, как это делалось выше в mappedblur и convolution. Матрица, опять же, может быть практически любого размера, и может расчитывать не только маску границ, но и шарпить, блюрить, сдвигать кадр на определенную величину и т.д.

Основной параметр mode определяет один из пресетов матрицы преобразования, чьи коэффициенты можно найти в мане Manao или в интернете. Имхо лучшие ядра - min/max и prewitt, однако последнее серьезно медленней всех остальных, и иногда лучше определить своё ядро.

Параметры thY1/thC1 определяют порог бинаризации для люмы и хромы соответственно - все значения, меньшие этого порога, будут сброшены в 0. thY2/thC2 работают наоборот - все значения, выше этого порога, будут установлены в 255. Собственно, в случае, если thY1 = thY2, то это равносильно вызову mt_binarize(thY1).

mt_merge

mt_merge : clip clip1, clip clip2, clip mask, bool "luma"(false)

То, ради чего всё и затевалось! Главная функция masktools, позволяющая соединить два клипа согласно маске. Расчетная формула довольно проста: y = ((256 - m) * x1 + m * x2 + 128) / 256 ), где y - значение точки выходного клипа, m - значение маски в этой точке, x1 и x2 - значение двух входных клипов соответственно. Из формулы видно, что чем больше значение маски в данной точке, тем больше в выходном клипе будет от второго входного, и меньше от первого, и наоборот.

Применяется очень просто, имеет всего один "кастомный" параметр - luma. Если этот параметр установлен в true, то все плоскости будут расчитываться по люме маски, в то время как при установке в false, по умолчанию клип содержит хрому первого клипа, а при установке u = 3, v = 3, будет расчитываться по цветовой составляющей маски. Учитывая то, что абсолютно большинство масок строится по яркостной составляющей, параметр действительно нужный.

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

overlap

Более сложный пример, с которым я впервые столкнулся в рипе bmg - хроматические линии. Скрипт выглядел примерно так (клип filt специально перефильтрован для наглядности):
filt = dfttest().gradfun2db(1.6)
mask = mt_edge("prewitt",8,8,8,8,u=3,v=3)
maskf = mt_logic(mask,mt_logic(mask.utoy(),mask.vtoy(),"or").spline144resize(width,height),"or").mt_expand()
mt_merge(last,filt,maskf.mt_invert(),luma=true)
В общем, в сравнении с сорцем, портфель выглядел довольно плачевно.

overlap

overlap

Первым делом строится маска люматических границ.

overlap

Портфель она совсем не захватывает, поэтому включим в построение маску границ по хроме.

overlap

И попробуем наложить с разделением каналов (каждому каналу своя маска).

overlap

Несмотря на все старания, линии всё равно убиваются. Поэтому объединим маску границ по яркости и обе маски границ по цветности, переведя последние в яркостный канал. После наложения с парамером luma=true получим то, чего и хотели.

overlap

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

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

(с) tp7