opencv2.0 DataType 实现

上一篇 opencv2.0 DataType  我们看了DataType的组成,这一篇就来研究一下他们的成员函数。


Point_(在operations.hpp里)

//7个构造函数
template<typename _Tp> inline Point_<_Tp>::Point_() : x(0), y(0) {}
template<typename _Tp> inline Point_<_Tp>::Point_(_Tp _x, _Tp _y) : x(_x), y(_y) {}
template<typename _Tp> inline Point_<_Tp>::Point_(const Point_& pt) : x(pt.x), y(pt.y) {}
template<typename _Tp> inline Point_<_Tp>::Point_(const CvPoint& pt) : x((_Tp)pt.x), y((_Tp)pt.y) {}
template<typename _Tp> inline Point_<_Tp>::Point_(const CvPoint2D32f& pt)
    : x(saturate_cast<_Tp>(pt.x)), y(saturate_cast<_Tp>(pt.y)) {}
template<typename _Tp> inline Point_<_Tp>::Point_(const Size_<_Tp>& sz) : x(sz.width), y(sz.height) {}
template<typename _Tp> inline Point_<_Tp>::Point_(const Vec<_Tp,2>& v) : x(v[0]), y(v[1]) {}

其中两个为了兼容1.0版本,这个就不考虑了。我们主要来看看剩余的5个

#include<iostream>
#include<opencv2/core/core.hpp>
using namespace std;
using namespace cv;

int main()
{
    Point p1;
    Point2f p2(1.1,2.2);
    Point2f p3(p2);
    Point2d p4(Size_<double>(0.5,0.6));
    Point2i p5(Vec<int,2>(0,0));
    return 0;
}

接下来推荐一篇博客 saturate_cast 研究 ,专门讨论saturate_cast模板类,因为以后到处要用到...看过之后,千万不要再使用强制类型转换了,否则出问题后果自负...

template<typename _Tp> inline Point_<_Tp>& Point_<_Tp>::operator = (const Point_& pt)
{ x = pt.x; y = pt.y; return *this; }
template<typename _Tp> template<typename _Tp2> inline Point_<_Tp>::operator Point_<_Tp2>() const
{ return Point_<_Tp2>(saturate_cast<_Tp2>(x), saturate_cast<_Tp2>(y)); }
template<typename _Tp> inline Point_<_Tp>::operator CvPoint() const
{ return cvPoint(saturate_cast<int>(x), saturate_cast<int>(y)); }
template<typename _Tp> inline Point_<_Tp>::operator CvPoint2D32f() const
{ return cvPoint2D32f((float)x, (float)y); }
template<typename _Tp> inline Point_<_Tp>::operator Vec<_Tp, 2>() const
{ return Vec<_Tp, 2>(x, y); }

复制构造函数,就是分别给x,y分量赋值,强制类型转换有转成Point_<_Tp>和Vec<_Tp,2>型的,对于转成Point_<_Tp>其实就是x,y分量分别通过saturate_cast一下。剩余两个是兼容1.0版本,不讲了。

Point p1(1,2);
Point2i p2 = p1;
Point2d p3(1.2,2.3);
Point_<int> p4 = p3; //此处x,y会强转为int
Point_<int> p5(-1,256);
Point_<char> p6 = Point_<char>(p5); //2.3.1编译报错
Point_<char> p6 = static_cast<Point_<char> >(p5); //2.3.1编译报错
Vec2i vec = (Vec2i)p5;

郁闷啊,找了很久的错误,最后一行一直编译通不过,最后发现可能是低版本的一个bug,高版本已修复可以编译通过了,至于编译错误的原因,可能是编译器将(Point_<char>)p5看做Point_<char>(p5),然后尝试调用p5的隐式转换,可以从上面看出有CvPoint,CvPoint2D32f,Point_<_Tp>这三种隐式转换,那么这3个都可以作为Point_的拷贝构造函数的参数,因此编译器就不清楚了,然后报错了。从侧面体现了强制类型转换实际上是被编译器认为调用构造函数,尝试从多个重载过的强制转换函数做隐式转换,所以当重载了多个强制转换函数和这些强转后的类型都有匹配的构造函数时,需要注意了,此时就会出现冲突。

一个问题折射了强制转换函数真正的运行原理,看来这么多时间花得是值得的,既然这样,那么尝试修改源码,注释掉那两个早期版本的构造函数或者在前面加关键字explicit,限制隐式调用,或者直接注释掉那两个早版本的强制转换函数,理论上可以得到解决,但是考虑到整个库是一个整体,关联紧密,可能这些东西在别处会被引用(实际上修改后确实在别处报错了),因此建议使用高版本。


两个点积函数,(x1,y1)与(x2,y2)的点积为x1*x2+y1*y2

template<typename _Tp> inline _Tp Point_<_Tp>::dot(const Point_& pt) const
{ return saturate_cast<_Tp>(x*pt.x + y*pt.y); }
template<typename _Tp> inline double Point_<_Tp>::ddot(const Point_& pt) const
{ return (double)x*pt.x + (double)y*pt.y; }
注意第一个函数是_Tp类型与_Tp类型的点积,最后结果通过saturate_cast转换回_Tp
第二个函数也是_Tp类型与_Tp类型的点积,最后结果以double形式返回

Point_<char> p1(1,2);
Point p2(-129,0);
p1.dot(p2); //ASCII:127
Point_<schar> p3(1,2);
Point p4(-129,0);
p3.dot(p4); //ASCII:-128
Point p5(-129,0),p6(12,12);
p5.ddot(p6); //1548

