OpenCV 笔记 ,C++模板template

在OpenCV中大量使用了模板编程,因为图像的数据类型有8字节,16字节,32字节,float,double等多种数据类型,如果对每一种数据类型都写一遍函数,这是很糟糕的,像Point_,Rect_等等很多的基础类都是用模板写出来的,现在补一下C++模板在OpenCV中的使用方式。

1. 模板函数

基本写法

template<typename(或class) T>

T fuc(T x, T y)

{

T x;

//……

}

typename或class没有区别,这里建议使用typename,一是意思明确,就是类型名;二是class在C++中已经是一个定义类的关键字,不宜再使用它。

OpenCV中的实例:

template<typename T> static void

inRange_(const T* src1, size_t step1, const T* src2, size_t step2,

const T* src3, size_t step3, uchar* dst, size_t step,

Size size)

{

step1 /= sizeof(src1[0]);

step2 /= sizeof(src2[0]);

step3 /= sizeof(src3[0]);

for( ; size.height--; src1 += step1, src2 += step2, src3 += step3, dst += step )

{

int x = 0;

#if CV_ENABLE_UNROLLED

for( ; x <= size.width - 4; x += 4 )

{

int t0, t1;

t0 = src2[x] <= src1[x] && src1[x] <= src3[x];

t1 = src2[x+1] <= src1[x+1] && src1[x+1] <= src3[x+1];

dst[x] = (uchar)-t0; dst[x+1] = (uchar)-t1;

t0 = src2[x+2] <= src1[x+2] && src1[x+2] <= src3[x+2];

t1 = src2[x+3] <= src1[x+3] && src1[x+3] <= src3[x+3];

dst[x+2] = (uchar)-t0; dst[x+3] = (uchar)-t1;

}

#endif

for( ; x < size.width; x++ )

dst[x] = (uchar)-(src2[x] <= src1[x] && src1[x] <= src3[x]);

}

}

static void inRange8u(const uchar* src1, size_t step1, const uchar* src2, size_t step2,

const uchar* src3, size_t step3, uchar* dst, size_t step, Size size)

{

inRange_(src1, step1, src2, step2, src3, step3, dst, step, size);

}

static void inRange8s(const schar* src1, size_t step1, const schar* src2, size_t step2,

const schar* src3, size_t step3, uchar* dst, size_t step, Size size)

{

inRange_(src1, step1, src2, step2, src3, step3, dst, step, size);

}

static void inRange16u(const ushort* src1, size_t step1, const ushort* src2, size_t step2,

const ushort* src3, size_t step3, uchar* dst, size_t step, Size size)

{

inRange_(src1, step1, src2, step2, src3, step3, dst, step, size);

}

static void inRange16s(const short* src1, size_t step1, const short* src2, size_t step2,

const short* src3, size_t step3, uchar* dst, size_t step, Size size)

{

inRange_(src1, step1, src2, step2, src3, step3, dst, step, size);

}

static void inRange32s(const int* src1, size_t step1, const int* src2, size_t step2,

const int* src3, size_t step3, uchar* dst, size_t step, Size size)

{

inRange_(src1, step1, src2, step2, src3, step3, dst, step, size);

}

static void inRange32f(const float* src1, size_t step1, const float* src2, size_t step2,

const float* src3, size_t step3, uchar* dst, size_t step, Size size)

{

inRange_(src1, step1, src2, step2, src3, step3, dst, step, size);

}

static void inRange64f(const double* src1, size_t step1, const double* src2, size_t step2,

const double* src3, size_t step3, uchar* dst, size_t step, Size size)

{

inRange_(src1, step1, src2, step2, src3, step3, dst, step, size);

}

static void inRangeReduce(const uchar* src, uchar* dst, size_t len, int cn)

