基于Opencv的计算机视觉软件很多,大家都是敏捷开发、不重新发明轮子。首先就是core模块,这里面包含了基本的类,只要用到opencv都会遇到core,比如cv::Mat,需要理解一下其结构,以便在开发过程中建立一些共识。

convertTo是Mat的数据类型转换函数。

首先,找到文档,点击Main modules,点击Core functionality,点击Basic structures,点击class Mat。

  • 函数的声明

◆ convertTo()

void convertTo( OutputArray m, int rtype, double alpha=1, double beta=0 ) const;

The method converts source pixel values to the target data type. saturate_cast<> is applied at the end to avoid possible overflows:Converts an array to another data type with optional scaling.

m(x,y)=saturate_cast<rType>(α(∗this)(x,y)+β)

Parameters

m

output matrix; if it does not have a proper size or type before the operation, it is reallocated.

rtype

desired output matrix type or, rather, the depth since the number of channels are the same as the input has; if rtype is negative, the output matrix will have the same type as the input.

alpha

optional scale factor.

beta

optional delta added to the scaled values.

实现在opencv-3.4.12\modules\core\src\convert.dispatch.cpp:

convertTo是Mat的常量成员函数,无权更改this对象的数据成员。

  • _dst, 输出Mat,可以与src相同。OutputArray另文描述。
  • _type,转出类型,只给出depth即可,如果给出完整类型,如CV_8UC3,也会根据src的channel,转出相同channel数据。即_type = CV_MAKETYPE(CV_MAT_DEPTH(_type), channels());所示操作。
  • alpha和beta,可选,默认为1,0。

关于alpha和beta的选值,我们参考opencv源码,搜索255与256,发现两种都有,但是255居多。因此,在CV_32F和V_8U之间转换,用alpha取值是255.0和1.0/255.0,(8U代表8位无符号整型,范围0-255,32F代表c++的float类型)。如果用默认的1,0,则根据 bool noScale = fabs(alpha-1) < DBL_EPSILON && fabs(beta) < DBL_EPSILON;判断是否对值转尺度

void Mat::convertTo(OutputArray _dst, int _type, double alpha, double beta) const
{
    CV_INSTRUMENT_REGION();

	/*this为空时,返回空Mat*/
    if( empty() )
    {
        _dst.release();
        return;
    }
    
	//如果depth和this相同且alpha、beta默认,直接输出this
    bool noScale = fabs(alpha-1) < DBL_EPSILON && fabs(beta) < DBL_EPSILON;

    // _type为负时,返回this同深度同通道数;_type为正时,指定_type为深度,通道数同this。注意无法指定输出通道数
    if( _type < 0 )
        _type = _dst.fixedType() ? _dst.type() : type();
    else
        _type = CV_MAKETYPE(CV_MAT_DEPTH(_type), channels());

    //进行depth转换,首先是src、dst相同depth,此时返回src
    int sdepth = depth(), ddepth = CV_MAT_DEPTH(_type);
    if( sdepth == ddepth && noScale )
    {
        copyTo(_dst);
        return;
    }

    // 分配输出dst的内存空间,size()和size的区别,size()是MatSize的括号运算符重载,返回类型是cv::Size(),只用在1、2维。
	// size是成员变量,类型是MatSize。
    Mat src = *this;
    if( dims <= 2 )
        _dst.create( size(), _type );
    else
        _dst.create( dims, size, _type );
    Mat dst = _dst.getMat();

    // 函数指针,getConvertFunc,getConvertScaleFunc具体类型转换函数
    BinaryFunc func = noScale ? getConvertFunc(sdepth, ddepth) : getConvertScaleFunc(sdepth, ddepth);
    double scale[] = {alpha, beta};
    int cn = channels();
    CV_Assert( func != 0 );

    if( dims <= 2 )
    {
        Size sz = getContinuousSize2D(src, dst, cn);
        func( src.data, src.step, 0, 0, dst.data, dst.step, sz, scale );
    }
    else
    {
        const Mat* arrays[] = {&src, &dst, 0};
        uchar* ptrs[2] = {};
        NAryMatIterator it(arrays, ptrs);
        Size sz((int)(it.size*cn), 1);

        for( size_t i = 0; i < it.nplanes; i++, ++it )
            func(ptrs[0], 1, 0, 0, ptrs[1], 1, sz, scale);
    }
}