我们这里用得非常规一下,主要是要注意以后这样用要小心,对于dot如果调用对象的模板类型与形参对象的模板类型不一致,则形参对象的模板类型会调用强制转换函数转成调用对象的模板类型,然后在尝试进行点积,这里就有一个疑问了,上面部分我说低版本2.3.1对于强制转换会报错,但是这个竟然编译通过了,原因解释如下:经过debug,发现实参强制转换后并没有调用形参的拷贝构造函数,至于为什么不调用,我也解释不清,因为如果传入的形参不需要强制转换,则是会调用拷贝构造函数的,难道这里编译器做了什么 ...不清楚了...
更神奇的事,上面第3行和第6行结果不一样...这个有点恐怖...经过查询,实际上c++确实规定了3种type,例如这里的char,unsigned char,signed char...   char与singed char是不一样的...所以对于第3行,调用(char)(-129),而第6行则调用saturate_cast<schar>(-129)...
所以啊,不要玩太高端了...不然连你自己都解释不了...而且C++语法是博大精深的,别以为你真的学透了...

还有一些运算符之类的...
Point p1(1,2),p2(2,1);
p1 += p2; // +=
p1 -= p2; // -=
p1 *= 1; // * int
p1 *= (float)1.0; // * float
p1 *= 1.0; // * double
norm(p1); //sqrt((double)p1.x*p1.x + (double)p1.y*p1.y)
p1 == p2; // ==
p1 != p2; // !=
p1 + p2; // +
p1 - p2; // -
- p1; // 取反
1 * p1; // int *
p1 * 1; // * int
(float)1.0 * p1; // float *
p1 * (float)1.0; // * float
1.0 * p1; // double *
p1 * 1.0; // * double

这里有一个判断点是否在一个矩形内的函数,调用了Rect_<_Tp>的contains()方法,详细见下面对Rect_的介绍...
template<typename _Tp> inline bool Point_<_Tp>::inside( const Rect_<_Tp>& r ) const
{ return r.contains(*this);}
简单的调用如下
Point(1,1).inside(Rect(1,1,1,1));

Point3_(在operations.hpp里)
//6个构造函数
template<typename _Tp> inline Point3_<_Tp>::Point3_() : x(0), y(0), z(0) {}
template<typename _Tp> inline Point3_<_Tp>::Point3_(_Tp _x, _Tp _y, _Tp _z) : x(_x), y(_y), z(_z) {}
template<typename _Tp> inline Point3_<_Tp>::Point3_(const Point3_& pt) : x(pt.x), y(pt.y), z(pt.z) {}
template<typename _Tp> inline Point3_<_Tp>::Point3_(const Point_<_Tp>& pt) : x(pt.x), y(pt.y), z(_Tp()) {}
template<typename _Tp> inline Point3_<_Tp>::Point3_(const CvPoint3D32f& pt) :
    x(saturate_cast<_Tp>(pt.x)), y(saturate_cast<_Tp>(pt.y)), z(saturate_cast<_Tp>(pt.z)) {}
template<typename _Tp> inline Point3_<_Tp>::Point3_(const Vec<_Tp, 3>& v) : x(v[0]), y(v[1]), z(v[2]) {}

同样道理不同类型的拷贝构造函数在调用时,会出现问题。
#include<iostream>
#include<opencv2/core/core.hpp>
using namespace std;
using namespace cv;

int main()
{
    Point3i p1;
    Point3i p2(1,1,1);
    Point3i p3(p2);
    Point3f p4;
    Point3i p5(p4); //2.3.1编译通不过
    Vec<double,3> vec;
    Point3d p6(vec);
    return 0;
}

强制转换函数
template<typename _Tp> template<typename _Tp2> inline Point3_<_Tp>::operator Point3_<_Tp2>() const
{ return Point3_<_Tp2>(saturate_cast<_Tp2>(x), saturate_cast<_Tp2>(y), saturate_cast<_Tp2>(z)); }
template<typename _Tp> inline Point3_<_Tp>::operator CvPoint3D32f() const
{ return cvPoint3D32f((float)x, (float)y, (float)z); }
template<typename _Tp> inline Point3_<_Tp>::operator Vec<_Tp, 3>() const
{ return Vec<_Tp, 3>(x, y, z); }

Point3i p1;
Point3_<short> p2 = (Point3_<short>)p1; //2.3.1编译通不过
Vec3i vec = (Vec3i)p1;


多了一个叉积
template<typename _Tp> inline Point3_<_Tp>& Point3_<_Tp>::operator = (const Point3_& pt)
{ x = pt.x; y = pt.y; z = pt.z; return *this; }
template<typename _Tp> inline _Tp Point3_<_Tp>::dot(const Point3_& pt) const
{ return saturate_cast<_Tp>(x*pt.x + y*pt.y + z*pt.z); }
template<typename _Tp> inline double Point3_<_Tp>::ddot(const Point3_& pt) const
{ return (double)x*pt.x + (double)y*pt.y + (double)z*pt.z; }
template<typename _Tp> inline Point3_<_Tp> Point3_<_Tp>::cross(const Point3_<_Tp>& pt) const
{ return Point3_<_Tp>(y*pt.z - z*pt.y, z*pt.x - x*pt.z, x*pt.y - y*pt.x); }

补充点计算几何知识

还有一些运算符,完全和Point_一样,这里不给出代码了...

Size_(在operations.hpp里)
//6个构造函数
template<typename _Tp> inline Size_<_Tp>::Size_()
    : width(0), height(0) {}
template<typename _Tp> inline Size_<_Tp>::Size_(_Tp _width, _Tp _height)
    : width(_width), height(_height) {}
