Мені просто трапилось реалізувати щось подібне на OpenGL ES 2.0 за допомогою кутового виявлення Гарріса, і, хоча я ще не повністю закінчений, я подумав, що поділяюся реалізацією, що базується на шейдері, до цього часу. Я робив це як частину платформи з відкритим кодом на базі iOS , тому ви можете перевірити код, якщо вам цікаво, як працює якийсь конкретний крок.
Для цього я використовую такі кроки:
- Зменшіть зображення до його значень яскравості, використовуючи крапковий добуток значень RGB з вектором (0,2125, 0,7154, 0,0721).
Обчисліть похідні X та Y, віднімаючи значення червоного каналу від пікселів зліва та справа та вище та нижче поточного пікселя. Потім я зберігаю похідну x у квадраті в червоному каналі, похідну Y в квадрат у зеленому каналі, а добуток похідних X та 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 кадрів в секунду на 640х480 кадрах відео, що надходять з камери, а iPhone 4S може легко обробляти відео такого розміру зі швидкістю 60 FPS. Це має бути набагато швидше, ніж обробка, пов'язана з процесором, для такого завдання, хоча зараз процес зчитування балів пов'язаний з процесором і трохи повільніше, ніж повинен бути.
Якщо ви хочете побачити це в дії, ви можете захопити код для моєї основи і запустити приклад FilterShowcase, який поставляється разом з ним. Приклад виявлення куточків Гарріса там працює на відео в реальному часі з камери пристрою, хоча, як я вже згадував, читання кутових точок в даний час відбувається на процесорі, що насправді сповільнює це. Я також переходжу до цього процесу на основі GPU.