Właśnie zdarzyło mi się implementować coś takiego w OpenGL ES 2.0 przy użyciu wykrywania narożników Harrisa i chociaż nie jestem całkowicie ukończony, pomyślałem, że podzielę się implementacją opartą na modułach cieniujących, którą do tej pory miałem. Zrobiłem to jako część platformy open source opartej na iOS , więc możesz sprawdzić kod, jeśli jesteś ciekawy, jak działa jakiś konkretny krok.
Aby to zrobić, wykonuję następujące kroki:
- Zmniejsz obraz do wartości luminancji, używając iloczynu iloczynu wartości RGB z wektorem (0,2125, 0,7154, 0,0721).
Oblicz pochodne X i Y, odejmując wartości kanału czerwonego od pikseli po lewej i prawej oraz powyżej i poniżej bieżącego piksela. Następnie przechowuję pochodną x podniesioną do kwadratu w kanale czerwonym, pochodną Y podniesioną do kwadratu w kanale zielonym, a iloczyn pochodnych X i Y w kanale niebieskim. Moduł cieniujący fragmenty wygląda następująco:
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);
}
gdzie różnice są tylko przesuniętymi współrzędnymi tekstury w każdym kierunku. Obliczam je wstępnie w module cieniującym wierzchołki, aby wyeliminować zależne odczyty tekstur, które są bardzo powolne na tych mobilnych GPU.
Zastosuj rozmycie gaussowskie do tego obrazu pochodnego. Użyłem oddzielnego rozmycia poziomego i pionowego i skorzystałem ze sprzętowego filtrowania tekstur, aby uzyskać rozmycie dziewięciokrotnie z tylko pięcioma odczytami tekstury przy każdym przejściu. Opisuję ten moduł cieniujący w tej odpowiedzi Przepełnienie stosu .
Uruchom rzeczywiste obliczenie detekcji narożnika Harrisa, używając niewyraźnych wartości pochodnych wejściowych. W tym przypadku faktycznie używam obliczeń opisanych przez Alison Noble w jej doktoracie. rozprawa „Opisy powierzchni obrazu”. Moduł cieniujący, który to obsługuje, wygląda następująco:
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);
}
Wykonuj lokalne nie maksymalne tłumienie i zastosuj próg, aby podświetlić piksele, które przechodzą. Używam następującego modułu cieniującego fragmenty do próbkowania ośmiu pikseli w sąsiedztwie centralnego piksela i stwierdzam, czy jest to maksimum w tej grupie:
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);
}
Ten proces generuje mapę pustkowia z twoich obiektów, która wygląda następująco:
Następujące punkty są identyfikowane jako rogi na podstawie nie-maksymalnego tłumienia i progowania:
Po ustawieniu odpowiednich progów dla tego filtra może on zidentyfikować wszystkie 16 narożników na tym obrazie, chociaż zwykle umieszcza narożniki o jeden piksel wewnątrz rzeczywistych krawędzi obiektu.
Na iPhonie 4 to wykrywanie narożników można uruchomić przy 20 klatkach na sekundę na klatkach 640 x 480 wideo pochodzących z kamery, a iPhone 4S może z łatwością przetwarzać wideo o tym rozmiarze przy 60+ klatkach na sekundę. Powinno to być o wiele szybsze niż przetwarzanie związane z procesorem w przypadku takiego zadania, chociaż w tej chwili proces odczytywania punktów jest związany z procesorem i nieco wolniejszy niż powinien.
Jeśli chcesz zobaczyć to w akcji, możesz pobrać kod dla mojego frameworka i uruchomić dołączony do niego przykład FilterShowcase. Przykład wykrywania narożników Harrisa działa na wideo na żywo z kamery urządzenia, chociaż jak wspomniałem, odczyt punktów narożnych odbywa się obecnie na procesorze, co naprawdę spowalnia to. W tym celu przechodzę również do procesu opartego na GPU.