4. Расширение на другое количество измерений
Оператор Собеля состоит из двух отдельный операций [1]:
Сглаживание треугольным фильтром в перпендикулярном к производной направлении: h( − 1) = 1,h(0) = 2,h(1) = 1
Нахождение простого центрального изменения в направлении производной: h'( − 1) = 1,h'(0) = 0,h'(1) = − 1
Фильтры Собеля для производных
изображения в разных пространствах
для
:
1D: hx'(x) = h'(x);
2D: hx'(x,y) = h'(x)h(y)
3D: hx'(x,y,z) = h'(x)h(y)h(z)
4D: hx'(x,y,z,t) = h'(x)h(y)h(z)h(t)
Вот пример трёхмерного ядра Собеля для оси z:
5. Технические детали
Как следует из определения, оператор Собеля можно реализовать простыми техническими и программными средствами: для приближения вектор-градиента нужны только восемь пикселов вокруг точки изображения и целочисленная арифметика. Более того, оба дискретных фильтра, описанных выше, можно разделить:
и две производные, Gx и Gy, теперь можно вычислить как
Раздельность этих вычислений может привести к уменьшению арифметических действий с каждым пикселом.
Применение свертки K к группе пикселей P можно представить псевдокодом:
N(x, y) = Сумма { K(i, j).P(x-i, y-j)}, для i, j от −1 до 1.
N(x, y) представляет собой результат применения матрицы свёртки K к P.
Программная реализация оператора Собела может эффективно использовать SIMD-расширения системы команд современных процессоров (т. н. векторизация кода), при этом выигрыш в скорости вычисления оператора может составлять до 5 раз по сравнению с высокоуровневой реализацией [2]. Ручное кодирование на языке ассемблера позволяет обогнать по скорости такие компиляторы как Microsoft Visual C++ и Intel C++ Compiler. Вычисление оператора Собела элементарно распараллеливается на произвольное число потоков (в пределе каждую точку результирующего изображения можно вычислять независимо от соседних). Например, при наличии двух процессоров (ядер) верхний полукадр изображения может быть обработан одним из них, а нижний — другим.
6. Примеры
Результат применения оператора Собеля есть двумерная карта градиента для каждой точки. Её можно обработать и показать как картинку, на которой участки с большой величиной градиента (в основном, грани) будут видны как белые линии. Нижеприведённые изображения иллюстрируют это на примере простого изображения:
Полутоновое изображение кирпичной стены и стойки для велосипеда |
Нормализованный Собелев градиент изображения кирпичной стены и стойки велосипеда |
Нормализованный Собелев градиент изображения по x |
Нормализованный Собелев градиент изображения по y |
Оператор Cобеля – это один лучших алгоритмов выделения границ, он часто применяется как один из шагов более сложных и точных алгоритмов (таких как оператор Кенни), так что в этой статье рассмотрим этот оператор.
Но для начала я немного изменю текущую реализацию модели освещения Toon. Обычно в такой модели не используется спекулярная (зеркальная) компонента.
Изменим шейдер следующим образом:
float4 DiffuseColor = float4(0,0,1,1);
float kd = 1;
// поставим для kd значение побольше, чем было раньше, чтобы получше осветить // модель
float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
// TODO: add your pixel shader code here.
float3 worldPosition = input.WorldPosition;
float3 worldNormal = normalize(input.Normal);
float4 Ambient = ka * AmbientColor;
float3 lightDirection = normalize(LightPosition – worldPosition);
float Diffuse = kd * max(0, dot(worldNormal, lightDirection));
float2 pos= float2(0,0);
pos.x = Diffuse;
float4 dColor = tex2D(LigthMaskSampler, pos) * DiffuseColor;
return Color + Ambient + dColor;
}
Результат будет выглядел примерно так:
Дальше немного описания с Википедии:
Оператор Собеля используется в области обработки изображений. Часто его применяют в алгоритмах выделения границ. По сути, это дискретный дифференциальный оператор, вычисляющий приближенное значение градиента яркости изображения. Результатом применения оператора Собеля в каждой точке изображения является либо вектор градиента яркости в этой точке, либо его норма. Оператор Собеля основан на свёртке изображения небольшими сепарабельными целочисленными фильтрами в вертикальном и горизонтальном направлениях, поэтому его относительно легко вычислять. С другой стороны, используемая им аппроксимация градиента достаточно грубая, особенно это сказывается на высокочастотных колебаниях изображения.
Упрощённое описание
Если проще, то оператор вычисляет градиент яркости изображения в каждой точке. Так находится направление наибольшего увеличения яркости и величина её изменения в этом направлении. Результат показывает, насколько “резко” или “плавно” меняется яркость изображения в каждой точке, а значит, вероятность нахождения точки на грани, а также ориентацию границы. На практике, вычисление величины изменения яркости(вероятности принадлежности к грани)надежнее и проще в интерпретации, чем расчет направления.
Математически, градиент функции двух переменных для каждой точки изображения (которой и является функция яркости) — двумерный вектор, компонентами которого являются производные яркости изображения по горизонтали и вертикали. В каждой точке изображения градиентный вектор ориентирован в направлении наибольшего увеличения яркости, а его длина соответствует величине изменения яркости. Это означает, что результатом оператора Собеля в точке области постоянной яркости будет нулевой вектор, а в точке, лежащей на границе областей различной яркости — вектор, пересекающий границу в направлении увеличения яркости.
Формализация
Строго говоря, оператор использует ядра 3×3, с которыми свёртывают исходное изображение для вычисления приближенных значений производных по горизонтали и по вертикали. Пусть A исходное изображение, а Gx и Gy — два изображения, где каждая точка содержит приближенные производные по x и по y. Они вычисляются следующим образом:
где * обозначает двумерную операцию свертки.
Координата x здесь возрастает «направо», а y — «вниз». В каждой точке изображения приближенное значение величины градиента можно вычислить, используя полученные приближенные значения производных:
Используя эту информацию, мы также можем вычислить направление градиента:
где, к примеру, угол Θ равен нулю для вертикальной границы, у которой тёмная сторона слева.
Поскольку в описании ничего сложного нет, просто реализуем данный эффект для полученного растра. В принципе направление градиента не является слишком важным, так что в данном примере я остановлюсь на том, что буду сравнивать величину градиента (на самом деле квадрат этой величины) с порогом, чтобы получить карту границ.
Так выглядит кусок шейдера, отвечающий за получение границ:
float color00 = dot(tex2D(Sampler, pos-deltaX-deltaY), luminance);
float color10 = dot(tex2D(Sampler, pos-deltaY), luminance);
float color20 = dot(tex2D(Sampler, pos+deltaX-deltaY), luminance);
float color01 = dot(tex2D(Sampler, pos-deltaX), luminance);
float color21 = dot(tex2D(Sampler, pos+deltaX), luminance);
float color02 = dot(tex2D(Sampler, pos-deltaX+deltaY), luminance);
float color12 = dot(tex2D(Sampler, pos+deltaY), luminance);
float color22 = dot(tex2D(Sampler, pos+deltaX+deltaY), luminance);
float gx = color00*1 + color10*2 + color20*1 +
color01*0 + 0 + color21*0 +
color02*-1 + color12*-2 + color22*-1;
float gy = color00*1 + color10*0 + color20*-1 +
color01*2 + 0 + color21*-2 +
color02*1 + color12*0 + color22*-1;
Далее, как обычно, сравнием с порогом, строим специальную текстуру с границами и складываем изображения, результат получится таким (для порога равного 0.3):
Судя по моим экпериментам, этот оператор показывает отличные результаты (особенно это заметно для обработки растровых изображений), однако требует достаточно много текстурных операций и умножений, что плохо сказывается на производительности.
Так что иногда имеет смысл задуматься над тем, нельзя ли применить более простой алгоритм.