函数指针getConvertFunc,getConvertScaleFunc定义在convert.simd.hpp,convert_scale.simd.hpp,前者是转类型,后者既转类型又转值。以getConvertScaleFunc为例,如下定义:

BinaryFunc getConvertScaleFunc(int sdepth, int ddepth)
{
    static BinaryFunc cvtScaleTab[][8] =
    {
        {
            (BinaryFunc)GET_OPTIMIZED(cvtScale8u), (BinaryFunc)GET_OPTIMIZED(cvtScale8s8u), (BinaryFunc)GET_OPTIMIZED(cvtScale16u8u),
            (BinaryFunc)GET_OPTIMIZED(cvtScale16s8u), (BinaryFunc)GET_OPTIMIZED(cvtScale32s8u), (BinaryFunc)GET_OPTIMIZED(cvtScale32f8u),
            (BinaryFunc)cvtScale64f8u, 0 //(BinaryFunc)cvtScale16f8u
        },
        {
            (BinaryFunc)GET_OPTIMIZED(cvtScale8u8s), (BinaryFunc)GET_OPTIMIZED(cvtScale8s), (BinaryFunc)GET_OPTIMIZED(cvtScale16u8s),
            (BinaryFunc)GET_OPTIMIZED(cvtScale16s8s), (BinaryFunc)GET_OPTIMIZED(cvtScale32s8s), (BinaryFunc)GET_OPTIMIZED(cvtScale32f8s),
            (BinaryFunc)cvtScale64f8s, 0 //(BinaryFunc)cvtScale16f8s
        },
        {
            (BinaryFunc)GET_OPTIMIZED(cvtScale8u16u), (BinaryFunc)GET_OPTIMIZED(cvtScale8s16u), (BinaryFunc)GET_OPTIMIZED(cvtScale16u),
            (BinaryFunc)GET_OPTIMIZED(cvtScale16s16u), (BinaryFunc)GET_OPTIMIZED(cvtScale32s16u), (BinaryFunc)GET_OPTIMIZED(cvtScale32f16u),
            (BinaryFunc)cvtScale64f16u, 0 //(BinaryFunc)cvtScale16f16u
        },
        {
            (BinaryFunc)GET_OPTIMIZED(cvtScale8u16s), (BinaryFunc)GET_OPTIMIZED(cvtScale8s16s), (BinaryFunc)GET_OPTIMIZED(cvtScale16u16s),
            (BinaryFunc)GET_OPTIMIZED(cvtScale16s), (BinaryFunc)GET_OPTIMIZED(cvtScale32s16s), (BinaryFunc)GET_OPTIMIZED(cvtScale32f16s),
            (BinaryFunc)cvtScale64f16s, 0 //(BinaryFunc)cvtScale16f16s
        },
        {
            (BinaryFunc)GET_OPTIMIZED(cvtScale8u32s), (BinaryFunc)GET_OPTIMIZED(cvtScale8s32s), (BinaryFunc)GET_OPTIMIZED(cvtScale16u32s),
            (BinaryFunc)GET_OPTIMIZED(cvtScale16s32s), (BinaryFunc)GET_OPTIMIZED(cvtScale32s), (BinaryFunc)GET_OPTIMIZED(cvtScale32f32s),
            (BinaryFunc)cvtScale64f32s, 0 //(BinaryFunc)cvtScale16f32s
        },
        {
            (BinaryFunc)GET_OPTIMIZED(cvtScale8u32f), (BinaryFunc)GET_OPTIMIZED(cvtScale8s32f), (BinaryFunc)GET_OPTIMIZED(cvtScale16u32f),
            (BinaryFunc)GET_OPTIMIZED(cvtScale16s32f), (BinaryFunc)GET_OPTIMIZED(cvtScale32s32f), (BinaryFunc)GET_OPTIMIZED(cvtScale32f),
            (BinaryFunc)cvtScale64f32f, 0 //(BinaryFunc)cvtScale16f32f
        },
        {
            (BinaryFunc)cvtScale8u64f, (BinaryFunc)cvtScale8s64f, (BinaryFunc)cvtScale16u64f,
            (BinaryFunc)cvtScale16s64f, (BinaryFunc)cvtScale32s64f, (BinaryFunc)cvtScale32f64f,
            (BinaryFunc)cvtScale64f, 0 //(BinaryFunc)cvtScale16f64f
        },
        {
            0, 0, 0, 0, 0, 0, 0, 0
            /*(BinaryFunc)cvtScale8u16f, (BinaryFunc)cvtScale8s16f, (BinaryFunc)cvtScale16u16f,
            (BinaryFunc)cvtScale16s16f, (BinaryFunc)cvtScale32s16f, (BinaryFunc)cvtScale32f16f,
            (BinaryFunc)cvtScale64f16f, (BinaryFunc)cvtScale16f*/
        },
    };

    return cvtScaleTab[CV_MAT_DEPTH(ddepth)][CV_MAT_DEPTH(sdepth)];
}

 其中,函数指针的元素定义,以cvtScale8u16u为例,是把usigned char转为usigned short int,这两种整型范围不同,分别是0-255和0-65535,通常要乘以a=255,把相关部分摘录出来:

