縮寫(uchar、ushort、Vec)

這邊介紹OpenCV常見的三種縮寫,分別是uchar、ushort、Vec。

  • typedef unsigned char uchar
  • typedef unsigned short ushort

OpenCV使用Vec簡化vector,數字表示這個vector有幾個元素,英文字母表示元素的資料結構,通常用於影像像素。

以最常見的8位元無負號3通道圖來說,我們可用Vec3b表示單獨的像素資料結構,3表示有3個元素,b表示每個元素的資料結構是uchar,假設我們想要取第1通道的值,後面加[0]。

以下為幾種縮寫方式,其他縮寫依此類推:

  • typedef Vec< uchar, 2 > Vec2b
  • typedef Vec< uchar, 3 > Vec3b

  • typedef Vec< short, 2 > Vec2s

  • typedef Vec< short, 3 > Vec3s

  • typedef Vec< int, 2 > Vec2i

  • typedef Vec< int, 3 > Vec3i

  • typedef Vec< float, 2 > Vec2f

  • typedef Vec< float, 3 > Vec3f

  • typedef Vec< double, 2 > Vec2d

  • typedef Vec< double, 3 > Vec3d

繼續閱讀 縮寫(uchar、ushort、Vec)

影像格式(Mat)

Mat是OpenCV訂定的資料型態,代表的是矩陣(Matrix)前三個字母,影像其實也可以看成是某個二維陣列,所以在OpenCV 2.0裡,不論矩陣計算,或是影像處裡的格式,都是以Mat類別進行處理,並有相關的成員變數和函式方便使用,使用時不用考慮記憶體管理,這也是OpenCV 1.0和2.0不同之處,這邊介紹常用的Mat類別成員和成員函式,包括如何得到影像資訊、創建影像、複製影像、改變位元深度和操作像素強度。

影像資訊

Mat最基本有長、寬、像素型態、像素深度、通道數等資訊,以下介紹的成員變數或成員函式,讓我們得到這些資訊。

成員變數 意義
rows影像的列數,也就是影像高
cols 影像的欄數,也就是影像寬


OpenCV通道數:int Mat::channels() const

  • 返回影像的通道數:灰階圖為1,彩色圖為3。

OpenCV像素深度:int Mat::depth() const

  • L以下表格列出部分返回的像素深度:
CV_8U位元深度為8,U代表無負號
CV_8S位元深度為8,S代表有負號
CV_16U位元深度為16,無負號
CV_32F 浮點數

OpenCV像素型態:int Mat::type() const

  • 型態和深度主要差在型態有通道數資訊,比如CV_8UC3代表影像通道數3,以下表格列出部分返回的像素深度:
CV_8U 位元深度為8,無負號,通道數1
CV_8S位元深度為8,有負號,通道數1
CV_16U 位元深度為16,無負號,通道數1
CV_32F 浮點數資料,通道數1
CV_8UC3位元深度為8,無負號,通道數3

OpenCV影像尺寸:Size Mat::size() const

  • 返回影像的尺寸Size(cols, rows),cols和rows分別表示寬和高。

影像創建

OpenCV Mat建構式:Mat(int rows, int cols, int type, const cv::Scalar &s)

  • rows:影像高度。
  • cols:影像寬度。
  • type:影像型態。
  • s:像素值,我們可以在一開始建構式,就指定像素值,像灰階圖的強度,或是BGR分別的像素強度。
  • 要注意建構式的參數,是先輸入高度在寬度。

以下為使用方式:

Mat img1(240, 320, CV_8U);
Mat img2(240, 320, CV_8U, Scalar(100));
Mat img3(240, 320, CV_8UC3, Scalar(200,100,0));

影像分配空間

我們可以對空的或已有資料的Mat,重新分配空間,也就是改變影像的長、寬或像素型態。

OpenCV 空間分配:void Mat::create(int rows, int cols, int type)

  • rows:影像高度。
  • cols:影像寬度。
  • type:影像型態。
  • 參數是先輸入高度再寬度,且因為效率的考量,假使輸入的尺寸、型態和呼叫影像相同,函式直接返回,不會重新分配空間。

以下為使用方式:

Mat img;
img.create(300, 400, CV_8U);  

影像複製

這邊介紹三種複製影像的方式,第一種為多載等號運算子,第二、三種分別為Mat的成員函式。

OpenCV等號多載Mat& Mat::operator=(const Mat& m)

  • m:輸入圖,左邊影像和右邊影像相同,不會另外複製一份資料。

OpenCV影像複製:Mat::copyTo(OutputArray& m) const

  • m:輸出圖,輸出圖會變成和呼叫影像一樣的長、寬、像素值。

OpenCV影像複製:Mat Mat::clone() const

  • 返回和呼叫影像相同的一份複製影像。

用等號運算子時,如上面的img1和img2,兩者共用一份數據,所以只要改變一個,另一個會隨之變更,而copyTo()和clone()為複製一份新的數據,所以不會互相影響,以下為使用方式:

Mat img1(240, 320, CV_8U, Scalar(100));
Mat img2, img3, img4;             
img2 = img1;         
img1.copyTo(img3);    
img4 = img1.clone();    

改變影像型態。

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

  • m:輸出圖,如果尺寸或型態和呼叫影像不同,會重新分配空間。
  • rtype:輸出圖的型態,呼叫影像和輸出圖的通道數會相同。
  • alpha:輸出圖放大倍率,預設為1。
  • beta :輸出圖增加量,預設為0。

操作像素

at()用來訪問像素,可返回左值或右值,所以我們可用at()得到或改變某個像素值,這函式使用模板,所以使用時除了輸入位置,還必須需入影像的像素型態,使用at()函式時,輸入參數順序同樣為先高再寬。。

  • OpenCV改變像素:template T& Mat::at(int i, int j)
  • OpenCV讀取像素:template const T& Mat::at(int i, int j) const
  • 在灰階圖中,OpenCV裡可用uchar代替uncigned char,在彩色圖中,OpenCV裡可用Vec3b代替將3個uchar組成的容器(vector),且可在後面加上[],註明是要操作此像素的哪個通道。

下面操作一個8位元灰階圖,分別改變某個像素的值,以及查看此像素的值:

Mat gray_img(100, 100, CV_8U, Scalar(100));
gray_img.at<uchar>(30,20) =255;            
uchar value1 = gray_img.at<uchar>(30,20); 

下面分別改變彩色圖某個像素的第一通道值,以及查看此像素第一通道的值:

Mat color_img(100, 100, CV_8UC3, Scalar(200,100,0));
img.at<Vec3b>(30,20)[0] =255;        
uchar value2 = img.at<Vec3b>(30,20)[0]; 

操作像素

ptr函式輸入指定列,返回一個指標指向此列的第一個像素,通常為尋訪影像用到,ptr可讀取或改變像素值,同樣使用模板,所以也必須需入影像的像素型態。

  • OpenCV改變像素:template T* Mat::ptr(int i=0)
  • OpenCV讀取像素:template const T* Mat::ptr(int i=0) const

歸零

將所有像素歸零:void Mat::clear()

繼續閱讀 影像格式(Mat)

卷積邊界處理(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)

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等)