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)で修正された画像は次のとおりです。

ここに画像の説明を入力してください

キャリブレーションされた方法で修正された画像は次のとおりです。

ここに画像の説明を入力してください

2種類の写真の違いがどれほど重要なのかわかりません。また、キャリブレーションされたメソッドの場合、調整されていないようです。

キャリブレーションされていない方法を使用した視差マップ:

ここに画像の説明を入力してください

深さは:C1[0,0]*T[0]/(disp) で計算されますstereoCalibrate。値は非常に高いです。

------------後で編集------------

「stereoRectifyUncalibrated」で得られたホモグラフィ行列を使用して再構成行列([Devernay97][Garcia01])を「マウント」しようとしましたが、結果はまだ良くありません。私はこれを正しく行っていますか?

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 2016

キャリブレーションが画像を整列させていないように見えるという事実については、おそらくカメラが垂直に積み重ねられていたためです(これはミドルベリーのmviewデータセットの場合です)。修正の前後にいくつかのエピラインを描画して、改善が見られるかどうかを確認することができます。
Gabriel Devillers 2017

31
この質問への回答にまだ興味がありますか?もしそうなら、あなたはあなたの生データファイル(画像)とあなたがそれらを読んだコード行へのリンクを投稿できますか?また、おおよその場合でも、データの専門用語を使用しない説明と、ジオメトリや距離などのその他のパラメータを含めてください。
DRM

2
生の画像またはそれらへのリンクを投稿してください。
DRM

1
まず、キャリブレーションされていないメソッドは、キャリブレーションされたメソッドよりも常に完全ではありません(適切なキャリブレーションが行われている場合)。第二に:StereoBMそれは最良のマッチングアルゴリズムではありません...あなたはを使用していくつかの改善を見つけるかもしれませんStereoSGBM。お手伝いしたいのですが、あなたの質問を完全には理解していませんでした...
デデンザ

回答:


12

TLDR; エッジが滑らかな画像にはStereoSGBM(Semi Global Block Matching)を使用し、さらに滑らかにしたい場合はポストフィルタリングを使用します

OPは元の画像を提供しなかったのでTsukubaミドルベリーのデータセットから使用しています

通常のStereoBMでの結果

ステレオブム

StereoSGBMでの結果(調整済み)

Stereosgbm

私が文学で見つけることができた最高の結果

ここに画像の説明を入力してください

詳細については、こちらの出版物を参照してください。

ポストフィルタリングの例(以下のリンクを参照)

ポストフィルターの例

OPの質問からの理論/その他の考慮事項

キャリブレーションされた修正画像の大きな黒い領域は、それらの場合、キャリブレーションがあまりうまく行われていなかったと私に信じさせます。物理的な設定、キャリブレーションを行ったときの照明など、さまざまな理由が考えられますが、そのためのカメラキャリブレーションチュートリアルはたくさんあります。私の理解では、次の方法を求めていると思います。キャリブレーションされていないセットアップからより良い深度マップを取得します(これは100%明確ではありませんが、タイトルはこれをサポートしているようで、人々がここで見つけようとするものだと思います)。

