計算機視覺--立體視覺(NCC視差匹配)

前言

立體匹配主要是通過找出每對圖像間的對應關係,根據三角測量原理,得到視差圖;在獲得了視差信息後,根據投影模型很容易地可以得到原始圖像的深度信息和三維信息。立體視覺匹配在立體視覺研究中是比較核心的問題。立體視覺應用:車導航,3D場景重建等。本文主要介紹窗口匹配方法NCC視差匹配

一、立體視覺原理

1.1 幾何原理

一個多視圖成像的特殊例子是主體視覺(或者立體成像),即使用兩臺只有水平(向一側) 偏移的照相機觀測同一場景。當照相機的位置如上設置,兩幅圖像具有相同的圖像平面,圖像的行是垂直對齊的,那麼稱圖像對是經過矯正的。
假設兩幅圖像經過了矯正,那麼對應點的尋找限制在圖像的同一行上。一旦找到對應點,由於深度是和偏移成正比的,那麼深度(Z座標)可以直接由水平偏移來計算。那麼Z該如何計算呢?先看下圖:
在這裏插入圖片描述
通過幾何知識運算,得到T+xlxrT=ZfZ \frac{T+x_{l}-x_{r}}{T}=\frac{Z-f}{Z} 進一步化簡,可得Z=fTxrxl Z=f\frac{T}{x_{r}-x_{l}} 其中,f是經過矯正圖像的焦距,T是兩個照相機中心之間的距離,xlxrx_{l}和x_{r}是左右兩幅圖像中對應點的x座標。分開照相機中心的距離稱爲基線。

1.2 立體匹配的步驟

1)匹配代價計算

計算匹配代價,即計算參考圖像上每個像素點IR(P)IR(P) ,以所有視差可能性去匹配目標圖像上對應點IT(pd)IT(pd)的代價值,因此計算得到的代價值可以存儲在一個hwd(MAX)h*w*d(MAX)的三維數組中,通常稱這個三維數組爲視差空間圖。匹配代價時立體匹配的基礎,設計抗噪聲干擾、對光照變化不敏感的匹配代價,能提高立體匹配的精度。因此,匹配代價的設計在全局算法和局部算法中都是研究的重點。

2)代價聚合:

通常全局算法不需要代價聚合,而局部算法需要通過求和、求均值或其他方法對一個支持窗口內的匹配代價進行聚合而得到參考圖像上一點p在視差d處的累積代價CA(p,d)CA(p,d),這一過程稱爲代價聚合。通過匹配代價聚合,可以降低異常點的影響,提高信噪比進而提高匹配精度。代價聚合策略通常是局部匹配算法的核心,策略的好壞直接關係到最終視差圖(Disparity maps)的質量。

3)視差計算

局部立體匹配算法的思想,在支持窗口內聚合完匹配代價後,獲取視差的過程通常採用‘勝者爲王’策略(WTA,Winner Take All),即在視差搜索範圍內選擇累積代價最優的點作爲對應匹配點,與之對應的視差即爲所求的視差。即P點的視差爲d=argminCA(p,d) d=arg minCA(p,d) 但是這種方法可能導致噪聲大,因爲如果直接用點的像素值座標匹配,風險太大,找到的匹配點不一定是正確的,這時可以採用窗口匹配方法,關於這種方法在下面會做詳細介紹。

4)後處理:

一般的,分別以左右兩圖爲參考圖像,完成上述三個步驟後可以得到左右兩幅視差圖像。但所得的視差圖還存在一些問題,如遮擋點視差不準確、噪聲點、誤匹配點等存在,因此還需要對視差圖進行優化,採用進一步執行後處理步驟對視差圖進行修正。常用的方法有插值(Interpolation)、亞像素增強(Subpixel Enhancement)、精細化(Refinement)、圖像濾波(Image Filtering)等操作。

1.3 窗口匹配方法

在上面提到過,如果使用點匹配方法的話,可能導致噪聲大,因爲直接找最匹配的點誤差太大,如下圖,比如我們要在(T)圖中找(R)圖標示的眼睛位置匹配點,由於兩邊眼睛的像素值相近,很可能找錯,造成誤差。
在這裏插入圖片描述
這時,我們就可以考慮改進的方法:窗口匹配方法。它的主要思路是:以當前點爲中心,切出一個圖像塊,每個圖像塊依次做對比,找到最接近的圖像塊。這時就不只是拿單個點做對比了,可以有效減小誤差。舉例見下圖:
在這裏插入圖片描述

