平滑处理也称作模糊处理(blurring),其用途很多,最常见的是用来减少图像上的噪声或者失真。
目前,OpenCV支持五种平滑处理,他们都通过 cvSmooth 函数实现:
CVAPI(void) cvSmooth( const CvArr* src, CvArr* dst,
int smoothtype CV_DEFAULT(CV_GAUSSIAN),
int size1 CV_DEFAULT(3),
int size2 CV_DEFAULT(0),
double sigma1 CV_DEFAULT(0),
double sigma2 CV_DEFAULT(0));
通过参数 smoothtype来确定对输入图像采取何种平滑处理:
平滑类型 | 名称 | IN-PLACE | 输入类型 | 输出类型 | 简要说明 |
CV_BLUE | 简单模糊(均值滤波) | Y | 8U, 32F | 8U, 32F | 对每个像素size1*size2邻域求和,并做缩放1/(size1*size2) |
CV_BLUE_NO_SCALE | 简单无缩放变化的模糊 | N | 8U` | 16S/32F | 对每个像素size1*size2邻域求和 |
CV_MEDIAN | 中值模糊 | N | 8U | 8U | 对源图像进行核大小为size1*size2的中值滤波 |
CV_GAUSSIAN | 高斯模糊 | Y | 8U,32F | 8U, 32F | 对源图像进行核大小为size1*size2的高斯卷积 |
CV_BILATERAL | 双边滤波 | N | 8U | 8U | 应用双线性3*3滤波 |
将中心像素的正方形邻域内的每个像素值用中间像素表示。
他相较简单模糊(均值滤波),对图像中孤立的噪声点不是那么敏感:均值滤波时,即使有极少量的点存在较大的差异,也会导致平均值有明显的波动,而中值滤波可以通过选择中间值来避免这些点的影响。
高斯滤波其实就是对邻域内的每个像素点进行加权平均,同时,离中心越近的像素权重越高,相对于均值滤波,他的平滑效果更加柔和,且能够更号的保留边缘;具体的做法是用卷积核和输入图像的每个点进行卷积。
OpenCV为高斯滤波提供了几个常见的核以提高性能:具有标准sigma值的3*3, 5*5, 7*7比其他的核具有更高的性能。
一维高斯分布的公式如下:
二维高斯高斯分布的公式如下:
高斯滤波后被平滑的图像取决于标准差。函数cvSmooth的参数三代表了高斯卷积核的sigma值,如果没有指定的化,函数将会通过如下的公式自动获取:
高斯滤波在保留信号的条件下,减少了噪声。但是,这种方法在接近边缘的地方就无效了,在边缘,你不希望像素和相邻像素有关。因此,高斯滤波会磨平边缘,而双边滤波能够提供一种不会将边缘平滑掉的方法。但是作为代价,需要更多的处理时间。
和高斯滤波类似,双边滤波会依据每个像素及其邻域构造一个加权平均,加权计算包括两个部分,其中第一部分加权方式和高斯平滑中的相同,第二部分也属于高斯加权 ,但是不是基于基于中心像素点与其他像素点的空间距离之上的加权,而是基于与中心像素的亮度值的加权。可以将双边滤波视为高斯平滑,对相似的像素赋予较高的权重,不相似的赋予较小的权重。
双边滤波的公式如下图所示:
其中w取决与定义域和和值域核的乘积:
膨胀操作是求局部最大值的操作。在对邻域求最大值之后,相当于前景(亮的部分)大了一圈。膨胀操作能够使图像中的高亮区域逐渐增长。
void cvDilate( const CvArr* srcarr, CvArr* dstarr, IplConvKernel* element, int iterations )
腐蚀操作要计算核区域像素的最小值。是膨胀操作的反操作。
void cvErode( const CvArr* srcarr, CvArr* dstarr, IplConvKernel* element, int iterations )
膨胀操作可以填补凹洞,而腐蚀操作能够消除细的凸起。腐蚀操作通常用来消除图像中的斑点噪声,而在试图找到联通分支的时候,通常需要用到膨胀操作,因为一个大的区域,可能因为噪声,阴影等类似的东西而分割成多个部分,而膨胀操作,能够将其重新融合在一起。
膨胀和腐蚀函数的第三个参数是IplCovKernel,我们也可以自定义核。通常,通过如下函数进行核的创建和释放:
IplConvKernel *
cvCreateStructuringElementEx( int cols, int rows,
int anchorX, int anchorY,
int shape, int *values );
void
cvReleaseStructuringElement( IplConvKernel ** element );
创建的时候,可以通过参数决定核的形状:
CV_SHAPE_RECT | 矩形 |
CV_SHAPE_CROSS | 十字交叉形 |
CV_SHAPE_ELLIPSE | 椭圆形 |
CV_SHAPE_CUSTOM | 用户自定义的值 |
如果不是二值图像,膨胀和腐蚀的操作结果往往不是很明显。在很多情况下,我们需要一些更加通用的形态学操作:
void
cvMorphologyEx( const void* srcarr, void* dstarr, void*,
IplConvKernel* element, int op, int iterations );
op的可选标记有:
操作 | 形态学操作 | 是否需要临时图像 |
CV_MOP_OPEN | 开运算 | 否 |
CV_MOP_CLOSE | 闭运算 | 否 |
CV_MOP_GRADIENT | 形态梯度 | 是 |
CV_MOP_TOPHAT | 礼帽 | in-place下需要 |
CV_MOP_BLACKHAT | 黑帽 | in-place下需要 |
运算 | 操作 | 说明 |
开运算 | 先腐蚀,再膨胀 | 消除了高于临近点的孤立点。通常用来统计二值图像中的区域数。 |
闭运算 | 先膨胀,再腐蚀 | 消除了低于临近点的孤立点。 |
从上图中可以看出,膨胀操作先其了邻域内的最大点,再对其进行腐蚀得到最终的图形,可以消除像素值低于临近点的孤立的点。
从上图中可以看出,腐蚀操作取邻域内的最小值,再对其进行膨胀得到最终图像。可以消除像素值高于临近点的孤立点。
需要注意,在开运算和闭运算下,函数cvMorphologyEx的iterations参数并不表示膨胀-腐蚀-膨胀-腐蚀的次数,而是表示膨胀-膨胀-...-腐蚀-腐蚀...这样的过程的次数。
形态学梯度的表达式如下:
从上图中可以看出形态学梯度的过程,从原图像的膨胀减去了源图像的收缩,就得到了图像的边缘。
同以上的形态学操作类似,礼貌和黑帽操作也是基本操作的组合,公式如下:
从2.3的阐述中,我们可以知道,开运算消除了高于临近点的孤立的点,如果我们用原图像减去开运算的结果;得到的就是开运算消除的部分,即高于临近点的部分。所以礼帽操作突出了比周围区域更加明亮的部分。
相反,闭运算消除了低于临近点的孤立的点,如果我们用闭运算的结果加你去源图像,得到的就是闭运算消除的部分,即低于临近点的部分。所以黑帽操作突出了比周围区域更加黯的部分。
漫水填充算法的思路很简单,就是从一个种子点开始,如果周围的点和目标点相同或者相似,就往周围扩散;就像水往周围漫开一样。它经常被用来标记或者分离图像的一部分,以便对其进行进一步的处理或者分析。
void
cvFloodFill( CvArr* arr, CvPoint seed_point,
CvScalar newVal, CvScalar lo_diff, CvScalar up_diff,
CvConnectedComp* comp, int flags, CvArr* maskarr )
该函数的入参比较复杂,下面我们来进行说明:
arr | 源图像 |
seedPoint | 种子点,从哪个点开始漫水 |
newVal | 像素点被染色的值 |
lo_diff, up_diff | 范围,如果目的像素点的值在lo_diff和up_diff之间,将会被染色成newVal |
comp | 如果该参数不是NULL,那么该cvConnectedComp结构将被设置为填充区域的统计属性 |
maskarr | 掩码,即代表函数的输入值,也代表输出值。如果maskarr非空,那么他一定要是一个单通道,8位,宽度和高度均比源图像大2个像素的图像。mask图像的(x+1, y+1)和原图像的(x,y)对应。cvFoodFill 不会覆盖mask的非0的点 |
flags | 低8位:4/8,填充算法考虑四邻域或者8邻域 高8位:CV_FLOODFILL_FIXED_RANGE - 目的像素和种子像素点比较。CV_FLOODFILL_MASK_ONLY - 函数不填充源图像,只填充mask图像。 中间8位:填充掩码图像的值,如果中间是0,则掩码图像用1填充。 eg: flags = 8 | CV_FLOODFILL_MASK_ONLY | CV_FLOODFILL_FIXED_RANGE | (47 << 8) |
我们使用函数 cvResize 来对图像进行方法或者缩小:
void cvResize( const CvArr* srcarr, CvArr* dstarr, int method )
如果源图像中设置了ROI,那么函数将会对源图像的ROI进行尺寸调整。同样,如果目的图像设置了ROI,那么将源图像调整之后,填充到目的图像的ROI中。
当图像被缩小时,目标像素的图像会被映射到源图像中的多个像素,这个时候需要进行插值。当放大图像时,目标图像上的像素可能无法在源图像中找到精确的对应像素,这个时候也需要进行插值。cvResize函数的method参数提供了选择插值方式的入口:
插值方式 | 含义 | 说明 |
CV_INTER_NN | 最近邻插值 | 将图像各点的像素值设为源图形中与其距离最近的像素点。 |
CV_INTER_LINER | 线性插值 | 将根据源图像附近的2*2范围的四个点的像素的线性加权得出。权重由这四个点到目的点的距离决定。 |
CV_INTER_AREA | 区域插值 | 用新的像素点映射到源图像的像素点,然后取覆盖区域的平均值。 |
CV_INTER_CUBIC | 三次样条插值 | 对源图像附近的4*4个临近像素进行三次样条拟合,然后将目标像素对应的三次样条值作为目标图像对应像素值的点。 |
图像金子塔是一个图像的集合,集合中所有的图像都源于同一个图像,而且是通过对源图像进行连续的降采样得到的。
有两种金子塔经常用到:高斯金子塔和拉普拉斯金字塔。高斯金子塔用来向下采样,而拉普拉斯金子塔用来从金子塔的底层向上重建一个图像。要想用金子塔的第i层获得第i+1层,我们要用高斯核对其进行卷积,然后删除偶数行和偶数列。
void cvPyrDown( const void* srcarr, void* dstarr, int _filter )
目前_filter只支持 CV_GAUSSIAN_5x5。
我们也可以使用下面相似的函数,将图像在每个维度上都放大两倍:
void cvPyrUp( const void* srcarr, void* dstarr, int _filter )
这个函数将元图下个的每个维度都扩大为原来的两倍,新增的偶数行以0填充,然后使用给定的滤波器进行卷积(实际上是一个在每一个维度上都扩大为两倍的过滤器)来估计丢失像素的近似值。
很容易开出up操作并不是down操作的逆操作。为了恢复原来的图像,我们需要获得又降采样操作丢失的信息,这些数据形成了拉普拉斯金子塔。
我们可以利用图像金字塔实现图像的分割,在图像金字塔的基础上,快速分割可以在低分辨率的图像上完成,然后逐层对分割图图像进行优化。
图像的阈值化就是给定一个阈值,并对像素上的点是高于或者低于该阈值进行一定的操作:
double cvThreshold( const void* srcarr, void* dstarr, double thresh, double maxval, int type )
type可以取值如下:
CV_THRESH_BINARY | 大于阈值:maxval 小于阈值:0 |
CV_THRESH_BINARY_INV | 大于阈值:0 小于阈值:maxval |
CV_THRESH_TRUNC | 大于阈值:maxval 小于阈值:值不变 |
CV_THRESH_TOZERO_INV | 大于阈值:0 小于阈值:值不变 |
CV_THRESH_TOZERO | 大于阈值:值不边 小于阈值:0 |
通常情况下,阈值并不是很好确定,或者如果图像本身光照不均匀的前提下,阈值需要针对不同的部分不同处理。在OpenCV中,提供了自适应阈值化函数:
void
cvAdaptiveThreshold( const void *srcIm, void *dstIm, double maxValue,
int method, int type, int blockSize, double delta )
当前有两种类型的自适应:
CV_ADAPTIVE_THRESH_MEAN_C:对于blockSize*blockSize区域内的像素点平均加权 - delta,来作为阈值。
CV_ADAPTIVE_THRESH_GAUSSIAN_C: 对blockSize*blockSize区域的所有像素按照高斯函数按照他们的中心距离进行加权计算。