基本的なアプローチは正しいですが、結果は間違いなく改善できます。この形式の深度マッピングは、最高品質のマップを生成するものの1つではありません(特にキャリブレーションされていない場合)。最大の改善は、異なるステレオマッチングアルゴリズムを使用することからもたらされる可能性があります。照明も大きな影響を及ぼしている可能性があります。右の画像(少なくとも私の肉眼では)はあまり明るくないように見え、再構成を妨げる可能性があります。まず、他の画像と同じレベルまで明るくしてみるか、可能であれば新しい画像を収集します。ここからは、元のカメラにアクセスできないと想定するため、新しい画像の収集、設定の変更、またはキャリブレーションの実行を範囲外と見なします。(セットアップとカメラにアクセスできる場合は、

動作するStereoBM視差(深度マップ)の計算に使用しStereoSGBMましたが、このアプリケーションにははるかに適しています(より滑らかなエッジをより適切に処理します)。以下に違いがあります。

この記事では、違いについてさらに詳しく説明します。

ブロックマッチングは、高テクスチャ画像(木の写真を考えてください)に焦点を合わせ、セミグローバルブロックマッチングは、サブピクセルレベルのマッチングとより滑らかなテクスチャの画像(廊下の写真を考えてください)に焦点を合わせます。

明示的な固有のカメラパラメータ、カメラ設定に関する詳細(焦点距離、カメラ間の距離、被写体までの距離など)、画像内の既知の寸法、またはモーション(モーションから構造を使用するため)がなくても、次のことができます。射影変換までの3D再構成のみを取得します。スケール感や必ずしも回転感はありませんが、相対深度マップを生成することはできます。適切なカメラキャリブレーションで取り除くことができるバレルやその他の歪みに悩まされる可能性がありますが、カメラがひどくなく(レンズシステムがあまり歪んでいない)、きれいにセットアップされている限り、それなしで妥当な結果を得ることができます正規構成に近い(これは基本的に、光軸が可能な限り平行に近くなり、視野が十分に重なるように方向付けられていることを意味します)。ただし、これはOPの問題ではないようです。彼は、キャリブレーションされていない方法で問題なく修正された画像を取得できたからです。

基本手順

  1. 基本マトリックスの計算に使用できる両方の画像で少なくとも5つのよく一致するポイントを見つけます(好きな検出器とマッチャーを使用できます。FLANNを維持しましたが、SIFTはOpenCVのメインバージョンにないため、ORBを使用して検出を行いました4.2.0の場合)
  2. で基本行列Fを計算します。 findFundamentalMat
  3. stereoRectifyUncalibratedとで画像の歪みを解消しますwarpPerspective
  4. との視差(深度マップ)の計算 StereoSGBM

結果ははるかに優れています:

ORBおよびFLANNと一致します

マッチス

歪みのない画像(左、次に右)

歪んでいない左
歪んでいない右

格差

StereoBM

この結果は、OPの問題(斑点、ギャップ、一部の領域での間違った深さ)に似ています。

ステレオブム

StereoSGBM(調整済み)

この結果ははるかに見栄えが良く、OPとほぼ同じ方法を使用していますが、最終的な視差の計算を除いているため、OPが提供されていれば、画像に同様の改善が見られると思います。

Stereosgbm

ポストフィルタリング

これに関する良い記事がOpenCVのドキュメントにあります。本当に滑らかなマップが必要な場合は、それを確認することをお勧めします。

上の写真の例ambush_2は、MPI SintelDatasetのシーンのフレーム1です。

ポストフィルターの例

完全なコード(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

  • 不完全な式

一言で言えばuncalibratedstereoRectifyUncalibratedインスタンスメソッドは、ステレオペアの固有のパラメータと環境内での相対的な位置がわからない、またはわからない場合に備えて、修正変換を計算します。

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))

だから、あなたのアカウントに3つのパラメータを取ることはありませんrhm1rhm2thres。の場合、ホモグラフィを計算する前に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、シーンの奥行きも過言ではなく、視聴者の物理的な不便さだけでなく結果。このトピックの詳細については、オートデスクのステレオスコピックフィルムメイキングホワイトペーパーをお読みください。

ここに画像の説明を入力してください


考えられる問題III

  • パラレルvsつま先-カメラモード

Disparity Mapカメラモードの計算が正しくないために、結果として生じる視覚的な不正確さが発生する可能性があります。多くのステレオグラファーは好みますToe-In camera modeが、たとえばピクサーは好みParallel camera modeます。

ここに画像の説明を入力してください

ここに画像の説明を入力してください


考えられる問題IV

  • 垂直方向の配置

ステレオスコピーでは、垂直方向のシフトが発生すると(ビューの1つが1 mm上にシフトされた場合でも)、堅牢なステレオエクスペリエンスが台無しになります。したがって、生成Disparity Mapする前に、ステレオペアの左右のビューが適切に配置されていることを確認する必要があります。ステレオの15の一般的な問題についてのTechnicolorSterreoscopicWhitepaperをご覧ください。

ステレオ整流マトリックス:

   ┌                  ┐
   |  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

  • レンズの歪み

レンズの歪みは、ステレオ構成において非常に重要なトピックです。を生成する前にDisparity Map、左右のビューの歪みを解消する必要があります。その後、視差チャネルを生成してから、両方のビューを再度歪みます。

ここに画像の説明を入力してください

ここに画像の説明を入力してください


考えられる問題VI

  • アンチエイリアシングのない低品質の深度チャネル

高品質Disparity Mapを作成するには、Depth Channels事前に生成する必要がある左右が必要です。3Dパッケージで作業する場合、ワンクリックで高品質の深度チャネル(鮮明なエッジ)をレンダリングできます。ただし、将来のモーションからの深度アルゴリズムの初期データを生成するためにステレオペアを環境内で移動する必要があるため、ビデオシーケンスから高品質の深度チャネルを生成することは容易ではありません。フレームに動きがない場合、深度チャネルは非常に貧弱になります。

ここに画像の説明を入力してください

また、Depthチャネル自体にはもう1つの欠点があります。アンチエイリアシングがないため、そのエッジが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 SalvatoreViglione20年

これらの画像は、合成ソフトウェアで生成されます。それらのサンプルコードはありません。
AndyFedoroff20年

1
ああ、それはもっと理にかなっています。OpenCV(特にキャリブレーションされていない)で単純なステレオペアできれいになる深度マップを見たことがありません
Matthew SalvatoreViglione20年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.