template<typename _Tp> inline Size_<_Tp>::Size_(const Size_& sz)
    : width(sz.width), height(sz.height) {}
template<typename _Tp> inline Size_<_Tp>::Size_(const CvSize& sz)
    : width(saturate_cast<_Tp>(sz.width)), height(saturate_cast<_Tp>(sz.height)) {}
template<typename _Tp> inline Size_<_Tp>::Size_(const CvSize2D32f& sz)
    : width(saturate_cast<_Tp>(sz.width)), height(saturate_cast<_Tp>(sz.height)) {}
template<typename _Tp> inline Size_<_Tp>::Size_(const Point_<_Tp>& pt) : width(pt.x), height(pt.y) {}

注意2个是为了兼容1.0版本
#include<iostream>
#include<opencv2/core/core.hpp>
using namespace std;
using namespace cv;

int main()
{
    Size s1;
    Size2f s2(1,2);
    Size2f s3(s2);
    Size2i s4(Point(1,1));
    Size2i s5(Vec<int,2> vec);
    Vec2i vec;
    Size2i s6(vec);
    return 0;
}

来看一点精彩的东西,对于s5,你们觉得这是什么?实际上这是一个函数声明,即s5是函数地址,因此如果输出s5.width必然报错,那s6呢?显然不是函数声明了,但为什么可以编译通过,是这样的Vec2i先调用了Point的构造函数Point(Vec),于是变成了Point,然后Size2i调用以Point为参数的构造函数...至于为什么Vec会自动调用Point的构造函数呢?这是一个值得思考的问题,编译器除了考虑强制转换类型的重载,难道还考虑其他类的构造函数,想方设法将其变成其他类?这个是单步debug出来的,毋庸置疑正确性...

强制转换
template<typename _Tp> template<typename _Tp2> inline Size_<_Tp>::operator Size_<_Tp2>() const
{ return Size_<_Tp2>(saturate_cast<_Tp2>(width), saturate_cast<_Tp2>(height)); }
template<typename _Tp> inline Size_<_Tp>::operator CvSize() const
{ return cvSize(saturate_cast<int>(width), saturate_cast<int>(height)); }
template<typename _Tp> inline Size_<_Tp>::operator CvSize2D32f() const
{ return cvSize2D32f((float)width, (float)height); }

这里还是低版本会报错,因为拷贝构造函数冲突了

一些运算符

Size2i s1(1,1);
Size s2 = s1 * 1; // * _Tp
s2 = s1; // =
s1 + s2; // +
s1 - s2; // -
s1 += s2; // +=
s1 -= s2; // -=
s1 == s2; // ==
s1 != s2; // !=
s1.area(); // s1.width * s1.height

Rect_(在operations.hpp里)
//6个构造函数
template<typename _Tp> inline Rect_<_Tp>::Rect_() : x(0), y(0), width(0), height(0) {}
template<typename _Tp> inline Rect_<_Tp>::Rect_(_Tp _x, _Tp _y, _Tp _width, _Tp _height) : x(_x), y(_y), width(_width), height(_height) {}
template<typename _Tp> inline Rect_<_Tp>::Rect_(const Rect_<_Tp>& r) : x(r.x), y(r.y), width(r.width), height(r.height) {}
template<typename _Tp> inline Rect_<_Tp>::Rect_(const CvRect& r) : x((_Tp)r.x), y((_Tp)r.y), width((_Tp)r.width), height((_Tp)r.height) {}
template<typename _Tp> inline Rect_<_Tp>::Rect_(const Point_<_Tp>& org, const Size_<_Tp>& sz) :
    x(org.x), y(org.y), width(sz.width), height(sz.height) {}
template<typename _Tp> inline Rect_<_Tp>::Rect_(const Point_<_Tp>& pt1, const Point_<_Tp>& pt2)
{
    x = std::min(pt1.x, pt2.x); y = std::min(pt1.y, pt2.y);
    width = std::max(pt1.x, pt2.x) - x; height = std::max(pt1.y, pt2.y) - y;
}

#include<iostream>
#include<opencv2/core/core.hpp>
using namespace std;
using namespace cv;

int main()
{
    Rect r1;
    Rect r2(1,1,2,2);
    Rect r3(r1);
    Rect r4(Point(1,1),Size(2,2));
    Rect r5(Point(2,2),Point(1,3));
    Rect r6(Point(2,2),Point(2,3));
    return 0;
}
我们来看最后一个构造函数,非常有意思,实际上最后一个函数是给你两个点,让你确定这个矩形。库的写法非常漂亮...
首先特殊情况,pt1和pt2相同,那么矩形缩成一个点,width和height均为0
其次是pt1.x == pt2.x && pt1.y != pt2.y,那么矩形退化成一条平行y轴的线段,width=0,height=abs(pt2.y-pt1.y)
再次是pt1.y == pt2.y && pt1.x != pt2.x,那么矩形退化成一条平行x轴的线段,height=0,width=abs(pt2.x-pt1.x)
最后就是pt1.x != pt2.x && pt1.y != pt2.y,这表明这两个点的连线段是矩形的对角线,并且矩形最最左上角的点为( min(pt1.x,pt2.x),min(pt1.y,pt2.y) ),
右下角的点为( max(pt1.x,pt2.x),max(pt1.y,pt2.y) )
可以发现这4种情况并不需要if/else来判断,可以统一为一种情况,就是库的极其精致的写法...值得我们学习...

