1 概述
在第五章,我们学习了图像处理;其中的大多数操作都是对图像增强,修改等;使之成为和源图像类似的全新的图像;比如我们可以用平滑消除图像的噪声,用阈值化得到图像的二值图像,或者缩放图像。
在本章中,我们要学习的是图像的变化;是将图像转变为另外一种表达方式;比如使用傅里叶变换,将图像从空域转变为频域;转变之后新图像的每个单独像素表示原始输入图像的频谱分量而不是我们通常所考虑的空间分量。
2 卷积
卷积是图像变换的基础,我们在第五章已经接触了卷积。一个卷积能实现的功能由其卷积核的形式决定。这个核实质上是一个大小固定的/;由数值参数构成的数组,数组的参考点通常位于数组的中心。在对图像做卷积的时候,首先将参考点定位到图像的第一个像素,卷积核的其他位置对应到图像中的其他像素点。在每一个位置,我们将卷积核中的值和像素中的值相乘,并求和。将结果写在参考点的位置,就完成了对对应像素点的卷积操作,我们对图像中的每个点重复该操作,完成对图像的卷积。
卷积操作使用函数 cvFilter2D 实现:
void cvFilter2D( const CvArr* srcarr, CvArr* dstarr, const CvMat* _kernel, CvPoint anchor )
srcarr, dstarr: 源宿图像,大小相同。
_kernel: 卷积核,一般情况下,卷积核的系数是浮点性的,所以我们必须用CV_32F来初始化矩阵。
anchor: 参考点的位置,默认情况下为(-1, -1)。如果是默认值的话,选取矩阵的中心作为参考点,所以,如果anchro没有指定值,_kernel的大小必须是奇数*奇数。而如果指定了anchor则无此限制。
在上面的阐述中,有一个很奇怪的点,就是为什么源宿图像的大小是相同的,在处理边缘点的时候,卷积核无法覆盖到值,所以,直观上理解,宿图像应该比源图像要小?
OpenCV解决边界问题的方法是先将源图像阔大,再进行卷积。默认扩大的方法是复制边界的值。我们也可以用如下函数来扩展边界:
void
cvCopyMakeBorder( const CvArr* srcarr, CvArr* dstarr, CvPoint offset,
int borderType, CvScalar value )
offset: 源图像在宿图像中的偏移,理论上,如果待处理的卷积核的大小是N*N(其中N为奇数),那么偏移的大小应该是((N-1)/2, (N-1)/2)
boardertype: 扩展的方式,当前有如下两种可选;IPL_BORDER_CONSTANT(使用固定值来扩展边界,该固定值为参数value);IPL_BORDER_REPLICATE(复制边界的值)。
在cvFilter2D中,固定是采用复制的方法来扩展的边界的,那我们怎么自定义我们的边界方式呢?我们可以先使用cvCopyMakeBorder将边界扩展之后,再进行卷积的操作,在输出图像中,选取恰当的位置作为ROI即可。
3 图像梯度和Sobel导数 Scharr滤波器
使用卷积的一个常见的操作就是图像导数的计算,或者说近似导数的计算。
求导最常用的方法就是使用sobel算子。三阶的sobel算子如下所示:
在计算得到了Gx和Gy之后,通过如下公式结合:
有的时候,为了简化计算,也可以直接用两个值绝对值的和来近似,在OpenCV中,采用的就是这种方法:
Sobel算子对于噪声有一定的抵抗能力。在OpenCV中使用如下函数来通过Sobel算子求导数:
void cvSobel( const void* srcarr, void* dstarr, int dx, int dy, int aperture_size )
srcarr, dstarr: 源宿图像,为了防止溢出,dstarr的深度应该比srcarr深,比如说,如果srcarr是8位的话,dstarr必须是IPL_DEPTH_16S
dx, dy: x方向和y方向的导数,比如如果我们只求x方向,不想求y方向的,我们可以将其值设置为1,0
aperure_size: 方形滤波器的宽,支持1,3, 5, 7 。其次,通过给这个参数传入CV_SCHARR来使用3*3的Scharr滤波器。
Scharr滤波器如下图所示:
对于一个3*3的Sobel,当梯度越接近水平或者垂直的时候,就越发不准确。所以在3*3大小的卷积核做变换时,应该使用Scharr滤波器。
最后,我们必须认识到一点,Sobel求得的并不是真正的导数,因为Sobel算子定义在一个离散的空间上,Sobel算子真正表示的是多项式拟合;他是对抛物线的局部拟合。所以人们也希望使用更大的核,也就在更多像素上计算这种拟合。
4 拉普拉斯变换
拉普拉斯变化的公式表示如下:
拉普拉斯变化常常被用来检测团块。二次求导表示的是一阶函数的变化率, 这也意味着,如果周围是更高值的单点或者小块会使这个函数的值最大化;反之,周围是更低值的点会使函数的负值最大化。
在OpenCV中,拉普拉斯变化直接使用Sobel求导得来。函数如下:
void cvLaplace( const void* srcarr, void* dstarr, int aperture_size )
其中,源图像既可以是8位无符号,也可以是32位浮点性;而目的图像必须是16位有符号或者32位浮点型。
Sobel和laplace变化的结果如图所示:
5 Canny算子
使用Canny算子进行边缘检测有如下几个步骤:
1. 对图像进行高斯平滑,以消除噪声。
2. 使用一阶差分卷积模板,以加强边缘,并得到梯度值和梯度方向。
3. 非极大值抑制,目的是为了保留梯度方向上的极大值。对于梯度图,将点和沿其梯度方向上的两个点比较,如果不是最大点,就设置该点的值为0,否则保持不变。
4. 双线性阈值:对非极大值抑制的结果通过两个阈值分别进行阈值化处理:Nmin = Nmax * 0.4。通过Nmax阈值化处理之后,图像具有比较少的假的边缘,使用Nmin处理之后,图像具有比较多的假的边缘点。通过第一步获取的边缘点,判断其8邻域内有无第二步获得的边缘点,然后进行连接。得到最后的边缘图像。
canny算子使用图函数实现:
void cvCanny( const CvArr* image, CvArr* edges, double threshold1,
double threshold2, int aperture_size )