{

int k = cn % 4 ? cn % 4 : 4;

size_t i, j;

if( k == 1 )

for( i = j = 0; i < len; i++, j += cn )

dst[i] = src[j];

else if( k == 2 )

for( i = j = 0; i < len; i++, j += cn )

dst[i] = src[j] & src[j+1];

else if( k == 3 )

for( i = j = 0; i < len; i++, j += cn )

dst[i] = src[j] & src[j+1] & src[j+2];

else

for( i = j = 0; i < len; i++, j += cn )

dst[i] = src[j] & src[j+1] & src[j+2] & src[j+3];

for( ; k < cn; k += 4 )

{

for( i = 0, j = k; i < len; i++, j += cn )

dst[i] &= src[j] & src[j+1] & src[j+2] & src[j+3];

}

}

typedef void (*InRangeFunc)( const uchar* src1, size_t step1, const uchar* src2, size_t step2,

const uchar* src3, size_t step3, uchar* dst, size_t step, Size sz );

static InRangeFunc getInRangeFunc(int depth)

{

static InRangeFunc inRangeTab[] =

{

(InRangeFunc)GET_OPTIMIZED(inRange8u), (InRangeFunc)GET_OPTIMIZED(inRange8s), (InRangeFunc)GET_OPTIMIZED(inRange16u),

(InRangeFunc)GET_OPTIMIZED(inRange16s), (InRangeFunc)GET_OPTIMIZED(inRange32s), (InRangeFunc)GET_OPTIMIZED(inRange32f),

(InRangeFunc)inRange64f, 0

};

return inRangeTab[depth];

}

}

void cv::inRange(InputArray _src, InputArray _lowerb,

InputArray _upperb, OutputArray _dst)

{

int skind = _src.kind(), lkind = _lowerb.kind(), ukind = _upperb.kind();

Mat src = _src.getMat(), lb = _lowerb.getMat(), ub = _upperb.getMat();

bool lbScalar = false, ubScalar = false;

if( (lkind == _InputArray::MATX && skind != _InputArray::MATX) ||

src.size != lb.size || src.type() != lb.type() )

{

if( !checkScalar(lb, src.type(), lkind, skind) )

CV_Error( CV_StsUnmatchedSizes,

"The lower bounary is neither an array of the same size and same type as src, nor a scalar");

lbScalar = true;

}

if( (ukind == _InputArray::MATX && skind != _InputArray::MATX) ||

src.size != ub.size || src.type() != ub.type() )

{

if( !checkScalar(ub, src.type(), ukind, skind) )

CV_Error( CV_StsUnmatchedSizes,

"The upper bounary is neither an array of the same size and same type as src, nor a scalar");

ubScalar = true;

}

CV_Assert( ((int)lbScalar ^ (int)ubScalar) == 0 );

int cn = src.channels(), depth = src.depth();

size_t esz = src.elemSize();

size_t blocksize0 = (size_t)(BLOCK_SIZE + esz-1)/esz;

_dst.create(src.dims, src.size, CV_8U);

Mat dst = _dst.getMat();

InRangeFunc func = getInRangeFunc(depth);

const Mat* arrays_sc[] = { &src, &dst, 0 };

const Mat* arrays_nosc[] = { &src, &dst, &lb, &ub, 0 };

uchar* ptrs[4];

NAryMatIterator it(lbScalar && ubScalar ? arrays_sc : arrays_nosc, ptrs);

size_t total = it.size, blocksize = std::min(total, blocksize0);

AutoBuffer<uchar> _buf(blocksize*(((int)lbScalar + (int)ubScalar)*esz + cn) + 2*cn*sizeof(int) + 128);

uchar *buf = _buf, *mbuf = buf, *lbuf = 0, *ubuf = 0;

buf = alignPtr(buf + blocksize*cn, 16);

if( lbScalar && ubScalar )

{

lbuf = buf;

ubuf = buf = alignPtr(buf + blocksize*esz, 16);

CV_Assert( lb.type() == ub.type() );

int scdepth = lb.depth();

if( scdepth != depth && depth < CV_32S )

{

int* ilbuf = (int*)alignPtr(buf + blocksize*esz, 16);

int* iubuf = ilbuf + cn;

BinaryFunc sccvtfunc = getConvertFunc(scdepth, CV_32S);

sccvtfunc(lb.data, 0, 0, 0, (uchar*)ilbuf, 0, Size(cn, 1), 0);

sccvtfunc(ub.data, 0, 0, 0, (uchar*)iubuf, 0, Size(cn, 1), 0);

int minval = cvRound(getMinVal(depth)), maxval = cvRound(getMaxVal(depth));

for( int k = 0; k < cn; k++ )

{

if( ilbuf[k] > iubuf[k] || ilbuf[k] > maxval || iubuf[k] < minval )

ilbuf[k] = minval+1, iubuf[k] = minval;

}

lb = Mat(cn, 1, CV_32S, ilbuf);

ub = Mat(cn, 1, CV_32S, iubuf);

}

convertAndUnrollScalar( lb, src.type(), lbuf, blocksize );

convertAndUnrollScalar( ub, src.type(), ubuf, blocksize );

}

for( size_t i = 0; i < it.nplanes; i++, ++it )

{

for( size_t j = 0; j < total; j += blocksize )

{

int bsz = (int)MIN(total - j, blocksize);

size_t delta = bsz*esz;

uchar *lptr = lbuf, *uptr = ubuf;

if( !lbScalar )

{

lptr = ptrs[2];

ptrs[2] += delta;

}

if( !ubScalar )

{

int idx = !lbScalar ? 3 : 2;

uptr = ptrs[idx];

ptrs[idx] += delta;

}

func( ptrs[0], 0, lptr, 0, uptr, 0, cn == 1 ? ptrs[1] : mbuf, 0, Size(bsz*cn, 1));

if( cn > 1 )

inRangeReduce(mbuf, ptrs[1], bsz, cn);

ptrs[0] += delta;

ptrs[1] += bsz;

}

}

}

