Постав на паузу будь-яке відео з дороги. На кадрі три машини. Тепер перемотай на секунду вперед — знову три машини. Питання, яке здається дитячим: чи ліва машина на другому кадрі — та сама, що була лівою на першому? Детектор із теми 25 цього не знає й знати не може: він дивиться на один кадр і віддає список рамок без жодних номерів. Кожен кадр для нього — нове знайомство.
Задача, у якій рамка мусить зберегти номер від кадру до кадру, зветься трекінг (tracking). Ми назвали її ще в темі 02, коли складали карту задач, і відклали до того моменту, коли буде чим її розвʼязувати. Тепер є: детектор із блоку 5, IoU з теми 23 і угорський алгоритм із теми 27.
Головне, що ця тема з собою несе, — це не алгоритм, а заміряна відповідь на питання, звідки беруться помилки. Інтуїція каже: номери плутаються там, де предмети перетинаються. Ми це перевіримо, і виявиться, що інтуїція помиляється майже на порядок.
ПостановкаПитання, якого на одному кадрі немає
Уся попередня частина курсу відповідала на питання про зміст кадру. Що зображено — класифікація. Що і де — детекція. Який предмет у кожному пікселі — panoptic. Усі ці питання мають відповідь усередині одного зображення.
Трекінг перший з них вибивається. Питання «чи це та сама машина» не має відповіді в кадрі: щоб на нього відповісти, треба памʼятати попередній кадр. Це й робить трекінг окремою задачею, а не надбудовою над детекцією.
Наскрізний приклад теми — синтетичне відео, у якому істина відома за побудовою. Пʼять куль літають полем 96×96 і відбиваються від країв; сорок кадрів. Ми знаємо точну рамку кожної кулі на кожному кадрі, тож будь-яку помилку трекера можна порахувати, а не оцінити на око. Знімати справжнє відео нема з чого: пакетів для читання відеофайлів у середовищі курсу немає, а тягнути дані з мережі ми не маємо права.
Два числа задають усю подальшу арифметику. Куля проходить за кадр у середньому 1.8750 пікселя, а сторона її рамки — 14 пікселів. Отже дві сусідні рамки однієї кулі перекриваються дуже сильно: IoU між ними 0.7144 у середньому і 0.6097 у найгіршому випадку. Запамʼятай цю пару — до кінця теми вона пояснить половину результатів.
ПотікОптичний потік: поле зсувів
Найпряміша відповідь на питання «що змінилось між двома кадрами» — порахувати, куди поїхав кожен піксель. Це й зветься оптичний потік (optical flow). Відповідь — не число й не рамка, а поле векторів розміру кадру: у кожній клітинці пара чисел, зсув по горизонталі й по вертикалі.
Ідея, з якої все росте, називається рівнянням постійної яскравості. Словами вона звучить так: коли точка предмета переїхала з одного місця в інше, її яскравість не змінилась — змінилось лише місце. Запишімо це.
Тут I — яскравість, x і y — координати пікселя, t — номер кадру, а пара (u, v) — те, що ми шукаємо: на скільки пікселів точка поїхала вправо й униз. Рівняння каже: яскравість там, куди точка приїхала, дорівнює яскравості там, звідки вона виїхала.
Якщо зсув маленький, праву частину можна замінити наближенням: яскравість у сусідній точці приблизно дорівнює яскравості тут плюс швидкість її зміни, помножена на відстань. Швидкість зміни яскравості вздовж горизонталі позначимо Ix, уздовж вертикалі — Iy, а між кадрами — It. Виходить одне рівняння:
І ось перша неприємність, видима неозброєним оком: рівняння одне, а невідомих два. З одного пікселя зсув не дістати ніяк. Отже треба взяти цілий окіл і вважати, що всі пікселі в ньому поїхали однаково — тоді рівнянь стане стільки, скільки пікселів у вікні, і система розвʼяжеться.
Це метод Лукаса — Канаде, і в ньому ховається найважливіше обмеження потоку. Система розвʼяжеться тільки якщо всередині вікна яскравість міняється в обидва боки. На однорідній плямі всі похідні нулі, рівняння перетворюються на «0 = 0», і зсув не визначається взагалі. Це зветься апертурною проблемою: дивлячись у маленьке віконце на однотонну поверхню, ти не бачиш руху, бо після зсуву картинка виглядає точнісінько так само.
Де потік бачить рух, а де ні
Куля зсувається на задане число пікселів. Праворуч — потік, порахований методом Лукаса — Канаде: стрілка на кожній клітинці й рожеве тло там, де система рівнянь вироджена й напрямок визначити неможливо.
calcOpticalFlowFarneback із OpenCV: усередині однорідної кулі він показує
(1.5369, 1.0246) замість істинних (3, 2), а на текстурованій —
(2.9922, 1.9948).Точність потоку перевіряють там, де істину задали самі: беруть текстуровану картинку й зсувають її на відоме число пікселів. Тоді правильна відповідь — та сама пара чисел у кожному пікселі, і похибку видно до останнього знака.
| Істинний зсув | Середня похибка | Медіана | Максимум |
|---|---|---|---|
| (1, 0) | 0.0453 | 0.0139 | 0.95 |
| (2, 1) | 0.1033 | 0.0311 | 2.15 |
| (3, 3) | 0.2085 | 0.0600 | 4.13 |
| (5, 2) | 0.2618 | 0.0746 | 5.23 |
| (8, 0) | 3.0428 | 0.7008 | 13.20 |
| (12, 5) | 13.8824 | 13.8988 | 19.05 |
Перші чотири рядки — точність до сотих пікселя. На (8, 0) медіана ще пристойна, а середнє стрибає в дванадцять разів: зіпсувалась не вся картинка, а окремі ділянки. На (12, 5) ламається все — похибка 13.8824 при істинному зсуві довжиною 13.00. Тобто алгоритм майже скрізь відповів «нікуди не поїхало».
Це друге обмеження потоку, і воно теж не косметичне: у потоку є стеля по швидкості, і задає її розмір вікна разом із кількістю рівнів піраміди. Предмет, що проходить за кадр більше, ніж вікно, для потоку зникає — не з помилкою, а з правдоподібним нулем.
goodFeaturesToTrack знаходить на
текстурованій кулі 40 точок, і calcOpticalFlowPyrLK проводить усі 40
із похибкою 0.0019 px. На однорідній кулі знайдено 20 точок — і всі вони
сидять на обідку, на відстані 19.9–20.0 px від центра при радіусі 20. Усередині
чіплятися нема за що.МежаЧого потік не дає: номерів
Потік точний. Спробуй тепер відповісти ним на питання «де зараз куля номер три».
Не вийде. Поле зсувів не знає слова «куля»: у ньому 96×96 стрілок і жодного номера. Єдиний спосіб дістати з нього траєкторію — вести рамку самому: узяти рамку на першому кадрі й на кожному наступному зсувати її на медіанний потік усередині. Заміряймо, скільки таке ведення живе.
Скільки живе рамка, яку веде тільки потік
Криві заміряні на нашому відео. Повзунок — скільки кадрів пройшло; перемикач — чи каже детектор час від часу, де куля насправді.
Один крок — 0.165 пікселя, чудово. Тридцять девʼять кроків — 22.134, тобто рамка відʼїхала далі, ніж сама куля завширшки. Причина проста й невиліковна: похибки додаються. Потік рахується між двома сусідніми кадрами й нічого не знає про те, де предмет був на початку, тож виправити накопичене нема звідки.
Ось чому серйозний трекінг не будують на потоці. Потрібне джерело, яке щокадру каже «предмет тут» незалежно від попереднього кадру. Таке джерело в нас уже є — детектор із блоку 5. Останній стан перемикача у фігурі це й показує: коли детектор поправляє рамку щокадру, накопичення зникає зовсім.
СхемаТрекінг за детекцією
Трекінг за детекцією (tracking-by-detection) — схема, за якою сьогодні працює майже все. Вона складається з двох частин, і друга займає сорок рядків коду:
- на кожному кадрі незалежно запускаємо детектор і дістаємо список рамок;
- зшиваємо ці рамки з траєкторіями, які вже є, — і кожна траєкторія зберігає свій номер.
Уся розумна частина сидить у детекторі, і вона вже написана. Друга частина не має ваг, не навчається й не знає, що таке машина. Вона розвʼязує суто геометричну задачу: яка нова рамка є продовженням якої траєкторії.
Трекер тримає список треків. Трек — це три речі: номер, остання відома рамка й вік, тобто скільки кадрів поспіль його не підтвердила жодна детекція. На кожному кадрі відбувається пʼять кроків:
Кожен трек каже, де він очікує побачити себе на цьому кадрі. Найпростіший прогноз — «там само, де був».
Будуємо таблицю: рядок — трек, стовпець — детекція, у клітинці IoU між їхніми рамками. Це і є міра «наскільки правдоподібно, що ця детекція — продовження цього треку».
Обираємо пари так, щоб один трек дістав не більше однієї детекції й навпаки. Пари з надто малим IoU відкидаємо — це не продовження, а випадкове сусідство.
Підтверджені треки беруть нову рамку, вік скидається в нуль. Непідтверджені
старіють; хто пережив більше ніж max_age кадрів без детекції —
помирає.
Кожна детекція, яка нікому не дісталась, народжує новий трек із новим номером. Іншого способу почати траєкторію немає.
Зверни увагу на пʼятий крок: саме він робить помилки видимими. Якщо детекція справжньої кулі не знайшла свого треку, куля дістане інший номер, ніж мала. І далі ми доведемо, що майже всі помилки трекінгу народжуються рівно тут.
ЗіставленняУгорський алгоритм проти жадібного
Третій крок заслуговує окремої розмови. Найпростіша відповідь — жадібна: беремо найкращу пару в таблиці, викреслюємо її рядок і стовпець, беремо наступну найкращу з решти, і так поки пари не скінчаться.
Вона швидка й у більшості кадрів дає те саме, що правильний алгоритм. Але в неї є властивість, яка накопичується: перша пара може забрати рамку, яка була критично потрібна іншому треку. Жадібний вибір дивиться на одну клітинку, а не на таблицю.
Правильна відповідь — угорський алгоритм, який ми вже вживали в
темі 27, коли DETR зіставляв усі свої запити
з еталонною розміткою одним призначенням (у нашій моделі їх було десять, в
оригінальній DETR — сто). Він шукає таке призначення, у якого найкраща сума
вартостей — не кожна пара окремо, а всі разом. У SciPy це
linear_sum_assignment, і аргумент у нього той самий, що був у DETR:
таблиця вартостей.
Де жадібний вибір ламається
Два треки й дві детекції. Верхній повзунок пересуває детекцію B, нижній розсуває треки. Розходяться алгоритми у вузькій смузі — веди B у діапазоні 34–37 і дивись на таблицю.
МетрикаПідміна номера
Щоб щось покращувати, це треба вміти міряти. Метрика трекінгу, яка нас цікавить, зветься підміна номера (ID switch) і рахується напрочуд просто.
Ми знаємо, якій кулі належить кожна детекція — це наша еталонна розмітка. Дивимось, під яким номером трекера ця куля йшла минулого разу. Якщо номер змінився — це одна підміна. Лічильник подій, безрозмірний і чесний.
Метрика має дві приємні властивості. Перша: вона не залежить від того, скільки предметів у кадрі й скільки триває відео, — це просто кількість подій, і її можна порівнювати між прогонами на тому самому наборі. Друга: вона ортогональна до якості детекції. Трекер може ідеально накривати всі предмети рамками й при цьому щокадру міняти їм номери; підміна номера це побачить, а IoU — ні.
І одна неприємна властивість, до якої ми повернемось у розділі 12: підміна — лічильник подій, а не шкоди. Вона не питає, коли подія сталась.
ГоловнеПідміна народжується з дірки, а не з перетину
Тепер найважливіший замір теми. Питання: звідки беруться підміни?
Інтуїтивна відповідь: із перетинів. Дві кулі зійшлися, рамки наклались, зіставлення переплутало їх місцями. Звучить переконливо. Перевірмо.
Проженімо пʼять різних детекторів через пʼять значень max_age,
кожну точку — на вісьмох зернах. Детектори відрізняються двома вадами: дрож
(рамка зсунута на кілька пікселів від правильної) і пропуск (рамки немає зовсім).
| Детектор | max_age=0 | 1 | 2 | 3 | 5 |
|---|---|---|---|---|---|
| ідеальний | 0.50 | 0.50 | 0.50 | 0.50 | 0.50 |
| дрож 1.5 px | 1.50 | 1.50 | 1.50 | 1.50 | 1.50 |
| пропуск 10 % | 18.62 | 2.88 | 0.75 | 0.50 | 0.50 |
| дрож + пропуск | 18.00 | 3.50 | 1.75 | 1.62 | 1.62 |
| дрож 3 + пропуск 20 % | 36.38 | 17.38 | 15.00 | 14.62 | 16.12 |
Прочитай перші два рядки. Ідеальний детектор дає 0.50 підміни на все відео —
і max_age не змінює нічого від краю до краю. Кулі в цій сцені
перетинаються постійно, але зіставлення з ними справляється: IoU сусідніх кадрів
0.7144, і навіть у гущавині правильна пара впевнено виграє в неправильної. Дрож у
півтора пікселя додає до цього рівно одну підміну.
Тепер третій рядок. Той самий рух, ті самі перетини, ідеально точні рамки — але десята частина рамок просто не зʼявилась. Підмін стало 18.62. Це в тридцять сім разів більше, ніж давав перетин.
Доказ прямий, без жодної статистики. Порахуймо, скільки рамок детектор пропустив
(окрім останнього кадру — там пропуск підміни дати вже не може, кулі більше не
зʼявляться) і скільки вийшло підмін при max_age = 0:
Числа майже збігаються, і це не збіг. При max_age = 0 кожна дірка
вбиває трек негайно, а наступна детекція тієї самої кулі змушена народити новий номер —
крок 5 із розділу 4. Одна дірка = одна підміна. Розбіжність у два — це випадки, коли
куля пропала двічі поспіль: дві дірки, а підміна одна.
І звідси лікування, яке коштує один рядок коду: дозволити треку пережити кадр
без детекції. max_age = 1 знімає 18.62 до 2.88 — понад
вісімдесят відсотків підмін за один параметр.
Дірка в детекціях і вік треку
Смуга — це номери, які трекер видавав кожній кулі кадр за кадром. Один колір — один номер; злам кольору посеред рядка і є підміна. Сірі клітинки — кадри, де детектор кулю не побачив.
max_age. Смуга
лишається однією й тією самою — вік треку не змінює нічого, бо треку нема від чого
вмирати. Тепер поверни пропуск на 10 % при max_age = 0: смуга
розсипається на різнокольорові шматки, і кожен шматок починається рівно після
сірої клітинки. Один крок max_age вгору — і шматки зростаються.
Числа тут порахував браузер на своєму генераторі, тож вони не збігаються з
таблицею вище цифра в цифру; замір на восьми зернах дає 18.62 → 2.88.ПасткаМодель руху й чистий детектор
Наступна природна думка: якщо трек мусить пережити кадр без детекції, хай він їде далі сам. Запамʼятаємо швидкість — різницю двох останніх положень — і додамо її до рамки при прогнозі. Так предмет, що зник на кадр, буде знайдений там, куди він мав приїхати, а не там, де його бачили востаннє.
Це модель руху (motion model), і вона стоїть у кожному описі трекінгу. Заміряймо.
| Детектор | без прогнозу | наївна швидкість | фільтр Калмана |
|---|---|---|---|
| ідеальний | 0.50 | 0.00 | 0.00 |
| дрож 1.5 px | 1.50 | 4.75 | 1.88 |
| пропуск 10 % | 2.88 | 2.38 | 2.38 |
| дрож + пропуск | 3.50 | 6.75 | 3.50 |
| дрож 3 + пропуск 20 % | 17.38 | 45.62 | 20.12 |
Перший рядок виглядає як чиста перемога: на ідеальному детекторі модель руху прибирає підміни повністю, 0.50 перетворюється на 0.00. Якби замір закінчився тут, у цю лекцію пішло б речення «модель руху розвʼязує задачу».
Другий рядок його спростовує. Досить детекторові задрижати на півтора пікселя — і наївна модель руху дає 4.75 замість 1.50, тобто робить утричі гірше, ніж її відсутність. Розкид по зернах теж не рятує винуватця: 0–4 без прогнозу і 1–11 із ним. На найгіршому детекторі різниця стає жахливою: 45.62 проти 17.38.
Причина шкоди арифметична, і її варто побачити числом. Швидкість ми рахуємо як різницю двох виміряних положень. Кожне з них має похибку. Різниця двох незалежних шумних чисел шумніша за кожне з них у корінь із двох разів — бо похибки не гасяться, а складаються.
Подивись на два останні рядки. Шум у «швидкості» більший за сам рух. Тобто число, яке ми називаємо швидкістю, насправді складається переважно з шуму детектора, і трек, який на нього спирається, стрибає геть від власної кулі — щоразу в новий бік.
Три способи передбачити, де трек буде
Ліворуч — куди прогноз ставить рамку треку відносно справжньої кулі. Праворуч — підміни для всіх трьох способів одразу, на тих самих детекціях.
КалманТой самий прогноз, але зі згладженою швидкістю
Фільтр Калмана робить із наївною швидкістю рівно одне: не вірить кожному виміру окремо. Він тримає оцінку стану — положення й швидкість разом — і поряд із нею міру власної невпевненості. Кожен новий вимір зсуває оцінку рівно настільки, наскільки він надійніший за прогноз.
Стан у нас чотиривимірний: x, y, швидкість по x, швидкість по y. Далі потрібні чотири матриці, і кожна з них — просто записана словами гіпотеза:
| Матриця | Що каже словами |
|---|---|
| F | як стан їде за один кадр сам собою: положення додає швидкість, швидкість не міняється |
| H | що ми взагалі бачимо: тільки положення, швидкість вимірювати нічим |
| R | наскільки шумний детектор — дисперсія його похибки в пікселях у квадраті |
| Q | наскільки рух може відхилитись від рівномірного між кадрами |
Крок фільтра — пʼять рядків. Спершу прогноз: стан їде вперед за F, невпевненість росте на Q. Потім оновлення: рахуємо, наскільки вимір розійшовся з прогнозом, і зсуваємо оцінку на частину цього розходження. Частка зветься коефіцієнтом Калмана і виводиться з того, що надійніше — прогноз чи вимір.
Усе це — десять рядків numpy, і вони збігаються з бібліотечним
filterpy до останнього знака. На пʼятьох вимірах наївна швидкість дає
(2.4000, 1.4000) — це просто останній крок із усім його шумом, — а Калман
дає (1.6121, 1.7059), бо бачив усі пʼять і зважив їх.
І тепер чесний висновок, який суперечить очікуванню. Подивись на таблицю розділу 8 ще раз: Калман не переміг відсутність прогнозу. На дрижачому детекторі 1.88 проти 1.50, на комбінованому 3.50 проти 3.50, на найгіршому 20.12 проти 17.38 — усе в межах розкиду або трохи гірше. Він лише прибрав шкоду, яку робила наївна швидкість.
МежаКоли прогноз руху заробляє
Прогноз потрібен там, де без нього рамки сусідніх кадрів перестають перетинатись. Поки IoU між ними 0.71, правильна пара виграє в неправильної з величезним запасом, і додавати до неї щось немає сенсу. Розженімо кулі й подивімось, де запас скінчиться.
| Зсув за кадр | IoU сусідніх | без прогнозу | наївна | Калман |
|---|---|---|---|---|
| 0.86 px | 0.8534 | 2.88 | 6.50 | 2.62 |
| 1.87 px | 0.7144 | 3.50 | 6.75 | 3.50 |
| 3.40 px | 0.5521 | 6.88 | 9.00 | 10.25 |
| 5.05 px | 0.4235 | 14.50 | 14.88 | 15.25 |
| 7.53 px | 0.2811 | 38.50 | 26.62 | 31.62 |
| 10.61 px | 0.1675 | 78.88 | 63.38 | 67.25 |
Ось межа, і вона видима неозброєним оком. Поки зсув за кадр малий проти сторони рамки, прогноз шкодить або нічого не дає. Коли зсув доростає приблизно до половини сторони рамки (7.53 при стороні 14) і IoU сусідніх кадрів падає нижче 0.30, картина перевертається: без прогнозу трекер розсипається (38.50), із прогнозом тримається (26.62).
Отже питання не «брати модель руху чи ні», а скільки предмет проходить за кадр у частках власного розміру. Це число можна порахувати до того, як писати трекер — і саме воно вирішує.
Швидкість вирішує, чи потрібен прогноз
Ліворуч — дві сусідні рамки однієї кулі й площа їхнього перекриття. Праворуч — підміни без прогнозу й із ним при цій швидкості.
MOTAТри доданки в одному числі
Підміна номера — не єдина помилка трекера. Він ще й губить предмети (їх немає у відповіді, хоч вони є в кадрі) і вигадує зайві (трек живе, а предмета вже немає). Класична збірна метрика трекінгу — MOTA — складає всі три види помилок і ділить на кількість істинних рамок:
Кожен доданок словами. Пропущені — істинні рамки, яким не знайшлося жодного треку (у метриках детекції це false negative). Вигадані — треки, яким не знайшлося істинної рамки (false positive). Підміни — те, що ми вже вміємо рахувати. Зіставлення треків з еталонною розміткою робиться тим самим угорським алгоритмом за порогом IoU 0.5 — тим самим порогом, що в темі 23.
| max_age | пропущені | вигадані | підміни | істинних | MOTA |
|---|---|---|---|---|---|
| 0 | 205 | 44 | 144 | 1600 | 0.7544 |
| 1 | 82 | 59 | 28 | 1600 | 0.8944 |
| 2 | 73 | 67 | 14 | 1600 | 0.9038 |
| 3 | 72 | 68 | 13 | 1600 | 0.9044 |
| 5 | 72 | 70 | 13 | 1600 | 0.9031 |
| 10 | 72 | 75 | 13 | 1600 | 0.9000 |
Три речі в цій таблиці варті окремої уваги.
Перша: доданки тягнуть у різні боки. Ростить max_age —
пропущених стає менше (трек не помирає й далі накриває предмет), зате вигаданих
більше (трек живе, коли предмета вже не видно). Тому MOTA має максимум усередині,
а не на краю: 0.9044 при max_age = 3 і спад в обидва боки. Це готовий
рецепт добору параметра.
Друга: підмін у MOTA майже не чути. При max_age = 3 вони
складають 8.5 % суми помилок. Якби підміни зникли зовсім, MOTA стала б 0.9125
замість 0.9044 — різниця в третьому знаку. Тобто число, яким галузь звикла міряти
трекінг, майже цілком описує якість детектора, а не якість зшивання.
Три помилки в одному числі
Ліворуч — з чого складається чисельник MOTA при обраному
max_age. Праворуч — сама MOTA на всіх шести значеннях, поточне
підсвічено.
max_age = 3» правдивий і
водночас дрібний. А верхній шматок, підміни, з третього значення взагалі
перестає рухатись: у чисельнику він займає 8.5 %, і його зникнення підняло б
MOTA з 0.9044 лише до 0.9125. Число, яким галузь міряє трекінг, майже цілком
про детектор.Третя: MOTA буває відʼємною. Це не поломка й не помилка в коді. У чисельнику сума трьох лічильників, і жоден із них не обмежений кількістю істинних рамок: вигаданих треків може бути скільки завгодно. Перевірмо це прямо: той самий трекер плюс шість вигаданих рамок на кадр дає 1985 вигаданих проти 1600 істинних і MOTA = −0.3162.
Сліпа плямаЧого не бачить підміна номера
Тепер повернімось до обіцянки з розділу 6. Підміна — лічильник подій. Вона не питає, коли подія сталась і скільки шкоди завдала.
Поставмо поряд другу міру — чистоту номера: яка частка появ предмета йшла під його найчастішим номером. Візьмімо сцену, у якій підмін немає взагалі, і зробімо в детекціях рівно одну дірку — щоразу на іншому кадрі.
| Дірка на кадрі | Підміни | Чистота номера |
|---|---|---|
| 2 | 1 | 0.9487 |
| 10 | 1 | 0.7436 |
| 20 | 1 | 0.5128 |
| 30 | 1 | 0.7692 |
| 38 | 1 | 0.9744 |
Стовпчик «підміни» стоїть нерухомо — скрізь одиниця. Стовпчик «чистота» гуляє майже вдвічі. Найгірше — дірка посередині: обидві половини траєкторії однакові завдовжки, і жодна не є головною. Дірка на другому кадрі коштує майже нічого, дірка на двадцятому розрізає траєкторію навпіл — а метрика в обох випадках каже «одна подія».
Одна підміна, але шкода різна
Смуга — сорок кадрів однієї кулі. Дірка в детекціях рівно одна; повзунок вирішує, на якому кадрі вона станеться.
Це та сама історія, що з mIoU й PQ у темі 32: метрика чесно міряє те, що обіцяла, і мовчить про решту. Саме тому поряд із підмінами галузь рахує IDF1 — метрику, збудовану не на подіях, а на тривалості правильного номера. Ідея в неї така сама, як у нашої чистоти: питати не «скільки разів зламалось», а «яку частку часу все було правильно».
ІнструментиВигляд предмета, SORT і порожня ніша OpenCV
Досі трек знав про предмет лише його координати. Але предмети ще й виглядають по-різному, і це можна покласти у вартість зіставлення поряд із IoU. Саме так влаштований DeepSORT: маленька мережа обертає вміст рамки на вектор, і треки порівнюються не тільки за положенням, а й за схожістю вигляду.
Мережа для того, щоб побачити суть, не потрібна. Дамо кулям «вигляд» вручну — одне число на кулю замість вектора мережі — і заміряймо, що з цього виходить на густішій сцені з дванадцяти куль:
| Вартість зіставлення | Підміни | Розкид |
|---|---|---|
| тільки IoU | 20.38 | 8–29 |
| IoU + вигляд, кулі різні | 3.88 | 2–7 |
| IoU + вигляд, кулі однакові | 20.12 | 8–29 |
| IoU + вигляд, вигляд шумний | 18.00 | 8–27 |
Вигляд предмета зрізає підміни в пʼять разів — але лише поки предмети справді різні. На однакових кулях він не дає нічого: 20.12 проти 20.38, розкид той самий до останнього числа. На шумному вигляді допомагає наполовину.
Це й є межа DeepSORT, про яку рідко пишуть: він працює на людях у різному одязі й не працює на однакових деталях на конвеєрі. Простіший SORT, без вигляду взагалі, — це рівно те, що ми зібрали в розділі 4 плюс фільтр Калмана з розділу 9.
Порожня ніша: що лишилось від трекерів в OpenCV 5
У кожному підручнику стоїть список готових однопредметних трекерів OpenCV: KCF,
CSRT, MOSSE, MedianFlow, Boosting. Усі вони жили в модулі cv2.legacy, а
в OpenCV 5 цього модуля немає. Лишився TrackerMIL;
TrackerDaSiamRPN, TrackerNano і TrackerVit є
як класи, але вимагають файл ONNX, якого в пакет не поклали.
Це та сама історія, що з HOGDescriptor у
темі 05: класична демонстрація з
підручника більше не запускається. І тут теж чесніше показати число, ніж робити
вигляд, що нічого не сталось. Заміряймо TrackerMIL на найлегшій
можливій сцені — одна текстурована куля їде по прямій, нічого її не затуляє:
Середній IoU 0.4546 означає, що рамка накриває предмет менш ніж наполовину — за порогом 0.5 із теми 23 це не зарахована детекція. І це без жодної перешкоди, за час, якого вистачило б на кілька проходів маленької згорткової мережі.
Заразом це історична відповідь на питання, чому трекінг за детекцією витіснив однопредметні трекери. Однопредметний трекер сам шукає предмет у наступному кадрі за його виглядом — і накопичує помилку так само, як накопичував її наш потік у розділі 3. Трекінг за детекцією щокадру починає з чистого аркуша, тож накопичувати нема чому. Щойно детектори стали достатньо добрі, ця перевага вирішила все.
Що з цього лишається
Сім чисел, які варто винести з теми:
- перетин предметів дає 0.50 підміни на відео, дірка в детекціях — 18.62;
- на детекторі без пропусків
max_ageне міняє нічого; - один дозвіл треку пережити кадр знімає вісімдесят відсотків підмін;
- наївна модель руху на чистому детекторі дає 0.00, а на дрижачому — 4.75 проти 1.50;
- прогноз руху заробляє, коли зсув за кадр доростає до половини сторони рамки;
- підміни складають 8.5 % помилок у MOTA, тож MOTA міряє переважно детектор;
- вигляд предмета зрізає підміни з 20.38 до 3.88 — і рівно нічого не дає на однакових предметах.
І одна думка, яка тримає їх разом. Трекінг виглядає як задача про рух, а виявляється задачею про надійність джерела. Усе, що ми пробували доробити з боку руху — швидкість, фільтр Калмана, — або нічого не дало, або нашкодило. Єдине, що справді допомогло, — це визнати, що детектор іноді мовчить, і не вбивати трек за одне мовчання.
Місток далі
Ми навчилися зберігати номер предмета в часі. Але номер — це ще не розуміння: трекер знає, що куля номер три була тут, а тепер тут, і не має жодної думки про те, що вона робить. Наступна тема ставить питання інакше: не «де предмет», а «що відбувається». Виявиться, що для цього одного кадру не досить за побудовою — і що є способи це довести, а не постулювати.