//! the top-left corner
Point_<_Tp> tl() const;
//! the bottom-right corner
Point_<_Tp> br() const;
//! size (width, height) of the rectangle
Size_<_Tp> size() const;
//! area (width*height) of the rectangle
_Tp area() const;

Rect r1(1,1,2,2);
r1.tl(); // 左上角(1,1)
r1.br(); // 右下角(3,3)
Size s = r1.size(); 
r1.area(); // r1.width * r1.height

强转函数
template<typename _Tp> template<typename _Tp2> inline Rect_<_Tp>::operator Rect_<_Tp2>() const
{ return Rect_<_Tp2>(saturate_cast<_Tp2>(x), saturate_cast<_Tp2>(y),
                     saturate_cast<_Tp2>(width), saturate_cast<_Tp2>(height)); }
template<typename _Tp> inline Rect_<_Tp>::operator CvRect() const
{ return cvRect(saturate_cast<int>(x), saturate_cast<int>(y),
                saturate_cast<int>(width), saturate_cast<int>(height)); }
同样低版本会报错

template<typename _Tp> inline bool Rect_<_Tp>::contains(const Point_<_Tp>& pt) const
{ return x <= pt.x && pt.x < x + width && y <= pt.y && pt.y < y + height; }

Rect r1(1,1,1,1);
r1.contains(Point(1,1)); // true
r1.contains(Point(1,2)); // false
r1.contains(Point(2,1)); // false
r1.contains(Point(2,2)); //false

注意矩形的右边界和下边界不算在矩形里,对于浮点型的要注意,因为浮点误差,右边界与下边界判断可能出现误差...

一些运算符
Rect r1(1,1,1,1);
r1 += Point(1,1); //平移
r1 -= Point(1,1); //平移
r1 = r1 + Point(1,1); //平移
r1 = r1 - Point(1,1); //平移
r1 += Size(1,1); //扩张
r1 -= Size(1,1); //缩小
r1 = r1 + Size(1,1); //扩张
r1 = r1 - Size(1,1); //缩小
r1 == r1;
r1 != r1;

两个神奇的运算
template<typename _Tp> static inline Rect_<_Tp>& operator &= ( Rect_<_Tp>& a, const Rect_<_Tp>& b )
{
    _Tp x1 = std::max(a.x, b.x), y1 = std::max(a.y, b.y);
    a.width = std::min(a.x + a.width, b.x + b.width) - x1;
    a.height = std::min(a.y + a.height, b.y + b.height) - y1;
    a.x = x1; a.y = y1;
    if( a.width <= 0 || a.height <= 0 )
        a = Rect();
    return a;
}
template<typename _Tp> static inline Rect_<_Tp>& operator |= ( Rect_<_Tp>& a, const Rect_<_Tp>& b )
{
    _Tp x1 = std::min(a.x, b.x), y1 = std::min(a.y, b.y);
    a.width = std::max(a.x + a.width, b.x + b.width) - x1;
    a.height = std::max(a.y + a.height, b.y + b.height) - y1;
    a.x = x1; a.y = y1;
    return a;
}

来参观一下代码,比较复杂,大致说说思想。&表示矩形求交集,可以肯定结果必然是一个矩形或者不存在。我们考虑矩形a和矩形b的左上角,如果两者有交集,那么交集矩形的左上角必然是 ( max(a.x,b.x),max(a.y,b.y) ),因为矩形a和b都是以左上角往右边和下边延伸的,因此例如a.x<b.x,那么在区间[a.x,b.x]之间必然不存在交集,因为b是往右延伸的,y方向也是这样。这样我们便得到了相交矩形的左上角顶点,那么width和height怎么定,显然我们知道a的右边界坐标a.x+a.width和b的右边界坐标b.x+b.width,显然要取最小值min(a.x+a.width,b.x+b.width),因为大于这个最小值部分是不会有交集的,然后我们知道左上角坐标,也就是左边界坐标,然后将这个最小值减去左边界,我们就得到了交集矩形的width,同理计算height...然而我们会发现当交集不存在的情况下,按照我们这种做法,左上角坐标还是存在的,但是会发现width和height小于等于0的情况,其实可以通过枚举法,把所有a和b可能的位置枚举一遍,可以发现上面这种做法均是正确的...
接下来是或运算,这个并不是表示矩形a和b的并集,因为我们知道a和b的并集很可能不是矩形,所以或运算表示包含矩形a和b并集的最小矩形,同样的我们可以采用上面的方法来确定这个矩形的左上角坐标、widht和height,左上角取( min(a.x,b.x),min(a.y,b.y) ),右边界取max(a.x+a.width,b.x+b.width),下边界取max(a.y+a.height,b.y+b.height)...同样的可以采用枚举法对所有情况验证...

Rect r1(1,1,1,1);
Rect r2(2,2,1,1);
r1 & r2;
r1 | r2;
r1 &= r2;
r1 |= r2;

当然如果要判断矩形a是否包含于矩形b,则只要判断a & b是否等于 b即可...

RotatedRect(在operations.hpp里)
//3个构造函数
inline RotatedRect::RotatedRect() { angle = 0; }
inline RotatedRect::RotatedRect(const Point2f& _center, const Size2f& _size, float _angle)
    : center(_center), size(_size), angle(_angle) {}
inline RotatedRect::RotatedRect(const CvBox2D& box)
    : center(box.center), size(box.size), angle(box.angle) {}

#include<iostream>
#include<opencv2/core/core.hpp>
using namespace std;
using namespace cv;

int main()
{
    RotatedRect rr1;
    RotatedRect rr2(Point2f(),Size2f(1,2),0);
    return 0;
}

