1. 程式人生 > 其它 >計算機視覺基本知識概念(二)

計算機視覺基本知識概念(二)

參考:http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/tutorials.html

矩陣的掩碼操作

矩陣的掩碼操作很簡單。其思想是:根據掩碼矩陣(也稱作核)重新計算影象中每個畫素的值。掩碼矩陣中的值表示近鄰畫素值(包括該畫素自身的值)對新畫素值有多大影響。從數學觀點看,我們用自己設定的權值,對畫素鄰域內的值做了個加權平均。

filter2D函式

濾波器在影象處理中的應用太廣泛了,因此OpenCV也有個用到了濾波器掩碼(某些場合也稱作核)的函式。不過想使用這個函式,你必須先定義一個表示掩碼的 Mat 物件:

Mat kern = (Mat_<char>(3,3) <<  0, -1,  0,
                               -1,  5, -1,
                                0, -1,  0);

然後呼叫 filter2D 函式,引數包括輸入、輸出影象以及用到的核:

filter2D(I, K, I.depth(), kern );

它還帶有第五個可選引數——指定核的中心,和第六個可選引數——指定函式在未定義區域(邊界)的行為。使用該函式有一些優點,如程式碼更加清晰簡潔、通常比 自己實現的方法 速度更快(因為有一些專門針對它實現的優化技術)等等。例如,我測試的濾波器方法僅花了13毫秒,而前面那樣自己實現迭代方法花了約31毫秒,二者有著不小差距。

示例:

A sample output of the program

使用OpenCV對兩幅影象求和(求混合(blending))

使用 addWeighted 進行兩幅影象求和。

改變影象的對比度和亮度

影象處理

  • 一般來說,影象處理運算元是帶有一幅或多幅輸入影象、產生一幅輸出影象的函式。
  • 影象變換可分為以下兩種:
    • 點運算元(畫素變換)
    • 鄰域(基於區域的)運算元

畫素變換

  • 在這一類影象處理變換中,僅僅根據輸入畫素值(有時可加上某些全域性資訊或引數)計算相應的輸出畫素值。
  • 這類運算元包括 亮度和對比度調整 ,以及顏色校正和變換。

亮度和對比度調整

  • 兩種常用的點過程(即點運算元),是用常數對點進行 乘法加法 運算:

    g(x) = \alpha f(x) + \beta

  • 兩個引數 \alpha > 0\beta 一般稱作 增益偏置 引數。我們往往用這兩個引數來分別控制 對比度亮度

  • 你可以把 f(x) 看成源影象畫素,把 g(x) 看成輸出影象畫素。這樣一來,上面的式子就能寫得更清楚些:

    g(i,j) = \alpha \cdot f(i,j) + \beta

    其中, ij 表示畫素位於 第i行第j列

Note

我們可以不用 for 迴圈來訪問每個畫素,而是直接採用下面這個命令:

image.convertTo(new_image, -1, alpha, beta);

這裡的 convertTo 將執行我們想做的 new_image = a*image + beta

基本繪圖

  • 用OpenCV的函式 line直線
  • 用OpenCV的函式 ellipse橢圓
  • 用OpenCV的函式 rectangle矩形
  • 用OpenCV的函式 circle
  • 用OpenCV的函式 fillPoly填充的多邊形

離散傅立葉變換

對一張影象使用傅立葉變換就是將它分解成正弦和餘弦兩部分。也就是將影象從空間域(spatial domain)轉換到頻域(frequency domain)。 這一轉換的理論基礎來自於以下事實:任一函式都可以表示成無數個正弦和餘弦函式的和的形式。傅立葉變換就是一個用來將函式分解的工具。 2維影象的傅立葉變換可以用以下數學公式表達:

F(k,l) = \displaystyle\sum\limits_{i=0}^{N-1}\sum\limits_{j=0}^{N-1} f(i,j)e^{-i2\pi(\frac{ki}{N}+\frac{lj}{N})}  e^{ix} = \cos{x} + i\sin {x}

式中 f 是空間域(spatial domain)值, F 則是頻域(frequency domain)值。 轉換之後的頻域值是複數, 因此,顯示傅立葉變換之後的結果需要使用實數影象(real image) 加虛數影象(complex image), 或者幅度影象(magitude image)加相點陣圖像(phase image)。 在實際的影象處理過程中,僅僅使用了幅度影象,因為幅度影象包含了原影象的幾乎所有我們需要的幾何資訊。 然而,如果你想通過修改幅度影象或者相點陣圖像的方法來間接修改原空間影象,你需要使用逆傅立葉變換得到修改後的空間影象,這樣你就必須同時保留幅度影象和相點陣圖像了。

