Глибина, стерео і 3D

Знімок плаский. Куля розміром із яблуко за два метри і куля розміром із автівку за пʼятдесят дають у кадрі той самий круг — і це не «важко розрізнити», це неможливо. Друге око знімає неоднозначність одним відніманням, але привозить із собою власну арифметику похибок.

Візьми будь-яку фотографію й скажи, скільки метрів до предмета на ній. Ти майже напевно назвеш число — і майже напевно вгадаєш. Але вгадаєш не тому, що виміряв: ти знаєш, якого розміру бувають двері, машини й люди, і підставляєш цей розмір замість вимірювання. Забери знайомі предмети — і здогад розсиплеться. Ця тема про те, звідки береться третій вимір, коли знайомих предметів немає.

МістокВимір, якого в кадрі немає

Блок 6 закінчився питанням «якому предмету належить цей піксель». Блок 7 узявся додавати до знімка виміри, яких у ньому немає. Тема 33 витягла з картинки рядок тексту — відповідь, яка взагалі не є зображенням. Тема 34 і тема 35 додали час: спершу як сталість номера предмета між кадрами, потім як порядок кадрів усередині дії.

Лишився третій вимір — глибина. Ми називали її ще в темі 02: модель віддає масив розміру фото, але в кожній клітинці не клас, а відстань від камери в метрах. Формою це сегментація, змістом — регресія на кожному пікселі.

Тільки ось із часом і текстом усе було чесно: у відео справді є сусідні кадри, у знімку вивіски справді є літери. А глибини в одному знімку немає фізично: камера кидає тривимірний світ на пласку матрицю, і те, що відрізняло далеке від близького, втрачається безповоротно. Тому починати доводиться не з архітектури мережі, а з доводу, що саме втрачається.

НеоднозначністьОдна картинка — нескінченно багато світів

Найпростіша модель камери зветься діркова (pinhole): світло йде прямо крізь одну точку й падає на матрицю. З неї виходить одна-єдина формула, яку варто вивчити напамʼять:

r = F · R / Z

Словами: видимий розмір предмета в пікселях дорівнює справжньому розміру, поділеному на відстань і помноженому на сталу камери. Тут R — справжній радіус предмета в метрах, Z — відстань від камери до предмета в метрах, r — радіус того самого предмета на знімку в пікселях, а F — фокусна відстань, теж у пікселях. Фокусна відстань — це просто число, яким камера переводить метри у пікселі; для наших прикладів вона дорівнює 500.

Порахуємо руками. Куля радіуса 0.10 м на 2 м дає r = 500 · 0.10 / 2 = 25 пікселів. Збільшимо й кулю, й відстань упʼятеро: r = 500 · 0.50 / 10 = 25. Ще раз упʼятеро: r = 500 · 2.50 / 50 = 25.

сценаR, мZ, мr, pxрізних пікселів
яблуко зблизька0.10225.0000—
мʼяч удалині0.501025.00000
цистерна дуже далеко2.505025.00000

Між першою й третьою сценою — 25 разів: яблуко радіуса 0.10 м за два метри й цистерна радіуса 2.50 м за пʼятдесят. Причина, чому r не змінився, видна прямо у формулі: помножили і чисельник, і знаменник на те саме число, а дріб від цього не міняється. Не важать R і Z окремо — важить лише їхнє відношення. Тому світів, що дають той самий круг, не три, а нескінченно багато: кожна пара (R, Z) із тим самим відношенням R/Z.

Однаковий радіус — ще пів біди: може, кулі відрізняються яскравістю чи тінню? Ні, і ось механізм. Яскравість точки на кулі задає нахил поверхні до світла; нахил у точці сферичної поверхні залежить тільки від того, наскільки ця точка далеко від центра у частках радіуса — на кулі будь-якого розміру точка «на пів радіуса від центра» нахилена однаково. Отже нахил, а з ним і яскравість, — це функція від частки, а сама картинка — функція від однієї величини r, яка задає, у скільки пікселів ту частку перевести. Три сцени з однаковим r підставляють у цю функцію однаковий аргумент і дістають однаковий результат. Порахована попіксельно різниця всіх трьох зображень дорівнює рівно нулю — не «мало», не «в межах шуму», а нуль: однакове r дає однакову картинку, байт у байт.

Це не задача складності, а задача неможливості. Розрізнити три сцени за одним знімком не зможе ані людина, ані мережа з мільярдом параметрів: різниці немає в самих даних. Тому монокулярна оцінка глибини — це здогад із досвіду, а не вимірювання; далі ми навчимо такий здогад самі.

Три світи, одна картинка

Угорі — вигляд збоку: камера ліворуч, три кулі різного розміру на різних відстанях. Унизу — те, що бачить камера, і різниця першої та третьої сцен. Повзунок радіуса міняє всі три світи разом, повзунок бази вмикає друге око.