强制转换
inline RotatedRect::operator CvBox2D() const
{
    CvBox2D box; box.center = center; box.size = size; box.angle = angle;
    return box;
}

CvBox2D是1.0版本的,这里不做讨论了...

接下来我们将深入探讨angle这个参数,以及库函数中两个函数的实现...

//! returns 4 vertices of the rectangle
void points(Point2f pts[]) const;
//! returns the minimal up-right rectangle containing the rotated rectangle
Rect boundingRect() const;
具体实现
void RotatedRect::points(Point2f pt[]) const
{
    double _angle = angle*CV_PI/180.;
    float b = (float)cos(_angle)*0.5f;
    float a = (float)sin(_angle)*0.5f;
    
    pt[0].x = center.x - a*size.height - b*size.width;
    pt[0].y = center.y + b*size.height - a*size.width;
    pt[1].x = center.x + a*size.height - b*size.width;
    pt[1].y = center.y - b*size.height - a*size.width;
    pt[2].x = 2*center.x - pt[0].x;
    pt[2].y = 2*center.y - pt[0].y;
    pt[3].x = 2*center.x - pt[1].x;
    pt[3].y = 2*center.y - pt[1].y;
}

Rect RotatedRect::boundingRect() const
{
    Point2f pt[4];
    points(pt);
    Rect r(cvFloor(min(min(min(pt[0].x, pt[1].x), pt[2].x), pt[3].x)),
           cvFloor(min(min(min(pt[0].y, pt[1].y), pt[2].y), pt[3].y)),
           cvCeil(max(max(max(pt[0].x, pt[1].x), pt[2].x), pt[3].x)),
           cvCeil(max(max(max(pt[0].y, pt[1].y), pt[2].y), pt[3].y)));
    r.width -= r.x - 1;
    r.height -= r.y - 1;
    return r;
}        

接下来,睁大眼睛看我的深入分析,首先我们来谈一谈这个angle参数的内涵,首先angle是角度制的,并且没有范围限制,也就是不需要【0,360),但是由于三角函数的周期性,超出这个范围也可以映射到这范围里的...
那angle是什么?我觉得这个概念不能以静态的角度来理解,也就是要以动态的观念来理解。首先我们考虑当angle=0时,这个是最标准的矩形,也就是英文里常说的up-right retangle,其实就是边平行x轴和y轴的情况...然后我们给此时矩形的4个顶点编号,左上角为0,然后逆时针绕行,分别编上1,2,3...这样我们得到了4个顶点的编号...
然后我们将4个顶点与中心点center连接起来...然后每个顶点与center的连线对应了该点的初始状态,也就是angle=0的情况,当angle不为0的,每个顶点绕center逆时针旋转angle角度,就是他们对应的初始边逆时针旋转angle度...这样我们得到4个顶点,且编号为旋转前点的编号,例如对于特殊情况angle=180,可以发现0号点转到了2号点,2号点转到了0号点,1号点转到3号点,3号点转到1号点...表现出来的效果就是0号和2号顶点坐标互换,1号和3号顶点坐标互换...

相信到这里,大家应该明白这个angle什么意思了吧...所以接下来我们要解决当angle不特殊的情况,怎么计算每个顶点旋转得到新的顶点的坐标...

首先补充一下计算几何知识(参加过ACM的选手就毫无压力了):

对于点(x,y)绕原点(0,0)逆时针旋转angle角度的公式,得到的新点的坐标为(x1,y1)
x1=x*cos(angle)-y*sin(angle)
y1=y*cos(angle)+x*sin(angle)

至于推导过程非常简单,画两条线初始和结束状态,与x轴的夹角分别为a和b
显然x=r*cos(a),y=r*sin(a)     这里的r为旋转点到原点的距离
x1=r*cos(b),y1=r*sin(b)
旋转的度数为angle = b-a
所以把b=a+angle带入x1和y1,然后根据三角和差公式展开,再将x和y的表达式带入,就得到了x1,y1与x,y的关系

但是注意这个是绕原点旋转的公式,那么我们需要的是绕center旋转的公式
其实也是非常简单的,我们做两次坐标平移变换,第一次将center平移到原点,得到旋转点( x-center.x, y-center.y ),此时center相当于是原点了,我们套用前面绕原点旋转的公式,得到
x1=(x-center.x)*cos(angle)-(y-center.y)*sin(angle)
y1=(y-center.y)*cos(angle)+(x-center.x)*sin(angle)
然后我们需要做一次坐标逆变换,x1=x1+center.x,y1=y1+center.y这样得到的才是原来坐标系的点
x1=(x-center.x)*cos(angle)-(y-center.y)*sin(angle) + center.x
y1=(y-center.y)*cos(angle)+(x-center.x)*sin(angle) +center.y
然后我们用width和height做一次代换
考虑0号顶点,即左上角顶点,此时x-center.x = - width/2, y-center.y = hegiht/2
那么x1 = 0.5*(-width*cos - hegiht*sin) +center.x
y1 = 0.5*(height*cos-width*sin)

呵呵,发现了吧,points函数里p[0]就是这么计算得到的,同理计算p[1]...p[2],p[3]则根据center是中点这个性质快速计算得到...
就这么简单...

Point2f p[4];
RotatedRect rr(Point2f(),Size2f(1,1.8),1);
rr.points(p);
Rect r = rr.boundingRect();

对于boundingRect这个函数,很无语啊,r.width -= r.x - 1;r.height -= r.y - 1; 这里为什么要减1呢?但从数学角度来说,这样的矩形并不是最小的,但是呢?写库者可能考虑图像处理是按像素点计算的,而矩形的定义不算下边界和右边界,所以width和height多了1,可能就是考虑图像多了一排像素和一列像素...有点坑,那让单纯为了几何计算的情何以堪...


