UPD: Заголовок изменен, что бы более соответствовать теме статьи
В статье пойдет речь об использовании convolution matrix (матрицы скручивания или матрицы свертки), с помощью которой можно создавать и накладывать на изображения фильтры, такие как blur, sharpen и многие другие.
Cтатья будет интересна не только веб-программистам, но и всем кто так или иначе занимается программной обработкой изображений, поскольку функции для работы с матрицей скручивания имеются во многих языках (точно известно о php и flash). Так же, статья будет интересна дизайнерам, использующим Adobe Photoshop, поскольку в нем имеется соответствующий фильтр (Filter-Other-Custom).
Примеры будут на языке PHP с использованием библиотеки GD. Теория, практика, примеры (осторожно, много картинок!)
Говоря не математическим языком, convolution – это преобразование одной матрицы с помощью другой, которая называется ядром («kernel»). При обработке изображений в качестве исходных выступают матрицы RGB-каналов пикселей в прямоугольных координатах.
В качестве ядра обычно используется матрица размером 3x3, но возможно и больше (5x5, 7x7 и т.д.). Ядро содержит степени влияния («ценности») окружающих значений элемента на сам элемент.
Преобразования происходит следующим образом. Каждый элемент исходной матрицы умножается центральное значение матрицы ядра. Кроме этого на соответствующие значения умножаются окружающие его элементы (при размере ядра 3x3 их будет 8), после чего результаты суммируются и принимаются как преобразованное значение.
Вот простой графический пример:
![image](https://habr.com/images/px.gif#%3D%22http%3A%2F%2Fimg385.imageshack.us%2Fimg385%2F3687%2Fcalcly1.jpg%22)
Преобразуемое значение выделено красным, область действия матрицы ядра – зеленым.
Что получислось в результате преобразования. Ценности всех окружающих пикселей, включая собственное значение равно нулю, кроме верхнего среднего, где она равна единице. Таким образом, результат:
(40*0)+(42*1)+(46*0)+(46*0)+(50*0)+(55*0)+(52*0)+(56*0)+(58*0) = 42
Как видно, данное преобразование смещает изображение вниз на 1 пиксель.
Таким образом, convolution в данном случае – это преобразование изображения, в результате которого на каждый пиксель результата влияет окружающая его область. Степень влияния этой области задается с помощью «ядра» или матрицы скручивания.
При обработке изображений одним только преобразованием не отделаешься, нужна еще нормализация. Что делать, если получившееся значение больше 255 или меньше 0? Цветов-то таких нет. Более того, что выход за границы цвета явление достаточно частое.
Для нормализации результата используются дополнительные переменные: div (делитель) и offset (коэффициент). Они работают очень просто: результат преобразования делится на div и к нему прибавляется offset.
Не трудно догадаться, что по умолчанию div = 1, offset = 0 (div = 0 выставлять нельзя!).
При преобразованиях в качестве div обычно принимается сумма всех элементов матрицы скручивания. Это условие позволяет не допустить цветовых искажений, если они не нужны.
Действительно, если преобразуемая область содержит один и тот же цвет, то результат получится как сумма элементов ядра умноженное на этот цвет. Соответственно, что бы оставить цвет без изменений, надо разделить результат преобразования на эту самую сумму.
В качестве исходного мы возьмем следующее изображение:
![image](https://habr.com/images/px.gif#%3D%22http%3A%2F%2Fimg385.imageshack.us%2Fimg385%2F8651%2Ftestuo2.jpg%22)
на примере него можно будет увидеть, как изменяется крупный и мелкий текст, картинка и линии. Теперь создадим матрицу скручивания для получения эффекта негатива:
![image](https://habr.com/images/px.gif#%3D%22http%3A%2F%2Fimg385.imageshack.us%2Fimg385%2F5416%2Fneglm1.jpg%22)
Согласно матрице, получается, что в результате преобразования все цвета будут иметь отрицательную величину. Чтобы цвета были негативными, нужно задать offset = 256, таким образом цвета всех пикселей вычитаются из 256, что является негативным изображением:
![image](https://habr.com/images/px.gif#%3D%22http%3A%2F%2Fimg385.imageshack.us%2Fimg385%2F3813%2Fresnegqh3.jpg%22)
В библиотеке GD на PHP существует функция imageconvolution, которая содержит 4 параметра. Первый – это идентификатор изображения. Второй – это матрица в виде массива из 3-х массивов с 3-мя переменными. Третий и четвертый — это div и offset.
Вот код, который делает изображение негативным:
Сразу стоит сказать об одной очень неприятной особенности GD: при преобразованиях с помощью imageconvolution «рушится» альфа-канал. Этот баг был описан уже давно, но насколько я знаю, его так и не исправили. Во flash этого нет, более того там имееются еще дополнительные параметры, которые отвечают за обработку краёв изображений, когда часть пикселей выпадает. В php края просто не обрабатываются.
Вот стандартный набор матриц эффектов:
![image](https://habr.com/images/px.gif#%3D%22http%3A%2F%2Fimg385.imageshack.us%2Fimg385%2F23%2Fbsedn6.jpg%22)
Обратите внимание, для blur коэффициент div = 9. Для такой матрицы только такой коэффициент не ведет к искажению цветов. Еще надо сказать, что вариантов blur-а несколько, они незначительно отличаются силой эффекта.
И вот какие получаются изображения:
Blur:
![image](https://habr.com/images/px.gif#%3D%22http%3A%2F%2Fimg385.imageshack.us%2Fimg385%2F4208%2Fresblurey6.jpg%22)
Sharpen:
![image](https://habr.com/images/px.gif#%3D%22http%3A%2F%2Fimg385.imageshack.us%2Fimg385%2F482%2Fressharpennh2.jpg%22)
Emboss:
![image](https://habr.com/images/px.gif#%3D%22http%3A%2F%2Fimg385.imageshack.us%2Fimg385%2F724%2Fresembossmt5.jpg%22)
Как видно из прошлого примера с blur, эффект накладывается на изображение, но достаточно сильно. А можно ли уменьшить силу эффекта на изображение? Оказывается, можно. Но для этого надо изменять не степень влияния окружающих пикселей, как можно показаться на первый взгляд, а количество влияющих пикселей:
![image](https://habr.com/images/px.gif#%3D%22http%3A%2F%2Fimg385.imageshack.us%2Fimg385%2F497%2Flbseod7.jpg%22)
Тогда получим эффекты, которые будут выглядеть намного аккуратнее:
Light-blur:
![image](https://habr.com/images/px.gif#%3D%22http%3A%2F%2Fimg385.imageshack.us%2Fimg385%2F6880%2Fressblurhr3.jpg%22)
Light-sharpen:
![image](https://habr.com/images/px.gif#%3D%22http%3A%2F%2Fimg385.imageshack.us%2Fimg385%2F7398%2Fresssharpiq0.jpg%22)
Light-emboss:
![image](https://habr.com/images/px.gif#%3D%22http%3A%2F%2Fimg385.imageshack.us%2Fimg385%2F8972%2Fressembac1.jpg%22)
Здесь стоит задаться вопросом, а как увеличивать силу эффекта? К сожалению, только многократным его наложением, поскольку как ни крути, а все равно обрабатывается область 3x3 пикселя. Естественно, это очень ресурсоемко, для получения размытия до пятен с помощью размытия по Гауссу иногда приходится накладывать фильтр 100-200 раз. Это занимает очень продолжительное время и очень много ресурсов.
… хочу сказать, что вы сами можете создать какой-нибудь интересный эффект. Для этого достаточно поэкспериментировать с матрицей скручивания.
Матрица скручивания может быть успешна применена при:
Результат:
![image](https://habr.com/images/px.gif#%3D%22http%3A%2F%2Fimg515.imageshack.us%2Fimg515%2F246%2Fresshadowyv7.jpg%22)
Спасибо за прочтение! Конструктивная критика и указание о грамматических ошибках с помощью лички приветствуется.
В статье пойдет речь об использовании convolution matrix (матрицы скручивания или матрицы свертки), с помощью которой можно создавать и накладывать на изображения фильтры, такие как blur, sharpen и многие другие.
Cтатья будет интересна не только веб-программистам, но и всем кто так или иначе занимается программной обработкой изображений, поскольку функции для работы с матрицей скручивания имеются во многих языках (точно известно о php и flash). Так же, статья будет интересна дизайнерам, использующим Adobe Photoshop, поскольку в нем имеется соответствующий фильтр (Filter-Other-Custom).
Примеры будут на языке PHP с использованием библиотеки GD. Теория, практика, примеры (осторожно, много картинок!)
Теория
Говоря не математическим языком, convolution – это преобразование одной матрицы с помощью другой, которая называется ядром («kernel»). При обработке изображений в качестве исходных выступают матрицы RGB-каналов пикселей в прямоугольных координатах.
В качестве ядра обычно используется матрица размером 3x3, но возможно и больше (5x5, 7x7 и т.д.). Ядро содержит степени влияния («ценности») окружающих значений элемента на сам элемент.
Преобразования происходит следующим образом. Каждый элемент исходной матрицы умножается центральное значение матрицы ядра. Кроме этого на соответствующие значения умножаются окружающие его элементы (при размере ядра 3x3 их будет 8), после чего результаты суммируются и принимаются как преобразованное значение.
Вот простой графический пример:
![image](https://habr.com/images/px.gif#%3D%22http%3A%2F%2Fimg385.imageshack.us%2Fimg385%2F3687%2Fcalcly1.jpg%22)
Преобразуемое значение выделено красным, область действия матрицы ядра – зеленым.
Что получислось в результате преобразования. Ценности всех окружающих пикселей, включая собственное значение равно нулю, кроме верхнего среднего, где она равна единице. Таким образом, результат:
(40*0)+(42*1)+(46*0)+(46*0)+(50*0)+(55*0)+(52*0)+(56*0)+(58*0) = 42
Как видно, данное преобразование смещает изображение вниз на 1 пиксель.
Таким образом, convolution в данном случае – это преобразование изображения, в результате которого на каждый пиксель результата влияет окружающая его область. Степень влияния этой области задается с помощью «ядра» или матрицы скручивания.
Значения div и offset
При обработке изображений одним только преобразованием не отделаешься, нужна еще нормализация. Что делать, если получившееся значение больше 255 или меньше 0? Цветов-то таких нет. Более того, что выход за границы цвета явление достаточно частое.
Для нормализации результата используются дополнительные переменные: div (делитель) и offset (коэффициент). Они работают очень просто: результат преобразования делится на div и к нему прибавляется offset.
Не трудно догадаться, что по умолчанию div = 1, offset = 0 (div = 0 выставлять нельзя!).
При преобразованиях в качестве div обычно принимается сумма всех элементов матрицы скручивания. Это условие позволяет не допустить цветовых искажений, если они не нужны.
Действительно, если преобразуемая область содержит один и тот же цвет, то результат получится как сумма элементов ядра умноженное на этот цвет. Соответственно, что бы оставить цвет без изменений, надо разделить результат преобразования на эту самую сумму.
Простой пример: фильтр «негатив».
В качестве исходного мы возьмем следующее изображение:
![image](https://habr.com/images/px.gif#%3D%22http%3A%2F%2Fimg385.imageshack.us%2Fimg385%2F8651%2Ftestuo2.jpg%22)
на примере него можно будет увидеть, как изменяется крупный и мелкий текст, картинка и линии. Теперь создадим матрицу скручивания для получения эффекта негатива:
![image](https://habr.com/images/px.gif#%3D%22http%3A%2F%2Fimg385.imageshack.us%2Fimg385%2F5416%2Fneglm1.jpg%22)
Согласно матрице, получается, что в результате преобразования все цвета будут иметь отрицательную величину. Чтобы цвета были негативными, нужно задать offset = 256, таким образом цвета всех пикселей вычитаются из 256, что является негативным изображением:
![image](https://habr.com/images/px.gif#%3D%22http%3A%2F%2Fimg385.imageshack.us%2Fimg385%2F3813%2Fresnegqh3.jpg%22)
Как это делается на PHP:
В библиотеке GD на PHP существует функция imageconvolution, которая содержит 4 параметра. Первый – это идентификатор изображения. Второй – это матрица в виде массива из 3-х массивов с 3-мя переменными. Третий и четвертый — это div и offset.
Вот код, который делает изображение негативным:
<?php
$img = imagecreatefromjpeg('images/pattern.jpg');
$matrix = array (
array( 0, 0, 0),
array( 0, -1, 0),
array( 0, 0, 0)
);
imageconvolution($img, $matrix, 1, 256);
imagejpeg($img, 'images/pattern_negative.jpg', 100);
?>
* This source code was highlighted with Source Code Highlighter.
Сразу стоит сказать об одной очень неприятной особенности GD: при преобразованиях с помощью imageconvolution «рушится» альфа-канал. Этот баг был описан уже давно, но насколько я знаю, его так и не исправили. Во flash этого нет, более того там имееются еще дополнительные параметры, которые отвечают за обработку краёв изображений, когда часть пикселей выпадает. В php края просто не обрабатываются.
Blur, sharpen, emboss
Вот стандартный набор матриц эффектов:
![image](https://habr.com/images/px.gif#%3D%22http%3A%2F%2Fimg385.imageshack.us%2Fimg385%2F23%2Fbsedn6.jpg%22)
Обратите внимание, для blur коэффициент div = 9. Для такой матрицы только такой коэффициент не ведет к искажению цветов. Еще надо сказать, что вариантов blur-а несколько, они незначительно отличаются силой эффекта.
И вот какие получаются изображения:
Blur:
![image](https://habr.com/images/px.gif#%3D%22http%3A%2F%2Fimg385.imageshack.us%2Fimg385%2F4208%2Fresblurey6.jpg%22)
Sharpen:
![image](https://habr.com/images/px.gif#%3D%22http%3A%2F%2Fimg385.imageshack.us%2Fimg385%2F482%2Fressharpennh2.jpg%22)
Emboss:
![image](https://habr.com/images/px.gif#%3D%22http%3A%2F%2Fimg385.imageshack.us%2Fimg385%2F724%2Fresembossmt5.jpg%22)
«Аккуратные» эффекты.
Как видно из прошлого примера с blur, эффект накладывается на изображение, но достаточно сильно. А можно ли уменьшить силу эффекта на изображение? Оказывается, можно. Но для этого надо изменять не степень влияния окружающих пикселей, как можно показаться на первый взгляд, а количество влияющих пикселей:
![image](https://habr.com/images/px.gif#%3D%22http%3A%2F%2Fimg385.imageshack.us%2Fimg385%2F497%2Flbseod7.jpg%22)
Тогда получим эффекты, которые будут выглядеть намного аккуратнее:
Light-blur:
![image](https://habr.com/images/px.gif#%3D%22http%3A%2F%2Fimg385.imageshack.us%2Fimg385%2F6880%2Fressblurhr3.jpg%22)
Light-sharpen:
![image](https://habr.com/images/px.gif#%3D%22http%3A%2F%2Fimg385.imageshack.us%2Fimg385%2F7398%2Fresssharpiq0.jpg%22)
Light-emboss:
![image](https://habr.com/images/px.gif#%3D%22http%3A%2F%2Fimg385.imageshack.us%2Fimg385%2F8972%2Fressembac1.jpg%22)
Здесь стоит задаться вопросом, а как увеличивать силу эффекта? К сожалению, только многократным его наложением, поскольку как ни крути, а все равно обрабатывается область 3x3 пикселя. Естественно, это очень ресурсоемко, для получения размытия до пятен с помощью размытия по Гауссу иногда приходится накладывать фильтр 100-200 раз. Это занимает очень продолжительное время и очень много ресурсов.
В заключение
… хочу сказать, что вы сами можете создать какой-нибудь интересный эффект. Для этого достаточно поэкспериментировать с матрицей скручивания.
Матрица скручивания может быть успешна применена при:
- создании «маленьких» картинок, напр. генерации аватаров и предпросмотров (особенно тут хорошо выглядит light-blur).
- для создания «теней» (если бы еще с альфа-каналом…)
- при создании CAPTHCA (текст + сильный Sharpen или Emboss)
- и др. :-)
UPD: Создание симпатичной тени (Bonus-track, for php-programmers only)
/**
* Создает красивую тень
* Внимание! Операция ресурсоемкая!
*
* @param res $image - исходная картинка
* @param int $shadow_width - толщина тени (1..10, выше не рекомендуется)
* @param int $shadow_deep - глубина цвета тени (1..20, чем выше, тем чернее)
* @param string $bg_color - цвет фона в формате #7def34
*/
function imageaddshadow (&$image, $shadow_width = 4, $shadow_deep = 7, $bg_color = false)
{
$w = imagesx($image);
$h = imagesy($image);
$iw = $w + 4*$shadow_width;
$ih = $h + 4*$shadow_width;
$img = imagecreatetruecolor($iw, $ih);
$shadow_deep= 255-$shadow_deep*12;
$shadow = imagecolorallocate($img, $shadow_deep, $shadow_deep, $shadow_deep);
if (!$bg_color) {
// Белый цвет по умолчанию
$bg = imagecolorallocate($img, 255, 255, 255);
}
else {
list($r, $g, $b) = array_map('hexdec', str_split(ltrim($bg_color, '#'), 2));
$bg = imagecolorallocate($img, $r+1, $g+1, $b+1);
}
// Заливаем область цветом фона
imagefilledrectangle($img,0,0,$iw,$ih,$bg);
// Создаем тень
imagefilledrectangle($img,
1+$shadow_width,
1+$shadow_width,
$iw-1-$shadow_width,
$ih-1-$shadow_width,
$shadow);
// Создаем размытие тени
$matrix = array (
array( 1, 1, 1),
array( 1, 1, 1),
array( 1, 1, 1)
);
// Применяем эффект несколько раз для хорошего размытия
for ($i=0; $i < $shadow_width*2; $i++, imageconvolution($img, $matrix, 9, 0));
// Помещаем над тенью исходное изображение
imagecopyresampled($img, $image, 2*$shadow_width,2*$shadow_width,0,0,$w,$h,$w,$h);
// И всё!
$image = $img;
}
* This source code was highlighted with Source Code Highlighter.
Результат:
![image](https://habr.com/images/px.gif#%3D%22http%3A%2F%2Fimg515.imageshack.us%2Fimg515%2F246%2Fresshadowyv7.jpg%22)
Спасибо за прочтение! Конструктивная критика и указание о грамматических ошибках с помощью лички приветствуется.