різних пікселів—
радіус, px—
d на 2 м—
d на 10 м—
d на 50 м—
Скільки не крути перший повзунок, різниця лишається нулем. Три світи, що різняться за масштабом у 25 разів, дають побітово однакові знімки. Другий повзунок додає другу камеру: щойно база більша за нуль, сцени дістають різні диспаритети, і неоднозначність зникає. На базі 0.10 м це 25.00, 5.00 і 1.00 пікселя.

Два окаДруге око: звідки береться диспаритет

Постав дві однакові камери поруч, обʼєктивами в один бік, на відстані B одна від одної — ця відстань зветься базою (baseline). Тепер точка світу потрапляє на обидві матриці, але не в те саме місце. Різниця горизонтальних координат двох відбитків і зветься диспаритетом (disparity):

d = xліва − xправа

Звідки береться формула для нього — видно з подібних трикутників, і це той випадок, коли варто пройти виведення повільно.

крок 1 · де опиниться точка в лівій камері

Візьмемо точку світу з координатами (X, Y, Z): X — скільки метрів праворуч від лівої камери, Z — скільки метрів уперед. Діркова камера кидає її на матрицю за тим самим правилом «поділи на відстань і помнож на F»:

xліва = F · X / Z
крок 2 · де опиниться та сама точка в правій

Права камера стоїть на B метрів правіше. Для неї та сама точка світу має координату X − B замість X: та сама точка, але відлічена від іншого початку.

xправа = F · (X − B) / Z
крок 3 · віднімаємо

Тепер віднімемо друге від першого. X скорочується — і це найважливіше, що тут стається: диспаритет не залежить від того, де точка стоїть по горизонталі, а залежить лише від того, як далеко вона:

d = F · X / Z − F · (X − B) / Z = F · B / Z
крок 4 · перевертаємо

Нам потрібне Z, а не d. Переставляємо:

Z = F · B / d

Оце й уся стереогеометрія. Диспаритет і глибина — обернено пропорційні: чим ближче предмет, тим сильніше він «стрибає» між двома кадрами.

Звідки береться віднімання

Ліворуч — вигляд згори: та сама точка світу й два трикутники, з яких беруться дві координати. Праворуч — де вона опиняється в кожному кадрі. Схема пояснює крок 3 виведення; довжини на ній не в масштабі.

Два трикутники мають спільну висоту Z і різні основи — X і X − B. Тому й координати відбитків різні, а їхня різниця вже не містить X: де саме точка стоїть по горизонталі, диспаритетові байдуже. Лишається тільки те, як далеко вона, — і це рівно те, що ми хотіли виміряти.

Підставимо наші числа: F = 500 px, B = 0.10 м, отже F · B = 50. Для предмета на 2 м диспаритет дорівнює 50 / 2 = 25 пікселів. На 10 м — 50 / 10 = 5. На 50 м — 1 піксель. Ті самі три сцени, які знімок не розрізняв узагалі, стерео розводить трьома різними числами.

Це та сама операція, що робить твій мозок. Затуляй по черзі одне око: палець за 20 сантиметрів помітно стрибне, дерево за сто метрів не зрушить. База людських очей близько 6.5 см, і саме тому стереозір людини перестає працювати далі кількох десятків метрів.

Одразу видно й межу. На 50 м диспаритет дорівнює одному пікселю: відбитки точки в двох кадрах стоять у сусідніх клітинках, а ще далі злипаються в одну. Стерео не «стає неточним» поступово — воно впирається в піксельну сітку. А де саме впирається, вирішує база.

БазаБаза: за що платимо за точність

З формули d = F·B/Z здається, що рішення очевидне: розсунь камери ширше, дістань більший диспаритет, отримай кращу точність задарма. Розсунемо. І одразу заплатимо двічі.

Перша плата — спільне поле зору. Ліва камера бачить смугу світу, права — таку саму смугу, зсунуту вбік. Спільна частина — це перетин двох смуг, і він тим менший, чим більша база. Формулу для нього виведемо так само повільно, як попередню — вона коротша, і в ній усього три кроки:

крок 1 · на скільки пікселів зсунулось

Візьмімо плоску стіну на глибині Z. Кожна її точка потрапляє в правий кадр лівіше, ніж у лівий, рівно на диспаритет — а він у всіх точок стіни один і той самий, бо глибина одна: d = F · B / Z. Тобто правий кадр — це той самий лівий, зсунутий на d пікселів.

крок 2 · скільки лишилось спільного

Позначмо ширину кадру через W — це просто скільки в ньому пікселів по горизонталі; у наших прикладах 320. Дві однакові смуги завширшки W, зсунуті одна відносно одної на d, перекриваються на W − d пікселях. Смужка завширшки d з одного боку пари не має: у лівому кадрі вона є, у правому її вже немає.