Range(在operations.hpp里)

//3个构造函数
inline Range::Range() : start(0), end(0) {}
inline Range::Range(int _start, int _end) : start(_start), end(_end) {}
inline Range::Range(const CvSlice& slice) : start(slice.start_index), end(slice.end_index)
{
    if( start == 0 && end == CV_WHOLE_SEQ_END_INDEX )
        *this = Range::all();
}


#include<iostream>
#include<opencv2/core/core.hpp>
using namespace std;
using namespace cv;

int main()
{
    Range r1;
    Range r2(1,2);
    return 0;
}


强制转换

inline Range::operator CvSlice() const
{ return *this != Range::all() ? cvSlice(start, end) : CV_WHOLE_SEQ; }


常用函数

inline int Range::size() const { return end - start; }
inline bool Range::empty() const { return start == end; }
inline Range Range::all() { return Range(INT_MIN, INT_MAX); }


Range r1(1,2);
r1.size();
r1.empty();
r1 = r1.all();

注意:all()返回的范围是【-2147483648,2147483647),显然如果取size(),那么会爆int...


一些运算符

Range r1(1,2),r2;
r1 == r2;
r1 != r2;
!r2; //true
r1 & r2; //交集
r1 &= r2;
r1 + 1; //r1.start + 1,r1.end + 1
1 + r1;
r1 - 1;


Matx(在operations.hpp里)
Matx();
Matx(_Tp v0); //!< 1x1 matrix
Matx(_Tp v0, _Tp v1); //!< 1x2 or 2x1 matrix
Matx(_Tp v0, _Tp v1, _Tp v2); //!< 1x3 or 3x1 matrix
Matx(_Tp v0, _Tp v1, _Tp v2, _Tp v3); //!< 1x4, 2x2 or 4x1 matrix
Matx(_Tp v0, _Tp v1, _Tp v2, _Tp v3, _Tp v4); //!< 1x5 or 5x1 matrix
Matx(_Tp v0, _Tp v1, _Tp v2, _Tp v3, _Tp v4, _Tp v5); //!< 1x6, 2x3, 3x2 or 6x1 matrix
Matx(_Tp v0, _Tp v1, _Tp v2, _Tp v3, _Tp v4, _Tp v5, _Tp v6); //!< 1x7 or 7x1 matrix
Matx(_Tp v0, _Tp v1, _Tp v2, _Tp v3, _Tp v4, _Tp v5, _Tp v6, _Tp v7); //!< 1x8, 2x4, 4x2 or 8x1 matrix
Matx(_Tp v0, _Tp v1, _Tp v2, _Tp v3, _Tp v4, _Tp v5, _Tp v6, _Tp v7, _Tp v8); //!< 1x9, 3x3 or 9x1 matrix
Matx(_Tp v0, _Tp v1, _Tp v2, _Tp v3, _Tp v4, _Tp v5, _Tp v6, _Tp v7, _Tp v8, _Tp v9); //!< 1x10, 2x5 or 5x2 or 10x1 matrix
Matx(_Tp v0, _Tp v1, _Tp v2, _Tp v3,
     _Tp v4, _Tp v5, _Tp v6, _Tp v7,
     _Tp v8, _Tp v9, _Tp v10, _Tp v11); //!< 1x12, 2x6, 3x4, 4x3, 6x2 or 12x1 matrix
Matx(_Tp v0, _Tp v1, _Tp v2, _Tp v3,
     _Tp v4, _Tp v5, _Tp v6, _Tp v7,
     _Tp v8, _Tp v9, _Tp v10, _Tp v11,
     _Tp v12, _Tp v13, _Tp v14, _Tp v15); //!< 1x16, 4x4 or 16x1 matrix
explicit Matx(const _Tp* vals); //!< initialize from a plain array

可以看到构造函数的参数可以是0~10个,12个或16个...
前面0~10个的必须保证矩阵元素个数m*n大于参数个数,大于参数部分全部置0,而12和16必须保证矩阵元素个数也为12和16.最后一个使用数组来构造一个Matx,必须保证数组元素个数大于等于m*n,否则数组越界...

Matx<int,1,1> m1;
Matx<int,1,1> m2(1);
Matx12d m3(1,1);
Matx13d m4(1,1,1);
//...
double val[12];
Matx66d m5(val);

强制转换
template<typename _Tp, int m, int n> template<typename T2>
inline Matx<_Tp, m, n>::operator Matx<T2, m, n>() const
{
    Matx<T2, m, n> M;
    for( int i = 0; i < m*n; i++ ) M.val[i] = saturate_cast<T2>(val[i]);
    return M;
}

访问矩阵元素
Matx<int,1,1> m1;
m1(0,0); // val[0*n+0]
m1(0); //val[0]
两个参数的(i,j)表示i行j列,对应1维数组i*n+j

static Matx all(_Tp alpha);
static Matx zeros();
static Matx ones();
static Matx eye();
static Matx diag(const diag_type& d);
static Matx randu(_Tp a, _Tp b);
static Matx randn(_Tp a, _Tp b);
_Tp dot(const Matx<_Tp, m, n>& v) const;
double ddot(const Matx<_Tp, m, n>& v) const;

