卷積邊界處理(copyMakeBorder)

進行卷積處理影像時,在影像的邊界,核心沒有足夠元素納入計算,OpenCV裡用copyMakeBorder()函式將原圖稍微放大,再開始進行卷積,OpenCV在空間濾波的相關函式內部已包含copyMakeBorder()了,所以實際上使用空間濾波時,不需要呼叫copyMakeBorder()。

在OpenCV裡進行卷積時,處理邊界問題通常分以下3個步驟:

  1. 先將原始影像的資料,複製到一個稍大的影像裡。
  2. 將邊界進行外推,有幾種BORDER型態決定外推值,以下列出較常見的幾個:

    BORDER_CONSTANT:外推的值為常數,常在仿射變換、透視變換中使用。

    BORDER_REPLICATE:外推的值和邊緣值相同,OpenCV的medianBlur默認的處理方式。

    BORDER_REFLECT_101:外推像素和影像邊界成鏡像映射,OpenCV的 filter2D、blur、GaussianBlur、bilateralFilter默認的處理方式。

  3. 處理完後將外推的部分切掉,使得輸出圖和輸入圖大小相同。


copyMakeBorder()為上述的第一、二步驟,就是輸入來源圖,得到一個稍大的有外推的輸出圖。

OpenCV邊緣複製:void copyMakeBorder(InputArray src, OutputArray dst, int top, int bottom, int left, int right, int borderType, const Scalar&value=Scalar())

  • src:輸入圖。
  • dst:輸出圖,深度和輸入圖相同,尺寸會依輸入參數而變得稍大,Size(src.cols+left+right, src.rows+top+bottom)。。
  • top、bottom、left、right:上下左右分別外推多少像素。
  • borderType:邊緣型態。
  • value:當borderType為BORDER_CONSTANT,此為外推的值。

以下示範如何用copyMakeBorder()讓影像邊界擴充,分別使用BORDER_CONSTANT和BORDER_REPLICATE來指定邊界的值:

#include <cstdio>
#include <opencv2/opencv.hpp>
using namespace cv;

int main(){
    Mat src = imread("cat.jpg");
    Mat dst1;
    Mat dst2;
    copyMakeBorder(src, dst1, 5, 5, 5, 5, BORDER_CONSTANT, Scalar(255,0,0));
    copyMakeBorder(src, dst2, 5, 5, 5, 5, BORDER_REPLICATE);
    imshow("origin", src);
    imshow("border1", dst1);
    imshow("border2", dst2);
    waitKey(0);

    return 0;
}

copyMakeBorder

copyMakeBorder

copyMakeBorder

繼續閱讀 卷積邊界處理(copyMakeBorder)

線性內插(Interpolation)

當我們圖像進行幾何轉換時,假使輸出像素映射的地方,不是輸入圖像某個整數像素位置,這時要用整數座標的灰度值進行推斷,這就是插值,這邊介紹幾種插值方式,通常較好的結果也導致較大的計算量。


最近插值法(Nearest Neighbor Interpolation):這是一種最簡單的插值算法,輸出像素的值為輸入圖像離映射點最近的像素值,如下圖假使(x0,y0)為映射點,則讓此點的強度值為(x1,y1)的值,這種算法作幾何轉換時,邊緣通常有較嚴重的鋸齒狀。

Nearest Neighbor Interpolation


雙線性插值法(Bilinear Interpolation):在兩個方向分別進行一次線性插值,輸出像素的值為映射點四周的2×2像素強度加權平均,如下圖我們簡化問題,四周位置分別為(0,0)、(1,0)、(0,1)、(1,1),強度分別為f(0,0)、f(1,0)、f(0,1)、f(1,1),(x,y)映射點強度為f(x,y),映射點到四邊的距離分別為d1、d2、d3、d4、我們依序進行以下三步驟:

  1. 對上端的兩個頂點進行線性插值得到x1的強度f(x1)。 f(x1) = f(0,0) + d1 * (f(1,0) – f(0,0))
  2. 對下端的兩個頂點進行線性插值得到x2的強度f(x2)。 f(x2) = f(0,1) + d1 * (f(1,1) – f(0,1))
  3. 由f(x1)和f(x2)來求得(x,y)的強度f(x,y)。 f(x,y)= f(x1)+ d3 * (f(x1)- f(x2))

