Пошук простого на складному: tips & tricks

    Дістався мені тут досить цікавий проектик: на рентгенограмах зварних швів знаходити дротяні зразки стандартних розмірів. Здавалося б, скільки вже було написано з приводу пошуку патернів на зображенні, вироблені стандартні підходи та методики, але коли мова заходить про реальні завданнях академічні методи виявляються не настільки ефективні, як від них очікується. Для затравочкі, спробуйте знайти тут всі сім зволікань:
 
 image
 
 
 
Насправді, це далеко не найпростіший кадр, але в підсумку навіть на ньому все знайшлося:
 
 image
 
Отже, які ж умови мені були озвучені:
 - Файли будуть tiff і можуть бути досить великими — більше 200 Мб
 - Зображення можуть бути як позитивні, так і негативні
 - На одній картинці можуть бути кілька еталонів, треба знайти всі
 - Зразки лежать або майже вертикально (+ -10 градусів) або майже горизонтально
 - На кадрах можуть бути інші еталони та інші елементи
 - Шви можуть бути як прямими, так і еліптичними, горизонтальними і вертикальними
 - Зразок може лежати на шві або поблизу від нього
Максимальний час пошуку еталонів на знімку:
— 3 секунди для знімка розміром менше 100 МБ
— 5 секунд для знімка більше 100 МБ і менше 200 МБ
— 10 секунд, для знімка розміром більше 200 МБ.
 
Так само мені були дані ГОСТи двох типів еталонів, які виглядають якось так:
 
 image
 
Т.е. кожен еталон складається з 7 зволікань певної довжини і зменшуваного діаметра розташованих на заданій відстані.
Довжини бувають різні: 10, 20, 25, 50 мм.
Для кожної довжини є набір еталонів з різними товщинами зволікань, наприклад, один еталон з довжиною зволікань 25 мм має діаметри зволікань 3.2мм, 2.5мм, 2мм, 1.6мм, 1.25мм, 1.0мм, 0.8мм, 0.63мм, а другий — 1.0 мм, 0.8мм, 0.63мм, 0.5мм, 0.4мм, 0.32мм, 0.25мм.
Всього таким чином було порядка 16 еталонів.
 
Таким чином, мені треба було локалізувати повністю детерміновані об'єкти на зображеннях різної якості.
 
 

Вибір шляху

 
Мені відразу подумалося про використання класифікаторів, що навчаються, але збентежив той факт, що у мене було всього 82 файлу з різними еталонами, а для нормального треннинга класифікатора все-таки потрібні тисячі зображень. Так що цей шлях довелося відмести відразу.
 
Завдання пошуку лінійних відрізків сама по собі непогано відображена в літературі.
Перше ж що приходить в голову це шукати відрізки перетворенням Хафа , але, по-перше, воно дуже ресурсномістке, а по-друге, слабкі сигнали серед сильніших після перетворення шукатиме нітрохи не легше. Навіть якщо взяти досить вдале кадрування:
 
 
 
то знайти зволікання все одно досить скрутно:
 
 
 
(От як воно мало б виглядати: image — & gt; image)
 
Інший варіант був би використовувати фільтрації контурів, але виникає проблема: а як вибирати поріг бинаризации в автоматичному режимі?
Та й що робити з контурами, які в основному вибирають зварної шов або щось ще?
 
  — & gt;
Детектор контурів з різними рівнями бинаризации
 
Те ж відноситься і до інших детекторам конутров типу бімлетов .
 
 

Що ж можна зробити щоб покращити ситуацію?

 
Як виявилося, вкрай продуктивним кроком було просте обчислення градиентной карти, тобто просто обчислення різниці між двома сусідніми пікселями. Подібний крок різко виділяє гострі краї і практично стирає всі повільні переходи. Ось що, наприклад, вийшло після застосування градієнтів двічі:
 
було:
 
 
стало:
 
 
На нижній картинці для наочності підвищений контраст.
 
Погодьтеся, навіть неозброєним оком стало набагато легше знайти зволікання.
Тут же відразу стала видна й інша проблема — шум.Прічем рівень шуму нерівномірний по картинці.
 
Як же нам виділяти області інтересу для більш детального опрацювання? Необхідний якийсь метод, який враховував би локальний рівень шуму і детектірвал викиди, значно переважаючі среднесатітстіческое відхилення.
Так як вся геометрія еталонів нам відома заздалегідь, то ми можемо вибрати розмір вікна в якому збиратиметься локальна статистика, тим самим ми зможемо автоматизувати вибір порога бинаризации. Мало того, так як нам заздалегідь відомо, що зволікання будуть приблизно вертикально, то я вирішив зробити додатковий фільтр: якщо в обраному вікні значущих викидів більше, ніж 1/2 вертикального розміру вікна, то всю вертикальну смугу вибираємо як область інтересу для подальшого аналізу ( забігаючи вперед скажу, що ще додав додатковий статистичний детектор для викидів, які в кілька разів перевищують стандартне відхилення — це знадобилося, щоб всякі букви і цифри на кадрі теж потрапили цілком в область подальшого аналізу, де їх можна було б відфільтрувати. Без цього дуже багато помилкових спрацьовувань було через часткове знаходження областей на буквах і цифрах).
 
