一仆嗦、Mat類
Mat
:Matrix
的縮寫枝恋,代表矩陣或者數(shù)組的意思。該 類的聲明在頭文件opencv2\core\core.hpp中携丁, 所以使用Mat
類時要引入該頭文件琢歇。
1.1、Mat類的構(gòu)造函數(shù)
構(gòu)造Mat
對象相當(dāng)于構(gòu)造了一個矩陣(數(shù)組)梦鉴,需要四個基本要素:行數(shù)(高)李茫、列數(shù)(寬)、通道數(shù)及其數(shù)據(jù)類型肥橙。
Mat(int rows, int cols, int type);
其中魄宏,
- rows代表矩陣的行數(shù)
- cols代表矩陣的列數(shù)
- type代表類型, 包括通道數(shù)及其 數(shù)據(jù)類型存筏, 可以設(shè)置為
CV_8UC(n)
宠互、CV_8SC(n)
、CV_16SC(n)
椭坚、CV_16UC(n)
予跌、CV_32SC(n)
、CV_32FC(n)
藕溅、CV_64FC(n)
匕得。
其中8U
、8S
巾表、16S
汁掠、16U
、32S
集币、32F
考阱、64F
前面的數(shù)字代表Mat中每一個數(shù)值所占的bit數(shù), 而 1byte=8bit鞠苟, 所以乞榨,32F
就是占4字節(jié)的float類型秽之,64F
是占8字節(jié)的doule類型,32S
是占4字 節(jié)的int類型吃既,8U
是占1字節(jié)的uchar類型考榨, 其他的類似;
C(n)
代表通道數(shù)鹦倚,當(dāng)n=1時河质, 即構(gòu)造單通道矩陣或稱二維矩陣, 當(dāng)n>1時震叙, 即構(gòu)造多通道矩陣即三維矩陣掀鹅, 直觀上就是n個二維矩陣組成的三維矩陣。
Mat(Size size, int type);
其中媒楼,需要注意的是乐尊, Size的第一個元素是矩陣的列數(shù)(寬),第二個元素是矩陣的行數(shù)(高)划址,即先存 寬扔嵌, 再存高。即Size(int cols,int rows)
Mat m;
m.create(2,3,CV_32FC1);
m.create(Size(3,2),CV_32FC1);
1.2猴鲫、初始化Mat類
Mat o=Mat::ones(2,3,CV_32FC1);
Mat m=Mat::zeros(Size(3,2),CV_32FC1);
Mat m=(Mat_<int>(2,3)<<1,2,3,4,5,6);
1.3对人、獲取單通道Mat的基本信息
1. 使用成員變量rows和 cols獲取矩陣的行數(shù)和列數(shù)
//構(gòu)造矩陣
Mat m=(Mat_<int>(3,2)<<1,2,3,4,5,6);
//矩陣的行數(shù)
cout<<"行數(shù):"<<m.rows<<endl;
//矩陣的列數(shù)
cout<<"列數(shù):"<<m.cols<<endl;
2. 使用成員函數(shù)size() 獲取矩陣的尺寸
Size size=m.size();
cout<<"尺寸:"<<size<<endl;
3. 使用成員函數(shù) channels() 得到矩陣的通道數(shù)
cout<<"通道數(shù):"<<m.channels()<<endl;
4. 使用成員函數(shù) total()得到矩陣的行數(shù)乘以列數(shù), 即面積拂共。 注意和通道數(shù)無關(guān)牺弄, 返回的不是矩陣中數(shù)據(jù)的個數(shù)
cout<<"面積:"<<m.total()<<endl;
5. 使用成員變量 dims 得到矩陣的維數(shù)。 顯然對于單通道矩陣來說就是一個二維矩陣宜狐, 對于多通道矩 陣來說就是一個三維矩陣势告。
cout<<"維數(shù):"<<m.dims<<endl;
1.4、訪問單通道Mat對象中的值
1. 利用成員函數(shù)at
//構(gòu)造單通道矩陣
Mat m=(Mat_<int>(3,2)<<11,22,33,44,55,66);
//通過for循環(huán)打印M中的每一個值
for(int r=0;r<m.rows;r++)
{
for(int c=0;c<m.cols;c++)
{
cout<<m.at<int>(r,c)<<",";//第r行第c列的值
cout<<m.at<int>(Point(c,r))<<",";//等價于上面一行代碼
}
cout<<endl'
}
2. 利用成員函數(shù)ptr
對于Mat
中的數(shù)值在內(nèi)存中的存儲抚恒, 每一行的值是存儲在連續(xù)的內(nèi)存區(qū)域中的咱台, 通過成員函數(shù)ptr
獲得指向每一行首地址的指針。 仍以“利用成員函數(shù)at
”部分的m存儲為例俭驮, m中所有的值在內(nèi)存中的存儲方式如圖2-1所示回溺, 其中如果行與行之間的存儲是有內(nèi)存間隔的, 那么間隔也是相等的混萝。
for(int=0;r<m.rows;r++)
{
//得到矩陣m的第r行行首的地址
const int *ptr=m.ptr<int>(r);
//打印第r行的所有值
for(int c=0;c<m.cols;c++)
{
cout<<ptr[c]<<",";
}
cout<<endl;
}
1. 利用成員函數(shù) isContinuous和ptr
每一行的所有值存儲在連續(xù)的內(nèi)存區(qū)域中遗遵, 行與行之間可能會有間隔, 如果isContinuous
返回值為true
逸嘀, 則代表行與行之間也是連續(xù)存儲的车要, 即所有的值都是連續(xù)存儲的。
if(m.isContinuous())
{
//得到矩陣m的第一個值的地址
int *ptr=m.ptr<int>(0);
for(int n=0;c<m.rows*m.cols;n++)
{
cout<<ptr[n]<<",";
}
}
2. 利用成員變量step和data
對于單通道矩陣來說崭倘,step[0]
代表每一行所占的字節(jié)數(shù)翼岁,而如果有間隔的話类垫, 這個間隔也作為字節(jié)數(shù)的一部分被計算在內(nèi);step[1]
代表每一個數(shù)值所占的字節(jié)數(shù)琅坡,data
是指向第一個數(shù)值 的指針悉患, 類型為uchar
。 所以脑蠕, 無論哪一種情況购撼, 如訪問一個int
類型的單通到矩陣的第r行 第c列的值跪削, 都可以通過以下代碼來實現(xiàn)谴仙。
*((int*)(m.data+m.step[0]*r+c*m.step[1]))
1.5、向量類Vec
默認是列向量
//構(gòu)造一個長度為3碾盐,數(shù)據(jù)類型為int并且初始化為11晃跺、22、33的列向量
Vec<int,3> vi(11,22,33);
cout<<"向量的行數(shù)"<<vi.rows<<endl;
cout<<"向量的列數(shù)<<vi.cols<<endl;
cout<<"訪問滴0個元素:"<<vi[0]<<endl;
OpenCV為向量類的聲明取了一個別名毫玖,在matx.hpp
401行開始 例如:
typedef Vec<uchar, 2> Vec2b;
typedef Vec<uchar, 3> Vec3b;
typedef Vec<uchar, 4> Vec4b;
typedef Vec<int, 2> Vec2i;
typedef Vec<int, 3> Vec3i;
...
單通道矩陣的每一個元素都是一個數(shù)值掀虎, 多通道矩陣的每一個元素都可以看作一個向量。
1.6付枫、構(gòu)造多通道的Mat對象
Mat mm=(Mat_<Vec3f>(2,2)<<Vec3f(1,1,1),Vec3f(2,2,2),Vec3f(3,3,3),Vec3f(4,4,4));
//打印第0行第0列的元素值
int r=0;
int c=0;
cout<<mm.at<Vec3f>(r,c)<<endl烹玉;
其余同單通道方法,只是類型變成了向量Vec
阐滩。
(1)分離通道
vector<Mat> planes;
split(mm,planes);
(2)合并通道
//三個單通道矩陣
Mat plane0=(Mat_<int>(2,2)(1,2,3,4);
Mat plane1=(Mat_<int>(2,2)(11,12,13,14);
Mat plane2=(Mat_<int>(2,2)(21,22,23,24);
//用三個單通道矩陣初始化一個數(shù)組
Mat plane[]={plane0,plane1,plane2};
Mat mat;
merge(plane,3,mat);
//將三個單通道矩陣一次放入vector容器中
vector<Mat> plane;
plane.push_back(plane0);
plane.push_back(plane1);
plane.push_back(plane2);
Mat mat;
merge(plane,mat);
1.7二打、獲得Mat中某一區(qū)域的值
1. 使用成員函數(shù)row(i) 或 col(j) 得到矩陣的第i行或者第 j列
2. 使用成員函數(shù)rowRange或 colRange得到矩陣的連續(xù)行或者連續(xù)列
Range(int _start,int _end);
這是一個左閉右開的序列[_start, _end)掂榔,比如Range(2继效, 5)
其實產(chǎn)生的是2、 3装获、 4 的序列瑞信,不包括5, 常用作rowRange
和colRange
的輸入?yún)?shù)穴豫,從而訪問矩陣中的連續(xù)行或者連續(xù)列
Mat r_range=mm.rowRange(Range(2,4));
//Mat r_range=mm.rowRange(2,4);等價于上面
for(int r=0;r<r_range.rows;r++)
{
for(int c=0;c<r_range.cols;c++)
{
cout<<r_range.at<int>(r,c)<<",";
}
cout<<endl;
}
需要特別注意的是凡简, 成員函數(shù)row
、 col
精肃、 rowRange
秤涩、 colRange
返回的矩陣其實是指向原矩陣的;有時候, 我們只訪問原矩陣的某些行或列肋杖, 但是不改變原矩陣的值溉仑,需要使用復(fù)制的方法
3. 使用成員函數(shù) clone和copy To
Mat r_range=mm.rowRange(2,4).clone();
Mat c_range=mm.colRange(1,3).copyTo(c_range);
4. 使用 Rect類
Rect的構(gòu)造函數(shù)
Rect_(_Tp _x, _Tp _y, _Tp _width, _Tp _height);
Rect_(const Rect_& r);
Rect_(const Point_<_Tp>& org, const Size_<_Tp>& sz);
Rect_(const Point_<_Tp>& pt1, const Point_<_Tp>& pt2);
Mat ROI1=mm(Rect(2,1,2,2));
Mat roi2=mm(Rect(Point(2,1),Size(2,2)));
Mat roi3=mm(Rect(Point(2,1),Point(3,2)));
但是與使用colRange
和rowRange
類似, 這樣得到的矩形區(qū)域是指向原矩陣的状植, 要改變roi
中的值浊竟, matrix
也會發(fā)生變化怨喘, 如果不想這樣, 則仍然可以使用clone
或者copyTo
振定。
二必怜、矩陣的運算
2.1、加法運算
矩陣的加法就是兩個矩陣對應(yīng)位置的數(shù)值相加
Mat src1=(Mat_<uchar>(2,2)<<11,22,33,60);
Mat src2=(Mat_<uchar>(2,2)<<191,192,193,204);
Mat dst=src1+src2;
注意:
- 60+204=264后频,但是梳庆,實際打印出來的值是255,因為兩個矩陣的數(shù)據(jù)類 型都是uchar卑惜, 所以用“+”運算符計算出來的和也是uchar類型的膏执, 但是uchar類型范圍的最大值是255, 所以只好將264截斷為255露久。
- 兩個Mat的數(shù)據(jù)類型必須是一 樣的更米,否則會報錯,也就是用“+”求和是比較嚴格的 毫痕。
- 一個數(shù)值與一個Mat對象相 加征峦, 也可以使用“+”運算符, 但是無論這個數(shù)值是什么數(shù)據(jù)類型消请, 返回的
Mat
的數(shù)據(jù)類型 都與輸入的Mat相同 栏笆。
為了彌補“+”運算符的這兩個缺點, 我們可以使用OpenCV提供的另一 個函數(shù)
void add(InputArray src1, InputArray src2, OutputArray dst,InputArray mask = noArray(), int dtype = -1);
使用add函數(shù)時臊泰, 輸入矩陣的數(shù)據(jù)類型可以不同蛉加, 而輸出矩陣的數(shù)據(jù)類型 可以根據(jù)情況自行指定。 需要特別注意的是因宇, 如果給dtype
賦值為-1七婴, 則表示dst
的數(shù)據(jù)類型和src1
、 src2
是相同的察滑, 也就是只有當(dāng)src1
和src2
的數(shù)據(jù)類型相同時打厘,才有可能令 dty pe=-1
,否則仍然會報錯贺辰。
Mat dst;
add(src1,src2,dst,Mat(),CV_64FC1);
2.2户盯、減法運算
矩陣的減法與加法類似
Mat dst=src1-src2;
注意:
- 輸出值不會最小為0,這是 因為
src1
和src2
均是uchar
類型的饲化, 所以返回的dst
也是uchar
類型的莽鸭;而uchar
類型的最小范圍是0, 所以會將小于0的數(shù)值截斷為0吃靠。 - Mat對象與一個數(shù)值相減硫眨, 也可以使用“-”運算符。
當(dāng)然巢块, 也存在與“+”運算符 一樣的不足礁阁, OpenCV提供的函數(shù):
void subtract(InputArray src1, InputArray src2, OutputArray dst,
InputArray mask = noArray(), int dtype = -1);
可以實現(xiàn)不同的數(shù)據(jù)類型的Mat
之間做減法運算巧号, 其與add函數(shù)類似。
2.3姥闭、點乘運算
矩陣的點乘即兩個矩陣對應(yīng)位置的數(shù)值相乘丹鸿。
Mat dst=src1.mul(src2);
注意
從打印結(jié)果就可以看出,也是對大于255的數(shù)值做了截斷處理棚品。 所以為了不損失精度靠欢,可以將兩個矩陣設(shè)置為int
、 float
等數(shù)值范圍更大的數(shù)據(jù)類型铜跑。
對于Mat
的點乘门怪, 也可以利用OpenCV提供的函數(shù):
void multiply(InputArray src1, InputArray src2,
OutputArray dst, double scale = 1, int dtype = -1);
這里的dst=sclae*src1*src2
, 即在點乘結(jié)果的基礎(chǔ)上還可以再乘以系數(shù)scale
疼进。
2.4薪缆、點除運算
點除運算與點乘運算類似, 是兩個矩陣對應(yīng)位置的數(shù)值相除伞广。
Mat dst=src2/src1;
注意
- 除數(shù)為0沒有意義,但是OpenCV在 處理這種分母為0的除法運算時疼电,默認得到的值為0嚼锄。
- 用一個數(shù)值與Mat對象相除也可以使用“/”運算符, 且返回的Mat的數(shù)據(jù)類型與輸入的Mat的數(shù)據(jù)類型相同蔽豺, 與輸入數(shù)值 的數(shù)據(jù)類型是沒有關(guān)系的区丑。
對于Mat
的點除, 也可以利用OpenCV提供的函數(shù):
divide(InputArray src1, InputArray src2, OutputArray dst,
double scale = 1, int dtype = -1);
2.5修陡、乘法運算
相當(dāng)于卷積
Mat dst=src1*src2;
注意:
- 對于
Mat
對象的乘法沧侥, 需要注意兩個Mat
只能同時是float
或者double
類型, 對于其他數(shù)據(jù)類型的矩陣做乘法會報錯魄鸦。 - 兩個雙通道矩陣也可以相乘宴杀,這里是把Mat對象當(dāng)做了復(fù)數(shù)矩陣,其中第一個通道存放的是所有值的實部拾因,第二個通道存放的是對應(yīng)的每一個虛部旺罢,也就是將
Vec2f
看作一個復(fù)數(shù), 比如Vec2f(1绢记, 2)
可以看作1+2i
扁达。
對于Mat的乘法, 還可以使用OpenCV提供的gemm
函數(shù)來實現(xiàn)蠢熄。
void gemm(InputArray src1, InputArray src2, double alpha,
InputArray src3, double beta, OutputArray dst, int flags = 0);
注意:gemm
也只能接受CV_32FC1
跪解、 CV_64FC1
、 CV_32FC2
签孔、 CV_64FC2
數(shù)據(jù)類型的Mat
該函數(shù)通過flags
控制src1
叉讥、 src2
砾跃、 src3
是否轉(zhuǎn)置來實現(xiàn)矩陣之間不同的運算, 當(dāng)將flags
設(shè)置為不同的參數(shù)時节吮, 輸出矩陣為:
當(dāng)然抽高,flags
可以組合使用, 比如需要src2
和src3
都進行轉(zhuǎn)置透绩, 則令flags=GEMM_2_T+GEMM_3_T
翘骂。
2.6、其他運算
開平方運算
void sqrt(InputArray src, OutputArray dst);
注意:sqrt
的輸入矩陣的數(shù)據(jù)類型只能是 CV_32F
或者CV_64F
冪指數(shù)運算
void pow(InputArray src, double power, OutputArray dst);
三帚豪、灰度圖像數(shù)字化
Mat imread( const String& filename, int flags = IMREAD_COLOR );
void imshow(const String& winname, InputArray mat);
四碳竟、彩色圖像數(shù)字化
灰度圖像的每一個像素都是由一個數(shù)字量化的, 而彩色圖像的每一個像素都是由三個數(shù)字組成的向量量化的狸臣。 最常用的是由R
莹桅、 G
、B
三個分量來量化的烛亦, RGB
模型使用加 性色彩混合以獲知需要發(fā)出什么樣的光來產(chǎn)生給定的色彩诈泼, 源于使用陰極射線管(CRT) 的彩色電視, 具體色彩的值用三個元素的向量來表示煤禽, 這三個元素的數(shù)值分別代表三種基色: Red铐达、Green、Blue的亮度檬果。 假設(shè)每種基色的數(shù)值量化成m=2^n個數(shù)瓮孙, 如同8位灰度 圖像一樣, 將灰度量化成28=256個數(shù)选脊。 RGB圖像的紅杭抠、綠、藍三個通道的圖像都是一張8 位圖恳啥, 因此顏色的總數(shù)為2563 =16777216偏灿, 如(0, 0角寸, 0) 代表黑色菩混,(255, 255扁藕, 255) 代表白色沮峡, (255, 0亿柑, 0) 代表紅色邢疙。
對于彩色圖像的每一個方格, 我們可以理解為一個Vec3b
。 需要注意的是疟游, 每一個像素的向量不是按照R
呼畸、 G
、 B
分量排列的颁虐, 而是按照B
蛮原、 G
、 R
順序排列的另绩, 所以通過split
函數(shù)分離通道后儒陨, 先后得到的是B
、 G
笋籽、 R
通道蹦漠。
Mat img=imread("apple.jpg",CV_LOAD_IMAGE_GRAYSCALE);
if(img.empty())
{
return -1;
}
imshow("BGR",img);
vector<Mat> planes;
split(img,planes);
imshow("B",planes[0]);
imshow("G",planes[1]);
imshow("R",planes[2]);
waitKey(0);
在OpenCV中實現(xiàn)將彩色像素(一個向量) 轉(zhuǎn)化為灰度像素(一個數(shù)值) 的公式如 下: