第6章 图像变换

卷积

1
2
// 卷积函数
void cvFilter2D(const CvArr* src, CvArr* dst, const CvMat* kernel, CvPoint anchor=cvPoint(-1, -1));
  • 卷积边界

对于卷积,存在的一个问题是如何处理卷积边界。通用的做法是,先将特定的图像轻微变大,然后以各种方式自动填充图像边界,也就是所谓的padding。

1
2
// 实现padding的函数
void cvCopyMakeBorder(const CvArr* src, CvArr* dst, CvPoint offset, int bordertype, CvScalar value=cvScalarALL(0));

Bordertype可以是CV_BORDER_CONSTANT或者CV_BORDER_REPLICATE。

梯度和Sobel导数

图像处理中一个最基本的卷积运算是导数的计算。通常来说,用来表达微分的最常用的操作是Soble微分算子,Sobel算子包含任意阶的微分以及融合偏导。

1
2
// sobel算子函数原型
cvSobel(const CvArr* src, CvArr* dst, int xorder, int yorder, int aperture_size=3);
  • Scharr滤波器

对于Sobel算子,当试图估计图像的方向导数时,往往不是很准确,当梯度角越接近水平或者垂直方向时,这样的不准确就更加明显。Scharr滤波器同sobel滤波器一样快,但是准确率更高,所以当利用3*3滤波器实现图像质量的时候应该使用Scharr滤波器。

拉普拉斯变换

因为拉普拉斯算子可以用二次导数的形式定义,可假设其离散实现类似于二阶Sobel导数,所以OpenCV在计算拉普拉斯算子时可以直接使用Sobel算子,但是同时也提供了拉普拉斯算子。

1
2
// Laplace算子
void cvLaplace(const CvArr* src, CvArr* dst, int apertureSize=3);

拉普拉斯算子一个通常的应用是检测“团块”。由于拉普拉斯算子的形式是沿着X和Y轴的二次导数的和,这就意味着周围是更高值的单点或者小块会将使这个函数值最大化,反过来说,周围是更低值的点将会是函数的负值最大化。
拉普拉斯算子也可以用于边缘检测。

Canny算子

1
2
// canny边缘检测算子
void cvCanny(const CvArr* img, CvArr* edges, double lowThresh, double highThresh, int apertureSize=3);

霍夫变换

霍夫变换是一种在图像中寻找直线,圆及其他简单形状的方法。

  • 霍夫线变换

霍夫线变换的基本理论是二值图像中的任何两点都可能是一些候选直线集合的一部分。如果要确定每条线进行参数化,例如一个斜率a和截距b,原始图像中华的一点会变换为(a,b)平面上的轨迹,轨迹上的点对应着所有过原始图像上点的直线。
OpenCV支持两种不同形式的霍夫变换:标准霍夫变换(SHT)和累计概率霍夫变换(PPHT)。两者可以通过一个函数实现:

1
2
// 霍夫变换检测直线
CvSeq* cvHoughLines2(CvArr* image, void* line_storage, int method, double rho, double theta, int threshold, double param1=0, double param2=0);
  • 霍夫圆变换
1
2
// 霍夫变换检测圆
CvSeq* cvHoughCircles(CvArr* image, void* circle_storage, int method, double dp, double min_dist, double param1=100, double param2=300, int min_radius=0, int max_dadius=0);

重映射

在某些情况,需要把一幅图像中一个位置的像素重映射到另一个位置。

1
2
// 重映射函数
void cvRemap(const CvArr* src, CvArr* dst, const CvArr* mapx, const CvArr* mapy, int flags=CV_INTER_LINEAR | CV_WARP_FILL_OUTLERS, CvScalar fillval=cvScalarAll(0));

拉伸、收缩、扭曲和旋转

在一些数据扩增的技术中离不开图像的几何操作,这些几何变换包括拉伸,扭曲,旋转等。一个任意的仿射变换可以表达为乘以一个矩阵再加上一个向量的形式。
仿射变换可以将矩形转换为平行四边形。它可以将矩形的边压扁但必须保持边是平行的,也可以将矩形旋转或者按比例变化。而透视变换提供了更大的灵活性,一个透视变换可以将矩形变成梯形。

  • 仿射变换

有两种情况会用到仿射变换。第一种是有一幅想要转换的图像,第二种是我们有一个点序列并想以此计算出变换。

稠密仿射变换
对于第一种情况,显然输入和输出的格式是图像,并且隐含的要求是扭曲假设对于所使用的图像,其像素必须是其稠密的表现形式。这意味着图像扭曲必须进行一些插值运算以使输出的图像平滑并且看起来自然一些。

