6.4自定义角点检测
6.4.1自定义角点检测概述
前文讲解了利用Harris进行角点检测和利用Shi-Tomasi方法进行角点检测外,也可以自己制作角点检测的函数:使用cornerEigenValsAndVecs()函数和minMaxLoc()函数结合来模拟Harris角点检测,或者使用cornerMinEigenVal()函数和minMaxLoc()函数结合来模拟Shi-Tomasi角点检测,最后特征点选取的判断条件要根据实际情况进行选择。
6.4.2自定义角点检测函数
cornerEigenValsAndVecs ()函数讲解
C++: void cornerEigenValsAndVecs(InputArray src,
OutputArray dst,
int blockSize,
int ksize,
int borderType=BORDER_DEFAULT );
【参数】
第一个参数,src:输入单通道8-bit或者浮点类型图像。
第二个参数,dst:用来存储结果的图像,大小与输入图像一致并且为CV_32FC(6)类型。
第三个参数,blockSize:邻域大小。
第四个参数,ksize:Sobel算子当中的核大小,只能取1、3、5、7。
第五个参数,borderType:像素扩展的方法,因为在滤波处理的过程中会扩展图像边缘,每扩张一个边界像素,都需要计算出该像素点在原图中的位置,这个功能被提炼出来就变成了borderInterpolate()函数。该函数输入一个点坐标,返回他在原图中的坐标。
对于每个像素点p,该函数考虑一个blockSize*blockSize的邻域S(p),它在邻域上计算导数的协方差矩阵:
其中导数是使用Sobel()算子计算得到的。随后,函数计算矩阵M的特征值和特征向量,并将它们以(λ1, λ2, x1, y1, x2,y2)的形式存储在目标图像dst中。其中λ1, λ2是M未经过排序的特征值;x1, y1是对应于λ1的特征向量;x2, y2是对应于λ2的特征向量。该函数的输出能够用于鲁棒的边缘或角点检测。
cornerEigenValsAndVecs ()函数源代码
/*【cornerEigenValsAndVecs ( )源代码】*************************************************
* @Version:OpenCV 3.0.0(Opnencv2和Opnencv3差别不大,Linux和PC的对应版本源码完全一样,均在对应的安装目录下)
* @源码路径:…\opencv\sources\modules\imgproc\src\ corner.cpp
* @起始行数:658行
********************************************************************************/
void cv::cornerEigenValsAndVecs( InputArray _src, OutputArray _dst, int blockSize, int ksize, int borderType )
{
Mat src = _src.getMat();
Size dsz = _dst.size();
int dtype = _dst.type();
if( dsz.height != src.rows || dsz.width*CV_MAT_CN(dtype) != src.cols*6 || CV_MAT_DEPTH(dtype) != CV_32F )
_dst.create( src.size(), CV_32FC(6) );
Mat dst = _dst.getMat();
cornerEigenValsVecs( src, dst, blockSize, ksize, EIGENVALSVECS, 0, borderType );
}
可以看出其实调用的()函数,其函数原代码如下。
/*【cornerEigenValsVecs ( )源代码】*****************************************************
* @Version:OpenCV 3.0.0(Opnencv2和Opnencv3差别不大,Linux和PC的对应版本源码完全一样,均在对应的安装目录下)
* @源码路径:…\opencv\sources\modules\imgproc\src\ corner.cpp
* @起始行数:266行
********************************************************************************/
static void cornerEigenValsVecs( const Mat& src, Mat& eigenv, int block_size,
int aperture_size, int op_type, double k=0.,
int borderType=BORDER_DEFAULT )
{
#ifdef HAVE_TEGRA_OPTIMIZATION
if (tegra::useTegra() && tegra::cornerEigenValsVecs(src, eigenv, block_size, aperture_size, op_type, k, borderType))
return;
#endif
#if CV_SSE2
bool haveSSE2 = checkHardwareSupport(CV_CPU_SSE2);
#endif
int depth = src.depth();
double scale = (double)(1 << ((aperture_size > 0 ? aperture_size : 3) - 1)) * block_size;
if( aperture_size < 0 )
scale *= 2.0;
if( depth == CV_8U )
scale *= 255.0;
scale = 1.0/scale;
CV_Assert( src.type() == CV_8UC1 || src.type() == CV_32FC1 );
Mat Dx, Dy;
if( aperture_size > 0 )
{
Sobel( src, Dx, CV_32F, 1, 0, aperture_size, scale, 0, borderType );
Sobel( src, Dy, CV_32F, 0, 1, aperture_size, scale, 0, borderType );
}
else
{
Scharr( src, Dx, CV_32F, 1, 0, scale, 0, borderType );
Scharr( src, Dy, CV_32F, 0, 1, scale, 0, borderType );
}
Size size = src.size();
Mat cov( size, CV_32FC3 );
int i, j;
for( i = 0; i < size.height; i++ )
{
float* cov_data = cov.ptr<float>(i);
const float* dxdata = Dx.ptr<float>(i);
const float* dydata = Dy.ptr<float>(i);
j = 0;
#if CV_NEON
for( ; j <= size.width - 4; j += 4 )
{
float32x4_t v_dx = vld1q_f32(dxdata + j);
float32x4_t v_dy = vld1q_f32(dydata + j);
float32x4x3_t v_dst;
v_dst.val[0] = vmulq_f32(v_dx, v_dx);
v_dst.val[1] = vmulq_f32(v_dx, v_dy);
v_dst.val[2] = vmulq_f32(v_dy, v_dy);
vst3q_f32(cov_data + j * 3, v_dst);
}
#elif CV_SSE2
if (haveSSE2)
{
for( ; j <= size.width - 8; j += 8 )
{
__m128 v_dx_0 = _mm_loadu_ps(dxdata + j);
__m128 v_dx_1 = _mm_loadu_ps(dxdata + j + 4);
__m128 v_dy_0 = _mm_loadu_ps(dydata + j);
__m128 v_dy_1 = _mm_loadu_ps(dydata + j + 4);
__m128 v_dx2_0 = _mm_mul_ps(v_dx_0, v_dx_0);
__m128 v_dxy_0 = _mm_mul_ps(v_dx_0, v_dy_0);
__m128 v_dy2_0 = _mm_mul_ps(v_dy_0, v_dy_0);
__m128 v_dx2_1 = _mm_mul_ps(v_dx_1, v_dx_1);
__m128 v_dxy_1 = _mm_mul_ps(v_dx_1, v_dy_1);
__m128 v_dy2_1 = _mm_mul_ps(v_dy_1, v_dy_1);
_mm_interleave_ps(v_dx2_0, v_dx2_1, v_dxy_0, v_dxy_1, v_dy2_0, v_dy2_1);
_mm_storeu_ps(cov_data + j * 3, v_dx2_0);
_mm_storeu_ps(cov_data + j * 3 + 4, v_dx2_1);
_mm_storeu_ps(cov_data + j * 3 + 8, v_dxy_0);
_mm_storeu_ps(cov_data + j * 3 + 12, v_dxy_1);
_mm_storeu_ps(cov_data + j * 3 + 16, v_dy2_0);
_mm_storeu_ps(cov_data + j * 3 + 20, v_dy2_1);
}
}
#endif
for( ; j < size.width; j++ )
{
float dx = dxdata[j];
float dy = dydata[j];
cov_data[j*3] = dx*dx;
cov_data[j*3+1] = dx*dy;
cov_data[j*3+2] = dy*dy;
}
}
boxFilter(cov, cov, cov.depth(), Size(block_size, block_size),
Point(-1,-1), false, borderType );
if( op_type == MINEIGENVAL )
calcMinEigenVal( cov, eigenv );
else if( op_type == HARRIS )
calcHarris( cov, eigenv, k );
else if( op_type == EIGENVALSVECS )
calcEigenValsVecs( cov, eigenv );
}
cornerMinEigenVal ()函数讲解
C++: void cornerMinEigenVal(InputArray src,
OutputArray dst,
int blockSize,
int ksize=3,
int borderType=BORDER_DEFAULT );
【参数】
第一个参数,src:输入单通道8-bit或者浮点类型图像。
第二个参数,dst:用来存储结果的图像,必须为CV_32FC1类型。
第三个参数,blockSize:邻域大小。
第四个参数,ksize:Sobel算子当中的核大小,只能取1、3、5、7。
第五个参数,borderType:像素扩展的方法,因为在滤波处理的过程中会扩展图像边缘,每扩张一个边界像素,都需要计算出该像素点在原图中的位置,这个功能被提炼出来就变成了borderInterpolate()函数。该函数输入一个点坐标,返回他在原图中的坐标。
【注】除了dst必须为CV_32FC1类型以外,其它与cornerEigenValsAndVecs()函数的一致。
cornerMinEigenVal ()函数源代码
/*【cornerMinEigenVal ( )源代码】******************************************************
* @Version:OpenCV 3.0.0(Opnencv2和Opnencv3差别不大,Linux和PC的对应版本源码完全一样,均在对应的安装目录下)
* @源码路径:…\opencv\sources\modules\imgproc\src\ corner.cpp
* @起始行数:526行
********************************************************************************/
void cv::cornerMinEigenVal( InputArray _src, OutputArray _dst, int blockSize, int ksize, int borderType )
{
CV_OCL_RUN(_src.dims() <= 2 && _dst.isUMat(),
ocl_cornerMinEigenValVecs(_src, _dst, blockSize, ksize, 0.0, borderType, MINEIGENVAL))
Mat src = _src.getMat();
_dst.create( src.size(), CV_32FC1 );
Mat dst = _dst.getMat();
#if defined(HAVE_IPP) && (IPP_VERSION_MAJOR >= 8)
CV_IPP_CHECK()
{
typedef IppStatus (CV_STDCALL * ippiMinEigenValGetBufferSize)(IppiSize, int, int, int*);
typedef IppStatus (CV_STDCALL * ippiMinEigenVal)(const void*, int, Ipp32f*, int, IppiSize, IppiKernelType, int, int, Ipp8u*);
IppiKernelType kerType;
int kerSize = ksize;
if (ksize < 0)
{
kerType = ippKernelScharr;
kerSize = 3;
} else
{
kerType = ippKernelSobel;
}
bool isolated = (borderType & BORDER_ISOLATED) != 0;
int borderTypeNI = borderType & ~BORDER_ISOLATED;
if ((borderTypeNI == BORDER_REPLICATE && (!src.isSubmatrix() || isolated)) &&
(kerSize == 3 || kerSize == 5) && (blockSize == 3 || blockSize == 5))
{
ippiMinEigenValGetBufferSize getBufferSizeFunc = 0;
ippiMinEigenVal minEigenValFunc = 0;
float norm_coef = 0.f;
if (src.type() == CV_8UC1)
{
getBufferSizeFunc = (ippiMinEigenValGetBufferSize) ippiMinEigenValGetBufferSize_8u32f_C1R;
minEigenValFunc = (ippiMinEigenVal) ippiMinEigenVal_8u32f_C1R;
norm_coef = 1.f / 255.f;
} else if (src.type() == CV_32FC1)
{
getBufferSizeFunc = (ippiMinEigenValGetBufferSize) ippiMinEigenValGetBufferSize_32f_C1R;
minEigenValFunc = (ippiMinEigenVal) ippiMinEigenVal_32f_C1R;
norm_coef = 255.f;
}
norm_coef = kerType == ippKernelSobel ? norm_coef : norm_coef / 2.45f;
if (getBufferSizeFunc && minEigenValFunc)
{
int bufferSize;
IppiSize srcRoi = { src.cols, src.rows };
IppStatus ok = getBufferSizeFunc(srcRoi, kerSize, blockSize, &bufferSize);
if (ok >= 0)
{
AutoBuffer<uchar> buffer(bufferSize);
ok = minEigenValFunc(src.ptr(), (int) src.step, dst.ptr<Ipp32f>(), (int) dst.step, srcRoi, kerType, kerSize, blockSize, buffer);
CV_SUPPRESS_DEPRECATED_START
if (ok >= 0) ok = ippiMulC_32f_C1IR(norm_coef, dst.ptr<Ipp32f>(), (int) dst.step, srcRoi);
CV_SUPPRESS_DEPRECATED_END
if (ok >= 0)
{
CV_IMPL_ADD(CV_IMPL_IPP);
return;
}
}
setIppErrorStatus();
}
}
}
#endif
cornerEigenValsVecs( src, dst, blockSize, ksize, MINEIGENVAL, 0, borderType );
}
可以看出也调用了cornerEigenValsVecs()函数。
6.4.3自定义角点检测实例
代码参看附件【demo1】。