template<typename _Ts, typename _Td> inline void
cvt_32f( const _Ts* src, size_t sstep, _Td* dst, size_t dstep,
         Size size, float a, float b )
{
#if CV_SIMD
    v_float32 va = vx_setall_f32(a), vb = vx_setall_f32(b);
    const int VECSZ = v_float32::nlanes*2;
#endif
    sstep /= sizeof(src[0]);
    dstep /= sizeof(dst[0]);

    for( int i = 0; i < size.height; i++, src += sstep, dst += dstep )
    {
        int j = 0;
#if CV_SIMD
        for( ; j < size.width; j += VECSZ )
        {
            if( j > size.width - VECSZ )
            {
                if( j == 0 || src == (_Ts*)dst )
                    break;
                j = size.width - VECSZ;
            }
            v_float32 v0, v1;
            vx_load_pair_as(src + j, v0, v1);
            v0 = v_fma(v0, va, vb);
            v1 = v_fma(v1, va, vb);
            v_store_pair_as(dst + j, v0, v1);
        }
#endif
        for( ; j < size.width; j++ )
            dst[j] = saturate_cast<_Td>(src[j]*a + b);
    }
}

#define DEF_CVT_SCALE_FUNC(suffix, cvt, stype, dtype, wtype) \
static void cvtScale##suffix( const uchar* src_, size_t sstep, const uchar*, size_t, \
                              uchar* dst_, size_t dstep, Size size, void* scale_) \
{ \
    const stype* src = (const stype*)src_; \
    dtype* dst = (dtype*)dst_; \
    double* scale = (double*)scale_; \
    cvt(src, sstep, dst, dstep, size, (wtype)scale[0], (wtype)scale[1]); \
}


DEF_CVT_SCALE_FUNC(8u16u,  cvt_32f, uchar,  ushort, float)


DEF_CVT_SCALE_FUNC宏做了声明 cvtScale8u16u DEF_CVT_SCALE_FUNC(8u16u,  cvt_32f, uchar,  ushort, float)


手动翻译一下这个宏就是

static void cvtScale8u6u( const uchar* src_, size_t sstep, const uchar*, size_t,  
                              uchar* dst_, size_t dstep, Size size, void* scale_)  
{  
    const uchar* src = (const uchar*)src_;  
    ushort* dst = (ushort*)dst_;  
    double* scale = (double*)scale_;  
    cvt_32f(src, sstep, dst, dstep, size, (float)scale[0], (float)scale[1]); 
}

发现,其中用了一个比较人为的定义就是把convertTo里double类型的alpha和beta(void* scale_),降为了float,当然,考虑到最后是转为ushort,这里降位数不影响,所以用到的转换函数是cvt_32f,就是float作为中介。对于cvt_32f,函数模板,对号入座,cvt_32f( const uchar* src, size_t sstep, ushort* dst, size_t dstep, Size size, float a, float b );

最后,用到了

template<> inline ushort saturate_cast<ushort>(float v)      { int iv = cvRound(v); return saturate_cast<ushort>(iv); }
  • convertTo支持的数据类型type

从depth到type,转换计算如下:type=depth+ (channel-1)*8,

例如CV_16UC3= 2+2*8=18

#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_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))