前言
normalize函数,在网上已有不少文章做了其原理的介绍及用法展示,在查找资料的过程中,针对网络所缺少的部分(参数的使用逻辑及源码解析)进行详解,也记录我学习源码的过程便于以后查阅。
参数的影响
原理就不做详细介绍了,该文章做了详细介绍,这里只分析两个参数对归一化结果的影响:
参数 | 影响 |
---|---|
src | 图像输入、矩阵输入 |
dst | 矩阵输出,支持各种depth和1、3通道 |
alpha | 归一化公式为src[i]/scale,其中scale为归一化分母,alpha作为系数使得公式变为src[i]*alpha/scale,这个值越大归一化效果越弱,越小归一化结果越强 |
beta | 只对CV_MAXMIN这种模式其作用,beta越大,归一化值越大。 |
beta大于alpha时,beta值变大,归一化的值变大
beta小于alpha时,beta值变大,归一化的值变大
源码分析
normalize框架代码
void normalize(InputArray _src, InputOutputArray _dst, double a, double b,
int norm_type, int rtype, InputArray _mask)
{
CV_INSTRUMENT_REGION();
double scale = 1, shift = 0;
int type = _src.type(), depth = CV_MAT_DEPTH(type);
//确定输出dst的depth
if( rtype < 0 )
rtype = _dst.fixedType() ? _dst.depth() : depth;
//若norm_type类型为CV_MINMAX时,走单独分支
if( norm_type == CV_MINMAX )
{
double smin = 0, smax = 0;
double dmin = MIN( a, b ), dmax = MAX( a, b );
minMaxIdx( _src, &smin, &smax, 0, 0, _mask );
scale = (dmax - dmin)*(smax - smin > DBL_EPSILON ? 1./(smax - smin) : 0);//根据公式计算,scale,shift,是公式变形后的写法
if( rtype == CV_32F )
{
scale = (float)scale;
shift = (float)dmin - (float)(smin*scale);
}
else
shift = dmin - smin*scale;
}
else if( norm_type == CV_L2 || norm_type == CV_L1 || norm_type == CV_C )//另外三种归一化类型时,采用norm函数统一计算
{
scale = norm( _src, norm_type, _mask );
scale = scale > DBL_EPSILON ? a/scale : 0.;
shift = 0;
}
else
CV_Error( CV_StsBadArg, "Unknown/unsupported norm type" );
//该函数是opencv的优化算法,但并没有用到
CV_OCL_RUN(_dst.isUMat(),
ocl_normalize(_src, _dst, _mask, rtype, scale, shift))
Mat src = _src.getMat();
if( _mask.empty() )//最后,借助convertTo函数,完成从src到dst的尺度变换和平移
src.convertTo( _dst, rtype, scale, shift );
else
{
Mat temp;
src.convertTo( temp, rtype, scale, shift );
temp.copyTo( _dst, _mask );
}
}
可以观察到,该框架涉及三个函数:minMaxIdx、norm、convertTo。
converTo这个函数可以独立于任何函数,使用中最为常见的是图像类型间的转换,同时还可对原图做尺度和平移变换。这里convertTo就采用了尺度和平移变换的作用。
minMaxIdx源码
void cv::minMaxIdx(InputArray _src, double* minVal,
double* maxVal, int* minIdx, int* maxIdx,
InputArray _mask)
{
CV_INSTRUMENT_REGION();
int type = _src.type(), depth = CV_MAT_DEPTH(type), cn = CV_MAT_CN(type);
CV_Assert( (cn == 1 && (_mask.empty() || _mask.type() == CV_8U)) ||
(cn > 1 && _mask.empty() && !minIdx && !maxIdx) );
//这里对输入图像做了要求,通道为1或者掩码为空必须满足其一
CV_OCL_RUN(OCL_PERFORMANCE_CHECK(_src.isUMat()) && _src.dims() <= 2 && (_mask.empty() || _src.size() == _mask.size()),
ocl_minMaxIdx(_src, minVal, maxVal, minIdx, maxIdx, _mask))
Mat src = _src.getMat(), mask = _mask.getMat();
if (src.dims <= 2)
CALL_HAL(minMaxIdx, cv_hal_minMaxIdx, src.data, src.step, src.cols, src.rows, src.depth(), minVal, maxVal,
minIdx, maxIdx, mask.data);
CV_OVX_RUN(!ovx::skipSmallImages<VX_KERNEL_MINMAXLOC>(src.cols, src.rows),
openvx_minMaxIdx(src, minVal, maxVal, minIdx, maxIdx, mask))
CV_IPP_RUN_FAST(ipp_minMaxIdx(src, minVal, maxVal, minIdx, maxIdx, mask))
//上面四个都是opencv的优化做法,不去考虑,下面这句话非常值得探究
MinMaxIdxFunc func = getMinmaxTab(depth);//根据src的depth,来获取不同的查找最大最小索引的函数,这里面的获取机制,可借鉴学习
CV_Assert( func != 0 );
const Mat* arrays[] = {&src, &mask, 0};
uchar* ptrs[2] = {};
NAryMatIterator it(arrays, ptrs);//将多个矩阵放在同一个类中,集中管理
size_t minidx = 0, maxidx = 0;
int iminval = INT_MAX, imaxval = INT_MIN;
float fminval = std::numeric_limits<float>::infinity(), fmaxval = -fminval;
double dminval = std::numeric_limits<double>::infinity(), dmaxval = -dminval;
size_t startidx = 1;
int *minval = &iminval, *maxval = &imaxval;
int planeSize = (int)it.size*cn;//获取最大最小值
if( depth == CV_32F )
minval = (int*)&fminval, maxval = (int*)&fmaxval;
else if( depth == CV_64F )
minval = (int*)&dminval, maxval = (int*)&dmaxval;
for( size_t i = 0; i < it.nplanes; i++, ++it, startidx += planeSize )
func( ptrs[0], ptrs[1], minval, maxval, &minidx, &maxidx, planeSize, startidx );//对src和mask做查找最大最小值的操作
if (!src.empty() && mask.empty())
{
if( minidx == 0 )
minidx = 1;
if( maxidx == 0 )
maxidx = 1;
}
if( minidx == 0 )
dminval = dmaxval = 0;
else if( depth == CV_32F )
dminval = fminval, dmaxval = fmaxval;
else if( depth <= CV_32S )
dminval = iminval, dmaxval = imaxval;
if( minVal )
*minVal = dminval;
if( maxVal )
*maxVal = dmaxval;
if( minIdx )
ofs2idx(src, minidx, minIdx);
if( maxIdx )
ofs2idx(src, maxidx, maxIdx);//minidx\maxidx都是1D数据,通过该函数,将索引转换为x,y,z坐标
}
下面我会记录 MinMaxIdxFunc func = getMinmaxTab(depth);的过程,在opencv中很多函数都用到这种方式,来保证同一个函数兼容多种数据类型。
首先:
static MinMaxIdxFunc getMinmaxTab(int depth)
{
static MinMaxIdxFunc minmaxTab[] =
{
(MinMaxIdxFunc)GET_OPTIMIZED(minMaxIdx_8u), (MinMaxIdxFunc)GET_OPTIMIZED(minMaxIdx_8s),
(MinMaxIdxFunc)GET_OPTIMIZED(minMaxIdx_16u), (MinMaxIdxFunc)GET_OPTIMIZED(minMaxIdx_16s),
(MinMaxIdxFunc)GET_OPTIMIZED(minMaxIdx_32s),
(MinMaxIdxFunc)GET_OPTIMIZED(minMaxIdx_32f), (MinMaxIdxFunc)GET_OPTIMIZED(minMaxIdx_64f),
0
};
return minmaxTab[depth];
}
存在一个数组,来存放不同数据类型所调用的函数,函数功能相同,针对数据类型,在细节上的处理不同。
其次
#define GET_OPTIMIZED(func) (func)
这个宏定义只是一个替换的概念,并无多大的作用
然后
typedef void (*MinMaxIdxFunc)(const uchar*, const uchar*, int*, int*, size_t*, size_t*, int, size_t);
定义一个函数指针以返回一个指向函数的指针。
随后
static void minMaxIdx_8u(const uchar* src, const uchar* mask, int* minval, int* maxval,
size_t* minidx, size_t* maxidx, int len, size_t startidx )
{ minMaxIdx_(src, mask, minval, maxval, minidx, maxidx, len, startidx ); }
static void minMaxIdx_8s(const schar* src, const uchar* mask, int* minval, int* maxval,
size_t* minidx, size_t* maxidx, int len, size_t startidx )
{ minMaxIdx_(src, mask, minval, maxval, minidx, maxidx, len, startidx ); }
static void minMaxIdx_16u(const ushort* src, const uchar* mask, int* minval, int* maxval,
size_t* minidx, size_t* maxidx, int len, size_t startidx )
{ minMaxIdx_(src, mask, minval, maxval, minidx, maxidx, len, startidx ); }
static void minMaxIdx_16s(const short* src, const uchar* mask, int* minval, int* maxval,
size_t* minidx, size_t* maxidx, int len, size_t startidx )
{ minMaxIdx_(src, mask, minval, maxval, minidx, maxidx, len, startidx ); }
static void minMaxIdx_32s(const int* src, const uchar* mask, int* minval, int* maxval,
size_t* minidx, size_t* maxidx, int len, size_t startidx )
{ minMaxIdx_(src, mask, minval, maxval, minidx, maxidx, len, startidx ); }
static void minMaxIdx_32f(const float* src, const uchar* mask, float* minval, float* maxval,
size_t* minidx, size_t* maxidx, int len, size_t startidx )
{ minMaxIdx_(src, mask, minval, maxval, minidx, maxidx, len, startidx ); }
static void minMaxIdx_64f(const double* src, const uchar* mask, double* minval, double* maxval,
size_t* minidx, size_t* maxidx, int len, size_t startidx )
{ minMaxIdx_(src, mask, minval, maxval, minidx, maxidx, len, startidx ); }
根据不同的函数,做具体的实现,可以看到,minMaxIdx_是真正的实现函数,它也是一个模板函数,可接受不同类型的数据。
最后
template<typename T, typename WT> static void
minMaxIdx_( const T* src, const uchar* mask, WT* _minVal, WT* _maxVal,
size_t* _minIdx, size_t* _maxIdx, int len, size_t startIdx )
{
WT minVal = *_minVal, maxVal = *_maxVal;
size_t minIdx = *_minIdx, maxIdx = *_maxIdx;
if( !mask )
{
for( int i = 0; i < len; i++ )
{
T val = src[i];
if( val < minVal )
{
minVal = val;
minIdx = startIdx + i;
}
if( val > maxVal )
{
maxVal = val;
maxIdx = startIdx + i;
}
}
}
else
{
for( int i = 0; i < len; i++ )
{
T val = src[i];
if( mask[i] && val < minVal )
{
minVal = val;
minIdx = startIdx + i;
}
if( mask[i] && val > maxVal )
{
maxVal = val;
maxIdx = startIdx + i;
}
}
}
*_minIdx = minIdx;
*_maxIdx = maxIdx;
*_minVal = minVal;
*_maxVal = maxVal;
}
最终处理的过程可参考如上。
这个过程中,运用到模板函数、函数指针、重载等概念,在多重概念的运用下,完成了多类型数据的处理,很值得我们借鉴。
norm源码解析
double cv::norm( InputArray _src, int normType, InputArray _mask )
{
CV_INSTRUMENT_REGION();
normType &= NORM_TYPE_MASK;
CV_Assert( normType == NORM_INF || normType == NORM_L1 ||
normType == NORM_L2 || normType == NORM_L2SQR ||
((normType == NORM_HAMMING || normType == NORM_HAMMING2) && _src.type() == CV_8U) );
//输入的条件判断
#if defined HAVE_OPENCL || defined HAVE_IPP
double _result = 0;
#endif
#ifdef HAVE_OPENCL
CV_OCL_RUN_(OCL_PERFORMANCE_CHECK(_src.isUMat()) && _src.dims() <= 2,
ocl_norm(_src, normType, _mask, _result),
_result)
#endif
Mat src = _src.getMat(), mask = _mask.getMat();
CV_IPP_RUN(IPP_VERSION_X100 >= 700, ipp_norm(src, normType, mask, _result), _result);
int depth = src.depth(), cn = src.channels();
if( src.isContinuous() && mask.empty() )
{//src内存连续,且掩码为空时
size_t len = src.total()*cn;//获取元素个数
if( len == (size_t)(int)len )
{
if( depth == CV_32F )
{
const float* data = src.ptr<float>();//获取src数据指针的起点
if( normType == NORM_L2 )//归一化方式为NORM_L2
{
double result = 0;
GET_OPTIMIZED(normL2_32f)(data, 0, &result, (int)len, 1);//调用了normL2_32f,而normL2_32f的实现过程与上一个函数的过程类似
return std::sqrt(result);
}
if( normType == NORM_L2SQR )
{
double result = 0;
GET_OPTIMIZED(normL2_32f)(data, 0, &result, (int)len, 1);
return result;
}
if( normType == NORM_L1 )
{
double result = 0;
GET_OPTIMIZED(normL1_32f)(data, 0, &result, (int)len, 1);
return result;
}
if( normType == NORM_INF )
{
float result = 0;
GET_OPTIMIZED(normInf_32f)(data, 0, &result, (int)len, 1);
return result;
}
}
if( depth == CV_8U )
{
const uchar* data = src.ptr<uchar>();
if( normType == NORM_HAMMING )
{
return hal::normHamming(data, (int)len);
}
if( normType == NORM_HAMMING2 )
{
return hal::normHamming(data, (int)len, 2);
}
}
}
}//这里面的每一种处理,都涉及到上一个函数的类型重载的操作,最终落实到核心的代码上,都是比较简单的逻辑
CV_Assert( mask.empty() || mask.type() == CV_8U );
if( normType == NORM_HAMMING || normType == NORM_HAMMING2 )
{
if( !mask.empty() )
{
Mat temp;
bitwise_and(src, mask, temp);
return norm(temp, normType);
}
int cellSize = normType == NORM_HAMMING ? 1 : 2;
const Mat* arrays[] = {&src, 0};
uchar* ptrs[1] = {};
NAryMatIterator it(arrays, ptrs);
int total = (int)it.size;
int result = 0;
for( size_t i = 0; i < it.nplanes; i++, ++it )
{
result += hal::normHamming(ptrs[0], total, cellSize);
}
return result;
}
//下面为模板非空或内存不连续时的处理方法,逻辑与 上一个函数处理的逻辑相同
NormFunc func = getNormFunc(normType >> 1, depth);
CV_Assert( func != 0 );
const Mat* arrays[] = {&src, &mask, 0};
uchar* ptrs[2] = {};
union
{
double d;
int i;
float f;
}
result;
result.d = 0;
NAryMatIterator it(arrays, ptrs);
int j, total = (int)it.size, blockSize = total, intSumBlockSize = 0, count = 0;
bool blockSum = (normType == NORM_L1 && depth <= CV_16S) ||
((normType == NORM_L2 || normType == NORM_L2SQR) && depth <= CV_8S);
int isum = 0;
int *ibuf = &result.i;
size_t esz = 0;
if( blockSum )
{
intSumBlockSize = (normType == NORM_L1 && depth <= CV_8S ? (1 << 23) : (1 << 15))/cn;
blockSize = std::min(blockSize, intSumBlockSize);
ibuf = &isum;
esz = src.elemSize();
}
for( size_t i = 0; i < it.nplanes; i++, ++it )
{
for( j = 0; j < total; j += blockSize )
{//每次按照一个超平面size的大小进行处理
int bsz = std::min(total - j, blockSize);
func( ptrs[0], ptrs[1], (uchar*)ibuf, bsz, cn );
count += bsz;
if( blockSum && (count + blockSize >= intSumBlockSize || (i+1 >= it.nplanes && j+bsz >= total)) )
{
result.d += isum;
isum = 0;
count = 0;
}
ptrs[0] += bsz*esz;
if( ptrs[1] )
ptrs[1] += bsz;
}
}
if( normType == NORM_INF )
{
if( depth == CV_64F )
;
else if( depth == CV_32F )
result.d = result.f;
else
result.d = result.i;
}
else if( normType == NORM_L2 )
result.d = std::sqrt(result.d);
return result.d;
}
这个函数实现较为简单,函数本身的逻辑也是大家耳熟能详的东西,核心的重载也已经点明,若表达存在错误,还请大家不吝赐教。。