【opencv】雙目匹配代碼分析

雙目匹配代碼分析

原理

通過將兩個水平放置的攝像頭獲取的圖像,匹配相應的區域視差。方式有很多種,SAD是速度最快的方式,主要原理是塊匹配,計算每個搜索塊在最大視差範圍內的sad值,選擇最小的sad值的水平位移距離作爲視差輸出。

一、區準函數

主要的算法只有一個函數,這裏輸出的是兩幅圖像在x方向上的sobel變化後的圖像。並且通過查找表歸一化數據,可以去掉一些亮度影響。

static void findStereoCorrespondence( const Mat& left, const Mat& right,
                            Mat& disp, Mat& cost, const CvStereoBMState& state,
                            uchar* buf, int _dy0, int _dy1 )
  1. 創建查找表tab
for( x = 0; x < TABSZ; x++ )
        tab[x] = (uchar)std::abs(x - ftzero);

得到tab(0:255) = (31:0, 1:224)

  1. 初始化第一列的搜索窗
for( x = -wsz2-1; x < wsz2; x++ )
    {
        hsad = hsad0 - dy0*ndisp;
        cbuf = cbuf0 + (x + wsz2 + 1)*cstep - dy0*ndisp;
        // 最大視差比搜索窗大很多的情況下,不需要判斷邊界
        lptr = lptr0 + x - dy0*sstep;//std::min(std::max(x, -lofs), width-lofs-1) - dy0*sstep;//
        rptr = rptr0 + x - dy0*sstep;//std::min(std::max(x, -rofs), width-rofs-1) - dy0*sstep;//
        //int val;
        for( y = -dy0; y < height + dy1; y++,
                         hsad += ndisp, cbuf += ndisp,
                         lptr += sstep, rptr += sstep )
        {
            // 左邊減去右邊差的絕對值,在最大視差範圍內計算
            int lval = lptr[0];
            for( d = 0; d < ndisp; d++ )
            {
                int diff = std::abs(lval - rptr[d]);
                //cbuf[dy0+height+dy1][ndisp]
                cbuf[d] = (uchar)diff; // 每一列,當前像素與對應像素差值
                hsad[d] = (int)(hsad[d] + diff);//搜索窗裏sad
            }
            htext[y] += tab[lval]; // 計算一個搜索窗裏左圖像素-31的絕對值之和
            //val = lval;
        }
    }

計算左圖與右圖殘差值,每個像素都減去右圖水平偏移0~disp-1個像素

for x = -wsz2-1:wsz2
    cbuf(x, -dy0:height+dy1, 0:disp-1) = abs(tab(Left(x, -dy0:height+dy1))-tab(right(x:x+disp, -dy0:height+dy1)))
end

先cbuf初始一個搜索窗的殘差值,一共(wsz, H, disp)

hsad[0] = \sum_{n=-wsz2-1}^{wsz2}abs(tab(left(n))-tab(right(n)))
hsad[1] = \sum_{n=-wsz2-1}^{wsz2}abs(tab(left(n))-tab(right(n+1)))
...
hsad[disp-1] = \sum_{n=-wsz2-1}^{wsz2}abs(tab(left(n))-tab(right(n+disp-1)))

上面是計算第(-wsz2-1~wsz2)列範圍內的hsad(hsad命名意思應該是水平方向上的sad)。

htext[i] = \sum_{n=-wsz2-1}^{wsz2}tab(left(n))
i=-wsz2-1:wsz2

htext計算水平方向的紋理。
3. 初始化邊界

// initialize the left and right borders of the disparity map
    for( y = 0; y < height; y++ )
    {
        for( x = 0; x < lofs; x++ )
            dptr[y*dstep + x] = FILTERED;
        for( x = lofs + width1; x < width; x++ )
            dptr[y*dstep + x] = FILTERED;
    }

disp[0:maxDisp-1, :] = -16
disp[W-maxDisp:W, :] = -16

  1. 在搜索窗裏計算sad
// 計算每列sad值,左圖像素-右圖0到最大視差偏移像素
        for( y = -dy0; y < height + dy1; y++,
                        cbuf += ndisp, cbuf_sub += ndisp,
                         hsad += ndisp, lptr += sstep,
                         lptr_sub += sstep, rptr += sstep )
        {
            int lval = lptr[0];
            for( d = 0; d < ndisp; d++ )
            {
                int diff = std::abs(lval - rptr[d]);
                cbuf[d] = (uchar)diff;
                hsad[d] = hsad[d] + diff - cbuf_sub[d];
            }
            htext[y] += tab[lval] - tab[lptr_sub[0]];
        }

在前面的基礎上,計算每個水平方向的sad和text,只需要去掉第一個值再加上當前值。

  1. 填充分界
        // fill borders
        for( y = dy1; y <= wsz2; y++ )
            htext[height+y] = htext[height+dy1-1];
        for( y = -wsz2-1; y < -dy0; y++ )
            htext[y] = htext[-dy0];