我們擴展這個概念,就可得到圖像所有的雙線性插值強度,這概念假設強度在兩個像素之間是線性變化的,顯然是合理的假設,因此在一般的情況之下,雙線性插值都能得到不錯的結果。

Bilinear Interpolation


高階插值: 在一些幾何運算中,雙線性插值的平滑作用會導致細節的退化,這些可以透過高階插值彌補,可能會採用4×4或8×8的鄰域進行加權平均。

繼續閱讀 線性內插(Interpolation)

像素強度變換(convertTo)

這邊介紹影像的強度變換,指的是對每個像素依序進行同樣的運算,假設r和s分別為輸入和輸出影像任一點的灰階值,可以定義為:s = T(r),其中T為強度變換,表示輸入和輸出圖間強度的某種映射關係,強度變換可分為線性變換和非線性變換。

最基本的線性變換就是一維線性變換:x’=a*x+b

  • 其中a為斜率,b為在y軸上的截距,x表示輸入影像的灰階值,x’為輸出影像的灰階值。
  • 當a>1時,輸出圖像的對比度增加,當a<1時,輸出圖像的對比度減小。
  • 當a=1且b!=0時,所有像素的灰階值增加或減少,使輸出圖像看起來更亮或更暗。
  • 當a=-1且b=255時,輸出影像的灰階值正好反轉,因為人的視覺特性的關係,這通常用來強調暗色影像中亮度較大的細節部分。

線性強度變換可以解決整體過亮或過暗的問題,但是對細節的改善有限,非線性變換才能對細節強度改善有較明顯的效果。


OpenCV 影像線性變換

void Mat::convertTo(OutputArray m, int rtype, double alpha=1, double beta=0)

  • m:輸出圖,如果和呼叫的Mat尺寸或型態不同,會再重新分配空間。
  • rtype:指定輸出圖型態,如果為負數的話,輸出圖型態會和呼叫的Mat相同。
  • alpha:選擇性的放大倍率,也就是線性變換:x’=a*x+b這個式子裡的a。
  • beta:選擇性的偏移量,也就是線性變換:x’=a*x+b這個式子裡的b。
  • 函式內有呼叫saturate_cast<>,避免發生overflow的現象。

以下示範兩種方法對影像進行線性變換,分別是自己寫的linearTrans()和OpenCV的convertTo(),兩種函式會帶來相同的結果:

#include <cstdio>
#include <opencv2/opencv.hpp>
using namespace cv;

void linearTrans(const Mat &src, Mat &dst);

int main(){
    Mat src = imread("lena.jpg",CV_LOAD_IMAGE_UNCHANGED);
    Mat dst1;
    Mat dst2;
    linearTrans(src, dst1);
    src.convertTo(dst2,-1,1.5,30);

    imshow("window1", src);
    imshow("window2", dst1);
    imshow("window3", dst2);
    waitKey(0); 

    return 0;
}

void linearTrans(const Mat &src, Mat &dst){
    dst.create(src.size(),src.type());
    int widthLimit = src.channels() * src.cols;
    for(int iH=0; iH<src.rows; iH++){
        const uchar *curPtr = src.ptr<const uchar>(iH);
        uchar *dstPtr = dst.ptr<uchar>(iH);
        for(int iW=0; iW<widthLimit; iW++){
            dstPtr[iW] = saturate_cast<uchar>(1.5*curPtr[iW]+30);
        }
    }
}

影像線性變換

影像線性變換

