Як виявити кути у двійкових зображеннях з 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.