спільних пікселів = W − d = W − F · B / Z
крок 3 · переводимо в частку

Ділимо на всю ширину W, щоб дістати частку, а не пікселі:

спільне поле = (W − F · B / Z) ÷ W = 1 − F · B / (Z · W)

Підставмо руками: кадр 320 px, F = 500, стіна на 3 м. При базі 0.02 м зсув дорівнює 500 · 0.02 / 3 = 3.33 пікселя, а це 3.33 ÷ 320 = 1.04 % ширини, тож спільними лишаються 99.0 %. При базі 0.40 м зсув уже 500 · 0.40 / 3 = 66.67 пікселя, тобто 20.8 % ширини, і спільними лишаються 79.2 %. Пʼята частина кадру просто не має пари, і глибини там не буде ніколи.

Одразу видно й межу формули: коли F · B / Z доростає до W, спільного поля не лишається взагалі. При F = 500 і W = 320 це станеться, щойно база перевищить 0.64 · Z — для стіни на 3 м це майже два метри між камерами. Формула не «псується» на цій позначці, вона просто чесно доходить до нуля.

Друга плата — затінення (occlusion). Ближній предмет затуляє від лівої й правої камери різні шматки фону, і ширина цих однобоких смужок теж росте з базою. Тому карта глибини стереопари завжди має «тіні» вздовж контурів ближніх предметів — це не поломка алгоритму, а геометрія.

Замір показує обидва боки угоди. Стіна на 3 м, знімок 320×240, StereoSGBM, для кожної бази свій діапазон пошуку:

база B, мd, pxспільне полепокриттяпохибка d, pxпохибка Z, м
0.023.3399.0 %94.8 %0.2270.220
0.058.3397.4 %89.7 %0.2270.084
0.1016.6794.8 %89.6 %0.2910.051
0.2033.3389.6 %84.7 %0.2270.021
0.4066.6779.2 %74.7 %0.2900.013

Читай таблицю по двох стовпчиках справа. Похибка диспаритету не змінюється взагалі — 0.227-0.291 пікселя незалежно від бази, бо це властивість алгоритму, а не геометрії. А похибка глибини падає з 22 до 1.3 сантиметра, тобто у сімнадцять разів. Заплатили двадцятьма відсотками кадру: покриття впало з 94.8 % до 74.7 %.

Звідси різниця між приладами. База стереокамери робота-пилососа — кілька сантиметрів: йому треба бачити ніжку стільця за пів метра. База стереопари на даху автівки — десятки сантиметрів, бо там цікаве починається з двадцяти метрів.

Що дає й що забирає ширша база

Вигляд згори: дві камери, їхні поля зору й спільна частина. Смуга праворуч від сірої межі видима лише лівій камері — глибини там немає.

диспаритет—
спільне поле—
ціна 0.25 px помилки—
Постав глибину на 12 м і покрути базу. При 0.02 м диспаритет дорівнює 0.83 px — менше за піксель, міряти нічого. При 0.40 м той самий предмет дає 16.67 px і похибку 17.7 см. А тепер лиши базу 0.40 і зведи глибину до 1 м: точність 0.1 см, зате спільними лишаються 37.5 % кадру. Точності без плати не буває.

Z²Ціна одного пікселя росте як Z²

Найважливіша практична властивість стерео ховається в тій самій формулі Z = F·B/d, і побачити її можна лише підставивши числа двічі.

Хай алгоритм помилився в диспаритеті рівно на один піксель. Для предмета на 1 метрі диспаритет дорівнює 50 px; помилившись на піксель, дістанемо 50 / 51 = 0.9804 м, тобто помилку 1.96 сантиметра. Для предмета на 20 метрах диспаритет дорівнює 2.5 px, і той самий піксель дає 50 / 3.5 = 14.29 м замість 20 — помилку 5.71 метра. Один і той самий піксель коштує то два сантиметри, то шість метрів. Загальна формула — просто різниця двох значень Z:

ΔZ = F·B/d − F·B/(d + 1) = F·B / (d · (d + 1))

Її незручно тримати в голові, тому зазвичай наводять наближення: якщо знехтувати одиницею поряд із d, вийде F·B/d², а оскільки d = F·B/Z, то

ΔZ ≈ Z2 / (F · B)

Ось звідки береться фраза «похибка стерео росте як квадрат відстані». Вона правильна — але має область застосовності, і знайти її можна прямо в таблиці.

Z, мd, pxціна 1 px, точнонаближення Z²/(F·B)завищення
150.001.96 см2.00 см1.020
316.6716.98 см18.00 см1.060
510.0045.45 см50.00 см1.100
95.561.37 м1.62 м1.180
202.505.71 м8.00 м1.400

