OpenCV - карта глибини з каліброваної стереосистеми


174

Я намагаюся отримати карту глибини некаліброваним методом. Я можу отримати фундаментальну матрицю, знайшовши відповідні точки за допомогою SIFT, а потім використовуючи cv2.findFundamentalMat. Потім я використовую cv2.stereoRectifyUncalibratedдля отримання матриць гомографії для кожного зображення. Нарешті, я використовую cv2.warpPerspectiveдля виправлення та обчислення диспропорції, але це не створює хорошої карти глибини. Значення дуже високі, тому мені цікаво, чи доводиться використовувати, warpPerspectiveчи потрібно обчислювати матрицю обертання на основі матриць гомографії, які я отримав stereoRectifyUncalibrated.

Я не впевнений у проективній матриці у випадку гомографічної матриці, отриманої з stereoRectifyUncalibratedвипрямленою.

Частина коду:

#Obtainment of the correspondent point with SIFT
sift = cv2.SIFT()

###find the keypoints and descriptors with SIFT
kp1, des1 = sift.detectAndCompute(dst1,None)
kp2, des2 = sift.detectAndCompute(dst2,None)

###FLANN parameters
FLANN_INDEX_KDTREE = 0
index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
search_params = dict(checks=50)

flann = cv2.FlannBasedMatcher(index_params,search_params)
matches = flann.knnMatch(des1,des2,k=2)

good = []
pts1 = []
pts2 = []

###ratio test as per Lowe's paper
for i,(m,n) in enumerate(matches):
    if m.distance < 0.8*n.distance:
        good.append(m)
        pts2.append(kp2[m.trainIdx].pt)
        pts1.append(kp1[m.queryIdx].pt)
    
    
pts1 = np.array(pts1)
pts2 = np.array(pts2)

#Computation of the fundamental matrix
F,mask= cv2.findFundamentalMat(pts1,pts2,cv2.FM_LMEDS)


# Obtainment of the rectification matrix and use of the warpPerspective to transform them...
pts1 = pts1[:,:][mask.ravel()==1]
pts2 = pts2[:,:][mask.ravel()==1]

pts1 = np.int32(pts1)
pts2 = np.int32(pts2)

p1fNew = pts1.reshape((pts1.shape[0] * 2, 1))
p2fNew = pts2.reshape((pts2.shape[0] * 2, 1))
    
retBool ,rectmat1, rectmat2 = cv2.stereoRectifyUncalibrated(p1fNew,p2fNew,F,(2048,2048))

dst11 = cv2.warpPerspective(dst1,rectmat1,(2048,2048))
dst22 = cv2.warpPerspective(dst2,rectmat2,(2048,2048))

#calculation of the disparity
stereo = cv2.StereoBM(cv2.STEREO_BM_BASIC_PRESET,ndisparities=16*10, SADWindowSize=9)
disp = stereo.compute(dst22.astype(uint8), dst11.astype(uint8)).astype(np.float32)
plt.imshow(disp);plt.colorbar();plt.clim(0,400)#;plt.show()
plt.savefig("0gauche.png")

#plot depth by using disparity focal length `C1[0,0]` from stereo calibration and `T[0]` the distance between cameras

plt.imshow(C1[0,0]*T[0]/(disp),cmap='hot');plt.clim(-0,500);plt.colorbar();plt.show()

Ось виправлені знімки некаліброваним методом (і warpPerspective):

введіть тут опис зображення

Ось виправлені знімки каліброваним методом:

введіть тут опис зображення

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

Карта розбіжностей з використанням некаліброваного методу:

введіть тут опис зображення

Глибини обчислюються з: C1[0,0]*T[0]/(disp) з Т від stereoCalibrate. Значення дуже високі.

------------ РЕДАКТУВАТИ ПІЗНЕ ------------

Я намагався "змонтувати" матрицю реконструкції ( [Devernay97] , [Garcia01] ) за допомогою матриці гомографії, отриманої за допомогою "stereoRectifyUncalibrated", але результат все ще не хороший. Чи правильно я це роблю?