Ось що вийшло на цьому етапі:
 
 image
 
Тут синім, рожевим і чорним цветоамі позначені отримані області інтересу. Рожевим виділений поточний кластер для якого виробляється розпізнання.
 
З предваріатльной обробкою впоралися, треба переходити безпосередньо до розпізнавання еталонів.
 
Перший крок тут — кластеризація. Потрібно якось розбити всі знайдені точки на кластери, щоб потім вже працювати з ними.
Тут досить добре підійшов варіант просто додавати до поточного кластеру все нові точки, якщо вони розташовані + -2 пікселя по вертикалі / горизонталі.
В принципі, цей метод може підвести, якщо зварений шов розбиває дротик на дві рознесені області, тому був передбачений другий варіант кластеризації, коли проглядалося +15 точок по вертикалі.
 
 

Розпізнавання еталонів

 
Тут знову ж можливі різні варіанти.
Оскільки кластери вже виділені, можна було б взяти їх і спробувати в початкову картинку вписати розраховані варіанти еталонів з різними кутами, масштабами, зрушеннями… Але як виявилося, це знову ж досить трудозатратно і можна придумати велосипед.
А саме: раз у нас майже вертикальні смужки, давайте просто вздовж них зробимо проекцію навіть не споконвічної картинки і не карти градієнтів, а знайдених кластерів. Цього виявилося достатньо, щоб визначити який саме еталон на знімку, якщо є кластери, соответсвующие хоча б трьом тяганини.
Насправді, я пробував робити проекції і за початковою картинці і по карті градієнтів, але там шум був досить сильний, а так як ми вже з ним успішно поборолися при пошуку кластерів, то можна просто скористатися отриманими результатами.
 
Все це призвело до наступного алгоритму:
1. для кожного еталона перевіряємо що довжина кластера приблизно відповідає довжині зволікань еталона.
2. беремо кластер, вважаємо проекцію вздовж нього + -100-150 пікселів справа і зліва, у нас утворюється одновимірний! масив з піками в місцях знайдених кластерів.
Те що масив одновимірний відразу дає нам виграш в продуктивності, плюс не треба шукати кути (зволікання-то паралельні).
3. для декількох близьких масштабів (нам масштаб відомий тільки наближено) обчислюємо як виглядатиме подібна проекція кожного еталона і робимо згортку отриманої проекції еталона і проекції вздовж кластера + вважаємо скільки взагалі піків в проекції вздовж кластера є.
4. якщо з обох сторін від кластера є інші кластери — ми в середині еталона, йдемо до наступного кластеру
5. якщо з обох сторін нічого не виявлено — ми швидше за все наткнулися на помилковий кластер, утворений «артефактами»
6. якщо з одного боку нічого немає, а з іншого менше двох піків — це або артефакти, або зволікання надто тонкі, в люом випадку ми не зможемо точно встановити куди наш еталон повернуть тільки за двома тяганини
7. якщо з одного боку більше 2ух піків — перевіряємо, що згортка з еталонною проекцією дає відчутний вклад.
 
сама згортка вважалася так:
 
if (WSKernel[i] == 1) 
                    {  // punish more if there is no line:
                        convlft += WSKernel[i] * (ltrtr[lngth - i] - k * tracemax);  //ltrtr is always positive as a sum of presences of clusters
                        convrgt += WSKernel[i] * (ltrtr[lngth + i] - k * tracemax); 
                    }
                    else // WSKernel[[i]]==-1
                    {
                        convlft += WSKernel[i] * ltrtr[lngth - i]; 
                        convrgt += WSKernel[i] * ltrtr[lngth + i];
                    }

де WSKernel — еталонна проекція, яка має 1 в місцях де є зволікання і -1 де її немає (тобто має вигляд 1,1,1,1,1,1, -1, -1, -1, -1, 1,1,1, ...)
ltrtr — це власне проекція вздовж кластера
tracemax — це максимум інтенсивності проекції вздовж кластера
 
Все це працює так, щоб у місцях де пік є і він збігається з еталоном давати позитивний вклад (-k * tracemax потрібен щоб штрафувати за незнаходження зволікання в потрібному місці, k = 0.1 виявилося найбільш продуктивним), а там де він є, але не збігається — негативний.
 
Після проходу по декількох масштабам і еталонам вибирається максимальний вклад і вважається, що цей кластер відповідає цьому еталону з обчисленим коефіцієнтом масштабу.
 
Далі — тільки отрисовка і видача результатів.
 
Ну і пара прикладів:
 
 
 
 
 
 
 
 
 
    
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.