很多函数都类似matlab的格式,注意dot和ddot是指矩阵对应位置相乘再累加。
Matx<int,1,2> m;
m = m.all(4); //返回一个元素全部为4的矩阵
m = m.zeros(); //返回一个元素全部为1的矩阵
m = m.ones(); //返回一个元素全部为0的矩阵
m = m.eye(); //返回一个对角线为1其余为0的矩阵
Matx<int,1,1> p(5);
m = m.diag(p); //返回一个对角线为p其余为0的矩阵,p的行为min(rows,cols),列为1
Matx<int,1,2> m1(1,1);
Matx<int,1,2> m2(1,2);
m1.dot(m2);
m1.ddot(m2);

template<int m1, int n1> Matx<_Tp, m1, n1> reshape() const;
template<int m1, int n1> Matx<_Tp, m1, n1> get_minor(int i, int j) const;
Matx<_Tp, 1, n> row(int i) const;
Matx<_Tp, m, 1> col(int i) const;
Matx<_Tp, MIN(m,n), 1> diag() const;
reshape和get_minor是模板函数,由于这个本身就是模板类,所以模板函数的参数不可以省略了,必须显示的写在函数名后,看例子。
Matx<int,1,2> m;
Matx<int,2,1> n;
n = m.reshape<2,1>(); //改变形状,必须保证元素个数相同
Matx<int,3,3> m1(1,2,3,4,5,6,7,8,9);
Matx<int,2,2> m2;
m2 = m1.get_minor<2,2>(1,1); //提取以m1(1,1)为左上角的2*2子矩阵
Matx<int,1,3> m3;
m3 = m1.row(0); //提取第0行
Matx<int,3,1> m4;
m4 = m1.col(0); //提取第0列
m4 = m1.diag(); //提取对角线,行为min(rows,cols),列为1

还有很多很多函数...
Matx<int,1,2> m1(1,2),m2(2,3);
m1 += m2;
m1 -= m2;
m1 + m2;
m1 - m2;
m1 *= 1; //int/float/double
m1 * 1; //int/float/double
m1 * Matx<int,2,1>(2,3); //矩阵乘法
- m1; //取反
Matx<int,1,2> m3(m1,m2,Matx_AddOp()); //对应元素相加
Matx<int,1,2> m4(m1,m2,Matx_SubOp()); //对应元素相减
Matx<int,1,2> m5(m1,5,Matx_ScaleOp()); //对应元素乘5
Matx<int,1,2> m6(m1,m2,Matx_MulOp()); //对应元素相乘
Matx<int,1,1> m7(m1,Matx<int,2,1>(2,3),Matx_MatMulOp()); //矩阵乘法
Matx<int,1,2> m8(Matx<int,2,1>(2,1),Matx_TOp()); //矩阵转置

Matx<int,2,2>() * Point();
Matx<int,3,3>() * Point(); //Point添加一维为1
Matx<int,3,3>() * Point3i();
Matx<int,4,4>() * Point3i(); //Point3i添加一维为1
m1.mul(m2); //对应元素相乘

Matx<int,2,2> m1(1,2,3,4);
Matx<int,2,2> m2;
m2 = m1.t(); //转置
trace(m1); //矩阵的迹,对角线之和
还有一些有关于矩阵分解的LU,CHOLESKY...涉及到算法了...以后有空补...


Vec(在operations.hpp里)
//! default constructor
Vec();
Vec(_Tp v0); //!< 1-element vector constructor
Vec(_Tp v0, _Tp v1); //!< 2-element vector constructor
Vec(_Tp v0, _Tp v1, _Tp v2); //!< 3-element vector constructor
Vec(_Tp v0, _Tp v1, _Tp v2, _Tp v3); //!< 4-element vector constructor
Vec(_Tp v0, _Tp v1, _Tp v2, _Tp v3, _Tp v4); //!< 5-element vector constructor
Vec(_Tp v0, _Tp v1, _Tp v2, _Tp v3, _Tp v4, _Tp v5); //!< 6-element vector constructor
Vec(_Tp v0, _Tp v1, _Tp v2, _Tp v3, _Tp v4, _Tp v5, _Tp v6); //!< 7-element vector constructor
Vec(_Tp v0, _Tp v1, _Tp v2, _Tp v3, _Tp v4, _Tp v5, _Tp v6, _Tp v7); //!< 8-element vector constructor
Vec(_Tp v0, _Tp v1, _Tp v2, _Tp v3, _Tp v4, _Tp v5, _Tp v6, _Tp v7, _Tp v8); //!< 9-element vector constructor
Vec(_Tp v0, _Tp v1, _Tp v2, _Tp v3, _Tp v4, _Tp v5, _Tp v6, _Tp v7, _Tp v8, _Tp v9); //!< 10-element vector constructor
explicit Vec(const _Tp* values); //数组
Vec(const Vec<_Tp, cn>& v); //Vec

这里的注意Vec是继承Matx的,所以上述所有的构造方法都是调用了Matx的构造方法
#include<iostream>
#include<opencv2/core/core.hpp>
using namespace std;
using namespace cv;

int main()
{
    Vec2i v1;
    Vec3i v2(1,2,3);
    Vec3i v3(v2);
    Vec4i v4(new int[4]{4,3,2,1});
    return 0;
}



强制转换
//! convertion to another data type
template<typename T2> operator Vec<T2, cn>() const;
//! conversion to 4-element CvScalar.
operator CvScalar() const;

访问向量元素
/*! element access */
const _Tp& operator [](int i) const;
_Tp& operator[](int i);
const _Tp& operator ()(int i) const;
_Tp& operator ()(int i);
可以用(),也可以类似数组形式,使用[],当然也可以直接访问内部数据成员val数组

