1.NCC匹配介紹
1.1原理
對於原始的圖像內任意一個像素點(px,py) 構建一個n×n的鄰域作爲匹配窗口。然後對於目標相素位置(px+d,py) 同樣構建一個n×n大小的匹配窗口,對兩個窗口進行相似度度量,注意這裏的d有一個取值範圍。對於兩幅圖像來說,在進行NCC計算之前要對圖像處理,也就是將兩幀圖像校正到水平位置,即光心處於同一水平線上,此時極線是水平的,否則匹配過程只能在傾斜的極線方向上完成,這將消耗更多的計算資源。
NCC計算公式如下圖所示:
其中NCC(p,d)得到的值得範圍將在[−1,1]之間 。
Wp 爲之前提到的匹配窗口。
I1 (x,y) 爲原始圖像的像素值。
I1(px,py) 爲原始窗口內像素的均值。
I2(x+d,y) 爲原始圖像在目標圖像上對應點位置在x方向上偏移d後的像素值。
I2(px+d,py) 爲目標圖像匹配窗口像素均值。
- 若NCC=−1則表示兩個匹配窗口完全不相關,相反,若NCC=1 時,表示兩個匹配窗口相關程度非常高。
1.2匹配流程
1.採集圖像:通過標定好的雙目相機採集圖像,當然也可以用兩個單目相機來組合成雙目相機。
2.極線校正:校正的目的是使兩幀圖像極線處於水平方向,或者說是使兩幀圖像的光心處於同一水平線上。通過校正極線可以方便後續的NCC操作。
- 由標定得到的內參中畸變信息中可以對圖像去除畸變。
- 通過校正函數校正以後得到相機的矯正變換R和新的投影矩陣P,接下來是要對左右視圖進行去畸變,並得到重映射矩陣。
3.特徵匹配:這裏便是我們利用NCC 做匹配的步驟啦,匹配方法如上所述,右視圖中與左視圖待測像素同一水平線上相關性最高的即爲最優匹配。完成匹配後,我們需要記錄其視差d,即待測像素水平方向xl與匹配像素水平方向xr之間的差值d=xr-xl,最終我們可以得到一個與原始圖像尺寸相同的視差圖D。
4.深度恢復:通過上述匹配結果得到的視差圖D,我們可以很簡單的利用相似三角形反推出以左視圖爲參考系的深度圖。
2.代碼
# -*- coding: utf-8 -*-
from PIL import Image
from pylab import *
import cv2
from numpy import *
from numpy.ma import array
from scipy.ndimage import filters
def plane_sweep_ncc(im_l,im_r,start,steps,wid):
""" 使用歸一化的互相關計算視差圖像 """
m,n = im_l.shape
# 保存不同求和值的數組
mean_l = zeros((m,n))
mean_r = zeros((m,n))
s = zeros((m,n))
s_l = zeros((m,n))
s_r = zeros((m,n))
# 保存深度平面的數組
dmaps = zeros((m,n,steps))
# 計算圖像塊的平均值
filters.uniform_filter(im_l,wid,mean_l)
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):
# 將左邊圖像移動到右邊,計算加和
filters.uniform_filter(np.roll(norm_l, -displ - start) * norm_r, wid, s) # 和歸一化
filters.uniform_filter(np.roll(norm_l, -displ - start) * np.roll(norm_l, -displ - start), wid, s_l)
filters.uniform_filter(norm_r*norm_r,wid,s_r) # 和反歸一化
# 保存 ncc 的分數
dmaps[:,:,displ] = s / 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 = zeros((m,n))
mean_r = zeros((m,n))
s = zeros((m,n))
s_l = zeros((m,n))
s_r = zeros((m,n))
# 保存深度平面的數組
dmaps = zeros((m,n,steps))
# 計算平均值
filters.gaussian_filter(im_l,wid,0,mean_l)
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):
# 將左邊圖像移動到右邊,計算加和
filters.gaussian_filter(np.roll(norm_l, -displ - start) * norm_r, wid, 0, s) # 和歸一化
filters.gaussian_filter(np.roll(norm_l, -displ - start) * np.roll(norm_l, -displ - start), wid, 0, s_l)
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 = array(Image.open(r'C:\Python\Pictrue\NCC/im2.ppm').convert('L'), 'f')
im_r = array(Image.open(r'C:\Python\Pictrue\NCC/im6.ppm').convert('L'),'f')
# 開始偏移,並設置步長
steps = 12
start = 4
# ncc 的寬度
wid = 2
res = plane_sweep_ncc(im_l,im_r,start,steps,wid)
import scipy.misc
scipy.misc.imsave('depth.png',res)
show()
3.結果和分析
3.1實驗結果展示
實驗使用圖片:
窗口值爲5時:
窗口值爲9時:
窗口值爲20時:
3.2小結
- 通過對比窗口值爲5,9和20時的視差圖可以看出,在窗口值爲9 時的匹配深度選取較好窗口值爲5和20時略好,層次較爲清晰,錯配也較少。在窗口值爲5時出現大幅度錯誤匹配。
- 通過調節窗口大小和圖片的大小,可以適當提高運行的速率,由原理可知,視差越大,在視差圖中體現出來的越亮。再通過對視差圖的對比可知,距離攝像機距離越近當窗口值增大時,去噪效果有顯著的提升,但是細節處的深度處理會隨着窗口值變大而逐漸被略去而使輪廓變得模糊。
- 雙目測距的精度是很難進行理論分析的,需要根據實際情況去驗證。一般來說,距離越遠,誤差越大,因此雙目測距不適宜測量太遠的目標。如果想要對與較遠的目標能夠得到較爲可靠的深度,需要提高相機的基線距離,但是同時會導致左右視圖的差異變大從而提高立體匹配的難度。
4.遇到的問題及解決方法
4.1圖片呈雪花狀
解決方法:在試過了多組圖片以後發現,圖片在300多k的情況下就會呈雪花狀,所以將圖片壓縮到了20多k就可以成功顯示視差圖了。