Мне просто довелось реализовать что-то подобное в OpenGL ES 2.0 с использованием определения угла Харриса, и, хотя я еще не закончил, я подумал, что поделюсь реализацией на основе шейдеров, которая у меня есть. Я сделал это как часть iOS-среды с открытым исходным кодом , так что вы можете проверить код, если вам интересно, как работает какой-то конкретный шаг.
Для этого я использую следующие шаги:
- Уменьшите изображение до его значений яркости, используя точечное произведение значений RGB с вектором (0,2125, 0,7154, 0,0721).
Вычислите производные X и Y, вычитая значения красного канала из пикселей слева и справа и выше и ниже текущего пикселя. Затем я сохраняю производную х в квадрате в красном канале, производную Y в квадрате в зеленом канале и произведение производных Х и Y в синем канале. Для этого фрагментный шейдер выглядит следующим образом:
precision highp float;
varying vec2 textureCoordinate;
varying vec2 leftTextureCoordinate;
varying vec2 rightTextureCoordinate;
varying vec2 topTextureCoordinate;
varying vec2 bottomTextureCoordinate;
uniform sampler2D inputImageTexture;
void main()
{
float topIntensity = texture2D(inputImageTexture, topTextureCoordinate).r;
float bottomIntensity = texture2D(inputImageTexture, bottomTextureCoordinate).r;
float leftIntensity = texture2D(inputImageTexture, leftTextureCoordinate).r;
float rightIntensity = texture2D(inputImageTexture, rightTextureCoordinate).r;
float verticalDerivative = abs(-topIntensity + bottomIntensity);
float horizontalDerivative = abs(-leftIntensity + rightIntensity);
gl_FragColor = vec4(horizontalDerivative * horizontalDerivative, verticalDerivative * verticalDerivative, verticalDerivative * horizontalDerivative, 1.0);
}
где изменения - просто координаты текстуры смещения в каждом направлении. Я предварительно вычисляю их в вершинном шейдере, чтобы исключить зависимые чтения текстур, которые, как известно, медленны на этих мобильных графических процессорах.
Примените размытие по Гауссу к этому производному изображению. Я использовал раздельное горизонтальное и вертикальное размытие и использовал аппаратную фильтрацию текстур, чтобы сделать размытие из девяти попаданий, используя только пять текстурных чтений на каждом проходе. Я описываю этот шейдер в ответе на переполнение стека .
Выполните фактический расчет определения угла Харриса, используя размытые входные производные значения. В данном случае я на самом деле использую расчет, описанный Элисон Ноубл в ее докторской диссертации. диссертация "Описания поверхностей изображений". Шейдер, который обрабатывает это, выглядит следующим образом:
varying highp vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
const mediump float harrisConstant = 0.04;
void main()
{
mediump vec3 derivativeElements = texture2D(inputImageTexture, textureCoordinate).rgb;
mediump float derivativeSum = derivativeElements.x + derivativeElements.y;
// This is the Noble variant on the Harris detector, from
// Alison Noble, "Descriptions of Image Surfaces", PhD thesis, Department of Engineering Science, Oxford University 1989, p45.
mediump float harrisIntensity = (derivativeElements.x * derivativeElements.y - (derivativeElements.z * derivativeElements.z)) / (derivativeSum);
// Original Harris detector
// highp float harrisIntensity = derivativeElements.x * derivativeElements.y - (derivativeElements.z * derivativeElements.z) - harrisConstant * derivativeSum * derivativeSum;
gl_FragColor = vec4(vec3(harrisIntensity * 10.0), 1.0);
}
Выполните локальное не максимальное подавление и примените порог, чтобы выделить проходящие пиксели. Я использую следующий фрагментный шейдер для выборки восьми пикселей в окрестности центрального пикселя и определения, является ли он максимальным в этой группе:
uniform sampler2D inputImageTexture;
varying highp vec2 textureCoordinate;
varying highp vec2 leftTextureCoordinate;
varying highp vec2 rightTextureCoordinate;
varying highp vec2 topTextureCoordinate;
varying highp vec2 topLeftTextureCoordinate;
varying highp vec2 topRightTextureCoordinate;
varying highp vec2 bottomTextureCoordinate;
varying highp vec2 bottomLeftTextureCoordinate;
varying highp vec2 bottomRightTextureCoordinate;
void main()
{
lowp float bottomColor = texture2D(inputImageTexture, bottomTextureCoordinate).r;
lowp float bottomLeftColor = texture2D(inputImageTexture, bottomLeftTextureCoordinate).r;
lowp float bottomRightColor = texture2D(inputImageTexture, bottomRightTextureCoordinate).r;
lowp vec4 centerColor = texture2D(inputImageTexture, textureCoordinate);
lowp float leftColor = texture2D(inputImageTexture, leftTextureCoordinate).r;
lowp float rightColor = texture2D(inputImageTexture, rightTextureCoordinate).r;
lowp float topColor = texture2D(inputImageTexture, topTextureCoordinate).r;
lowp float topRightColor = texture2D(inputImageTexture, topRightTextureCoordinate).r;
lowp float topLeftColor = texture2D(inputImageTexture, topLeftTextureCoordinate).r;
// Use a tiebreaker for pixels to the left and immediately above this one
lowp float multiplier = 1.0 - step(centerColor.r, topColor);
multiplier = multiplier * 1.0 - step(centerColor.r, topLeftColor);
multiplier = multiplier * 1.0 - step(centerColor.r, leftColor);
multiplier = multiplier * 1.0 - step(centerColor.r, bottomLeftColor);
lowp float maxValue = max(centerColor.r, bottomColor);
maxValue = max(maxValue, bottomRightColor);
maxValue = max(maxValue, rightColor);
maxValue = max(maxValue, topRightColor);
gl_FragColor = vec4((centerColor.rgb * step(maxValue, centerColor.r) * multiplier), 1.0);
}
Этот процесс генерирует карту углов из ваших объектов, которая выглядит следующим образом:
Следующие точки определены как углы на основе не максимального подавления и порогового значения:
С правильными пороговыми значениями, установленными для этого фильтра, он может идентифицировать все 16 углов в этом изображении, хотя он склонен размещать углы на пиксель или около того внутри фактических краев объекта.
На iPhone 4 это определение угла может быть выполнено со скоростью 20 кадров в секунду на кадрах видео 640x480, поступающих с камеры, а iPhone 4S может легко обрабатывать видео такого размера со скоростью более 60 кадров в секунду. Это должно быть намного быстрее, чем обработка с привязкой к процессору для такой задачи, как сейчас, хотя сейчас процесс считывания точек привязан к процессору и немного медленнее, чем должен быть.
Если вы хотите увидеть это в действии, вы можете взять код для моей платформы и запустить пример FilterShowcase, который поставляется вместе с ним. Пример обнаружения углов Harris запускается в режиме реального времени с камеры устройства, хотя, как я уже упоминал, считывание обратных угловых точек в настоящее время происходит на процессоре, что действительно замедляет это. Для этого я перехожу к процессу на основе графического процессора.