常用函数
static Vec all(_Tp alpha); //全部置为alpha
//! per-element multiplication
Vec mul(const Vec<_Tp, cn>& v) const; //对应分量相乘
/*!
  cross product of the two 3D vectors.
  For other dimensionalities the exception is raised
*/
Vec cross(const Vec& v) const; //这个貌似已废弃,直接抛异常了,但是特化版本是有的,见下面

实例:
Vec3i v1(1,2,3);
Vec3i v2 = v1.all(-1); //全部元素置为-1
v2 = v1.mul(v2); //对应分量相乘
v2.cross(v1);

其他三个构造函数
Vec(const Matx<_Tp, cn, 1>& a, const Matx<_Tp, cn, 1>& b, Matx_AddOp);
Vec(const Matx<_Tp, cn, 1>& a, const Matx<_Tp, cn, 1>& b, Matx_SubOp);
template<typename _T2> Vec(const Matx<_Tp, cn, 1>& a, _T2 alpha, Matx_ScaleOp);
调用父类的方法

一些常用的运算符
Vec3i v1(1,2,3),v2(1,2,3);
v1 += v2; // +=
v1 -= v2; // -=
v1 + v2; // +
v1 - v2; // -
v1 *= 1; // *= int
v1 *= (float)1.0; // *= float
v1 *= 1.0; // *= double
v1 * 1; // * int
1 * v1; // int *
v1 * (float)1.0; // * float
(float)1.0 * v1; // float *
v1 * 1.0; // * double
1.0 * v1; // double *
- v1; //取反
Vec3f v3(1,2,3);
v3.cross(v3); //叉积,float型的特化版本
Vec3f v4(1,2,3);
v4.cross(v4); //叉积,double型的特化版本
//特化版本,不同类型的向量+=,结果通过saturate_cast转换成第一种类型
Vec2i v5;
v5 += Vec2d(1,2); // 长度为2
Vec3i v6;
v6 += Vec3d(1,2,3); // 长度为3
Vec4i v7;
v7 += Vec4d(1,2,3,4); // 长度为4

//一些移位运算,先留空...


Scalar_(在operations.hpp里)
//! various constructors
Scalar_();
Scalar_(_Tp v0, _Tp v1, _Tp v2=0, _Tp v3=0);
Scalar_(const CvScalar& s);
Scalar_(_Tp v0);

从上面可以看出Scalar_的构造函数可以带0~4个参数,其中剩余成员则置零...
#include<iostream>
#include<opencv2/core/core.hpp>
using namespace std;
using namespace cv;

int main()
{
    Scalar_<int> s1;
    Scalar_<int> s2(1);
    Scalar_<int> s3(1,2);
    Scalar_<int> s4(1,2,3);
    Scalar_<int> s5(1,2,3,4);
    return 0;
}

因为Scalar_继承自Vec,所以Vec的函数都可以使用了...

强转函数
//! conversion to the old-style CvScalar
operator CvScalar() const;
//! conversion to another data type
template<typename T2> operator Scalar_<T2>() const;

常用函数
//! returns a scalar with all elements set to v0
static Scalar_<_Tp> all(_Tp v0); //全部置为v0
//! per-element product
Scalar_<_Tp> mul(const Scalar_<_Tp>& t, double scale=1 ) const; //对应分量相乘再乘上scale
// returns (v0, -v1, -v2, -v3)
Scalar_<_Tp> conj() const;
// returns true iff v1 == v2 == v3 == 0
bool isReal() const;

实例:
Scalar_<int> s1 = Scalar_<int>::all(3);
Scalar_<int> s2(1,2,3,4);
s1 = s1.mul(s2,2);
s1 = s1.conj();
s1.isReal();

运算符
Scalar s1,s2;
s1 += s2;
s1 -= s2;
s1 *= 1; // *_Tp
s1 + s2;
s1 - s2;
s1 * 1; // *_Tp
1 * s1; // *_Tp
- s1; //取反
s1 == s2;
s1 != s2;
s1 /= 1.0;
s1 / 1.0;
1.0 / s1; //s1.conj()*1.0/||s1||

s1 *= s2;
s1 * s2;
s1 /= s2;
s1 / s2;

后面4个定义很奇葩,不知道有没有什么意义...
template<typename _Tp> static inline Scalar_<_Tp>
operator * (const Scalar_<_Tp>& a, const Scalar_<_Tp>& b)
{
    return Scalar_<_Tp>(saturate_cast<_Tp>(a[0]*b[0] - a[1]*b[1] - a[2]*b[2] - a[3]*b[3]),
                        saturate_cast<_Tp>(a[0]*b[1] + a[1]*b[0] + a[2]*b[3] - a[3]*b[2]),
                        saturate_cast<_Tp>(a[0]*b[2] - a[1]*b[3] + a[2]*b[0] - a[3]*b[1]),
                        saturate_cast<_Tp>(a[0]*b[3] + a[1]*b[2] - a[2]*b[1] - a[3]*b[0]));
}
template<typename _Tp> static inline Scalar_<_Tp>&
operator *= (Scalar_<_Tp>& a, const Scalar_<_Tp>& b)
{
    a = a*b;
    return a;
}
template<typename _Tp> static inline
Scalar_<_Tp> operator / (const Scalar_<_Tp>& a, const Scalar_<_Tp>& b)
{
    return a*((_Tp)1/b);
}
template<typename _Tp> static inline
Scalar_<_Tp>& operator /= (Scalar_<_Tp>& a, const Scalar_<_Tp>& b)
{
    a = a/b;
    return a;
}


猜你喜欢

转载自blog.csdn.net/u012866104/article/details/50810974