Наближення завищує похибку, і завищує тим сильніше, чим далі. На метрі різниця два відсотки, на двадцяти метрах — сорок. Причина видна, якщо поділити точну формулу на наближену: одиниця, якою ми знехтували, дає рівно множник

наближення / точне = (d + 1) / d = 1 + 1 / d

Отже похибка самої формули для похибки дорівнює оберненому диспаритету. Поки d великий, наближення чудове; щойно d падає до одиниць пікселів — а це саме та зона, заради якої формулу й дістають, — воно перестає бути наближенням. Урок ширший за стерео: перевіряй область застосовності навіть у тієї формули, яку взяв, щоб оцінити похибку.

Не плутай похибку з роздільністю. Таблиця рахує ціну однієї помилки в диспаритеті, а реальні алгоритми дають субпіксельну точність: наш замір показав розкид 0.23-0.29 пікселя. Але закон Z² від цього не змінюється — помнож увесь стовпчик на 0.25 і подивись на останній рядок.

Як росте похибка глибини

Точна крива й підручникове наближення на одному графіку. Повзунки міняють базу й субпіксельну точність зіставлення.

похибка диспаритету
точно на 5 м—
точно на 20 м—
завищення на 20 м—
межа 1 px—
Дивись на розрив між двома кривими праворуч. Рожева — точна формула, бірюзова — наближення Z²/(F·B). Ліворуч вони злипаються, праворуч розходяться, і множник розходження дорівнює (d + δ) / d. «Межа 1 px» — відстань, на якій диспаритет падає до одного пікселя: далі за неї стерео вже нічого не міряє.

ВирівнюванняВирівнювання пари: чому пошук іде по рядку

Досі ми мовчки припускали, що камери стоять ідеально: обидві дивляться строго вперед, обидві на одній висоті, обидві не перекошені. Тоді точка, що в лівому кадрі лежить у рядку 137, у правому теж лежить у рядку 137 — і щоб знайти відповідність, досить пробігти один рядок.

Реальні камери так не стоять ніколи. Достатньо повернути одну з них на градус — і відповідник поїде з рядка. Наскільки поїде, залежить від того, навколо якої осі поворот. Розведімо три випадки — замір зроблено на сітці з 20 точок у кадрі:

перекіс правої камери1°2°5°
нахил уперед (вісь X)8.86 px17.71 px44.36 px
поворот убік (вісь Y)0.13 px0.26 px0.67 px
крен, навколо осі зору1.20 px2.40 px6.00 px

Числа — середній вертикальний розбіг у пікселях.

Операція, яка прибирає всі три випадки одразу, зветься вирівнюванням пари (rectification): замість того щоб пересувати камери, ми перебудовуємо зображення так, ніби камери стояли ідеально. Це поворот обох кадрів у спільну площину — дві гомографії, по одній на кадр.

В OpenCV це cv2.stereoRectify: на вхід — параметри обох камер і взаємне положення, на виході — два повороти й дві нові матриці проєкції. На парі з перекосом на 2° вертикальний розбіг після вирівнювання дорівнює 4.26·10⁻¹⁵ пікселя. Це не «майже нуль», це нуль із точністю до похибки чисел із рухомою комою.

Поруч живе друга функція — cv2.triangulatePoints. Вона робить зворотну дію: маючи дві матриці проєкції й пару відбитків однієї точки, повертає її тривимірні координати. На вирівняній парі вона відновила всі точки з похибкою 1.16·10⁻¹⁴ метра. Тобто геометрія працює точно; уся справжня похибка стерео живе не в геометрії, а в пошуку відповідностей.

Півградуса перекосу вбивають стерео. Це не риторика, а замір: на тій самій парі, нахиливши праву камеру на 0.5°, покриття StereoSGBM впало з 99.9 % до 17.1 %, а середня похибка диспаритету зросла з 0.390 до 21.07 пікселя. Частка грубих помилок (більше пікселя) підскочила з 0.8 % до 99.0 %. Уже 0.25° — зсув на 2.18 пікселя — коштують чверті покриття.

Куди тікає відповідник, коли камера перекошена

Ліворуч — лівий кадр із поміченою точкою. Праворуч — правий кадр: хрестик показує, де відповідник насправді, рожева смуга — де його шукали б, рахуючи, що камери стоять ідеально.

вісь перекосу
розбіг, середній—
розбіг, найбільший—
точок у смузі пошуку—
Порівняй три осі при однаковому куті. Нахил уперед виносить усі точки з рядка разом, поворот убік майже нічого не псує, а крен розкидає точки віялом — що далі від центра, то більше. Третій повзунок задає, яку смугу навколо рядка ще «прощає» блок зіставлення заввишки в кілька пікселів.

ЗіставленняЯк шукають відповідність: BM і SGBM