Y=np.arange(0,2048)
X=np.arange(0,2048)
(XX_field,YY_field)=np.meshgrid(X,Y)

#I mount the X, Y and disparity in a same 3D array 
stock = np.concatenate((np.expand_dims(XX_field,2),np.expand_dims(YY_field,2)),axis=2)
XY_disp = np.concatenate((stock,np.expand_dims(disp,2)),axis=2)

XY_disp_reshape = XY_disp.reshape(XY_disp.shape[0]*XY_disp.shape[1],3)

Ts = np.hstack((np.zeros((3,3)),T_0)) #i use only the translations obtained with the rectified calibration...Is it correct?


# I establish the projective matrix with the homography matrix
P11 = np.dot(rectmat1,C1)
P1 = np.vstack((np.hstack((P11,np.zeros((3,1)))),np.zeros((1,4))))
P1[3,3] = 1

# P1 = np.dot(C1,np.hstack((np.identity(3),np.zeros((3,1)))))

P22 = np.dot(np.dot(rectmat2,C2),Ts)
P2 = np.vstack((P22,np.zeros((1,4))))
P2[3,3] = 1

lambda_t = cv2.norm(P1[0,:].T)/cv2.norm(P2[0,:].T)


#I define the reconstruction matrix
Q = np.zeros((4,4))

Q[0,:] = P1[0,:].T
Q[1,:] = P1[1,:].T
Q[2,:] = lambda_t*P2[1,:].T - P1[1,:].T
Q[3,:] = P1[2,:].T

#I do the calculation to get my 3D coordinates
test = []
for i in range(0,XY_disp_reshape.shape[0]):
    a = np.dot(inv(Q),np.expand_dims(np.concatenate((XY_disp_reshape[i,:],np.ones((1))),axis=0),axis=1))
    test.append(a)

test = np.asarray(test)

XYZ = test[:,:,0].reshape(XY_disp.shape[0],XY_disp.shape[1],4)

3
Ви дивились на якість матчів? З огляду на імідж, це може бути проблемою. Це допомогло б, якщо ви опублікували оригінальне зображення.
yhenon

Той факт, що калібрування, здається, не вирівнює зображення, можливо, це тому, що камери були розташовані вертикально (це стосується набору даних mview Міддлбері). Ви можете спробувати намалювати кілька епіліній до і після виправлення, щоб побачити, чи бачите ви покращення.
Габріель Девільєр

31
Вас все ще цікавить відповідь на це питання? Якщо так, чи можете ви розмістити посилання на файли необроблених даних (зображення) та рядки коду, де ви їх читаєте? Будь ласка, додайте безжаргонний опис даних та будь-яких інших наявних у вас параметрів, включаючи геометрію та відстані, навіть якщо вони приблизні.
DrM

2
Будь ласка, опублікуйте вихідні зображення або посилання на них.
DrM

1
По-перше: некалібровані методи завжди менш досконалі від каліброваних (з хорошим калібруванням). По-друге: StereoBMце не найкращий алгоритм узгодження ... Ви можете знайти певне покращення, використовуючи StereoSGBM. Я хотів би допомогти, але я не до кінця зрозумів ваше запитання ...
decadenza

Відповіді:


12

TLDR; Використовуйте StereoSGBM (Semi Global Block Matching) для зображень із більш плавними краями та використовуйте деяку фільтрацію повідомлень, якщо хочете, щоб вона була більш плавною

OP не надав оригінальні зображення, тому я використовую Tsukubaз набору даних Middlebury .

Результат за допомогою звичайного StereoBM

стереобм

Результат за допомогою StereoSGBM (налаштований)

стереосгбм

Найкращий результат, який я міг знайти в літературі

введіть тут опис зображення

Дивіться публікацію тут для деталей.

Приклад фільтрації повідомлень (див. Посилання нижче)

приклад фільтра повідомлення

Теорія / Інші міркування з питання ОП