當搜索窗和上下邊界不一致時候,需要擴展大小
6. 初始化sad,方便後面判斷

        // initialize sums
        for( d = 0; d < ndisp; d++ )
            sad[d] = (int)(hsad0[d-ndisp*dy0]*(wsz2 + 2 - dy0));

        hsad = hsad0 + (1 - dy0)*ndisp;
        for( y = 1 - dy0; y < wsz2; y++, hsad += ndisp )
            for( d = 0; d < ndisp; d++ )
                sad[d] = (int)(sad[d] + hsad[d]);

計算一個搜索窗口(21x21)內的sad,第一個循環(wsz2+2-dy0)按照設定的參數計算出來爲2,不清楚爲什麼要這麼做。

sad(0:ndisp-1) = \sum_{i=1-dy0}^{wsz2}hsad(i)(0:ndisp)
  1. 初始化tsum
int tsum = 0;
        for( y = -wsz2-1; y < wsz2; y++ )
            tsum += htext[y];
tsum = \sum_{i=-wsz2-1}^{wsz2}htext[i]

計算搜索窗口裏的紋理大小

  1. 匹配,找到合適的匹配窗口,計算出偏移大小
// finally, start the real processing
        for( y = 0; y < height; y++ )
        {
            int minsad = INT_MAX, mind = -1;
            hsad = hsad0 + MIN(y + wsz2, height+dy1-1)*ndisp;
            hsad_sub = hsad0 + MAX(y - wsz2 - 1, -dy0)*ndisp;

            for( d = 0; d < ndisp; d++ )
            {
                int currsad = sad[d] + hsad[d] - hsad_sub[d];
                sad[d] = currsad;
                if( currsad < minsad )
                {
                    minsad = currsad;
                    mind = d;
                }
            }
            tsum += htext[y + wsz2] - htext[y - wsz2 - 1];
            // 紋理閾值,在21x21裏計算左圖像素與31差值的sad
            // 值太小,容易出現誤匹配;值太大,匹配點變少
            if( tsum < textureThreshold )
            {
                dptr[y*dstep] = FILTERED;
                continue;
            }

            // 唯一性判斷,次小值不能太小
            if( uniquenessRatio > 0 )
            {
                int thresh = minsad + (minsad * uniquenessRatio/100);
                for( d = 0; d < ndisp; d++ )
                {
                    if( sad[d] <= thresh && (d < mind-1 || d > mind+1))
                        break;
                }
                if( d < ndisp )
                {
                    dptr[y*dstep] = FILTERED;
                    continue;
                }
            }

            sad[-1] = sad[1];
            sad[ndisp] = sad[ndisp-2];
            int p = sad[mind+1], n = sad[mind-1];
            d = 0;//p + n - 2*sad[mind] + std::abs(p - n);
            // 輸出值爲16bit short類型,原值先乘265再加上一個修正值,最後除16,
            // 最終擴大了16倍, 修正值在正負16之間(擴大了16倍)
            dptr[y*dstep] = (short)(((ndisp - mind - 1 + mindisp)*256 + (d != 0 ? (p-n)*256/d : 0) + 15) >> 4);
            costptr[y*coststep] = sad[mind];
        }

將上面的分支分別分析如下:

  • 計算最小sad
for( d = 0; d < ndisp; d++ )
{
    int currsad = sad[d] + hsad[d] - hsad_sub[d];
    sad[d] = currsad;
    if( currsad < minsad )
    {
        minsad = currsad;
        mind = d;
    }
}
mind = \sum_{i=0:ndisp-1}^{ndisp}sad(i)
  • 計算當前窗口的紋理值
tsum += htext[y + wsz2] - htext[y - wsz2 - 1];
// 紋理閾值,在21x21裏計算左圖像素與31差值的sad
// 值太小,容易出現誤匹配;值太大,匹配點變少
if( tsum < textureThreshold )
{
    dptr[y*dstep] = FILTERED;
    continue;
}

tsum如果太小,當前窗口認爲是無紋理區域,disp直接填充-16。這樣平滑區域不做判斷。

  • 唯一性判斷,次小值不能太小
// 唯一性判斷,次小值不能太小
            if( uniquenessRatio > 0 )
            {
                int thresh = minsad + (minsad * uniquenessRatio/100);
                for( d = 0; d < ndisp; d++ )
                {
                    if( sad[d] <= thresh && (d < mind-1 || d > mind+1))
                        break;
                }
                if( d < ndisp )
                {
                    dptr[y*dstep] = FILTERED;
                    continue;
                }
            }

次小值不能太接近最小值。減少誤判,但匹配點會減少,當紋理不豐富時,這兩個地方能減少誤判,紋理變化大則影響不大。

  • 輸出,增加修正值,修正值對數據大小影響不大,主要是爲了增加一些紋理區別
            sad[-1] = sad[1];
            sad[ndisp] = sad[ndisp-2];
            int p = sad[mind+1], n = sad[mind-1];
            d = 0;//p + n - 2*sad[mind] + std::abs(p - n);
            // 輸出值爲16bit short類型,原值先乘265再加上一個修正值,最後除16,
            // 最終擴大了16倍, 修正值在正負16之間(擴大了16倍)
            dptr[y*dstep] = (short)(((ndisp - mind - 1 + mindisp)*256 + (d != 0 ? (p-n)*256/d : 0) + 15) >> 4);
            costptr[y*coststep] = sad[mind];
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章