影像線性變換


以下為兩種較常見的非線性強度變換,分別為對數變換和伽瑪變換:

對數變換: x’ = c*log(1 + x)

  • x’為輸出像素值,x為原始像素值,c為比例常數。
  • 此變換可以增強圖像中較暗部分的細節,通常在傅立葉頻譜時,強度範圍非常大,直接顯示頻譜時,顯示設備的範圍往往不能滿足,進而丟失大量暗部細節,這時可以使用對數變換,將範圍進行非線性壓縮,以至於能夠清楚的顯示影像。

伽瑪變換:x’ = (x + esp)r

  • x’與x的範圍皆為0到1,esp為補償係數,r為伽瑪係數。
  • 與對數變換不同,伽瑪變化可以根據r的不同,增強低灰度或高灰度區域的對比度。
  • 當r>1時,高灰度區域對比度增強。
  • 當r<1時,低灰度區域對比度增強。
  • 當r=1時,此變換為線性變換。
  • 伽瑪變化並不僅可以改變影像的對比度,還能增強細節,帶來整體效果的改善。

繼續閱讀 像素強度變換(convertTo)

OpenCV基本結構(Point、Size、Rect等)

OpenCV有定義一些基本結構,像Point、Point2f、Size、Size2f、Rect、RotatedRect、Scalar等,方便我們進行影像處理,這些結構通常都當參數輸入,或是函式內部計算之用。

Point:2維整數點類別,通常用於影像的座標點,成員有x和y,要是輸入浮點數的話,則四捨五入取整數,以下為建構式和最基本的兩種賦值方法:

Point(int x, int y) //建構式
Point pt1(20,30);
Point pt2;
pt2.x = 20;
pt2.y = 30;

Point2f:2維浮點數點類別,通常用於幾何計算,用法和Point雷同,只是x和y是浮點數。

Point3i、Point3f、Point3d代表3維點x、y、z,而成員型態分別為int、float、double。


Size:尺寸類別,成員有width和height,分別表示寬和長(int型態),可用area()函式得到面積,以下為建構式最基本兩種賦值方法:

Size(int width, int height) //建構式
Size size1(150, 100);
Size size2;
size2.width = 150;
size2.height = 100;
int myArea = size2.area();

Size2f:尺寸類別,和Size用法雷同,只是width和height為float型態。


Rect:矩形類別,成員有x、y、width、height,分別表示左上角頂點的x座標,左上角頂點的y座標,矩形寬、矩形高,可用area()函式得到面積,以下為建構式和最基本的兩種賦值方法:

Rect(int x, int y, int width, int height) //建構式
Rect rect1(20,30,150,100);
Rect rect2;
rect2.x = 20;
rect2.y = 30;
rect2.width = 150;
rect2.height = 100;
int myRectArea = rect2.area();

RotatedRect:斜矩形類別,成員有矩形的質心center(Point2f類別),四周長size(Size2f類別)、旋轉角度angle(float類別),可用points()函式得到四個頂點,以下為建構式和最基本的兩種賦值方法:

RotatedRect(const Point2f &center, const Size2f &size, float angle) //建構式
RotatedRect rRect1(Point2f(150,150), Size2f(100,50), 30.0);
RotatedRect rRect2; 
rRect2.center = Point2f(150,150);
rRect2.size = Size2f(100,50);
rRect2.angle = 30.0;
Point2f vertices[4];
rRect2.points(vertices);

Scalar:代表4元素的向量,一般用於像素顏色,以下設定給灰階圖和彩色圖像素的方式:

  • Scalar(a, b, c):省略透明通道,由於OpenCV的彩色影像通常為BGR的順序,a代表藍色、b代表綠色、c代表紅色。
  • Scalar(a):通常用於灰階圖,像素強度為a。

繼續閱讀 OpenCV基本結構(Point、Size、Rect等)

OpenCV下載與設定

