**
OPENCV学习
**
基础数据结构
主要是图像处理中需要用到的数据结构:
1.Point(点)
点在图像处理中通常用来标记特征点(交点、角点、拐点等)
Point( intx,int y )表示二维整型坐标,以(0,0)基点
Point2f(float x,float y)表示浮点坐标,以(0,0)为基点
图像上两点,坐标为A(200,300)和B(150.2,260.5)在代码中表示为
Point A A={200,300}或者 A.x=200; A.y=300; Point2f B B={150.2,260.5} 或 B.x=150.2;B=260.5;
2.矩阵Mat
opencv中的矩阵计算很大程度上与matlab相似,从OpenCV2开始,开始使用Mat类存储图像,具有以下优势:
(1)图像的内存分配和释放由Mat类自动管理
(2)Mat类由两部分数据组成:矩阵头(包含矩阵尺寸、存储方法、存储地址等)和一个指向存储所有像素值的矩阵(根据所选存储方法的不同,矩阵可以是不同的维数)的指针。Mat在进行赋值和拷贝时,只复制矩阵头,而不复制矩阵,提高效率。如果矩阵属于多个Mat对象,则通过引用计数来判断,当最后一个使用它的对象,则负责释放矩阵。
(3)可以使用clone和copyTo函数,不仅复制矩阵头还复制矩阵。
2.1构造Mat函数
- 使用构造函数(使用Mat图像容器类创建Mat类的对象)
//! default constructor
Mat();
//! constructs 2D matrix of the specified size and type
// (_type is CV_8UC1, CV_64FC3, CV_32SC(12) etc.)
Mat(int rows, int cols, int type);
Mat(Size size, int type);
//! constucts 2D matrix and fills it with the specified value _s.
Mat(int rows, int cols, int type, const Scalar& s);
Mat(Size size, int type, const Scalar& s);
//! constructs n-dimensional matrix
Mat(int ndims, const int* sizes, int type);
Mat(int ndims, const int* sizes, int type, const Scalar& s);
例:
cv::Mat M1(3, 3, CV_8UC4, cv::Scalar(0, 0, 0, 255));
std::cout << "M1 = " << std::endl << M1 << std::endl;
这里指定矩阵的行和列,并表示为4通道的矩阵,每个点的颜色值为(0, 0, 0, 255)。输出结果如下:
M1 =
[ 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255;
0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255;
0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255]
- 通过数组初始化矩阵维数
int sz[2] = { 3, 3 };
cv::Mat M2(2, sz, CV_8UC1, cv::Scalar::all(0));
std::cout << "M2 = " << std::endl << M2 << std::endl;
如上,构造函数的第一个参数指定的是矩阵的维数,那么sz数组表示的是每一维数的数量,即这里表示的是3行3列。如果第一个参数是3的话,数组的大小也应该是3,表示的就是x,y,z三个维度,每个维度有3个。输出如下:
M2 =
[ 0, 0, 0;
0, 0, 0;
0, 0, 0]
3. 通过create函数来初始化
cv::Mat M3;
M3.create(4, 4, CV_8UC(1));
std::cout << "M3 = " << std::endl << M3 << std::endl;
这里是4*4的二维单通道矩阵,矩阵中的数据为随机值。如下:
M3=
[205, 205, 205, 205;
205, 205, 205, 205;
205, 205, 205, 205;
205, 205, 205, 205]
4. 通过opencv提供的类matlab的函数创建
//! Matlab-style matrix initialization
static MatExpr zeros(int rows, int cols, int type);
static MatExpr zeros(Size size, int type);
static MatExpr zeros(int ndims, const int* sz, int type);
static MatExpr ones(int rows, int cols, int type);
static MatExpr ones(Size size, int type);
static MatExpr ones(int ndims, const int* sz, int type);
static MatExpr eye(int rows, int cols, int type);
static MatExpr eye(Size size, int type);
例:
cv::Mat Me = cv::Mat::eye(4, 4, CV_64F);
std::cout << "Me = " << std::endl << Me << std::endl;
cv::Mat Mo = cv::Mat::ones(4, 4, CV_64F);
std::cout << "Mo = " << std::endl << Mo << std::endl;
cv::Mat Mz = cv::Mat::zeros(4, 4, CV_64F);
std::cout << "Mz = " << std::endl << Mz << std::endl;
eye函数表示的是单位矩阵,ones顾名思义是全是1的矩阵,zeros表示全是0的矩阵。输出如下:
Me =
[1, 0, 0, 0;
0, 1, 0, 0;
0, 0, 1, 0;
0, 0, 0, 1]
Mo =
[1, 1, 1, 1;
1, 1, 1, 1;
1, 1, 1, 1;
1, 1, 1, 1]
Mz =
[0, 0, 0, 0;
0, 0, 0, 0;
0, 0, 0, 0;
0, 0, 0, 0]
5. 数据自定义矩阵Mat创建
cv::Mat M4 = (cv::Mat_<double>(3, 3) << 0, -1, 0, -1, 0, 0, 0, 0, 1);
std::cout << "M4 = " << std::endl << M4 << std::endl;
我们可以自己定义自己需要的数据量比较小的矩阵,然后通过如上的方式将其封装到cv::Mat中。如下:
M4 =
[0, -1, 0;
-1, 0, 0;
0, 0, 1]
6. 通过clone函数创建不同的Mat
cv::Mat M5 = M4.row(1).clone();
std::cout << "M5 = " << std::endl << M5 << std::endl;
通过克隆函数clone获取我们需要的某一行或列的数据,这里构建出来的矩阵是深拷贝出来的Mat类对象。输出如下:
M5 =
[-1, 0, 0]
创建Mat矩阵/图像容器类的很多构造方法或者其他成员方法在创建Mat对象的时候,都需要指定type–所创建图像/矩阵的类型。
(_type is CV_8UC1, CV_64FC3, CV_32SC(12) etc.)
通过转到定义法,看一下CV_8UC1,CV_64FC3等这些宏到底是什么,OpenCv的源代码显示如下(源代码在在types_c.h中):
#define CV_CN_MAX 512
#define CV_CN_SHIFT 3
#define CV_DEPTH_MAX (1 << CV_CN_SHIFT)
#define CV_8U 0
#define CV_8S 1
#define CV_16U 2
#define CV_16S 3
#define CV_32S 4
#define CV_32F 5
#define CV_64F 6
#define CV_USRTYPE1 7
#define CV_MAT_DEPTH_MASK (CV_DEPTH_MAX - 1)
#define CV_MAT_DEPTH(flags) ((flags) & CV_MAT_DEPTH_MASK)
#define CV_MAKETYPE(depth,cn) (CV_MAT_DEPTH(depth) + (((cn)-1) << CV_CN_SHIFT))
#define CV_MAKE_TYPE CV_MAKETYPE
#define CV_8UC1 CV_MAKETYPE(CV_8U,1)
#define CV_8UC2 CV_MAKETYPE(CV_8U,2)
#define CV_8UC3 CV_MAKETYPE(CV_8U,3)
#define CV_8UC4 CV_MAKETYPE(CV_8U,4)
#define CV_8UC(n) CV_MAKETYPE(CV_8U,(n))
#define CV_8SC1 CV_MAKETYPE(CV_8S,1)
#define CV_8SC2 CV_MAKETYPE(CV_8S,2)
#define CV_8SC3 CV_MAKETYPE(CV_8S,3)
#define CV_8SC4 CV_MAKETYPE(CV_8S,4)
#define CV_8SC(n) CV_MAKETYPE(CV_8S,(n))
#define CV_16UC1 CV_MAKETYPE(CV_16U,1)
#define CV_16UC2 CV_MAKETYPE(CV_16U,2)
#define CV_16UC3 CV_MAKETYPE(CV_16U,3)
#define CV_16UC4 CV_MAKETYPE(CV_16U,4)
#define CV_16UC(n) CV_MAKETYPE(CV_16U,(n))
#define CV_16SC1 CV_MAKETYPE(CV_16S,1)
#define CV_16SC2 CV_MAKETYPE(CV_16S,2)
#define CV_16SC3 CV_MAKETYPE(CV_16S,3)
#define CV_16SC4 CV_MAKETYPE(CV_16S,4)
#define CV_16SC(n) CV_MAKETYPE(CV_16S,(n))
#define CV_32SC1 CV_MAKETYPE(CV_32S,1)
#define CV_32SC2 CV_MAKETYPE(CV_32S,2)
#define CV_32SC3 CV_MAKETYPE(CV_32S,3)
#define CV_32SC4 CV_MAKETYPE(CV_32S,4)
#define CV_32SC(n) CV_MAKETYPE(CV_32S,(n))
#define CV_32FC1 CV_MAKETYPE(CV_32F,1)
#define CV_32FC2 CV_MAKETYPE(CV_32F,2)
#define CV_32FC3 CV_MAKETYPE(CV_32F,3)
#define CV_32FC4 CV_MAKETYPE(CV_32F,4)
#define CV_32FC(n) CV_MAKETYPE(CV_32F,(n))
#define CV_64FC1 CV_MAKETYPE(CV_64F,1)
#define CV_64FC2 CV_MAKETYPE(CV_64F,2)
#define CV_64FC3 CV_MAKETYPE(CV_64F,3)
#define CV_64FC4 CV_MAKETYPE(CV_64F,4)
#define CV_64FC(n) CV_MAKETYPE(CV_64F,(n))
这里的type可以是任何的预定义类型,预定义类型的结构如下所示:CV_<bit_depth>(S|U|F)C<number_of_channels>
1--bit_depth---比特数---代表8bite,16bites,32bites,64bites---举个例子吧--比如说,如
如果你现在创建了一个存储--灰度图片的Mat对象,这个图像的大小为宽100,高100,那么,现在这张
灰度图片中有10000个像素点,它每一个像素点在内存空间所占的空间大小是8bite,8位--所以它对
应的就是CV_8
2--S|U|F--S--代表---signed int---有符号整形
U--代表--unsigned int--无符号整形
F--代表--float---------单精度浮点型
3--C<number_of_channels>----代表---一张图片的通道数,比如:
1--灰度图片--grayImg---是--单通道图像
2--RGB彩色图像---------是--3通道图像
3--带Alph通道的RGB图像--是--4通道图像
OpenCv的矩阵类型源代码解释:
//【1】CV_8UC1---则可以创建----8位无符号的单通道---灰度图片------grayImg
#define CV_8UC1 CV_MAKETYPE(CV_8U,1)
#define CV_8UC2 CV_MAKETYPE(CV_8U,2)
//【2】CV_8UC3---则可以创建----8位无符号的三通道---RGB彩色图像---colorImg
#define CV_8UC3 CV_MAKETYPE(CV_8U,3)
//【3】CV_8UC4--则可以创建-----8位无符号的四通道---带透明色的RGB图像
#define CV_8UC4 CV_MAKETYPE(CV_8U,4)
2.2 Mat的基本计算
1.初始化
Mat I(img,Rect(10,10,100,100));//用一块地方初始化。
Mat I=img(Range:all(),Range(1,3));//所有行,1~3列
Mat I=img.clone();//完全复制
img.copyTo(I);//传递矩阵头
Mat I(2,2,CV_8UC3,Scalar(0,0,255));//I=[0,0,255,0,0,255;0,0,255,0,0,255];
Mat E=Mat::eye(4,4,CV_64F);//对角矩阵
Mat O=Mat::ones(2,2,CV_32F);//全一矩阵
Mat Z=Mat::zeros(3,3,CV_8UC1);//全零矩阵
Mat C=(Mat_(2,2)<<0,-1,2,3);//如果是简单矩阵的初始化
Mat::row(i);Mat::row(j);Mat::rowRange(start,end);Mat::colRange(start,end);都只是创建个头
Mat::diag(int d);d=0是是主对角线,d=1是比主低的对角线,d=-1....
static Mat Mat::diag(const Mat& matD)
Mat::setTo(Scalar &s);以s初始化矩阵
Mat::push_back(Mat);在原来的Mat的最后一行后再加几行
Mat::pop_back(size_t nelems=1);//移出最下面几行
b=a.clone();
2.运算符
//注意Mat的行列号是从0开始的
//定义矩阵a,b,c
Mat a,b,c;
//生成三行四列的全一矩阵 CV_64F表示精度
a=Mat::ones(3,4,CV_64F);
//a=mat::zeros(3,4,CV_64F);为生成全0
//把矩阵a复制给矩阵b 注意不能用b=a
b=a.clone();
//矩阵a每一个元素乘以2
a=a.mul(2);
//矩阵b每一个元素乘以4
b=b.mul(4);
//矩阵a点乘矩阵b
c=a.mul(b);
cout<<"a"<<a<<endl;
cout<<"b"<<b<<endl;
cout<<"c"<<c<<endl;
//常用的矩阵运算
Mat d,e,f,g;
//带权重的矩阵加法 d=a*1+b*1+4,4为常数,1,1,4均可以改变值
addWeighted(a,1,b,1,4,d);
//矩阵a除以a,结果为e
divide(d,a,e);
//常数6除以矩阵e中的每一个元素,结果为f
divide(6,e,f);
//常数2加矩阵f的每一个元素,结果为g,其中2可以为矩阵
add(2,f,g);
cout<<"d"<<d<<endl;
cout<<"e"<<e<<endl;
cout<<"f"<<f<<endl;
cout<<"g"<<g<<endl;
3.Mat类成员函数
//更改矩阵的行数。
Mat::resize
//更改矩阵的行数。
C++: void Mat::resize(size_t sz)
C++: void Mat::resize(size_t sz, const Scalar& s)
//参数
//sz –新的行数。
//s –分配给新添加的元素的值。
//该方法更改矩阵的行数。如果矩阵重新分配,第一最少(Mat::rows,sz) 行数
//要保留下来。该方法模拟相应的 STL 向量类的方法
//保留一定数量的行的空间。
C++: void Mat::reserve(size_t sz)
//参数
//sz –的行数。
//该方法sz行存储空间。如果矩阵已经有足够的空间来存储sz行,没有任何异常
//发生。如果矩阵重新分配,保留前(Mat::rows) 行。该方法模拟了相应的
//STL 向量类的方法。
//将元素添加到矩阵的底部
C++: template<typename T> voidMat::push_back(const T& elem)
C++: void Mat::push_back(const Mat& elem)
//参数
//elem –增加的一个或多个元素。
//该方法将一个或多个元素添加到矩阵的底部。他们是模拟相应的 STL 向量类的
//方法。元素为Mat时,其类型和列的数目必须和矩阵容器是相同的。
//从底部的列表中删除元素
C++: template<typename T> voidMat::pop_back(size_t nelems=1)
//参数
//nelems –删除的行的数目。如果它大于总的行数,则会引发异常。
//该方法从底部的列表中删除一行或多行。
//提取矩形子阵
C++: Mat Mat::operator()(Range rowRange, RangecolRange) const
C++: Mat Mat::operator()(const Rect& roi) const
C++: Mat Mat::operator()(const Ranges* ranges) const
//参数:
//rowRange –提取的子阵的开始和结束的行。不包括的上限。若要选择的所有
//行,请使用 Range::all()。
//colRange –提取的子阵的开始和结束的列。不包括的上限。若要选择的所有
//列,请使用 Range::all()。
//roi – 抽出子阵 specified 作为一个矩形。
//ranges – 选定范围沿每个数组维度的数组。
//该运算符为*this的子数组创建新的头。他们是Mat::row()、 Mat::col()、 //Mat::rowRange(),和Mat::colRange()最普遍的形式。例如,A(Range(0, 10),Range::all()) 是相当于A.rowRange(0, 10)。与上述所有操作相同,该操
//作运算符是复杂度为O(1)的操作,就是没有矩阵数据将被复制。
//矩阵第二行到第三行
h = M4.rowRange(1, 3);
//取矩阵第二行,行数和列数从0开始
I = M4.row(1);
Mat::size
//返回一个矩阵大小。
C++: Size Mat::size() const
//该方法返回一个矩阵大小:Size(cols, rows)。矩阵超过 2 维时返回大小为(-1,-1)。
Mat::empty
//如果数组有没有 elemens,则返回 true。
C++: bool Mat::empty() const
//如果 Mat::total() 是 0 或 Mat::data 为 NULL,则方法返回 true。
//因为pop_back() 和 resize()方法M.total()= = 0,并不意味着M.data = =NULL。
指针
Mat::ptr
//返回指定矩阵行的指针。
C++: uchar* Mat::ptr(int i=0)
C++: const uchar* Mat::ptr(int i=0) const
C++: template<typename _Tp> _Tp* Mat::ptr(inti=0)
C++: template<typename _Tp> const _Tp*Mat::ptr(int i=0) const
//参数:
//i –一个基于0的行索引。
//该方法返回uchar*,或指向由输入指定矩阵行的指针。参看Mat::isContinuous()的中示例了解如何使用这些方法。
At访问Mat矩阵
Mat::at
返回对指定数组元素的引用。
C++: template<typename T> T& Mat::at(int i)const
C++: template<typename T> const T&Mat::at(int i) const
C++: template<typename T> T& Mat::at(int i,int j)
C++: template<typename T> const T&Mat::at(int i, int j) const
C++: template<typename T> T& Mat::at(Pointpt)
C++: template<typename T> const T&Mat::at(Point pt) const
C++: template<typename T> T& Mat::at(int i,int j, int k)
C++: template<typename T> const T&Mat::at(int i, int j, int k) const
C++: template<typename T> T& Mat::at(constint* idx)
C++: template<typename T> const T&Mat::at(const int* idx) const
//参数
//i –索引 0 维度
//j – 1 维度的索引
//k – 沿 2 维度的索引
//pt – Point(j,i) 作为指定元素的位置。
//idx – Mat::dims 数组的索引。
//该模板方法返回指定数组元素的引用。为了具有更高的性能,索引范围检查只
//在调试配置下执行。请注意使用具有单个索引 (i) 的变量可以访问的单行或单
//列的2 维的数组元素。也就是比方说,如果A是1 x N 浮点矩阵和B是M x 1的
//整数矩阵,您只需编写A.at<float>(k+4) 和 B.at<int>(2*i+1) 分别代替A.at<float>(0,k+4)和
B.at<int>(2*i+1,0)。
//下面的示例将初始化希尔伯特矩阵:
Mat H(100, 100, CV_64F);
for(inti= 0; i<H.rows; i++)
for(intj= 0; j<H.cols; j++)
H.at<double>(i,j)=1./(i+j+1);
迭代器
Mat::begin
//返回矩阵迭代器,并将其设置为第一矩阵元。
C++: template<typename _Tp>MatIterator_<_Tp> Mat::begin()
C++: template<typename _Tp>MatConstIterator_<_Tp> Mat::begin() const
//该方法返回矩阵的只读或读写的迭代器。矩阵迭代器的使用和双向 STL 迭代器
//的使用是非常相似的。在下面的示例中,alpha融合函数是使用矩阵迭代器重写:
template<typename T>
void alphaBlendRGBA(const Mat& src1,const Mat& src2, Mat& dst)
{
typedef Vec<T, 4> VT;
const float alpha_scale =(float)std::numeric_limits<T>::max(),
inv_scale = 1.f/alpha_scale;
CV_Assert( src1.type() == src2.type()&&
src1.type() == DataType<VT>::type&&
src1.size() == src2.size());
Size size = src1.size();
dst.create(size, src1.type());
MatConstIterator_<VT> it1 =src1.begin<VT>(), it1_end = src1.end<VT>();
MatConstIterator_<VT> it2 =src2.begin<VT>();
MatIterator_<VT> dst_it =dst.begin<VT>();
for( ; it1 != it1_end; ++it1, ++it2,++dst_it )
{
VT pix1 = *it1, pix2 = *it2;
float alpha = pix1[3]*inv_scale, beta =pix2[3]*inv_scale;
*dst_it =VT(saturate_cast<T>(pix1[0]*alpha + pix2[0]*beta),
saturate_cast<T>(pix1[1]*alpha + pix2[1]*beta),
saturate_cast<T>(pix1[2]*alpha +pix2[2]*beta),
saturate_cast<T>((1 -(1-alpha)*(1-beta))*alpha_scale));
}
}
Mat::end
//返回矩阵迭代器,并将其设置为 最后元素之后(after-last)的矩阵元。
C++: template<typename _Tp>MatIterator_<_Tp> Mat::end()
C++: template<typename _Tp>MatConstIterator_<_Tp> Mat::end() const
//该方法返回矩阵只读或读写的迭代器,设置为紧随最后一个矩阵元素的点。