Вирівняна пара звела задачу до одновимірної: для пікселя лівого кадру треба знайти його близнюка в тому самому рядку правого. Найпростіший спосіб — блокове зіставлення (block matching): вирізаємо навколо пікселя квадратик, скажімо 7×7, і зсуваємо його вздовж правого рядка, рахуючи для кожного зсуву суму абсолютних різниць яскравостей. Де сума найменша — там і відповідник, а зсув до нього і є диспаритет. Це cv2.StereoBM.

Спосіб працює, поки в квадратику є за що зачепитись. Посеред білої стіни всі зсуви дають майже однакову суму, і переможця визначає шум. StereoBM тоді відмовляється відповідати й лишає піксель порожнім.

Напівглобальне зіставлення (semi-global matching, SGM) додає до суми другий доданок: штраф за те, що диспаритет сусіда інший. Рішення для пікселя перестає бути автономним — посеред білої стіни він бере відповідь у сусіда, який доказ мав. Це cv2.StereoSGBM, а параметри P1 і P2 — це рівно ці штрафи: перший за зміну на один диспаритет, другий за стрибок більше.

Замір на парі з навмисно гладкою ділянкою посередині — 320×240, numDisparities=64, blockSize=7, середнє з пʼяти прогонів:

алгоритмчаспокриттяпохибкагрубихгладка ділянка
StereoBM5.3 мс88.7 %0.390 px1.2 %20.0 %
StereoSGBM13.3 мс99.9 %0.390 px0.8 %100.0 %

Дивись, де саме SGBM виграв. Не в точності: 0.390 проти 0.390, тобто взагалі ніяк. Не в частці грубих помилок: 0.8 % проти 1.2 %. Виграш увесь в останньому стовпчику — на гладкій ділянці BM відповів на 20 % пікселів, а SGBM на 100 %, і там він ще й точніший: 0.321 px проти 0.941. Ціна теж в одному стовпчику: 13.3 мілісекунди проти 5.3, тобто у два з половиною раза. Час залежить від машини, тому дивись на відношення, а не на самі числа.

Стовідсоткове покриття — не те саме, що правильна відповідь. SGBM заповнив гладку ділянку, перетягши туди диспаритет сусідів, і в нашій сцені сусіди мали рацію. Якби посеред білої стіни стояв невидимий уступ, SGBM так само впевнено намалював би пласку стіну й не сказав би ні слова. BM зізнається, що не знає; SGBM здогадується. Вибір між ними — це вибір, а не оптимізація.

Блокове проти напівглобального, на одному рядку

Угорі — лівий і правий рядки текстури з гладкою ділянкою посередині. Унизу — істинний профіль диспаритету й те, що знайшли два алгоритми. Обидва рахуються тут-таки, у браузері.

покриття BM—
покриття SGM—
похибка BM—
похибка SGM—
Постав штраф на нуль — і напівглобальний алгоритм стане блоковим: обидва дають однакові 86.3 % і 0.68 px. Уся різниця між ними — саме цей доданок. Розширюй гладку ділянку: покриття BM падає до 74.8 %, а SGM тримає 97.3 % і вгадує правильно, бо сусіди мають рацію. Збільши блок до 21 px — дірка в BM майже зникає (94.7 %), зате похибка росте з 0.68 до 0.90 px. І зверни увагу на десяток пікселів одразу за уступом: обидва алгоритми впевнено дають там ближній диспаритет. Це затінена смуга — вона видима лише лівій камері, і правильної відповіді в ній не існує.

ПасткаnumDisparities переселяє предмет мовчки

Параметр numDisparities задає, скільки пікселів переглядати, шукаючи відповідник. Виглядає як налаштування швидкості, а насправді це межа найближчої відстані, яку камера здатна побачити: диспаритет ближнього предмета великий, а поза діапазоном пошуку його немає.

Здавалося б, помилка з таким параметром має бути помітною: предмет зникне з карти. Замір показує гірше. Сцена — три смуги текстури на глибинах 1, 3 і 9 метрів, тобто істинні диспаритети 50.00, 16.67 і 5.56 пікселя, і StereoSGBM із різним діапазоном пошуку:

numDisparitiesпокриттясмуга 1 мсмуга 3 мсмуга 9 м
1676.7 %Z = 6.90 мZ = 3.33 мZ = 8.51 м
3271.3 %Z = 3.10 мZ = 2.94 мZ = 8.51 м
4864.8 %Z = 1.97 мZ = 2.94 мZ = 8.51 м
6479.7 %Z = 1.00 мZ = 2.94 мZ = 8.51 м
8074.7 %Z = 1.00 мZ = 2.94 мZ = 8.51 м
9669.7 %Z = 1.00 мZ = 2.94 мZ = 8.51 м
11264.7 %Z = 1.00 мZ = 2.94 мZ = 8.51 м
12859.7 %Z = 1.00 мZ = 2.94 мZ = 8.51 м