Великі чорні області ваших відкаліброваних випрямлених зображень змусили б мене думати, що для них калібрування було зроблено не дуже добре. Існує безліч причин, які можуть бути присутніми, можливо, фізична настройка, можливо освітлення під час калібрування тощо, але для цього існує безліч підручників з калібрування камери, і я розумію, що ви просите про спосіб отримати кращу карту глибини за допомогою некаліброваної установки (це не на 100% зрозуміло, але заголовок, схоже, це підтверджує, і я думаю, що це те, що люди прийдуть сюди, щоб спробувати знайти).

Ваш основний підхід правильний, але результати однозначно можна покращити. Ця форма картографування глибини не належить до тих, що виробляють карти найвищої якості (особливо не калібровані). Найбільше поліпшення, ймовірно, відбудеться завдяки використанню іншого алгоритму стереозбігу. Освітлення також може мати значний ефект. Правильне зображення (принаймні для мого неозброєного ока) виглядає менш освітленим, що може перешкодити реконструкції. Спочатку можна спробувати освітлити його до того ж рівня, що й інший, або зібрати нові зображення, якщо це можливо. Відтепер я припускаю, що у вас немає доступу до оригінальних камер, тому я буду розглядати можливість збирання нових зображень, зміни налаштувань або калібрування поза межами сфери застосування. (Якщо у вас є доступ до налаштування та камер,

Ви використовували StereoBMдля обчислення вашої диспропорції (карта глибини), яка працює, але StereoSGBMнабагато краще підходить для цієї програми (вона краще обробляє більш плавні краї). Ви можете побачити різницю нижче.

Ця стаття пояснює різницю в деталях:

Збіг блоків фокусується на зображеннях з високою текстурою (уявіть зображення дерева), а напівглобальні збіги блоків зосереджуються на відповідності рівня пікселів та зображеннях з більш гладкими текстурами (уявіть зображення коридору).

Без явних внутрішніх параметрів камери, особливостей налаштування камери (наприклад, фокусна відстань, відстань між камерами, відстань до об'єкта тощо), відомий розмір зображення або рух (щоб використовувати структуру від руху ), ви можете отримати 3D-реконструкцію лише до проективного перетворення; у вас не буде відчуття масштабу або обов’язково обертання, але ви все одно можете створити відносну карту глибини. Ви, ймовірно, будете страждати від деяких стволів та інших спотворень, які можна було б усунути за допомогою належної калібрування камери, але ви можете отримати розумні результати без цього, якщо камери не страшні (система об'єктивів не надто спотворена) і налаштовані досить близька до канонічної конфігурації(що в основному означає, що вони орієнтовані таким чином, що їх оптичні осі максимально наближені до паралельних, а поля зору достатньо перекриваються). Однак, схоже, це не проблема ОП, оскільки йому вдалося отримати добре виправлені зображення за допомогою некаліброваного методу.

Основна процедура

  1. Знайдіть принаймні 5 добре узгоджених точок на обох зображеннях, які ви можете використовувати для обчислення Фундаментальної матриці (ви можете використовувати будь-який сподобався детектор і збіг, я зберігав FLANN, але використовував ORB для виявлення, оскільки SIFT відсутня в основній версії OpenCV для 4.2.0)
  2. Обчислити фундаментальну матрицю, F, с findFundamentalMat
  3. Не спотворюйте свої зображення за допомогою stereoRectifyUncalibratedіwarpPerspective
  4. Розрахувати нерівність (карта глибини) за допомогою StereoSGBM

Результати набагато кращі:

Збіги з ORB та FLANN

Матчі

Неспотворені зображення (ліворуч, потім праворуч)

неспотворений ліворуч
неспотворене право

Диспропорція

StereoBM

Цей результат виглядає схожим на проблеми ОЗ (вкраплення, прогалини, неправильна глибина в деяких районах).

стереобм

StereoSGBM (налаштований)

Цей результат виглядає набагато краще і використовує приблизно той самий метод, що і OP, за вирахуванням остаточного розрахунку диспропорції, що змушує мене думати, що OP побачить подібні покращення на своїх зображеннях, якби вони були надані.

стереосгбм

Фільтрування повідомлень

Про це є хороша стаття в документах OpenCV. Я б порадив переглянути його, якщо вам потрібні справді плавні карти.

В наведеному вище прикладі фотографії є кадр 1 зі сцени ambush_2в MPI Сінтел Dataset .

приклад фільтра повідомлення

Повний код (протестовано на OpenCV 4.2.0):

import cv2
import numpy as np
import matplotlib.pyplot as plt

imgL = cv2.imread("tsukuba_l.png", cv2.IMREAD_GRAYSCALE)  # left image
imgR = cv2.imread("tsukuba_r.png", cv2.IMREAD_GRAYSCALE)  # right image


def get_keypoints_and_descriptors(imgL, imgR):
    """Use ORB detector and FLANN matcher to get keypoints, descritpors,
    and corresponding matches that will be good for computing
    homography.
    """
    orb = cv2.ORB_create()
    kp1, des1 = orb.detectAndCompute(imgL, None)
    kp2, des2 = orb.detectAndCompute(imgR, None)

    ############## Using FLANN matcher ##############
    # Each keypoint of the first image is matched with a number of
    # keypoints from the second image. k=2 means keep the 2 best matches
    # for each keypoint (best matches = the ones with the smallest
    # distance measurement).
    FLANN_INDEX_LSH = 6
    index_params = dict(
        algorithm=FLANN_INDEX_LSH,
        table_number=6,  # 12
        key_size=12,  # 20
        multi_probe_level=1,
    )  # 2
    search_params = dict(checks=50)  # or pass empty dictionary
    flann = cv2.FlannBasedMatcher(index_params, search_params)
    flann_match_pairs = flann.knnMatch(des1, des2, k=2)
    return kp1, des1, kp2, des2, flann_match_pairs


def lowes_ratio_test(matches, ratio_threshold=0.6):
    """Filter matches using the Lowe's ratio test.

    The ratio test checks if matches are ambiguous and should be
    removed by checking that the two distances are sufficiently
    different. If they are not, then the match at that keypoint is
    ignored.

    /programming/51197091/how-does-the-lowes-ratio-test-work
    """
    filtered_matches = []
    for m, n in matches:
        if m.distance < ratio_threshold * n.distance:
            filtered_matches.append(m)
    return filtered_matches


def draw_matches(imgL, imgR, kp1, des1, kp2, des2, flann_match_pairs):
    """Draw the first 8 mathces between the left and right images."""
    # https://docs.opencv.org/4.2.0/d4/d5d/group__features2d__draw.html
    # https://docs.opencv.org/2.4/modules/features2d/doc/common_interfaces_of_descriptor_matchers.html
    img = cv2.drawMatches(
        imgL,
        kp1,
        imgR,
        kp2,
        flann_match_pairs[:8],
        None,
        flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS,
    )
    cv2.imshow("Matches", img)
    cv2.imwrite("ORB_FLANN_Matches.png", img)
    cv2.waitKey(0)


def compute_fundamental_matrix(matches, kp1, kp2, method=cv2.FM_RANSAC):
    """Use the set of good mathces to estimate the Fundamental Matrix.

    See  https://en.wikipedia.org/wiki/Eight-point_algorithm#The_normalized_eight-point_algorithm
    for more info.
    """
    pts1, pts2 = [], []
    fundamental_matrix, inliers = None, None
    for m in matches[:8]:
        pts1.append(kp1[m.queryIdx].pt)
        pts2.append(kp2[m.trainIdx].pt)
    if pts1 and pts2:
        # You can play with the Threshold and confidence values here
        # until you get something that gives you reasonable results. I
        # used the defaults
        fundamental_matrix, inliers = cv2.findFundamentalMat(
            np.float32(pts1),
            np.float32(pts2),
            method=method,
            # ransacReprojThreshold=3,
            # confidence=0.99,
        )
    return fundamental_matrix, inliers, pts1, pts2


############## Find good keypoints to use ##############
kp1, des1, kp2, des2, flann_match_pairs = get_keypoints_and_descriptors(imgL, imgR)
good_matches = lowes_ratio_test(flann_match_pairs, 0.2)
draw_matches(imgL, imgR, kp1, des1, kp2, des2, good_matches)


############## Compute Fundamental Matrix ##############
F, I, points1, points2 = compute_fundamental_matrix(good_matches, kp1, kp2)


############## Stereo rectify uncalibrated ##############
h1, w1 = imgL.shape
h2, w2 = imgR.shape
thresh = 0
_, H1, H2 = cv2.stereoRectifyUncalibrated(
    np.float32(points1), np.float32(points2), F, imgSize=(w1, h1), threshold=thresh,
)

############## Undistort (Rectify) ##############
imgL_undistorted = cv2.warpPerspective(imgL, H1, (w1, h1))
imgR_undistorted = cv2.warpPerspective(imgR, H2, (w2, h2))
cv2.imwrite("undistorted_L.png", imgL_undistorted)
cv2.imwrite("undistorted_R.png", imgR_undistorted)

############## Calculate Disparity (Depth Map) ##############

# Using StereoBM
stereo = cv2.StereoBM_create(numDisparities=16, blockSize=15)
disparity_BM = stereo.compute(imgL_undistorted, imgR_undistorted)
plt.imshow(disparity_BM, "gray")
plt.colorbar()
plt.show()

# Using StereoSGBM
# Set disparity parameters. Note: disparity range is tuned according to
#  specific parameters obtained through trial and error.
win_size = 2
min_disp = -4
max_disp = 9
num_disp = max_disp - min_disp  # Needs to be divisible by 16
stereo = cv2.StereoSGBM_create(
    minDisparity=min_disp,
    numDisparities=num_disp,
    blockSize=5,
    uniquenessRatio=5,
    speckleWindowSize=5,
    speckleRange=5,
    disp12MaxDiff=2,
    P1=8 * 3 * win_size ** 2,
    P2=32 * 3 * win_size ** 2,
)
disparity_SGBM = stereo.compute(imgL_undistorted, imgR_undistorted)
plt.imshow(disparity_SGBM, "gray")
plt.colorbar()
plt.show()


6

Може виникнути декілька можливих проблем, що спричиняють низьку якість Depth Channelі Disparity Channelте, що призводить до низькоякісної стереопослідовності. Ось 6 із цих питань:

Можливе питання I

  • Неповна формула

Як сказано в слові uncalibrated, stereoRectifyUncalibratedметод екземпляра обчислює для вас перетворення випрямлень, якщо ви не знаєте або не можете знати внутрішні параметри вашої стереопари та її відносне положення в оточенні.

cv.StereoRectifyUncalibrated(pts1, pts2, fm, imgSize, rhm1, rhm2, thres)

де:

# pts1    –> an array of feature points in a first camera
# pts2    –> an array of feature points in a first camera
# fm      –> input fundamental matrix
# imgSize -> size of an image
# rhm1    -> output rectification homography matrix for a first image
# rhm2    -> output rectification homography matrix for a second image
# thres   –> optional threshold used to filter out outliers

А ваш метод виглядає так:

cv2.StereoRectifyUncalibrated(p1fNew, p2fNew, F, (2048, 2048))

Таким чином, ви не будете брати до уваги три параметри: rhm1, rhm2і thres. Якщо a threshold > 0, всі пари точок, які не відповідають епіполярній геометрії, відкидаються перед обчисленням гомографій. В іншому випадку всі точки вважаються інлієрами. Ця формула виглядає так:

(pts2[i]^t * fm * pts1[i]) > thres

# t   –> translation vector between coordinate systems of cameras

Таким чином, я вважаю, що візуальні неточності можуть з’явитися через неповний розрахунок формули.

Ви можете прочитати Калібрування камери та Реконструкцію 3D на офіційному ресурсі.


Можливе питання II

  • Міжвісна відстань

interaxial distanceМіж лівою та правою лінзами камери має бути міцність not greater than 200 mm. Коли interaxial distanceвеличина більша за interocularвідстань, ефект викликається hyperstereoscopyабо hyperdivergenceі призводить не лише до глибокого перебільшення сцени, але і до фізичних незручностей глядача. Прочитайте довідку про стереоскопічне створення фільмів Autodesk, щоб дізнатися більше про цю тему.

введіть тут опис зображення


Можливе питання III

  • Паралельний режим проти камери Toed-In

Наведені візуальні неточності Disparity Mapможуть виникати через неправильне обчислення режиму камери. Багато стереографів віддають перевагу, Toe-In camera modeале, наприклад, віддає перевагу Pixar Parallel camera mode.

введіть тут опис зображення

введіть тут опис зображення


Можливе питання IV

  • Вертикальне вирівнювання

При стереоскопії, якщо відбувається вертикальний зсув (навіть якщо один із видів зміщений вгору на 1 мм), це руйнує надійний стереосистему. Отже, перед генерацією Disparity Mapви повинні бути впевнені, що лівий та правий види вашої стереопари відповідно вирівняні. Подивіться на Technicolor Sterreoscopic Whitepaper про 15 типових проблем у стерео.

Матриця стереовиправлення:

   ┌                  ┐
   |  f   0   cx  tx  |
   |  0   f   cy  ty  |   # use "ty" value to fix vertical shift in one image
   |  0   0   1   0   |
   └                  ┘

Ось StereoRectifyметод:

cv.StereoRectify(cameraMatrix1, cameraMatrix2, distCoeffs1, distCoeffs2, imageSize, R, T, R1, R2, P1, P2, Q=None, flags=CV_CALIB_ZERO_DISPARITY, alpha=-1, newImageSize=(0, 0)) -> (roi1, roi2)


Можливе питання V

  • Спотворення лінзи

Спотворення об'єктива - дуже важлива тема стерео композиції. Перш ніж генерувати a, Disparity Mapвам потрібно спотворити лівий і правий подання, після цього згенеруйте канал невідповідності, а потім знову переконайте обидва подання.

введіть тут опис зображення

введіть тут опис зображення


Можливе питання VI

  • Низькоякісний канал глибини без згладжування

Для створення високоякісного Disparity Mapвам потрібні лівий і правий, Depth Channelsякі повинні бути попередньо створеними. Коли ви працюєте в 3D-пакеті, ви можете зробити високоякісний канал глибини (із чіткими краями) лише одним клацанням миші. Але створити високоякісний канал глибини з відеопослідовності непросто, тому що стереопара повинна рухатися у вашому середовищі для отримання вихідних даних для майбутнього алгоритму глибини від руху. Якщо у кадрі немає руху, глибинний канал буде надзвичайно поганим.

введіть тут опис зображення

Крім того, у Depthсамого каналу є ще один недолік - його краї не збігаються з краями RGB, оскільки він не має згладжування .

введіть тут опис зображення


Фрагмент коду каналу невідповідності:

Тут я хотів би представити швидкий підхід для створення Disparity Map:

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt

imageLeft = cv.imread('paris_left.png', 0)
imageRight = cv.imread('paris_right.png', 0)
stereo = cv.StereoBM_create(numDisparities=16, blockSize=15)
disparity = stereo.compute(imageLeft, imageRight)
plt.imshow(disparity, 'gray')
plt.show()

введіть тут опис зображення


Як ви створили ці кінцеві зображення? У вас є приклад коду, який я можу спробувати?
Matthew Salvatore Viglione

Ці зображення генеруються за допомогою композиційного програмного забезпечення. Для них немає прикладу коду.
Енді Федоров,

1
Ах, це має більше сенсу. Я ніколи не бачив карти глибини, яка очищається за допомогою простої стереопари в OpenCV (особливо некаліброваної)
Метью Сальваторе Вільоне
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.