OpenCV中封装了很多函数,一般我们只需要调用它提供的API函数即可实现各种图像处理操作,如滤波、边缘检测等,这对我们项目开发来说是非常方便的。但是一个优秀的算法工程师必然也是一位优秀的程序员,因为就算你熟知各种算法的原理,但是不知道怎么实现也是不行的,(举个例子:假如你是一位销售员,你脑袋里面有很多非常棒的点子,可是你不知道怎么将它表述出来,那么到最后你依然还是一位处在销售底层的人。) 只有既熟悉原理又会用代码将大脑中好的想法实现,那才是优秀的算法工程师。当然,要完全靠自己从头实现OpenCV中的各种函数也是不现实的,就算你有那能力但是也不一定会有那么多精力。因此折中一点,至少要让自己能看懂OpenCV中各种算法的实现代码(比如说你做人脸识别,至少也要能完全理解SIFT是怎么实现的吧),这样如果有一天需要自己开发新的或者优化现有算法,那对自己的帮助就很大了。
OpenCV中Sobel算子被封装在
CV_EXPORTS_W void Sobel( InputArray src, OutputArray dst, int ddepth,
int dx, int dy, int ksize=3,
double scale=1, double delta=0,
int borderType=BORDER_DEFAULT );
这个函数里面,其中:
enum { BORDER_REPLICATE=IPL_BORDER_REPLICATE,
BORDER_CONSTANT=IPL_BORDER_CONSTANT,
BORDER_REFLECT=IPL_BORDER_REFLECT,
BORDER_WRAP=IPL_BORDER_WRAP,
BORDER_REFLECT_101=IPL_BORDER_REFLECT_101,
BORDER_REFLECT101=BORDER_REFLECT_101,
BORDER_TRANSPARENT=IPL_BORDER_TRANSPARENT,
BORDER_DEFAULT=BORDER_REFLECT_101,
BORDER_ISOLATED=16 };
默认值为BORDER_DEFAULT,具体边界定义方法参考图像剪切的扩展和高级用法:任意裁剪,边界扩充这篇博客,里面对图像边界的扩充说得很详细。
openCV中Sobel函数定义如下:
void cv::Sobel( InputArray _src, OutputArray _dst, int ddepth, int dx, int dy,
int ksize, double scale, double delta, int borderType )
{
Mat src = _src.getMat();
if (ddepth < 0)
ddepth = src.depth();
_dst.create( src.size(), CV_MAKETYPE(ddepth, src.channels()) );
Mat dst = _dst.getMat();
#ifdef HAVE_TEGRA_OPTIMIZATION
if (scale == 1.0 && delta == 0)
{
if (ksize == 3 && tegra::sobel3x3(src, dst, dx, dy, borderType))
return;
if (ksize == -1 && tegra::scharr(src, dst, dx, dy, borderType))
return;
}
#endif
#if defined (HAVE_IPP) && (IPP_VERSION_MAJOR >= 7)
if(dx < 3 && dy < 3 && src.channels() == 1 && borderType == 1)
{
if(IPPDeriv(src, dst, ddepth, dx, dy, ksize,scale))
return;
}
#endif
int ktype = std::max(CV_32F, std::max(ddepth, src.depth()));
Mat kx, ky;
getDerivKernels( kx, ky, dx, dy, ksize, false, ktype );
if( scale != 1 )
{
// usually the smoothing part is the slowest to compute,
// so try to scale it instead of the faster differenciating part
if( dx == 0 )
kx *= scale;
else
ky *= scale;
}
sepFilter2D( src, dst, ddepth, kx, ky, Point(-1,-1), delta, borderType );
}
下面一步一步分解:
Mat src = _src.getMat();
if (ddepth < 0)
ddepth = src.depth();
_dst.create( src.size(), CV_MAKETYPE(ddepth, src.channels()) );
Mat dst = _dst.getMat();
这段将InputArray & OutputArray 类转换为 Mat类,需要注意的是InputArray 类可以通过 getMat() 函数直接转换,但是 OutputArray 类的转换必须先使用 create()函数分配内存,再使用getMat()函数才能将OutputArray 类转换为Mat类。
int ktype = std::max(CV_32F, std::max(ddepth, src.depth()));
Mat kx, ky;
getDerivKernels( kx, ky, dx, dy, ksize, false, ktype );
#endif
前面函数介绍那部分有讲过depth()得到的是一个0~6的数字,分别代表不同的位数,因此可以看出ktype的取值只能为CV_32F或者是CV_64F,下面讲getDerivKernels( kx, ky, dx, dy, ksize, false, ktype );
这个函数:
void cv::getDerivKernels( OutputArray kx, OutputArray ky, int dx, int dy, int ksize, bool normalize, int ktype )
{
if( ksize <= 0 )
getScharrKernels( kx, ky, dx, dy, normalize, ktype );
else
getSobelKernels( kx, ky, dx, dy, ksize, normalize, ktype );
}
可以看出当我们给Sobel()函数输入的 ksize <= 0 时,我们实际上是使用的Scharr内核作为我们的卷积核,Scharr核为
[ − 3 − 10 − 3 0 0 0 + 3 + 10 + 3 ] \left[ \begin{matrix} -3 & -10 & -3 \\ 0 & 0 & 0 \\ +3 & +10 & +3 \end{matrix} \right] ⎣⎡−30+3−100+10−30+3⎦⎤
我们看看getScharrKernels()这个函数是怎么生成Scharr核的:
static void getScharrKernels( OutputArray _kx, OutputArray _ky, int dx, int dy, bool normalize, int ktype )
{
const int ksize = 3;
CV_Assert( ktype == CV_32F || ktype == CV_64F ); //ktype 只能取CV_32F或者CV_64F ,否则报错
_kx.create(ksize, 1, ktype, -1, true);
_ky.create(ksize, 1, ktype, -1, true);
Mat kx = _kx.getMat(); //生成3行1列的矩阵并转换为Mat类
Mat ky = _ky.getMat();
CV_Assert( dx >= 0 && dy >= 0 && dx+dy == 1 ); //判断求的偏导方向是否正确
/***** 为矩阵赋值并转换为浮点数矩阵*******
当求水平方向一阶导时,dx = 1, dy = 0;
此时 kx = {-1, 0, 1} ky = {3, 10, 3}
当求垂直方向一阶导时,dx = 0, dy = 1;
此时 kx = {3, 10, 3} ky = {-1, 0, 1}
***************************************/
for( int k = 0; k < 2; k++ )
{
Mat* kernel = k == 0 ? &kx : &ky;
int order = k == 0 ? dx : dy;
int kerI[3];
if( order == 0 )
kerI[0] = 3, kerI[1] = 10, kerI[2] = 3;
else if( order == 1 )
kerI[0] = -1, kerI[1] = 0, kerI[2] = 1;
Mat temp(kernel->rows, kernel->cols, CV_32S, &kerI[0]);
double scale = !normalize || order == 1 ? 1. : 1./32;
temp.convertTo(*kernel, ktype, scale);
}
}
getSobelKernels()函数生成Sobel核与生成Scharr核类似,只是其生成的核的大小更多样性,可以为1、3、5、7、9等奇数核,但最大不能超过31:
static void getSobelKernels( OutputArray _kx, OutputArray _ky, int dx, int dy, int _ksize, bool normalize, int ktype )
{
int i, j, ksizeX = _ksize, ksizeY = _ksize;
if( ksizeX == 1 && dx > 0 )
ksizeX = 3;
if( ksizeY == 1 && dy > 0 )
ksizeY = 3;
CV_Assert( ktype == CV_32F || ktype == CV_64F );
_kx.create(ksizeX, 1, ktype, -1, true);
_ky.create(ksizeY, 1, ktype, -1, true);
Mat kx = _kx.getMat();
Mat ky = _ky.getMat();
if( _ksize % 2 == 0 || _ksize > 31 )
CV_Error( CV_StsOutOfRange, "The kernel size must be odd and not larger than 31" );
vector<int> kerI(std::max(ksizeX, ksizeY) + 1);
CV_Assert( dx >= 0 && dy >= 0 && dx+dy > 0 );
for( int k = 0; k < 2; k++ )
{
Mat* kernel = k == 0 ? &kx : &ky;
int order = k == 0 ? dx : dy;
int ksize = k == 0 ? ksizeX : ksizeY;
CV_Assert( ksize > order );
if( ksize == 1 )
kerI[0] = 1;
else if( ksize == 3 )
{
if( order == 0 )
kerI[0] = 1, kerI[1] = 2, kerI[2] = 1;
else if( order == 1 )
kerI[0] = -1, kerI[1] = 0, kerI[2] = 1;
else
kerI[0] = 1, kerI[1] = -2, kerI[2] = 1;
}
else
{
int oldval, newval;
kerI[0] = 1;
for( i = 0; i < ksize; i++ )
kerI[i+1] = 0;
for( i = 0; i < ksize - order - 1; i++ )
{
oldval = kerI[0];
for( j = 1; j <= ksize; j++ )
{
newval = kerI[j]+kerI[j-1];
kerI[j-1] = oldval;
oldval = newval;
}
}
for( i = 0; i < order; i++ )
{
oldval = -kerI[0];
for( j = 1; j <= ksize; j++ )
{
newval = kerI[j-1] - kerI[j];
kerI[j-1] = oldval;
oldval = newval;
}
}
}
Mat temp(kernel->rows, kernel->cols, CV_32S, &kerI[0]);
double scale = !normalize ? 1. : 1./(1 << (ksize-order-1));
temp.convertTo(*kernel, ktype, scale);
}
}
卷积核生成以后就要进行最后一步了,对图像进行卷积滤波,使用sepFilter2D()这个函数
void cv::sepFilter2D( InputArray _src, OutputArray _dst, int ddepth,
InputArray _kernelX, InputArray _kernelY, Point anchor,
double delta, int borderType )
{
Mat src = _src.getMat(), kernelX = _kernelX.getMat(), kernelY = _kernelY.getMat();
if( ddepth < 0 )
ddepth = src.depth();
_dst.create( src.size(), CV_MAKETYPE(ddepth, src.channels()) );
Mat dst = _dst.getMat();
Ptr<FilterEngine> f = createSeparableLinearFilter(src.type(),
dst.type(), kernelX, kernelY, anchor, delta, borderType & ~BORDER_ISOLATED ); //创建可分离线系滤波器
f->apply(src, dst, Rect(0,0,-1,-1), Point(), (borderType & BORDER_ISOLATED) != 0 ); //使用创建的滤波器进行滤波
}
这段的重点在最后两行,创建滤波器并使用创建的滤波器进行滤波,这里就不再深入到滤波器引擎了,后面使用专门的一篇来讲解滤波器引擎FilterEngine。