Три речі варто прочитати окремо.

Перше: предмет не зникає — він переїжджає. При numDisparities=16 смуга, що стоїть за метр від камери, дістає глибину 6.90 метра. Не «немає значення» й не попередження в консолі — просто число, майже всемеро більше за правду, у карті, яка виглядає цілком нормально. Алгоритм зробив рівно те, що йому сказали: знайшов найкращий збіг у дозволеному діапазоні. Правильної відповіді там не було.

Друге: помилка спадає плавно, а не стрибком. 6.90 → 3.10 → 1.97 → 1.00. Немає порогу, за яким «стало добре». Тому підбирати numDisparities «поки не виглядає правдоподібно» — найгірша стратегія: правдоподібно воно виглядає завжди.

Третє: за широкий діапазон платить край кадру. Починаючи з 64, покриття падає рівно на 5 відсоткових пунктів на кожні 16 додаткових диспаритетів: 79.7 → 74.7 → 69.7 → 64.7 → 59.7. Це не збіг: перші numDisparities стовпчиків лівого кадру пари в правому не мають, а на кадрі шириною 320 пікселів 16 диспаритетів — рівно 5 % ширини.

А от перші три рядки цього правила не слухаються: 76.7 → 71.3 → 64.8, і аж потім покриття підскакує до 79.7. Виглядає як шум, але це не шум — і побачити чому можна, не виходячи за межі таблиці. Відніми від сотні спершу втрачений край, а потім покриття, і дістанеш, скільки кадру алгоритм не зміг прочитати з власної вини:

nd = 16: 100 − 5 − 76.7 = 18.3 % nd = 32: 100 − 10 − 71.3 = 18.7 % nd = 48: 100 − 15 − 64.8 = 20.2 % nd = 64: 100 − 20 − 79.7 = 0.3 % nd = 128: 100 − 40 − 59.7 = 0.3 %

Поки діапазон менший за 64, приблизно пʼята частина кадру лишається без відповіді — і це рівно та ближня смуга, істинний диспаритет якої (50 px) у діапазон не вміщається. Щойно діапазон її накрив, невідповідених пікселів лишається 0.3 %, і далі покриття їсть тільки край. Тобто спад 76.7 → 71.3 → 64.8 — це майже цілком той самий край, що росте по 5 пунктів, а не погіршення роботи. Рівний спад по 5 пунктів починається рівно там, де закінчуються справжні помилки — це й є найдешевша ознака, за якою видно, що діапазону вже вистачає.

Правило, яке варто записати. Спершу вирішуєш, з якої найближчої відстані Zmin камера має бачити, потім рахуєш numDisparities ≥ F·B/Zmin і округлюєш угору до кратного 16 (OpenCV вимагає кратності). Робити навпаки — брати діапазон «із запасом» — означає задарма викидати смугу кадру.

І остання деталь тієї самої таблиці, на яку легко не звернути уваги. Далека смуга стоїть на 9 метрах, а виміряна глибина скрізь дорівнює 8.51 м — похибка майже пів метра, і жоден діапазон пошуку її не виправляє. Причина не в діапазоні: знайдений диспаритет дорівнює 5.88 замість істинних 5.56, тобто помилка в 0.32 пікселя. Порівняй із середньою смугою: там диспаритет 17.00 замість 16.67 — помилка 0.33 пікселя, майже така сама, — а глибина промахується лише на 6 сантиметрів. Третина пікселя коштує 6 см на трьох метрах і 49 см на девʼяти — закон Z² видно просто всередині одного заміру.

Діапазон пошуку і те, що він робить зі сценою

Числа тут — ті самі, що в таблиці вище. Повзунок обирає діапазон пошуку; ліворуч видно сцену збоку з істинними й виміряними положеннями смуг, праворуч — скільки кадру лишається придатним.

смуга 1 м—
смуга 3 м—
смуга 9 м—
покриття—
втрачений край—
Веди повзунок зліва направо й дивись лише на ближню смугу. Вона не блимає й не зникає — вона повільно приїжджає з 6.90 м на своє місце. Далі 64 ближня смуга вже правильна, і кожен наступний крок лише зʼїдає край кадру. Дві дальні смуги при цьому не рухаються взагалі: їхні диспаритети вміщались у діапазон із самого початку.

Хмара точокВід диспаритету до хмари точок

Карта диспаритету — ще не тривимірна сцена, а двовимірна картинка, у якій замість яскравості стоїть число. Щоб дістати справжні координати, кожен піксель переводять у трійку (X, Y, Z) за трьома формулами:

Z = F·B/d   ·   X = (x − cx)·Z/F   ·   Y = (y − cy)·Z/F