1
2
3
4
5
6
7
8
// 稠密变换函数
void cvWarpAffine(const CvArr* src, CvArr* dst, const CvMat* map_matirx, int flags=CV_INTER_LINEAR | CV_WARP_FILL_OUTLIERS, CvScalar fillval=cvScalarAll(0));
// 开销更小的稠密变换函数
void cvGetQuadrangeleSubPix(const CvArr* src, CvArr* dst, const CvMat* map_matrix);
// 仿射映射矩阵的计算
CvMat* cvGetAffineTransform(const CvPoint2D32f* pts_src, const CvPoint2D32f* pts_dst, CvMat* map_matrix);
// 仿射映射矩阵的计算2
CvMat* cv2DRotationMatrix(CvPoint2D32f center, double angle, double scale, CvMat* map_matrix);

稀疏仿射变换
对于稀疏映射(如,对一系列独立点的映射),一般采用以下方法:

1
2
// 稀疏映射函数
void cvTransform(const CvArr* src, CvArr* dst, const CvMat* transmat, const CvMat* shiftvec=NULL);
  • 透视变换

密集透视变换

1
2
3
4
// 密集透视变换函数
void cvWarpPerspective(const CvArr* src, CvArr* dst, const CvMat* map_matrix, int flags=CV_INTER_LINERA+CV_WARP_FILL_OUTLIERS, CvScalar fillval=cvScalarAll(0));
// 透视映射矩阵map_matrix的计算
CvMat* cvGetPerspectiveTransform(const CvPoint2D32f* pts_src, const CvPoint2D32f* pts_dst, CvMat* map_matrix);

稀疏透视变换

1
2
// 稀疏仿射变换
void cvPerspectiveTransform(const CvArr* src, CvArr* dst, const CvMat* mat);

CartToPolar与PolarToCart

两者通常会被用于更复杂的变换之中,如cvLogPolar()。函数本身是将数值在笛卡尔空间和极性或者径向空间之间进行映射。

1
2
3
4
// 笛卡尔空间映射到极性空间
void cvCartToPolar(const CvArr* x, const CvArr* y, CvArr* magnitude, CvArr* angle=NULL, int angle_in_degrees=0);
// 极性空间映射到笛卡尔空间
void cvPolarToCart(const CvArr* magnitude, const CvArr* angle, CvArr* x, CvArr* y, int angle_in_degrees=0);

LogPolar

对于二维图像,Log-polar转换表示从笛卡尔坐标到极坐标的变换。

1
2
// 对数极坐标转换函数
void cvLogPolar(const CvArr* src, CvArr* dst, CvPoint2D32f center, double m, int flags=CV_INTER_LINEAR | CV_WARP_FILL_OUTFIERS);

离散傅里叶变换(DFT)

函数cvDFT()可以计算输入是一维或二维数组时的FFT。

1
2
// 实现FFT函数
void cvDFT(const CvArr* src, CvArr* dst, int flags, int nonzero_rows=0);
  • 频谱乘法
    在许多包含计算DFT的应用中,还必须将两个频谱中的每个元素分别相乘。由于DFT的结果是以其特殊的高密度格式封装,并且通常是复数,OpenCV通过cvMulSpectrums()函数解除它们的封装以及通过普通的矩阵操作来进行乘法运算。

    1
    2
    // 将两个频谱对应元素相乘
    void cvMulSpectrums(const CvArr* src1, const CvArr* src2, CvArr* dst, int flags);
  • 卷积和DFT
    利用DFT可以大大加快卷积运算的速度,因为卷积定理说明空间域的卷积运算可以转换为频域的乘法运算。通常过程是,首先计算图像的傅里叶变换,然后计算卷积核的傅里叶变换,然后在变换域中以相对于图像像素数目的线性时间内进行卷积运算。

离散余弦变换(DCT)

1
2
// 余弦变换函数
void cvDCT(const CvArr* src, CvArr* dst, int flags);

积分图像

积分图是一个数据结构,可实现子区域的快速求和。这样的求和在人脸识别及相关算法中应用的Haar小波变换是很用的。

1
2
// 求积分图像
void cvIntegral(const CvArr* image, CvArr* sum, CvArr* sqsum=NULL, CvArr* tilted_sum=NULL);

距离变换

图像的距离变换被定义为一幅新图像,该图像的每个输出像素被设成与输入像素中0像素最近的距离。显然,典型的距离变换的输入应为某些边缘图像。在多数应用中,距离变换的输入是例如Canny边缘检测的检测图像的转换输出。
在实际应用中,距离变换通常是利用3x3或5x5数组掩模进行的。数组中的每个点被定义为这个特殊位置同其他相关的掩模中心的距离。较大的距离以由整个掩模定义的“动作”序列的形式被建立,这就意味着要用更大的掩模将生成更准确的距离。

1
2
// 距离变换函数
void cvDistTransform(const CvArr* src, CvArr* dst, int distance_type=CV_DIST_L2, int mask_size=3, const float* kernel=NULL, CvArr* labels=NULL);

直方图均衡化

1
2
// 直方图均衡化函数
void cvEqualizeHist(const CvArr* src, CvArr* dst);