匹配代價

相似性測度函數用於度量參考圖像中的匹配基元和目標圖像中的匹配基元的相似性,即判斷參考圖像和目標圖像中兩點爲對應匹配點的可能性,也稱爲匹配代價,這裏記爲C(x,y,d)C(x,y,d),表示像素點(x,y)(x,y)在視差爲d情況下的匹配誤差。
下面介紹最常見的三種匹配代價:
(1)絕對差值和:(Sum of Absolute Differences,SADC(x,y,d)=xεSIR(x,y)IT(x+d,y) C(x,y,d)=\sum_{x\varepsilon S}^{}\left | I_{R}(x,y)-I_{T}(x+d,y)\right | (2)差值平方和(Sum of squared Differences,SSDC(x,y,d)=xεS(IR(x,y)IT(x+d,y))2 C(x,y,d)=\sum_{x\varepsilon S}^{}(I_{R}(x,y)-I_{T}(x+d,y))^{2} (3)截斷絕對差值和(Sum of Truncated Absolute Differences,STADC(x,y,d)=xεSmin{IR(x,y)IT(x+d,y),T} C(x,y,d)=\sum_{x\varepsilon S}^{}min\left \{\left | I_{R}(x,y)-I_{T}(x+d,y)\right |,T\right \} 另外常用的匹配代價還有歸一化互相關NCC(Normalized Cross Correlation),在下面做詳細介紹。

1.4 歸一化互相關(NCC)

原理:

對於原始的圖像內任意一個像素點(px,py)(p_{x},p_{y})構建一個n x n的鄰域作爲匹配窗口。然後對於目標相素位置(px+d,py)(p_{x}+d,p_{y})同樣構建一個n x n大小的匹配窗口,對兩個窗口進行相似度度量,注意這裏的d有一個取值範圍。對於兩幅圖像來說,在進行NCC計算之前要對圖像處理,也就是將兩幀圖像校正到水平位置,即光心處於同一水平線上,此時極線是水平的,否則匹配過程只能在傾斜的極線方向上完成,這將消耗更多的計算資源。

NCC計算公式:
在這裏插入圖片描述
其中 NCC(p,d)NCC(p,d)得到的值得範圍將在[−1,1]之間。

  • WpW_{p}爲之前提到的匹配窗口。
  • I1(x,y)I_{1}(x,y)爲原始圖像的像素值。
  • I1ˉ(px,py)\bar{I_{1}}(p_{x},p_{y})爲原始窗口內像素的均值。
  • I2(x+d,y)I_{2}(x+d,y)爲原始圖像在目標圖像上對應點位置在x方向上偏移d後的像素值。
  • I2ˉ(px+d,py)\bar{I_{2}}(p_{x}+d,p_{y})爲目標圖像匹配窗口像素均值。

若NCC=−1,則表示兩個匹配窗口完全不相關,相反,若NCC=1時,表示兩個匹配窗口相關程度非常高。

匹配流程:

  • 採集圖像:通過標定好的雙目相機採集圖像,當然也可以用兩個單目相機來組合成雙目相機。

  • 極線校正:校正的目的是使兩幀圖像極線處於水平方向,或者說是使兩幀圖像的光心處於同一水平線上。通過校正極線可以方便後續的NCC操作。

    由標定得到的內參中畸變信息中可以對圖像去除畸變。
    通過校正函數校正以後得到相機的矯正變換R和新的投影矩陣P,接下來是要對左右視圖進行去畸變,並得到重映射矩陣。

  • 特徵匹配:這裏便是我們利用NCC做匹配的步驟啦,匹配方法如上所述,右視圖中與左視圖待測像素同一水平線上相關性最高的即爲最優匹配。完成匹配後,我們需要記錄其視差d,即待測像素水平方向xl與匹配像素水平方向xr之間的差值d=xrxld=x_{r}-x_{l},最終我們可以得到一個與原始圖像尺寸相同的視差圖D。

  • 深度恢復:通過上述匹配結果得到的視差圖D,我們可以很簡單的利用相似三角形反推出以左視圖爲參考系的深度圖。計算原理同上述1.1中介紹的幾何原理(這裏不做重述),即通過公式:Z=fTxrxlZ=f\frac{T}{x_{r}-x_{l}}可以簡單得到以左視圖爲參考系的深度圖了。

二、實驗內容

2.1 實驗目的與要求

  • 實現NCC 視差匹配方法,即給定左右兩張視圖,根據NCC計算視差圖
  • 分析不同窗口值對匹配結果的影響,重點考查那些點(或者哪些類型的點)在不同窗口大小下的匹配精度影響

2.2 代碼實現

# -*- coding: utf-8

from pylab import *
from numpy import *

np.seterr(divide='ignore', invalid='ignore')
from PIL import Image
import numpy as np
import math
from scipy import ndimage


def plane_sweep_ncc(im_l, im_r, start, steps, wid):
    """ 使用歸一化的互相關計算視差圖像 該函數返回每個像素的最佳視差"""
    m, n = im_l.shape
    # 保存不同求和值的數組
    mean_l = np.zeros((m, n))
    mean_r = np.zeros((m, n))
    s = np.zeros((m, n))
    s_l = np.zeros((m, n))
    s_r = np.zeros((m, n))
    # 保存深度平面的數組
    dmaps = np.zeros((m, n, steps))
    # 計算圖像塊的平均值
    ndimage.filters.uniform_filter(im_l, wid, mean_l)
    ndimage.filters.uniform_filter(im_r, wid, mean_r)
    # 歸一化圖像
    norm_l = im_l - mean_l
    norm_r = im_r - mean_r
    # 嘗試不同的視差
    for displ in range(steps):
        # 將左邊圖像移動到右邊,計算加和
        ndimage.filters.uniform_filter(np.roll(norm_l, -displ - start) * norm_r, wid, s)  # 和歸一化
        ndimage.filters.uniform_filter(np.roll(norm_l, -displ - start) * np.roll(norm_l, -displ - start), wid, s_l)
        ndimage.filters.uniform_filter(norm_r * norm_r, wid, s_r)  # 和反歸一化
        # 保存 ncc 的分數
        dmaps[:, :, displ] = s / np.sqrt(s_l * s_r)
    # 爲每個像素選取最佳深度
    return np.argmax(dmaps, axis=2)


def plane_sweep_gauss(im_l, im_r, start, steps, wid):
    """ 使用帶有高斯加權周邊的歸一化互相關計算視差圖像 """
    m, n = im_l.shape
    # 保存不同加和的數組
    mean_l = np.zeros((m, n))
    mean_r = np.zeros((m, n))
    s = np.zeros((m, n))
    s_l = np.zeros((m, n))
    s_r = np.zeros((m, n))
    # 保存深度平面的數組
    dmaps = np.zeros((m, n, steps))
    # 計算平均值
    ndimage.filters.gaussian_filter(im_l, wid, 0, mean_l)
    ndimage.filters.gaussian_filter(im_r, wid, 0, mean_r)
    # 歸一化圖像
    norm_l = im_l - mean_l
    norm_r = im_r - mean_r
    # 嘗試不同的視差
    for displ in range(steps):
        # 將左邊圖像移動到右邊,計算加和
        ndimage.filters.gaussian_filter(np.roll(norm_l, -displ - start) * norm_r, wid, 0, s)  # 和歸一化
        ndimage.filters.gaussian_filter(np.roll(norm_l, -displ - start) * np.roll(norm_l, -displ - start), wid, 0, s_l)
        ndimage.filters.gaussian_filter(norm_r * norm_r, wid, 0, s_r)  # 和反歸一化
        # 保存 ncc 的分數
        dmaps[:, :, displ] = s / np.sqrt(s_l * s_r)
    # 爲每個像素選取最佳深度
    return np.argmax(dmaps, axis=2)


"""載入圖像,並使用該函數計算偏移圖"""
im_l = np.array(Image.open('../data/data_six/p1.png').convert('L'), 'f')
im_r = np.array(Image.open('../data/data_six/p2.png').convert('L'), 'f')
figure()
gray()
subplot(221)
imshow(im_l)
subplot(222)
imshow(im_r)

# 開始偏移,並設置步長
steps = 12
start = 4
# ncc 的寬度
wid = 13
res = plane_sweep_ncc(im_l, im_r, start, steps, wid)
wid2 = 5
gauss = plane_sweep_gauss(im_l, im_r, start, steps, wid2)
subplot(223)
imshow(res)
subplot(224)
imshow(gauss)
show()
import scipy.misc

scipy.misc.imsave('depth.png', res)

2.3 實驗結果與分析

圖片來源:
下載鏈接爲: 視差匹配實驗圖片集.

NCC版本與高斯版本的視差圖:
在這裏插入圖片描述

分析:如上圖所示,在標準版本中設置wid=9,高斯版本中設置wid=3。上面一行圖像所示爲圖像對,左下方圖像是標準的NCC掃平面法重建的視差圖,右下方是高斯版本的視差圖。可以看到,與標準版本相比,高斯版本具有較少的噪聲,但缺少很多細節信息。

NCC視差匹配及不同窗口值對匹配結果的影響:
爲了實現一次性輸出不同wid值下的視差圖,修改代碼:

wid = np.array([1,3,5,7,9,11])
res=[]
for i in range(0, 6):
    res.append(plane_sweep_ncc(im_l, im_r, start, steps, wid[i]))
a = np.array([231,232,233,234,235,236])
color_d = ['ncc wid=1', 'ncc wid=3', 'ncc wid=5','ncc wid=7','ncc wid=9','ncc wid=11']
for i in range(0, 6):
    subplot(a[i])
    imshow(res[i])
    title(color_d[i])
    axis('off')

原圖:
在這裏插入圖片描述NCC視差匹配(不同窗口值):
分別爲wid=1、3、5、7、9、11的視差圖:
在這裏插入圖片描述
分析:

  • 如上圖所示,首先對單張NCC視差匹配的結果做分析,比如觀察當wid=9時的結果(如下圖):
    在這裏插入圖片描述
    可以看到離相機越近的物體亮度越高,比如靠近相機的筆,它們整體呈現的更亮,而後面的背景則更暗。這是因爲前景的位移比背景的位移更多,越靠近攝像機的目標,它在左右視圖位移的距離會更多,這是由近大遠小導致。結合原理來分析,我們可以觀察公式:Z=fTxrxl Z=f\frac{T}{x_{r}-x_{l}} 發現ZxrxlZ和x_{r}-x_{l}呈反比關係,因此視差越大,Z越小,即目標越近,視差越大,深度越小。
  • 觀察不同窗口值(wid)的運行結果,初步觀察可得知:隨着wid值的增大,圖像深度信息的圖像的輪廓逐漸明顯,可以看到離照相機最近的目標是筆,隨着wid增大,筆的輪廓逐漸清晰,但較遠處的物體,比如牆壁以及畫布,則越來越難以辨認。
  • 進一步觀察結果,這次拿出wid=3與wid=11的結果作對比,如下圖:
    在這裏插入圖片描述 可以看到wid越小,能得到更多的細節,比如見wid=3時的結果圖,雖然圖像整體比較模糊,但是可以看到我們能夠得到更多筆的細節信息,從原理分析,當wid值較小,進行濾波時考慮的因素少,只受某像素點自身影響以及周圍少量像素點的影響,所以保留下來更多的自身特徵。但同時噪聲也大,見wid=3時的背景牆,上面出現了許多噪聲點。
  • 而當窗口值增大時,魯棒性增強,噪聲明顯減少,但我們可以發現圖像的細節也在逐漸減少,可物體的輪廓逐漸清晰,尤其是近景目標,如wid=11的視差圖,我們能夠更清晰地看出筆的輪廓,但是由於細節減少,遠處的物體就比較不易判斷。在wid值較大情況下,NCC匹配下充分考慮了周圍像素點的影響,能更好的觀察出視差。

三、總結

實驗內容總結:
    經過本次實驗,主要實現了NCC視差匹配,發現越靠近攝像機的目標,視差越大,因此視差圖中近景目標的圖像更亮。對於不同wid值,wid越小,能得到更多的細節,但是噪聲大;wid越大,具有更好的魯棒性,但是得到的細節少。

實驗過程總結:
①問題:在使用高斯濾波器做視差匹配時報錯:
在這裏插入圖片描述解決辦法:改變sqrt()函數調用的庫,即將原本的從math庫調用改爲從numpy庫調用,修改代碼:

dmaps[:, :, displ] = s / np.sqrt(s_l * s_r)

②問題:輸出結果模糊不清,無法看到輪廓。
解決辦法:調整圖片的像素值,而且注意左右兩張圖拍攝時位移不能太大。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章