Тут x, y — координати пікселя в кадрі, а cx, cy — координати центра кадру. Набір таких трійок для всіх пікселів і зветься хмарою точок (point cloud).

В OpenCV це робить cv2.reprojectImageTo3D, а всі числа камери подаються йому однією матрицею 4×4 на імʼя Q — її повертає той самий cv2.stereoRectify. Перевіримо на знайомих числах: при F = 500 і B = 0.10 диспаритет 25 пікселів дає рівно Z = 2.0 метра — ту саму першу сцену, з якої ми починали.

На хмару не обовʼязково дивитись у спеціальному переглядачі. Щоб побачити її під кутом, вистачає шкільної геометрії: беремо точку (X, Y, Z), повертаємо її навколо вертикальної осі на потрібний кут і дивимось на результат паралельною проєкцією — як на інженерному кресленні. Це десяток рядків арифметики, і саме так намальована фігура нижче.

Хмара робить видимим те, що в таблиці лишалось абстракцією. Пласка стіна на двох метрах виглядає пласкою. Та сама стіна на девʼяти, виміряна з тією самою точністю в пікселях, розсипається на шар товщиною в пів метра. Це закон Z², побачений збоку.

Хмара точок і як її розмиває відстань

Три пласкі площини на 1, 3 і 9 метрах, перетворені на хмару точок. Ліворуч — вигляд згори, праворуч — під кутом. Повзунок шуму додає похибку саме до диспаритету, як і буває насправді.

товщина 1 м—
товщина 3 м—
товщина 9 м—
відношення—
Дивись на відношення товщин. Похибка в пікселях однакова для всіх трьох площин, а розкид у метрах відрізняється приблизно у вісімдесят разів — стільки, скільки дають квадрати відстаней: 9² / 1² = 81. Точне число трохи гуляє від вибірки до вибірки (на іншій сітці точок виходить 85), бо Z залежить від d нелінійно. При нульовому шумі всі три площини — тонкі лінії: далека розмита не сама по собі, а через ціну пікселя.

Одне окоМонокулярна глибина як вивчений здогад

Повернімось до початку. Ми довели, що з одного знімка глибина не відновлюється. Але монокулярні моделі глибини існують, працюють і виглядають вражаюче. Суперечності тут немає — просто вони роблять не те, що здається.

Модель не міряє — вона згадує: на всіх картинках, які вона бачила під час навчання, двері були заввишки два метри, а кулі — приблизно такого-то розміру. Побачивши знайомий предмет знайомого видимого розміру, вона підставляє запамʼятаний справжній розмір і рахує ту саму формулу Z = F·R/r. Це називають вивченим пріором — знанням про світ, узятим не з цього знімка.

Готових ваг монокулярної глибини під рукою немає, та вони тут і не потрібні: робимо як у темі 22 з CLIP і в темі 32 із SAM: будуємо крихітну власну модель на власних сценах і пробуємо її зламати.

Сцена — три кулі на стіні. Радіуси беруться з вузького проміжку 0.16-0.24 м, відстані — з проміжку 2-7 м. Мережа — енкодер-декодер на 14 641 вагу, вихід — карта оберненої глибини (величини 1/Z, як роблять усі сучасні монокулярні моделі: обернена глибина рівномірніша й не вибухає на далеких пікселях). AdamW, 24 епохи, 600 сцен, три зерна (навчання — двадцять із чимось секунд на одному потоці):

зернопохибка, мδ < 1.25той самий світ ×2.5після одного множникасам множник
00.8790.848012.6582.1680.390
10.6540.891312.6871.5670.391
20.5050.907612.6361.2460.396

Стовпчик δ < 1.25 — стандартна метрика монокулярної глибини: частка пікселів, де відношення передбаченої глибини до справжньої лежить між 0.8 і 1.25. 0.85-0.91 для мережі на чотирнадцять тисяч ваг — цілком пристойно. Здогад працює.

Тепер ламаємо. Беремо ті самі 200 перевірних сцен і множимо і радіуси, і відстані на 2.5: зовсім інший світ. А зображення при цьому ті самі, побітово — бо в r = F·R/Z множник у чисельнику скорочується з множником у знаменнику, і різниця знімків до й після множення дорівнює нулю. Мережа бачить ті самі пікселі й видає ті самі числа, тож похибка підскакує з 0.5-0.9 метра до 12.6 метра на всіх трьох зернах.

Останній стовпчик показує, чого саме бракує. Ми підбираємо одне-єдине число — множник, спільний для всіх пікселів усіх 200 сцен. Похибка падає з 12.6 м до 1.2-2.2 м, тобто приблизно у 2.5 раза більше за похибку на рідному світі, а підібраний множник виходить 0.390, 0.391 і 0.396 — одиниця, поділена на 2.5. Мережа не помилилась у формі сцени; вона не знала одного числа, і цього числа не було в зображенні.

