Як виявити кути у двійкових зображеннях з OpenGL?


13

У мене є бінарні зображення 160х120, такі як:

оригінальне зображення

Я хотів би виявити куточки цих білих крапок. Вони раніше закриті математичною морфологією, тому не повинно бути внутрішніх куточків. У цьому конкретному випадку я хотів би 16 кутів, наприклад:

приклад виявлення кутів

Моєю першою спробою було використання деяких функцій OpenCV, таких як goodFeaturesToTrack або FAST, але вони особливо повільні (плюс FAST дуже нестабільний). Моя ідея полягала б у тому, щоб зробити такий обчислення на графічному процесорі, оскільки з нього походить моє вихідне зображення. Я шукав в Інтернеті ідеї, як писати такі шейдери (я використовую OpenGL ES 2.0), але нічого конкретного не знайшов. Будь-яка ідея, як я міг запустити такий алгоритм?


2
ШВИДКО повільно? :)
ендоліт

1
так, смішно, правда? насправді, це швидше, ніж алгоритми прецедентів, такі як SURF або SIFT, але він менш точний, досить нестабільний від одного зображення до іншого і все ще недостатньо швидкий, щоб зробити це на процесорі
Stéphane Péchard

Наскільки важливо точно їх виявити на кожному кадрі? Як швидко рухаються прямокутники? Чи добре визначити кути на більшості кадрів та інтерполювати їх на кадри, де алгоритм пропущений?
justis

@justis добре, те, як я це роблю зараз (через використання функцій cvFindContours () і cvApproxPoly () OpenCV з часом не дуже стабільний, тому я фільтрую результат за фільтром із низькою прохідністю, вводячи відставання. Як ви вважаєте, я можу отримати стабільніший результат за допомогою інтерполяції?
Стефан Пешард

Відповіді:


3

Які зображення розміру ви працюєте? З якою швидкістю кадрів? На якому апаратному забезпеченні? Швидкий - мій досвід, швидко, мій досвід.

Я також бачив, як FAST використовується як детектор рентабельності інвестицій (ROI) з функцією goodFeaturesToTrack на ROI, визначених для забезпечення кращої стабільності, без виконання покарання gFTT для всього зображення.

«Харріс» кутовий детектор також потенційно дуже швидко , як вона складається з дуже простих операцій (без SQRT () на піксель, наприклад!) - не так стабільна , як gFTT, але , можливо , в більшій мірі , ніж швидко.

(Що стосується реалізації GPU, Google, gpu cornerсхоже, представляє досить багато посилань, але я не маю уявлення, наскільки вони можуть бути придатні - я схильний до впровадження в FPGA.)


Мої зображення розміром 160x120, начебто, в 30 кадрів в секунду, на iPhone, але, звичайно, у програмі ще багато чого: --) Я бачив додаток, що реалізує FAST досить швидко на такому пристрої, але це був лише демонстраційний приклад робити це ... Ось чому я дивлюся на рішення на основі gpu.
Стефан Пешард

15

Мені просто трапилось реалізувати щось подібне на 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.


1
Дуже хороша! Я стежу за вашими рамками на Github, це здається справді цікавим, вітаю!
Стефан Пешард

Чи є у вас десь приклад, як повернути кутові координати насправді назад до центрального процесора? Чи є якийсь розумний спосіб графічного процесора чи потрібен перезапис, а потім циклічне проходження на процесорі через повернутий растровий файл, який шукає позначені пікселі?
Квазімондо

@Quasimondo - Я працював над використанням пірамід гістограми для вилучення точок: tevs.eu/files/vmv06.pdf , щоб уникнути ітерації, пов'язаної з процесором, над пікселями для кутового виявлення. Останнім часом трохи відволікався, тому ще не зовсім закінчив це, але хотів би незабаром.
Бред Ларсон

Привіт @BradLarson, я знаю, що це дуже стара тема і дякую за вашу відповідь. Я щойно перевірив KGPUImageHarrisCornerDetection.m в рамках GPUImage. Щоб витягнути кутове розташування з зображення, ви використовували glReadPixels для зчитування зображення в буфер, а потім петлювали на буфері, щоб зберігати точки з colotByte> 0 в масиві. Чи є спосіб зробити це все в GPU, коли нам не потрібно читати зображення в буфері та циклі?
Sahil Bajaj

1
@SahilBajaj - Один із прийомів, які я бачив (а ще не встиг реалізувати), - це використовувати піраміди гістограми для швидкого вилучення очок із таких розріджених зображень. Це значно пришвидшило б це.
Бред Ларсон

3

"Міцні" кутові детектори, такі як Ши-Томасі та Моравець, як правило, повільно. Перевірте їх тут - http://en.wikipedia.org/wiki/Corner_detection ШВИДКО, напевно, є єдиним досить хорошим легким кутовим детектором. Ви можете покращити FAST, зробивши не максимальне придушення - вибрали швидкий вихід із найкращим показником "кутового" (існує декілька інтуїтивно зрозумілих способів його обчислити, включаючи Ши-Томасі та Моравець як оцінку кута) У вас також є вибір декількох швидких детекторів - від FAST-5 до FAST-12 та FAST_ER (останній, мабуть, занадто величезний для мобільних пристроїв) Інший спосіб - це генерувати FAST - отримати генератор FAST коду з сайту автора та тренувати його на наборі ймовірних зображень. http://www.edwardrosten.com/work/fast.html


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.