在此示例中,我將展示如何計算以及顯示傅立葉變換後的幅度影象。由於數字影象的離散性,畫素值的取值範圍也是有限的。比如在一張灰度影象中,畫素灰度值一般在0到255之間。 因此,我們這裡討論的也僅僅是離散傅立葉變換(DFT)。 如果你需要得到影象中的幾何結構資訊,那你就要用到它了。請參考以下步驟(假設輸入影象為單通道的灰度影象 I):

  1. 將影象延擴到最佳尺寸. 離散傅立葉變換的執行速度與圖片的尺寸息息相關。當影象的尺寸是2, 3,5的整數倍時,計算速度最快。 因此,為了達到快速計算的目的,經常通過添湊新的邊緣畫素的方法獲取最佳影象尺寸。函式 getOptimalDFTSize() 返回最佳尺寸,而函式 copyMakeBorder() 填充邊緣畫素:

    Mat padded;                            //將輸入影象延擴到最佳的尺寸
    int m = getOptimalDFTSize( I.rows );
    int n = getOptimalDFTSize( I.cols ); // 在邊緣新增0
    copyMakeBorder(I, padded, 0, m - I.rows, 0, n - I.cols, BORDER_CONSTANT, Scalar::all(0));
    

    新增的畫素初始化為0.

  2. 為傅立葉變換的結果(實部和虛部)分配儲存空間. 傅立葉變換的結果是複數,這就是說對於每個原影象值,結果是兩個影象值。 此外,頻域值範圍遠遠超過空間值範圍, 因此至少要將頻域儲存在 float 格式中。 結果我們將輸入影象轉換成浮點型別,並多加一個額外通道來儲存複數部分:

    Mat planes[] = {Mat_<float>(padded), Mat::zeros(padded.size(), CV_32F)};
    Mat complexI;
    merge(planes, 2, complexI);         // 為延擴後的影象增添一個初始化為0的通道
    
  3. 進行離散傅立葉變換. 支援影象原地計算 (輸入輸出為同一影象):

    dft(complexI, complexI);            // 變換結果很好的儲存在原始矩陣中
    
  4. 將複數轉換為幅度.複數包含實數部分(Re)和複數部分 (imaginary - Im)。 離散傅立葉變換的結果是複數,對應的幅度可以表示為:

    M = \sqrt[2]{ {Re(DFT(I))}^2 + {Im(DFT(I))}^2}

轉化為OpenCV程式碼:

split(complexI, planes);                   // planes[0] = Re(DFT(I), planes[1] = Im(DFT(I))
magnitude(planes[0], planes[1], planes[0]);// planes[0] = magnitude
Mat magI = planes[0];
  1. 對數尺度(logarithmic scale)縮放. 傅立葉變換的幅度值範圍大到不適合在螢幕上顯示。高值在螢幕上顯示為白點,而低值為黑點,高低值的變化無法有效分辨。為了在螢幕上凸顯出高低變化的連續性,我們可以用對數尺度來替換線性尺度:

    M_1 = \log{(1 + M)}

    轉化為OpenCV程式碼:

    magI += Scalar::all(1);                    // 轉換到對數尺度
    log(magI, magI);
    
  2. 剪下和重分佈幅度圖象限. 還記得我們在第一步時延擴了影象嗎? 那現在是時候將新新增的畫素剔除了。為了方便顯示,我們也可以重新分佈幅度圖象限位置(注:將第五步得到的幅度圖從中間劃開得到四張1/4子影象,將每張子影象看成幅度圖的一個象限,重新分佈即將四個角點重疊到圖片中心)。 這樣的話原點(0,0)就位移到影象中心。

    magI = magI(Rect(0, 0, magI.cols & -2, magI.rows & -2));
    int cx = magI.cols/2;
    int cy = magI.rows/2;
    
    Mat q0(magI, Rect(0, 0, cx, cy));   // Top-Left - 為每一個象限建立ROI
    Mat q1(magI, Rect(cx, 0, cx, cy));  // Top-Right
    Mat q2(magI, Rect(0, cy, cx, cy));  // Bottom-Left
    Mat q3(magI, Rect(cx, cy, cx, cy)); // Bottom-Right
    
    Mat tmp;                           // 交換象限 (Top-Left with Bottom-Right)
    q0.copyTo(tmp);
    q3.copyTo(q0);
    tmp.copyTo(q3);
    
    q1.copyTo(tmp);                    // 交換象限 (Top-Right with Bottom-Left)
    q2.copyTo(q1);
    tmp.copyTo(q2);
    
  3. 歸一化. 這一步的目的仍然是為了顯示。 現在我們有了重分佈後的幅度圖,但是幅度值仍然超過可顯示範圍[0,1] 。我們使用 normalize() 函式將幅度歸一化到可顯示範圍。

normalize(magI, magI, 0, 1, CV_MINMAX); // 將float型別的矩陣轉換到可顯示影象範圍
                                        // (float [0, 1]).