Саме тому в статтях про монокулярну глибину міряють «масштабно-інваріантну» похибку, а передбачення перед порівнянням домножують на множник, підібраний за еталонною розміткою (median scaling). Це не хитрість авторів, а визнання геометрії: абсолютний масштаб узяти нізвідки. Модель, яка все-таки видає метри, отримує їх із того, що камера відома, а сцени в датасеті одноманітні — і переносить свій здогад на чужі камери тим гірше, чим більше вони відрізняються.
Розкид по зернах тут великий, і його не можна ховати. 0.505, 0.654 і 0.879 метра — це майже вдвічі між найкращим і найгіршим зерном. На таких числах не можна заявляти, що якась зміна архітектури «покращила модель на 10 %»: різниця, менша за розкид, не є різницею. Зате твердження про ×2.5 тримається залізно — 12.636, 12.658 і 12.687 на трьох зернах: розкид у пʼять сантиметрів проти ефекту в дванадцять метрів.

Поза стереоЧим міряють глибину, коли пари немає

Стерео — не єдиний спосіб, і в приладах воно часто йде другим номером.

Практика ж майже завжди змішує джерела: лідар дає рідкі, але надійні відстані, камера дає густу картинку, модель зшиває їх разом. Лідарні точки грають роль тих самих «відомих розмірів», яких бракувало монокулярній мережі — вони приносять масштаб ззовні.

ПідсумокПідсумок блоку 7

Блок 7 закінчено, і це заразом кінець великої частини курсу. Перші шість блоків питали одне зображення про те, що на ньому намальовано: який це клас, де рамка, якому предмету належить піксель. Блок 7 узявся за інше питання — що з цього зображення можна дізнатися про світ, якого в ньому немає.

ТемаЯкий вимір додаєЗвідки він береться
33текст — відповідь, яка не є картинкоюз форми гліфів плюс алфавіт, заданий ззовні
34час як сталість предметаіз того, що детекції сусідніх кадрів звʼязали в один трек
35час як порядок кадрівіз самого порядку — перемішай кадри, і вимір зникне
36глибина в метрахз другої камери; з одного кадру — нізвідки

Спільне в цих чотирьох темах не в задачах — задачі різні — а в тому, як саме беруться нові виміри.

Новий вимір ніколи не «витягується» із самого зображення. Його або приносить другий вимір даних (сусідній кадр, друга камера), або доводиться підставляти знання ззовні. Тема 33 показала це на алфавіті: розпізнавач без кирилиці не помиляється «трохи» — він розкладає українські літери на схожі латинські, бо іншого виходу не має. Ця тема показала те саме на масштабі. Різниця лише в тому, що алфавіт моделі можна дати, а масштаб — узяти нізвідки.

Дірка в даних коштує дорожче за неточність моделі. Тема 34 знайшла, що підміна номера народжується не з перетину предметів, а з пропущеної детекції, і що дозвіл треку пережити один кадр без детекції важить більше за модель руху. У стерео та сама історія має інше імʼя: гладка ділянка без текстури — це дірка в даних, і всі три способи з нею впоратись (більший блок, штраф за розрив, зізнатись і не відповісти) — це способи жити з діркою, а не закрити її.

Чесна перевірка коштує один рядок. Тема 35 перемішала кадри й довела, що модель справді користується часом. Ця тема помножила світ на 2.5 й довела, що мережа не користується масштабом, бо його немає. Обидві перевірки ставлять те саме питання: «чи справді модель бере відповідь звідти, звідки я думаю?»

Метрика мусить говорити мовою задачі. Блок увів три виміри похибки, яких у курсі не було: посимвольну похибку в темі 33, підміну номера в темі 34 і похибку глибини в метрах тут. У всіх попередніх блоках метрики були безрозмірні — частки, IoU, mAP. Тут число має одиниці, і саме через одиниці стало видно, що та сама похибка «третина пікселя» означає 6 сантиметрів на трьох метрах і майже пів метра на девʼяти.

І головне, що варто забрати з теми окремо. Формула Z = F·B/d має рівно три числа, і кожне хтось мусить знати: F — з калібрування камери, B — з конструкції приладу, d — з пошуку відповідностей. Забери будь-яке — і метри перетворяться на «щось ближче, щось далі». Кожне з них має ціну: база платить полем зору, диспаритет — часом і дірками на гладких ділянках, а фокусна відстань — калібруванням, без якого півградуса перекосу знищують 99 % правильних відповідей.

Далі курс іде від «що зображено» до «що з цим робити»: як зібрати з окремих моделей працюючу систему й що ламається, коли вона виходить із лабораторії у світ. Набір вимірів для цього тепер повний — простір, час, текст і глибина.