从这段代码可以看出,OpenCV中先定义一个模板函数,然后再定义一组出入参和返回值一样的函数,将这些函数放入一个函数指针数组,根据不同的数据类型,取不同的函数调用,最后在接口cv::inRange中直接使用函数指针。这样的写法在OpenCV中十分普遍,特别是在有原来的C代码的地方。这样的用法在cv::add

2. 模板类

写法

template <typename T>

class Foo

{

……

}

OpenCV实例:

template<typename _Tp> class Point_

{

public:

typedef _Tp value_type;

// various constructors

Point_();

Point_(_Tp _x, _Tp _y);

Point_(const Point_& pt);

Point_(const CvPoint& pt);

Point_(const CvPoint2D32f& pt);

Point_(const Size_<_Tp>& sz);

Point_(const Vec<_Tp, 2>& v);

Point_& operator = (const Point_& pt);

//! conversion to another data type

template<typename _Tp2> operator Point_<_Tp2>() const;

//! conversion to the old-style C structures

operator CvPoint() const;

operator CvPoint2D32f() const;

operator Vec<_Tp, 2>() const;

//! dot product

_Tp dot(const Point_& pt) const;

//! dot product computed in double-precision arithmetics

double ddot(const Point_& pt) const;

//! cross-product

double cross(const Point_& pt) const;

//! checks whether the point is inside the specified rectangle

bool inside(const Rect_<_Tp>& r) const;

_Tp x, y; //< the point coordinates

};

typedef Point_<int> Point2i;

typedef Point2i Point;

3. 转载一段关于仿函数的内容

仿函数

仿函数这个词经常会出现在模板库里(比如 STL),那么什么是仿函数呢?

顾名思义:仿函数就是能像函数一样工作的东西,请原谅我用东西这样一个代词,下面我会慢慢解释。

void dosome( int i )

这个 dosome 是一个函数,我们可以这样来使用它: dosome(5);

那么,有什么东西可以像这样工作么?