1、進入OpenCV官網,選擇下載檔案。

download


2、由於筆者的運作系統是微軟,因此選擇OpenCV for Windows,實際因個人的需求而異。

download


3、在C槽增加資料夾opencv,將剛剛下載的檔案解壓縮後放置於此處,位置可隨意放置,但環境變數設定要依檔案位置而變。

4、設定PATH (控制台->系統->進階系統設定->環境變數->編輯),在最後面增加以下兩項,如果opencv位置不同要更改:

  • C:\opencv\build;
  • C:\opencv\build\x86\vc10\bin;

5、筆者的編譯器是visual studio,對需要用到OpenCV的專案,點選:專案->屬性->VC++目錄 include目錄,添加以下兩項:

  • C:\opencv\build\include;
  • C:\opencv\build\include\opencv;

在程式庫目錄添加:

  • C:\opencv\build\x86\vc10\lib;

6、點選 專案->屬性->連結器->輸入 其他相依性,在裡面輸入會用到的*.lib檔,以下是筆者比較常用的:

  • opencv_core2411d.lib
  • opencv_calib3d2411d.lib
  • opencv_contrib2411d.lib
  • opencv_features2d2411d.lib
  • opencv_highgui2411d.lib
  • opencv_imgproc2411d.lib

之後可依需求在C:\opencv\build\x86\vc10\lib,查看缺少那些.lib檔再加入。

7、如果我們要編譯release版,重複進行步驟5和6,此時加入的lib檔少字符d,也就是像:opencv_core2411d.lib改成opencv_core2411.lib,其他*.lib檔依此類推。

繼續閱讀 OpenCV下載與設定

OpenCV介紹

OpenCV全名是Open Source Computer Vision Library,是一個影像處理函式庫,由Intel發起並參與開發,以BSD授權條款發行,可在商業和研究領域中免費使用,目前是非營利的基金組織OpenCV.org在維護,關於授權可參考:幾種開源授權介紹

OpenCV 1.0版於2006年釋出,以C語言作為開發主體,當使用OpenCV函式庫時,程式設計師要自行注意記憶體管理,因此在開發大型程式時較不方便。

OpenCV 2.0在2009年10月釋出,該版本的主要以C++開發設計,使得記憶體管理方便許多,以我們示範使用的2.4.11就是2.0版,4代表小改版,可能是功能增強或提供新功能,11代表功能錯誤的排除,2.0有部分函式放在Nonfree模組內(像SURF),如果商用有使用到此模組要注意專利問題,而這模組在3.0的時候移除了。

OpenCV 3.0於2015年6月釋出,有部分的模組變更,模組減少了contrib、dynamicuda、legacy、nonfree、gpu、ocl,增加了cuda、cudaarithm、cudacodec、cudafeatures2d等模組,看起來GPU的平行處理是3.0的重點,取代了2.0的GPU模組。

OpenCV在影像處理方面應用廣泛,可以讀取儲存圖片、視訊、矩陣運算、統計、影像處理等,可用在物體追蹤、人臉辨識、傅立葉轉換、紋理分析、動態視訊的影像處理等。

OpenCV提供簡單的GUI介面,像將影像顯示在螢幕上,在視窗上加上滑動桿和偵測滑鼠和鍵盤輸入,方便我們驗證或呈現結果。但因為OpenCV當初設計的時候著重在演算法的處理,所以關於系統硬體的支援,和介面元件的完整度都不高,所以假使想要開發完整的C/C++應用程式,還是需要像Qt、wxWidets之類的應用程式框架。

OpenCV提供的函式方便我們推演更進階的影像處理演算法,就好像MATLAB的功用,但是執行速度比MATLAB快上許多,通常也比我們自己用C/C++寫的函式還快,而除了C/C++之外,OpenCV也提供其他語言的支援,像Java或Python等,詳細還請閱讀官方說明文件。

繼續閱讀 OpenCV介紹