DataType
我们都知道,类型是编译阶段的事情,尤其是对C++这种语言来说,那么,如果想在运行阶段对类型进行标记,我们该怎么办呢?
DataType的产生就是来解决该问题的,我们可以看一个简单的DataType的定义,来猜测它是如何解决这个问题的,代码如下:
1 template<typename _Tp> class DataType
2 {
3 public:
4 typedef _Tp value_type;
5 typedef value_type work_type;
6 typedef value_type channel_type;
7 typedef value_type vec_type;
8 enum { generic_type = 1, depth = -1, channels = 1, fmt=0,
9 type = CV_MAKETYPE(depth, channels) };
10 };
11
12 template<> class DataType<bool>
13 {
14 public:
15 typedef bool value_type;
16 typedef int work_type;
17 typedef value_type channel_type;
18 typedef value_type vec_type;
19 enum { generic_type = 0, depth = DataDepth<channel_type>::value, channels = 1,
20 fmt=DataDepth<channel_type>::fmt,
21 type = CV_MAKETYPE(depth, channels) };
22 };
这是模板的重载,同函数重载一样,比如下面代码:
DataType<double> d1;
DataType<bool> d2;
生成对象的时候,符合d1的类模板只有第一个,故d1是按照第一个类来生成对象的;但是,对于第二个,两个类模板都满足,这样,编译器该如何选择呢? 编译器会优先选择第二个,细细体味一下,第二个类模板更贴近程序员的意图。
好! 回到正轨上,可以看出DataType里面没有任何变量和方法,只有一些类型,这样,那么,我们是怎样来识别类型的呢? 我们大可以那个具体实例来进行说明:
1 // allocates a 30x40 floating-point matrix
2 Mat A(30, 40, DataType<bool>::type);
很显然,在这个例子中,是通过type来对类型进行识别,CV_MAKETYPE这个会取得对应类型的标志, DataDepth::value获取基本类型的标志,channels=1,则是说明它是基本类型,fmt则是获得一个有意义的字符。
通过上述的描述,你应该可以知道,美剧类型中的type为该类型的标志号,通过样,通过DataType,你也可以了解到除了类型之外更多的信息。
我们在看一个复杂的DataType:
1 template<typename _Tp, int m, int n> class DataType<Matx<_Tp, m, n> >
2 {
3 public:
4 typedef Matx<_Tp, m, n> value_type;
5 typedef Matx<typename DataType<_Tp>::work_type, m, n> work_type;
6 typedef _Tp channel_type;
7 typedef value_type vec_type;
8 enum { generic_type = 0, depth = DataDepth<channel_type>::value, channels = m*n,
9 fmt = ((channels-1)<<8) + DataDepth<channel_type>::fmt,
10 type = CV_MAKETYPE(depth, channels) };
11 };
这是为复杂的数据类型Matx创建标志,上面的DataType我们很难区分每个字段的意义,这个示例将会揭示这一切。
value_type:也就是其本身的类型.
workd_type: 这个比较难以理解,我们举个例子吧:
DataType< Matx< Matx<char, 1, 5>, 5, 3> >;
那么,value_type就是Matx< Matx<char, 1, 5>, 5, 3>
work_type就是Matx<char, 5, 3>
因为其使用了递归定义,所以,不容易理解。
channel_type: 通道的类型(这里说的通道也就是元素的意思,为了与英文保持一直,我们姑且从此以后称之为“通道”)
enum这里面应该是关键,因为,这才是我们在运行时候能够用到的东西,我们一一进行说明:
generic_type: 是不是一般类型(通过对比得知)
channels: 通道的大小
fmt: 为类型提供标记,规律是,通道大小作为高8位,type的标志作为低八位
fmt的理解,我们可以把他们一一罗举出来:
CV_8U 'u'
CV_8S 'c'
CV_16U 'w'
CV_16S 's'
CV_32S 'i'
CV_32F 'f'
CV_64F 'd'
CV_USRTYPE1 'r'
你可以自行观察规律
type 类型标志
为何提供两个标志, 我猜测是DataType是类型标记不足而打上的补丁,功能重复也是难免的事情,具体是不是这样,还有待考究。
以上是类型学习的基础,如果不懂,请反复研究。
Point_
这一个类会引出一个非常有意思的问题!先把问题抛出来:
如何是C++兼容C库?
这个问题稍后一起研究一下。先来看看Point_的定义, 代码如下:
1 template<typename _Tp> class CV_EXPORTS Point_
2 {
3 public:
4 typedef _Tp value_type;
5
6 // various constructors
7 Point_();
8 Point_(_Tp _x, _Tp _y);
9 Point_(const Point_& pt);
10 Point_(const CvPoint& pt);
11 Point_(const CvPoint2D32f& pt);
12 Point_(const Size_<_Tp>& sz);
13 Point_(const Vec<_Tp, 2>& v);
14
15 Point_& operator = (const Point_& pt);
16 //! conversion to another data type
17 template<typename _Tp2> operator Point_<_Tp2>() const;
18
19 //! conversion to the old-style C structures
20 operator CvPoint() const;
21 operator CvPoint2D32f() const;
22 operator Vec<_Tp, 2>() const;
23
24 //! dot product
25 _Tp dot(const Point_& pt) const;
26 //! dot product computed in double-precision arithmetics
27 double ddot(const Point_& pt) const;
28 //! cross-product
29 double cross(const Point_& pt) const;
30 //! checks whether the point is inside the specified rectangle
31 bool inside(const Rect_<_Tp>& r) const;
32
33 _Tp x, y; //< the point coordinates
34 };
其实这个代码很简单,你细细看,肯定能够理解,这并没有展示出Point所有的功能,还有一大部分的符号重载,比如+ - +=等
CvPoint是在C语言里定义的代码,在构造函数和运算符中都有体现,有一个函数不知道挺有意思,即:
operator CvPoint() const;
这是在干什么呢? 把CvPoint当作一个运算符,我们可以联想到--运算符重载,--ojbect,同样,CvPoint ojbect,这正是我们想要的,我们想兼容C代码,正式通过这种方法进行兼容的。
实例代码如下:
1 #include "opencv2/core/core.hpp"
2 #include <iostream>
3 using namespace cv;
4 using namespace std;
5
6 void (*func)();
7
8 void func_Point_()
9 {
10 Point_<int> p1(2, 3), p2(4, 5);
11 p1 += p2;
12 p1 -= p2;
13
14 CvPoint p3;
15
16 cout << p1.x << ", " << p1.y << endl;
17 cout << p2.x << ", " << p2.y << endl;
18
19 cout << "p1.cross(p2): " << p1.cross(p2) << endl;
20 cout << "p1.dot(p2): " << p1.dot(p2) << endl;
21
22 }
23
24 int main(int argn, char**args)
25 {
26 func = func_Point_;
27
28 func();
29 return 0;
30 }
还需要附加的就是对其的扩展,通过宏定义进行扩展,如下:
1 typedef Point_<int> Point2i;
2 typedef Point2i Point;
3 typedef Point_<float> Point2f;
4 typedef Point_<double> Point2d;
Point3_
同Point_几乎一样,此处略去!
Size_
同Point_几乎一样,此处略去!
Rect_
代码如下:
1 template<typename _Tp> class CV_EXPORTS Rect_
2 {
3 public:
4 typedef _Tp value_type;
5
6 //! various constructors
7 Rect_();
8 Rect_(_Tp _x, _Tp _y, _Tp _width, _Tp _height);
9 Rect_(const Rect_& r);
10 Rect_(const CvRect& r);
11 Rect_(const Point_<_Tp>& org, const Size_<_Tp>& sz);
12 Rect_(const Point_<_Tp>& pt1, const Point_<_Tp>& pt2);
13
14 Rect_& operator = ( const Rect_& r );
15 //! the top-left corner
16 Point_<_Tp> tl() const;
17 //! the bottom-right corner
18 Point_<_Tp> br() const;
19
20 //! size (width, height) of the rectangle
21 Size_<_Tp> size() const;
22 //! area (width*height) of the rectangle
23 _Tp area() const;
24
25 //! conversion to another data type
26 template<typename _Tp2> operator Rect_<_Tp2>() const;
27 //! conversion to the old-style CvRect
28 operator CvRect() const;
29
30 //! checks whether the rectangle contains the point
31 bool contains(const Point_<_Tp>& pt) const;
32
33 _Tp x, y, width, height; //< the top-left corner, as well as width and height of the rectangle
34 };
上面的代码只供阅读,但是,有意思的并不是上面的代码,而是其重载运算符,两个:
1. |: 能够容纳左右矩形的最小矩形
2. &: 两矩形的相交的矩形
RotatedRect
这是站在另一个角度来看待矩形,以不同的方式来表示它,其代码如下:
1 class CV_EXPORTS RotatedRect
2 {
3 public:
4 //! various constructors
5 RotatedRect();
6 RotatedRect(const Point2f& center, const Size2f& size, float angle);
7 RotatedRect(const CvBox2D& box);
8
9 //! returns 4 vertices of the rectangle
10 void points(Point2f pts[]) const;
11 //! returns the minimal up-right rectangle containing the rotated rectangle
12 Rect boundingRect() const;
13 //! conversion to the old-style CvBox2D structure
14 operator CvBox2D() const;
15
16 Point2f center; //< the rectangle mass center
17 Size2f size; //< width and height of the rectangle
18 float angle; //< the rotation angle. When the angle is 0, 90, 180, 270 etc., the rectangle becomes an up-right rectangle.
19 };
用四个参数来表示:
中心点
长度
宽度
旋转的角度
同样C语言也是以这种方式来实现,即CvBox2D:
1 typedef struct CvBox2D
2 {
3 CvPoint2D32f center; /* Center of the box. */
4 CvSize2D32f size; /* Box width and length. */
5 float angle; /* Angle between the horizontal axis */
6 /* and the first side (i.e. length) in degrees */
7 }
应用代码如下:
1 #include "opencv2/core/core.hpp"
2 #include "highgui.h"
3 #include "cv.h"
4 #include <iostream>
5 using namespace cv;
6 using namespace std;
7
8 void func_RotatedRect()
9 {
10 Mat image(200, 200, CV_8UC3, Scalar(0));
11 RotatedRect rRect = RotatedRect(Point_<float>(100, 100), Size_<float>(100, 50), 30);
12
13 Point_<float> vertices[4];
14 rRect.points(vertices);
15 for(int i = 0; i < 4; i++)
16 line(image, vertices[i], vertices[(i+1)%4], Scalar(0, 255, 0));
17
18 Rect_<int> brect = rRect.boundingRect();
19 rectangle(image, brect, Scalar(255, 0, 0));
20
21 imshow("rectangles", image);
22
23 waitKey(0);
24 }
25 int main(int argn, char**args)
26 {
27 func = func_RotatedRect;
28
29 func();
30 return 0;
31 }
TermCriteria
用于迭代算法的条件控制,代码如下:
1 class CV_EXPORTS TermCriteria
2 {
3 public:
4 enum
5 {
6 COUNT=1, //!< the maximum number of iterations or elements to compute
7 MAX_ITER=COUNT, //!< ditto
8 EPS=2 //!< the desired accuracy or change in parameters at which the iterative algorithm stops
9 };
10
11 //! default constructor
12 TermCriteria();
13 //! full constructor
14 TermCriteria(int type, int maxCount, double epsilon);
15 //! conversion from CvTermCriteria
16 TermCriteria(const CvTermCriteria& criteria);
17 //! conversion to CvTermCriteria
18 operator CvTermCriteria() const;
19
20 int type; //!< the type of termination criteria: COUNT, EPS or COUNT + EPS
21 int maxCount; // the maximum number of iterations/elements
22 double epsilon; // the desired accuracy
23 };
在用到的时候, 会进行详细的研究。
Matx:
它的定义也没有想象中的那么复杂,其实不难,由于代码比较多,贴在上面不变阅读,建议您找出源代码细细品读。我就简要概括一下:
1. 仅仅是二位数组
2. 包含矩阵运算的函数重载运算符
3. 宏定义很多类型。
从1*1 到 6*6
Vec:
看它的声明:
1 template<typename _Tp, int cn> class CV_EXPORTS Vec : public Matx<_Tp, cn, 1>
很自然的,我们发现,其实Vec是Matx的子类,只不过Vec是一维而Matx是二维而已。 除此之外,
Vec<T, 2> 可转化为 Point_
Vec<T, 3> 可转化为 Point3_
Vec<T, 4> 可转化为 CvScalar
Scalar_
OpenCV使用与处理视频的,图例视频是以处理单副图像的像素为基础,所以,像素是其基础,为什么要说着呢? 其实,就是想引出Scalar_,同样看其声明:
template<typename _Tp> class CV_EXPORTS Scalar_ : public Vec<_Tp, 4>
它则集成Vec,具体意义则是,元素大小为4的一维数组。它通常用来表示像素。
Range
我们的视频有一个帧有一个范围,我们的图像像素也是有范围的,因此,我们迫切需要一个类去标定这个范围,那么,Range就是我们想要的类。
它有两个属性:
[start,end)
来标定我们的范围, 如下代码:
1 #include "opencv2/core/core.hpp"
2 #include <iostream>
3 using namespace cv;
4 using namespace std;
5 void (*func)();
6 void func_Range()
7 {
8 Range a(1, 100);
9 for(int i = a.start; i != a.end; i++)
10 {
11 cout << i << ", ";
12 }
13 }
14 int main(int argn, char**args)
15 {
16 func = func_Range;
17 func();
18 return 0;
19 }
Ptr
终于到了只能指针的环节,代码如下:
1 template<typename _Tp> class CV_EXPORTS Ptr
2 {
3 public:
4 //! empty constructor
5 Ptr();
6 //! take ownership of the pointer. The associated reference counter is allocated and set to 1
7 Ptr(_Tp* _obj);
8 //! calls release()
9 ~Ptr();
10 //! copy constructor. Copies the members and calls addref()
11 Ptr(const Ptr& ptr);
12 template<typename _Tp2> Ptr(const Ptr<_Tp2>& ptr);
13 //! copy operator. Calls ptr.addref() and release() before copying the members
14 Ptr& operator = (const Ptr& ptr);
15 //! increments the reference counter
16 void addref();
17 //! decrements the reference counter. If it reaches 0, delete_obj() is called
18 void release();
19 //! deletes the object. Override if needed
20 void delete_obj();
21 //! returns true iff obj==NULL
22 bool empty() const;
23
24 //! cast pointer to another type
25 template<typename _Tp2> Ptr<_Tp2> ptr();
26 template<typename _Tp2> const Ptr<_Tp2> ptr() const;
27
28 //! helper operators making "Ptr<T> ptr" use very similar to "T* ptr".
29 _Tp* operator -> ();
30 const _Tp* operator -> () const;
31
32 operator _Tp* ();
33 operator const _Tp*() const;
34
35 _Tp* obj; //< the object pointer.
36 int* refcount; //< the associated reference counter
37 };
智能指针真的不麻烦,这是我想强调的一点。
Ptr<_Tp>类将指针封装为对应的类,同Bost库的shared_ptr(当然,这也是C++0x的标准之一), 为我们提供的功能如下:
1. 提供了构造函数、拷贝构造函数、赋值运算符
2. 对上述操作仅O(1)的复杂度
3. 自动的析构函数
4. 可以用它封装不同类型的数据,然后放入容器中。
属性:
_Tp* obj; //这是我们封装的指针
int* refcount; //这是对该指针所指向内存区域的引用计数
//该引用计数的操作是原子操作,所以我们大可以在多线程中使用,无需有过多的担心。
方法:
方法过多,不能一一讲述,就列举连个容易混淆的方法吧!
release(); //减少计数器
delete_obj(); //释放指针所指向的内存空间
empty(); //如果ojb为空,那么返回true
Mat
我们在前面已经说过Matx, 既然有Matx,有为什么要出现Mat呢?
好,先就能够给你一个肯定的回答,Matx的维数小于3,其衍生的Scalar_ 和Vec_也不会超过2, 如果,我们要解决3维或者三维以上的问题呢?自然,我们需要新的数据结构来解决,那么,Mat就是问题解决的关键。
它是核心数据结构,我们将对其进行大篇幅的解读。其代码简要如下:
1 class CV_EXPORTS Mat
2 {
3 public:
4 // ... a lot of methods ...
5 ...
6
7 /*! includes several bit-fields:
8 - the magic signature
9 - continuity flag
10 - depth
11 - number of channels
12 */
13 int flags;
14 //! the array dimensionality, >= 2
15 int dims;
16 //! the number of rows and columns or (-1, -1) when the array has more than 2 dimensions
17 int rows, cols;
18 //! pointer to the data
19 uchar* data;
20
21 //! pointer to the reference counter;
22 // when array points to user-allocated data, the pointer is NULL
23 int* refcount;
24
25 // other members
26 ...
27 };
Mat的内部实现是以一维数组的形式实现的,也就是用一维来模拟多维的情况,如何模拟呢?你可以试着写一下用一维数组来模拟二位数组的代码。通过中间夹层函数,我们就可以实现这类模拟。
那么,我们作出这个数据结构的最主要的目的是访问,如何访问呢? Mat提供了step数组,这个数组的表现很诡异,比如下面代码:
1 #include "cv.h"
2 #include "opencv2/core/core.hpp"
3 #include <iostream>
4 using namespace cv;
5 using namespace std;
6
7 void func_Mat()
8 {
9
10 Mat_<char> m1(2, 3);
11 cout << m1.step[0] << ", " << m1.step[1] << endl;
12 }
13 int main(int argn, char**args)
14 {
15 func = func_Mat;
16 func();
17 return 0;
18 }
输出的结果是: 3, 1
当我们把Mat_的模板参数改为 int16_t
输出的结果是: 6, 2
当我们把Mat_的模板参数改为 int
输出的结果是: 12, 4
当我们把Mat_的模板参数改为 double
输出的结果是: 24, 8
至此,我们可以推断,其实step保存的并不是对应维的元素个数,而是保存的是同一维度相邻两组间的字节差距,描述起来有些不变,距离说明吧:
Mat_<char> m1(2, 3);
step[0]: 第0行与第一行起始元素之间相差的字节数
step[1]: 第0列与第1列其实元素之间相差的字节数。
至此,如果您还不理解step,请细细体味。现在的问题是为什么要存在step,其实,这个问题很容易回答,为了访问。如下代码:
1 #include "opencv2/core/core.hpp"
2 #include "highgui.h"
3 #include "cv.h"
4 #include <iostream>
5 using namespace cv;
6 using namespace std;
7 void func_Mat()
8 {
9 int a[] = {3, 4, 5, 6};
10 Mat_<char> m(4, a);
11 cout << m.step[0] << ", " << m.step[1] << ", " << m.step[2] << ", " << m.step[3] << endl;
12 }
13 int main(int argn, char**args)
14 {
15 func = func_Mat;
16 func();
17 return 0;
18 }
我们看到,在func_Mat函数中我们创建的是4维空间,那么, 如果我们要访问(2, 3, 3, 4)的位置呢?
答案是:m.data + m.step[0]*2 + m.step[1]*3 + m.step[2]*3 + m.step[3]*4
Mat_的这种布局与CvMat, IplImage, CvMatND以及大部分的工具箱和SDK的存储结构一致,这种一致性使符合为用户分配的数据提供头,使用OpenCV内置的函数处理它。
如何创建它呢? 除了上面例子无意涉及到的构造函数创建之外还可以调用create函数直接创建,格式:
1 create(nrows, ncols, type);
2 Mat(nrows, ncols, type[, fillValue]);
比如:
1 // make a 7x7 complex matrix filled with 1+3j.
2 Mat M(7,7,CV_32FC2,Scalar(1,3));
3 // and now turn M to a 100x60 15-channel 8-bit matrix.
4 // The old content will be deallocated
5 M.create(100,60,CV_8UC(15));
注意,在使用create创建的时候,create会根据当前的行、列、类型选择性的创建,如果行、列、类型与已有的相同,则不进行创建。
创建多维数组:
1 //! equivalent to Mat::create(_ndims,_sizes,DatType<_Tp>::type)
2 void create(int _ndims, const int* _sizes);
3 //! n-dim array constructor
4 Mat_(int _ndims, const int* _sizes);
5 //! n-dim array constructor that sets each matrix element to specified value
6 Mat_(int _ndims, const int* _sizes, const _Tp& value);
区域标记:
我们可以创建区域标记,比如:
Mat A = Mat::eye(10, 10, CV_32S);
Mat B = A(Range::all(), Range(1, 3);
那么B则是A的0~9行,!~3列的区域标记,也就是B是A的这一区域的影子。当然,在构造的时候,也可以构造出区域标记。
深拷贝浅拷贝
区域标记是浅拷贝,我们仅仅创建这一区域的标记而已
Mat::clone()则是深拷贝,会创建一个新的内存区域。
之前已经说过,Mat_的结构使其能够与其他的类衔接,如下代码:
1 IplImage* img = cvLoadImage("greatwave.jpg", 1);
2 Mat mtx(img); // convert IplImage* -> Mat
3 CvMat oldmat = mtx; // convert Mat -> CvMat
4 CV_Assert(oldmat.cols == img->width && oldmat.rows == img->height &&
5 oldmat.data.ptr == (uchar*)img->imageData && oldmat.step == img->widthStep);
在将Mat_转化为CvMat其实是老套路了,即:
1 //! converts header to CvMat; no data is copied
2 operator CvMat() const;
快速创建矩阵的方法是zero(), ones(), eye();
对于二维矩阵,我们可以逐行访问,也可以直接访问,住行访问的意思就是,先获取行的指针,然后处理每一行,如下代码:
1 // compute sum of positive matrix elements
2 // (assuming that M isa double-precision matrix)
3 double sum=0;
4 for(int i = 0; i < M.rows; i++)
5 {
6 const double* Mi = M.ptr<double>(i);
7 for(int j = 0; j < M.cols; j++)
8 sum += std::max(Mi[j], 0.);
9 }
注意,ptr是Mat_的成员函数,其有很多重载函数,这里是以double类型的格式返回行指针。然后,对没一行进行操作。
当然, 有些时候,我们并不需要逐行或者来来进行,我们只需要处理该容器中每个一个数据,那么,我们可以按照下面的访问方法进行访问:
1 // compute the sum of positive matrix elements, optimized variant
2 double sum=0;
3 int cols = M.cols, rows = M.rows;
4 if(M.isContinuous())
5 {
6 cols *= rows;
7 rows = 1;
8 }
9 for(int i = 0; i < rows; i++)
10 {
11 const double* Mi = M.ptr<double>(i);
12 for(int j = 0; j < cols; j++)
13 sum += std::max(Mi[j], 0.);
14 }
isContinuous()使用来判断其在内存空间中的存储是否是连续,如果不是连续,那么下面的访问会出现问题,所以,我们需要按照上一实例的格式来进行处理而不是武断是使用如下代码:
1 double sum=0;
2 int cols = M.cols, rows = M.rows;
3 if(M.isContinuous())
4 {
5 cols *= rows;
6 rows = 1;
7 }
8 const double* Mi = M.ptr<double>(0);
9 for(int j = 0; j < cols; j++)
10 sum += std::max(Mi[j], 0.);
除了以上的访问外,我们还有类似与STL的迭代器的访问,我们可以使用MatIterator_<Tp> 和 MatConstIterator_<Tp> 访问里面元素,如下:
1 // compute sum of positive matrix elements, iterator-based variant
2 double sum=0;
3 MatConstIterator_<double> it = M.begin<double>(), it_end = M.end<double>();
4 for(; it != it_end; ++it)
5 sum += std::max(*it, 0.);
完成上面之后,我们自然会问道Mat与Mat_之间的关系是怎样的?其实,它们之间并不是想Rect_与Rect之间的关系,相反Mat为父类,Mat_对其进行了进一步的封装。为什么要封装呢?我们发现,在Ma里面的很多函数都是模板函数,为什么呢?因为,Mat仅仅是以uchar的形式来无格式的组织内存,但是,有些时候波们需要格式,比如ptr获取行的指针,我们需要,因此,ptr会的一部分重载函数是作为模板函数存在的,为了减少模板函数,和一直的处理数据,我们对Mat进行封装,即:Mat_,当然,初次之外还有很多原因导致Mat_的出现。
上面。
Mat的运算
Mat A, B;
Scalar s;
double alpha;
+-:
A + B
A - B
A + s;
A - s;
s + A;
s - A;
-A;
乘:
A*alpha;
A*B;
A.cross(B);
A.dot(B);
逆运算和转置:
A.t();
A.inv()
比较运算:
A cmpop B;
A cmpop alpha;
alpha cmpop A;
[max, min](A, [B, alpha]);
cmpop: >, <, >=, <=, ==, !=
位运算:
A logicop B;
A logicop s;
s logicop A;
~A;
logicop: : &, |, ^
取绝对值:
abs(A);
总之,Mat可以运行绝大部分的矩阵操作。
有趣函数介绍:
row函数:
声明:inline Mat row(int y) const
说明:以Mat的形式返回指定的行
如代码:
1 void func_Mat1()
2 {
3 Mat m2(2, 3, CV_32S);
4 int i = 0;
5 for(MatIterator_<int> it = m2.begin<int>(); it != m2.end<int>(); it++)
6 {
7 *it = i++;
8 }
9 m2.row(1) = m2.row(0)*1;
10 for(MatConstIterator_<int> it = m2.begin<int>(); it != m2.end<int>(); it++)
11 {
12 cout << *it << ", ";
13 }
14
15 }
16
17 void func_Mat2()
18 {
19 Mat m2(2, 3, CV_32S);
20 int i = 0;
21 for(MatIterator_<int> it = m2.begin<int>(); it != m2.end<int>(); it++)
22 {
23 *it = i++;
24 }
25 m2.row(1) = m2.row(0);
26 for(MatConstIterator_<int> it = m2.begin<int>(); it != m2.end<int>(); it++)
27 {
28 cout << *it << ", ";
29 }
30 }
func_Mat1与func_Mat2的输出是不一样的,为什么会产生这种结果呢?
m2.row(0)*1
返回MatExpr,而:
m2.row(0)
返回Mat
恰恰我们的operator=有两个形式,其中一个参数传入的是
Mat
另一个参数传入的则是
MatExpr
传入Mat参数的只是浅操作,而传入MatExpr的=则是深操作,这就是造成了在Mat中最混淆的一点。
那么,我们如何应对呢?
我们使用copytTo()来避免这种混淆。
1 m2.row(0).copyTo(m2.row(1));
copyTo:
声明:
// It calls m.create(this->size(), this->type()).
void copyTo( OutputArray m ) const;
//! copies those matrix elements to "m" that are marked with non-zero mask elements.
void copyTo( OutputArray m, InputArray mask ) const;
想说两个地方:
1. 在拷贝之前m调用create(),这就意味着,m如果不符合大小或者类型的要求的时候,会重新分配。
2. mask就是规定条件,满足条件的可以拷贝,不满足的则不成。
reshape:
声明:
//! creates alternative matrix header for the same data, with different
// number of channels and/or different number of rows. see cvReshape.
Mat reshape(int cn, int rows=0) const;
Mat reshape(int cn, int newndims, const int* newsz) const;
想说:
reshape调整的是Mat的组织格式,Mat本身,组织格式包括:行、通道数、维数
代码:
std::vector<Point3f> vec;
...
Mat pointMat = Mat(vec). // convert vector to Mat, O(1) operation
reshape(1). // make Nx3 1-channel matrix out of Nx1 3-channel.
// Also, an O(1) operation
t(); // finally, transpose the Nx3 matrix.
// This involves copying all the elements
create:
//! allocates new matrix data unless the matrix already has specified size and type.
// previous data is unreferenced if needed.
void create(int rows, int cols, int type);
void create(Size size, int type);
void create(int ndims, const int* sizes, int type);
只想说明一点,create有个判断个过程,如果大小和类型符合已有,则不调用release
addref
//! increases the reference counter; use with care to avoid memleaks
void addref();
两点:
1. 原子操作
2. 只增加引用计数器
resize
//! resizes matrix to the specified number of hyper-planes
void resize(size_t sz);
//! resizes matrix to the specified number of hyper-planes; initializes the newly added elements
void resize(size_t sz, const Scalar& s);
一点:
不同与reshape,类似STL中的resize。
locateROI:
//! locates matrix header within a parent matrix. See below
void locateROI( Size& wholeSize, Point& ofs ) const;
以矩形的方式在矩阵上创建映像。
至此,把Mat的林林总总过了一遍,其核心内容已经详述。我们接着往下研读。
Mat_
上面已经阐述过Mat_的存在的意义,其实,Mat_就是对Mat的一个简单的封装,没有增加任何属性和函数,只是简单的封装而已。当然,它们两个个有特色,一个是高效灵活,一个则是方便,比如我们在访问元素的时候, Mat_更加方便,不过,在他们之间转换的时候,一定要小心转换,如下代码:
1 void func_Mat_()
2 {
3 //这种情况会出现错误, 因为, mat是以CV_8U的格式实现的,而以float的格式访问mat_(99,99)肯定超出范围,所以,在他们之间进行转换的时候要格外的小心。
4 Mat mat(100, 100, CV_8U);
5 Mat_<float>& mat_ = (Mat_<float>&)mat;
6 mat_(99, 99) = 99.999;
7 }
我们再列举一个实例吧,如下:
1 void func_Mat_()
2 {
3 //通过这种比较,我们发现,Mat_的数据访问更加方便一些,直接使用()运算符重载即可。
4 Mat_<double>mat1(20, 20);
5 for(int i = 0; i < mat1.rows; i++)
6 for(int j = 0; j < mat1.cols; j++)
7 mat1(i, j) = 1./(i+j+1);
8
9 Mat E, V;
10 eigen(mat1, E, V);
11 cout << E.at<double>(0, 0)/E.at<double>(mat1.rows-1, 0);
12
13
14 //使用通道的实例
15 Mat_<Vec3b> img(320, 240, Vec3b(0,255,0));
16 for(int i = 0; i < 100; i++)
17 img(i, i)=Vec3b(255, 255, 255);
18
19 imshow("img1", img);
20
21 waitKey(1000);
22 for(int i = 0; i < img.rows; i++)
23 for(int j = 0; j < img.cols; j++)
24 img(i, j)[2] ^= (uchar)(i^j);
25
26 imshow("img2", img);
27 waitKey(0);
28 }
_InputArray
是其它类型的统一封装,不可思议的是它也是_OutputArray的父类,他将stl:vector, Mat, Matx, Mat_等等很多的类型进行封装,需要说明的函数是getMat,其声明如下:
virtual Mat getMat(int i=-1) const;
其中的参数是什么意思呢?一个Mat_可以有很多层,比如, Mat_<Mat_<Mat_<double> > > mat; 那么我们便可以通过其参数指定我们获取的是那一层的影像。(这个地方,并没有示范详述的描述,还有一些疑问,比如,如何定位层,应该不是这样理解的!)
用一个宏定义,定义出我们经常使用的InputArray
typedef const _InputArray& InputArray
实例代码:
1 void myAffineTransform(InputArray _src, OutputArray _dst, InputArray _m)
2 {
3 // get Mat headers for input arrays. This is O(1) operation,
4 // unless _src and/or _m are matrix expressions.
5 Mat src = _src.getMat(), m = _m.getMat();
6 CV_Assert( src.type() == CV_32FC2 && m.type() == CV_32F && m.size() == Size(3, 2) );
7
8 // [re]create the output array so that it has the proper size and type.
9 // In case of Mat it calls Mat::create, in case of STL vector it calls vector::resize.
10 _dst.create(src.size(), src.type());
11 Mat dst = _dst.getMat();
12
13 for( int i = 0; i < src.rows; i++ )
14 for( int j = 0; j < src.cols; j++ )
15 {
16 Point2f pt = src.at<Point2f>(i, j);
17 dst.at<Point2f>(i, j) = Point2f(m.at<float>(0, 0)*pt.x +
18 m.at<float>(0, 1)*pt.y +
19 m.at<float>(0, 2),
20 m.at<float>(1, 0)*pt.x +
21 m.at<float>(1, 1)*pt.y +
22 m.at<float>(1, 2));
23 }
24 }
_OutputArray:
与InputArray类似,此处从略
NAryMatIterator
使用这个类,我们在多维数组上的一元、二元或者多元的运算,一些n元函数的参数肯肯能是连续的数组,还有一些则不是!使用方便的MatIteratord,但是对于每一步很小的操作都会引起迭代器的增加将会是一个很大的开销,在这种情况下,就需要考虑使用NAryMatIterator迭代器,用它来访问多个矩阵,当然前提是,如果这些矩阵有相同的几何构型(维数和每一维的尺寸相同), 在每一次迭代的过程中,it.planes[0], it.planes[1], ..将会被切割成对应的矩阵。
这个很不容易理解,我们可以先看一下声明:
1 class CV_EXPORTS NAryMatIterator
2 {
3 public:
4 //! the default constructor
5 NAryMatIterator();
6 //! the full constructor taking arbitrary number of n-dim matrices
7 NAryMatIterator(const Mat** arrays, uchar** ptrs, int narrays=-1);
8 //! the full constructor taking arbitrary number of n-dim matrices
9 NAryMatIterator(const Mat** arrays, Mat* planes, int narrays=-1);
10 //! the separate iterator initialization method
11 void init(const Mat** arrays, Mat* planes, uchar** ptrs, int narrays=-1);
12
13 //! proceeds to the next plane of every iterated matrix
14 NAryMatIterator& operator ++();
15 //! proceeds to the next plane of every iterated matrix (postfix increment operator)
16 NAryMatIterator operator ++(int);
17
18 //! the iterated arrays
19 const Mat** arrays;
20 //! the current planes
21 Mat* planes;
22 //! data pointers
23 uchar** ptrs;
24 //! the number of arrays
25 int narrays;
26 //! the number of hyper-planes that the iterator steps through
27 size_t nplanes;
28 //! the size of each segment (in elements)
29 size_t size;
30 protected:
31 int iterdepth;
32 size_t idx;
33 };
NAryMatIterator就是对MatIterator的数组封装,只不过是以另一种形式来进行的封装,
对这块而的理解,等回头给予详细的补充。
SparseMat
稀疏矩阵,本来已经存在了一个矩阵为何有创建一个稀疏矩阵,其实,其目的简单,就是为了更高效的执行矩阵的操作,我们都知道稀疏矩阵很有特点,那么,我们就是紧紧抓住这些特点来对其数据结构进行设计。
其底层实现是使用哈希表的形式实现的。这样,在访问的时候,我们能够达到O(1)的访问速度。
真正要说的并不多,还是其API函数的使用,用一个函数来说明这一切:
1 void func_SparseMat()
2 {
3 const int dims = 5;
4 int size[] = {10, 10, 10, 10, 10};
5 SparseMat sparse_mat(dims, size, CV_32F);
6 for(int i = 0; i < 1000; i++)
7 {
8 int idx[dims];
9 for(int k = 0; k < dims; k++)
10 idx[k] = rand()%10;
11 sparse_mat.ref<float>(idx) += 1.f;
12 }
13 //输出维度
14 cout << sparse_mat.dims() << endl;
15
16 /*//这是一种方法进行便利,很麻烦的一种
17 int idx1[5];
18 for(int i = 0; i < 10; i++)
19 {
20 for(int j = 0; j < 10; j++)
21 {
22 for(int k = 0; k < 10; k++)
23 {
24 for(int l = 0; l < 10; l++)
25 {
26 for(int m = 0; m < 10; m++)
27 {
28 idx1[0] = i;
29 idx1[1] = j;
30 idx1[2] = k;
31 idx1[3] = l;
32 idx1[4] = m;
33
34 cout << sparse_mat.ref<float>(idx1) << ", ";
35 }
36 cout << "\t\t\t第四维度" << endl;
37 }
38 cout << "\t\t\t第三维度" << endl << endl;
39 }
40 cout << "\t\t\t第二维度" << endl << endl << endl;
41 }
42 cout << "\t\t\t第一维度" << endl << endl << endl << endl;
43 }*/
//下面提供了访问稀疏矩阵比较好的方法,如果你仔细看的话,会发现,它是按照哈希表本身的顺序进行输出的,系数矩阵的特点就是值为0的数并不保存在空间中,要记住这种访问的方法。
SparseMat_<float> sparse_mat_;
sparse_mat.copyTo(sparse_mat_);
for(SparseMatIterator_<float> it = sparse_mat_.begin(); it != sparse_mat_.end(); ++it)
{
const SparseMat::Node* n = it.node();
printf("(");
for(int i = 0; i < dims; i++)
printf("%d%s", n->idx[i], i < dims-1 ? ", " : ")");
printf(": %g\n", it.value<float>());
}
}
我们在对其元素进行访问的时候,可以通过ref直接访问,从it获取node,然后从node获取idx,之后进行访问, 第二种访问要好一些。
当然,SpareMat的迭代器同Mat的迭代器很相似,再次就不进行详细的说明了。
初次之外,还需要注意SpareMat和SpareMat_之间的转化, 以及SpareMatIterator与SpareMatIterator_之间的转化,使用begin和end的时候,如果是模板函数,则返回SpareMatIterator_,否则则返回SpareMatIterator。
Algorithm:
基础类型终于要结束了,这是最后的一个类型,当然,还有很多的类型没有罗列出来,不过,大部分重要的基础类型都展现在这份文档中了。最后这个类的声明也不是太长,我们姑且将其声明全部拷贝到上面:
1 class CV_EXPORTS_W Algorithm
2 {
3 public:
4 Algorithm();
5 virtual ~Algorithm();
6 string name() const;
7
8 template<typename _Tp> typename ParamType<_Tp>::member_type get(const string& name) const;
9 template<typename _Tp> typename ParamType<_Tp>::member_type get(const char* name) const;
10
11 CV_WRAP int getInt(const string& name) const;
12 CV_WRAP double getDouble(const string& name) const;
13 CV_WRAP bool getBool(const string& name) const;
14 CV_WRAP string getString(const string& name) const;
15 CV_WRAP Mat getMat(const string& name) const;
16 CV_WRAP vector<Mat> getMatVector(const string& name) const;
17 CV_WRAP Ptr<Algorithm> getAlgorithm(const string& name) const;
18
19 void set(const string& name, int value);
20 void set(const string& name, double value);
21 void set(const string& name, bool value);
22 void set(const string& name, const string& value);
23 void set(const string& name, const Mat& value);
24 void set(const string& name, const vector<Mat>& value);
25 void set(const string& name, const Ptr<Algorithm>& value);
26 template<typename _Tp> void set(const string& name, const Ptr<_Tp>& value);
27
28 CV_WRAP void setInt(const string& name, int value);
29 CV_WRAP void setDouble(const string& name, double value);
30 CV_WRAP void setBool(const string& name, bool value);
31 CV_WRAP void setString(const string& name, const string& value);
32 CV_WRAP void setMat(const string& name, const Mat& value);
33 CV_WRAP void setMatVector(const string& name, const vector<Mat>& value);
34 CV_WRAP void setAlgorithm(const string& name, const Ptr<Algorithm>& value);
35 template<typename _Tp> void setAlgorithm(const string& name, const Ptr<_Tp>& value);
36
37 void set(const char* name, int value);
38 void set(const char* name, double value);
39 void set(const char* name, bool value);
40 void set(const char* name, const string& value);
41 void set(const char* name, const Mat& value);
42 void set(const char* name, const vector<Mat>& value);
43 void set(const char* name, const Ptr<Algorithm>& value);
44 template<typename _Tp> void set(const char* name, const Ptr<_Tp>& value);
45
46 void setInt(const char* name, int value);
47 void setDouble(const char* name, double value);
48 void setBool(const char* name, bool value);
49 void setString(const char* name, const string& value);
50 void setMat(const char* name, const Mat& value);
51 void setMatVector(const char* name, const vector<Mat>& value);
52 void setAlgorithm(const char* name, const Ptr<Algorithm>& value);
53 template<typename _Tp> void setAlgorithm(const char* name, const Ptr<_Tp>& value);
54
55 CV_WRAP string paramHelp(const string& name) const;
56 int paramType(const char* name) const;
57 CV_WRAP int paramType(const string& name) const;
58 CV_WRAP void getParams(CV_OUT vector<string>& names) const;
59
60
61 virtual void write(FileStorage& fs) const;
62 virtual void read(const FileNode& fn);
63
64 typedef Algorithm* (*Constructor)(void);
65 typedef int (Algorithm::*Getter)() const;
66 typedef void (Algorithm::*Setter)(int);
67
68 CV_WRAP static void getList(CV_OUT vector<string>& algorithms);
69 CV_WRAP static Ptr<Algorithm> _create(const string& name);
70 template<typename _Tp> static Ptr<_Tp> create(const string& name);
71
72 virtual AlgorithmInfo* info() const /* TODO: make it = 0;*/ { return 0; }
73 };
这是OpenCV复杂算法的基础类。
这个类提供了如下的特性:
1. 所谓的虚构造。即每一个算法在程序刚刚启动的时候就注册,将其名称放入对应的注册表中(可以参加create)。因此,如果你想创建一个你自己的函数,最好是为自己的函数创建自己的前缀加以区分。
2. 通过名称来设置或者获取函数的参数。
3. 从XML或者YAML中读写参数。每一个衍生算法都可以保存他所有的参数供以后读取,没有必要重复实现。
至此,结束我们的基础类