答案1:重载了 () 操作符的对象,因此,这里需要明确两点:

  1 仿函数不是函数,它是个类;

  2 仿函数重载了()运算符,使得它的对你可以像函数那样子调用

代码的形式好像是在调用函数,比如:

struct DoSome

{

void operator()( int i );

}

DoSome dosome;

这里类(对 C++ 来说,struct 和类是相同的) 重载了()操作符,因此它的实例 dosome 可以这样用 dosome(5);

和上面的函数调用一模一样,不是么?所以 dosome 就是一个仿函数了。

实际上还有答案2:

函数指针指向的对象。

typedef void( *DoSomePtr )( int );

typedef void( DoSome )( int );

DoSomePtr *ptr=&func;

DoSome& dosome=*ptr;

dosome(5); // 这里又和函数调用一模一样了。

当然,答案3 成员函数指针指向的成员函数就是意料之中的答案了。

仿函数的用处

不管是对象还是函数指针等等,它们都是可以被作为参数传递,或者被作为变量保存的。因此我们就可以把一个仿函数传递给一个函数,

由这个函数根据需要来调用这个仿函数(有点类似回调)。

STL 模板库中,大量使用了这种技巧,来实现库的“灵活”。

比如:

for_each, 它的源代码大致如下:

template< typename Iterator, typename Functor >

void for_each( Iterator begin, Iterator end, Fucntor func )

{

for( ; begin!=end; begin++ )

func( *begin );

}

这个 for 循环遍历了容器中的每一个元素,对每个元素调用了仿函数 func,这样就实现了 对“每个元素做同样的事”这样一种编程的思想。

特别的,如果仿函数是一个对象,这个对象是可以有成员变量的,这就让 仿函数有了“状态”,从而实现了更高的灵活性。

OpenCV中也是使用了很多仿函数的形式。

为便于说明,代码有删减

template <typename Cvt>

class CvtColorLoop_Invoker : public ParallelLoopBody

{

typedef typename Cvt::channel_type _Tp;

public:

CvtColorLoop_Invoker(const Mat& _src, Mat& _dst, const Cvt& _cvt) :

ParallelLoopBody(), src(_src), dst(_dst), cvt(_cvt)

{

}

virtual void operator()(const Range& range) const

{

const uchar* yS = src.ptr<uchar>(range.start);

uchar* yD = dst.ptr<uchar>(range.start);

for( int i = range.start; i < range.end; ++i, yS += src.step, yD += dst.step )

cvt((const _Tp*)yS, (_Tp*)yD, src.cols);

}

private:

const Mat& src;

Mat& dst;

const Cvt& cvt;

const CvtColorLoop_Invoker& operator= (const CvtColorLoop_Invoker&);

};

template <typename Cvt>

void CvtColorLoop(const Mat& src, Mat& dst, const Cvt& cvt)

{

parallel_for_(Range(0, src.rows), CvtColorLoop_Invoker<Cvt>(src, dst, cvt), src.total()/(double)(1<<16) );

}

template<typename _Tp>

struct Gray2RGB

{

typedef _Tp channel_type;

Gray2RGB(int _dstcn) : dstcn(_dstcn) {}

void operator()(const _Tp* src, _Tp* dst, int n) const

{

if( dstcn == 3 )

for( int i = 0; i < n; i++, dst += 3 )

{

dst[0] = dst[1] = dst[2] = src[i];

}

else

{

_Tp alpha = ColorChannel<_Tp>::max();

for( int i = 0; i < n; i++, dst += 4 )

{

dst[0] = dst[1] = dst[2] = src[i];

dst[3] = alpha;

}

}

}

int dstcn;

};

template<typename _Tp> struct RGB2Gray

{

typedef _Tp channel_type;

RGB2Gray(int _srccn, int blueIdx, const float* _coeffs) : srccn(_srccn)

{

static const float coeffs0[] = { 0.299f, 0.587f, 0.114f };

memcpy( coeffs, _coeffs ? _coeffs : coeffs0, 3*sizeof(coeffs[0]) );

if(blueIdx == 0)

std::swap(coeffs[0], coeffs[2]);

}

void operator()(const _Tp* src, _Tp* dst, int n) const

{

int scn = srccn;

float cb = coeffs[0], cg = coeffs[1], cr = coeffs[2];

for(int i = 0; i < n; i++, src += scn)

dst[i] = saturate_cast<_Tp>(src[0]*cb + src[1]*cg + src[2]*cr);

}

int srccn;

float coeffs[3];

};

//为便于说明,代码有删减

void cv::cvtColor( InputArray _src, OutputArray _dst, int code, int dcn )

{

Mat src = _src.getMat(), dst;

Size sz = src.size();

int scn = src.channels(), depth = src.depth(), bidx;

CV_Assert( depth == CV_8U || depth == CV_16U || depth == CV_32F );

switch( code )

{

//为便于说明,代码有删减

//...............................................

case CV_BGR2GRAY: case CV_BGRA2GRAY: case CV_RGB2GRAY: case CV_RGBA2GRAY:

CV_Assert( scn == 3 || scn == 4 );

_dst.create(sz, CV_MAKETYPE(depth, 1));

dst = _dst.getMat();

bidx = code == CV_BGR2GRAY || code == CV_BGRA2GRAY ? 0 : 2;

if( depth == CV_8U )

{

CvtColorLoop(src, dst, RGB2Gray<uchar>(scn, bidx, 0));

}

else if( depth == CV_16U )

CvtColorLoop(src, dst, RGB2Gray<ushort>(scn, bidx, 0));

else

CvtColorLoop(src, dst, RGB2Gray<float>(scn, bidx, 0));

break;

case CV_BGR5652GRAY: case CV_BGR5552GRAY:

CV_Assert( scn == 2 && depth == CV_8U );

_dst.create(sz, CV_8UC1);

dst = _dst.getMat();

CvtColorLoop(src, dst, RGB5x52Gray(code == CV_BGR5652GRAY ? 6 : 5));

break;

case CV_GRAY2BGR: case CV_GRAY2BGRA:

if( dcn <= 0 ) dcn = (code==CV_GRAY2BGRA) ? 4 : 3;

CV_Assert( scn == 1 && (dcn == 3 || dcn == 4));

_dst.create(sz, CV_MAKETYPE(depth, dcn));

dst = _dst.getMat();

if( depth == CV_8U )

{

CvtColorLoop(src, dst, Gray2RGB<uchar>(dcn));

}

else if( depth == CV_16U )

CvtColorLoop(src, dst, Gray2RGB<ushort>(dcn));

else

CvtColorLoop(src, dst, Gray2RGB<float>(dcn));

break;

//................................................

default:

CV_Error( CV_StsBadFlag, "Unknown/unsupported color conversion code" );

}

}

这是cvtColor的基本实现方式,

类CvtColorLoop_Invoker实现了virtual void operator()(const Range& range) const

这是一个仿函数形式,并且它将在CvtColorLoop函数的parallel_for_中调用,在template <typename Cvt>中的Cvt代表一个实现了仿函数形式的struct,并在cvt((const _Tp*)yS, (_Tp*)yD, src.cols)调用了,而template<typename _Tp>struct Gray2RGB和template<typename _Tp> struct RGB2Gray就是对应于Cvt结构的两个struct,这两个struct都实现了operator()。

在这种编程的方式中,比如cvtColor这个颜色空间转换函数,而这种转换有很多种类型,比如RGB2GRAY, RGB2HSV,RGB2HLS等等,但是对像素的操作方式确是类似的,所以这里将每一种转换都独立出来,写在一个struct中,并且都实现了operator()(这个operator()有同样的参数),然后将这些不同的转换放在一个统一的对像素的操作流程中,这里是CvtColorLoop函数。

来自为知笔记(Wiz)