[Python图像处理] 二十九.傅里叶变换(图像去噪)与霍夫变换(特征识别)万字详细总结 丨【拜托了,物联网!】
前面一篇文章通过OpenCV快速实现人脸检测,涉及图像、视频、摄像头。这篇将详细介绍两个重要的算法——傅里叶变换和霍夫变换,万字长文整理,希望对您有所帮助。 同时,该部分知识均为作者查阅资料撰写,未经授权禁止转载,谢谢!!如果有问题随时私聊我,只望您能从这个系列中学到知识,一起加油喔~
PS:写这篇文章另一个重要的原因是Github资源有作者提交了新的贡献,发现提交的是霍夫变换,因此作者也总结这篇文章。CSDN博客专栏9比1分成,真的挺不错的,也希望大家能分享更好的文章。
前文参考:
- [Python图像处理] 一.图像处理基础知识及OpenCV入门函数
- [Python图像处理] 二.OpenCV+Numpy库读取与修改像素
- [Python图像处理] 三.获取图像属性、兴趣ROI区域及通道处理
- [Python图像处理] 四.图像平滑之均值滤波、方框滤波、高斯滤波及中值滤波
- [Python图像处理] 五.图像融合、加法运算及图像类型转换
- [Python图像处理] 六.图像缩放、图像旋转、图像翻转与图像平移
- [Python图像处理] 七.图像阈值化处理及算法对比
- [Python图像处理] 八.图像腐蚀与图像膨胀
- [Python图像处理] 九.形态学之图像开运算、闭运算、梯度运算
- [Python图像处理] 十.形态学之图像顶帽运算和黑帽运算
- [Python图像处理] 十一.灰度直方图概念及OpenCV绘制直方图
- [Python图像处理] 十二.图像几何变换之图像仿射变换、图像透视变换和图像校正
- [Python图像处理] 十三.基于灰度三维图的图像顶帽运算和黑帽运算
- [Python图像处理] 十四.基于OpenCV和像素处理的图像灰度化处理
- [Python图像处理] 十五.图像的灰度线性变换
- [Python图像处理] 十六.图像的灰度非线性变换之对数变换、伽马变换
- [Python图像处理] 十七.图像锐化与边缘检测之Roberts算子、Prewitt算子、Sobel算子和Laplacian算
- [Python图像处理] 十八.图像锐化与边缘检测之Scharr算子、Canny算子和LOG算子
- [Python图像处理] 十九.图像分割之基于K-Means聚类的区域分割
- [Python图像处理] 二十.图像量化处理和采样处理及局部马赛克特效
- [Python图像处理] 二十一.图像金字塔之图像向下取样和向上取样
- [Python图像处理] 二十二.Python图像傅里叶变换原理及实现
- [Python图像处理] 二十三.傅里叶变换之高通滤波和低通滤波
- [Python图像处理] 二十四.图像特效处理之毛玻璃、浮雕和油漆特效
- [Python图像处理] 二十五.图像特效处理之素描、怀旧、光照、流年以及滤镜特效
- [Python图像处理] 二十六.图像分类原理及基于KNN、朴素贝叶斯算法的图像分类案例丨【百变AI秀】
- [Python图像处理] 二十七.OpenGL入门及绘制基本图形(一)
- [Python图像处理] 二十八.OpenCV快速实现人脸检测及视频中的人脸 丨【百变AI秀】
- [Python图像处理] 二十九.傅里叶变换(图像去噪)与霍夫变换(特征识别)万字详细总结 丨【拜托了,物联网!】
文章目录
在数字图像处理中,有两个经典的变换被广泛应用——傅里叶变换和霍夫变换。其中,傅里叶变换主要是将时间域上的信号转变为频率域上的信号,用来进行图像除噪、图像增强等处理;霍夫变换主要用来辨别找出物件中的特征,用来进行特征检测、图像分析、数位影像处理等处理。
傅里叶变换(Fourier Transform,简称FT)常用于数字信号处理,它的目的是将时间域上的信号转变为频率域上的信号。随着域的不同,对同一个事物的了解角度也随之改变,因此在时域中某些不好处理的地方,在频域就可以较为简单的处理。同时,可以从频域里发现一些原先不易察觉的特征。傅里叶定理指出“任何连续周期信号都可以表示成(或者无限逼近)一系列正弦信号的叠加。”
傅里叶公式(1)如下,其中w表示频率,t表示时间,为复变函数。它将时间域的函数表示为频率域的函数f(t)的积分。
傅里叶变换认为一个周期函数(信号)包含多个频率分量,任意函数(信号)f(t)可通过多个周期函数(或基函数)相加合成。从物理角度理解,傅里叶变换是以一组特殊的函数(三角函数)为正交基,对原函数进行线性变换,物理意义便是原函数在各组基函数的投影。如上图所示,它是由三条正弦曲线组合成。其函数为(2)所示。
傅里叶变换可以应用于图像处理中,经过对图像进行变换得到其频谱图。从谱频图里频率高低来表征图像中灰度变化剧烈程度。图像中的边缘信号和噪声信号往往是高频信号,而图像变化频繁的图像轮廓及背景等信号往往是低频信号。这时可以有针对性的对图像进行相关操作,例如图像除噪、图像增强和锐化等。二维图像的傅里叶变换可以用以下数学公式(3)表达,其中f是空间域(Spatial Domain))值,F是频域(Frequency Domain)值。
对上面的傅里叶变换有了大致的了解之后,下面通过Numpy和OpenCV分别讲解图像傅里叶变换的算法及操作代码。
Numpy中的 FFT包提供了函数 np.fft.fft2()可以对信号进行快速傅里叶变换,其函数原型如下所示,该输出结果是一个复数数组(Complex Ndarry)。
- fft2(a, s=None, axes=(-2, -1), norm=None)
– a表示输入图像,阵列状的复杂数组
– s表示整数序列,可以决定输出数组的大小。输出可选形状(每个转换轴的长度),其中s[0]表示轴0,s[1]表示轴1。对应fit(x,n)函数中的n,沿着每个轴,如果给定的形状小于输入形状,则将剪切输入。如果大于则输入将用零填充。如果未给定’s’,则使用沿’axles’指定的轴的输入形状
– axes表示整数序列,用于计算FFT的可选轴。如果未给出,则使用最后两个轴。“axes”中的重复索引表示对该轴执行多次转换,一个元素序列意味着执行一维FFT
– norm包括None和ortho两个选项,规范化模式(请参见numpy.fft)。默认值为无
Numpy中的fft模块有很多函数,相关函数如下:
- numpy.fft.fft(a, n=None, axis=-1, norm=None)
计算一维傅里叶变换 - numpy.fft.fft2(a, n=None, axis=-1, norm=None)
计算二维的傅里叶变换 - numpy.fft.fftn()
计算n维的傅里叶变换 - numpy.fft.rfftn()
计算n维实数的傅里叶变换 - numpy.fft.fftfreq()
返回傅里叶变换的采样频率 - numpy.fft.shift()
将FFT输出中的直流分量移动到频谱中央
下面的代码是通过Numpy库实现傅里叶变换,调用np.fft.fft2()快速傅里叶变换得到频率分布,接着调用np.fft.fftshift()函数将中心位置转移至中间,最终通过Matplotlib显示效果图。
# -*- coding: utf-8 -*-
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
import matplotlib
#读取图像
img = cv.imread('test.png', 0)
#快速傅里叶变换算法得到频率分布
f = np.fft.fft2(img)
#默认结果中心点位置是在左上角,
#调用fftshift()函数转移到中间位置
fshift = np.fft.fftshift(f)
#fft结果是复数, 其绝对值结果是振幅
fimg = np.log(np.abs(fshift))
#设置字体
matplotlib.rcParams['font.sans-serif']=['SimHei']
#展示结果
plt.subplot(121), plt.imshow(img, 'gray'), plt.title('(a)原始图像')
plt.axis('off')
plt.subplot(122), plt.imshow(fimg, 'gray'), plt.title('(b)傅里叶变换处理')
plt.axis('off')
plt.show()
输出结果如图2所示,图2(a)为原始图像,图2(b)为频率分布图谱,其中越靠近中心位置频率越低,越亮(灰度值越高)的位置代表该频率的信号振幅越大。
需要注意,傅里叶变换得到低频、高频信息,针对低频和高频处理能够实现不同的目的。同时,傅里叶过程是可逆的,图像经过傅里叶变换、逆傅里叶变换能够恢复原始图像。
下列代码呈现了原始图像在变化方面的一种表示:图像最明亮的像素放到中央,然后逐渐变暗,在边缘上的像素最暗。这样可以发现图像中亮、暗像素的百分比,即为频域中的振幅AA的强度。
# -*- coding: utf-8 -*-
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
#读取图像
img = cv.imread('Na.png', 0)
#傅里叶变换
f = np.fft.fft2(img)
#转移像素做幅度普
fshift = np.fft.fftshift(f)
#取绝对值:将复数变化成实数取对数的目的为了将数据变化到0-255
res = np.log(np.abs(fshift))
#展示结果
plt.subplot(121), plt.imshow(img, 'gray'), plt.title('Original Image')
plt.subplot(122), plt.imshow(res, 'gray'), plt.title('Fourier Image')
plt.show()
输出结果如图3所示,图3(a)为原始图像,图3(b)为频率分布图谱。
下面介绍Numpy实现傅里叶逆变换,它是傅里叶变换的逆操作,将频谱图像转换为原始图像的过程。通过傅里叶变换将转换为频谱图,并对高频(边界)和低频(细节)部分进行处理,接着需要通过傅里叶逆变换恢复为原始效果图。频域上对图像的处理会反映在逆变换图像上,从而更好地进行图像处理。
图像傅里叶变化主要使用的函数如下所示:
- numpy.fft.ifft2(a, n=None, axis=-1, norm=None)
实现图像逆傅里叶变换,返回一个复数数组 - numpy.fft.fftshift()
fftshit()函数的逆函数,它将频谱图像的中心低频部分移动至左上角 - iimg = numpy.abs(逆傅里叶变换结果)
将复数转换为0至255范围
下面的代码分别实现了傅里叶变换和傅里叶逆变换。
# -*- coding: utf-8 -*-
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
import matplotlib
#读取图像
img = cv.imread('Lena.png', 0)
#傅里叶变换
f = np.fft.fft2(img)
fshift = np.fft.fftshift(f)
res = np.log(np.abs(fshift))
#傅里叶逆变换
ishift = np.fft.ifftshift(fshift)
iimg = np.fft.ifft2(ishift)
iimg = np.abs(iimg)
#设置字体
matplotlib.rcParams['font.sans-serif']=['SimHei']
#展示结果
plt.subplot(131), plt.imshow(img, 'gray'), plt.title(u'(a)原始图像')
plt.axis('off')
plt.subplot(132), plt.imshow(res, 'gray'), plt.title(u'(b)傅里叶变换处理')
plt.axis('off')
plt.subplot(133), plt.imshow(iimg, 'gray'), plt.title(u'(c)傅里叶逆变换处理')
plt.axis('off')
plt.show()
输出结果如图4所示,从左至右分别为原始图像、频谱图像、逆傅里叶变换转换图像。
OpenCV 中相应的函数是cv2.dft()和用Numpy输出的结果一样,但是是双通道的。第一个通道是结果的实数部分,第二个通道是结果的虚数部分,并且输入图像要首先转换成 np.float32 格式。其函数原型如下所示:
- dst = cv2.dft(src, dst=None, flags=None, nonzeroRows=None)
– src表示输入图像,需要通过np.float32转换格式
– dst表示输出图像,包括输出大小和尺寸
– flags表示转换标记,其中DFT _INVERSE执行反向一维或二维转换,而不是默认的正向转换;DFT _SCALE表示缩放结果,由阵列元素的数量除以它;DFT _ROWS执行正向或反向变换输入矩阵的每个单独的行,该标志可以同时转换多个矢量,并可用于减少开销以执行3D和更高维度的转换等;DFT _COMPLEX_OUTPUT执行1D或2D实数组的正向转换,这是最快的选择,默认功能;DFT _REAL_OUTPUT执行一维或二维复数阵列的逆变换,结果通常是相同大小的复数数组,但如果输入数组具有共轭复数对称性,则输出为真实数组
– nonzeroRows表示当参数不为零时,函数假定只有nonzeroRows输入数组的第一行(未设置)或者只有输出数组的第一个(设置)包含非零,因此函数可以处理其余的行更有效率,并节省一些时间;这种技术对计算阵列互相关或使用DFT卷积非常有用
注意,由于输出的频谱结果是一个复数,需要调用cv2.magnitude()函数将傅里叶变换的双通道结果转换为0到255的范围。其函数原型如下:
- cv2.magnitude(x, y)
– x表示浮点型X坐标值,即实部
– y表示浮点型Y坐标值,即虚部
该函数最终输出结果为幅值,即:
下面的代码是调用cv2.dft()进行傅里叶变换的一个简单示例。
# -*- coding: utf-8 -*-
import numpy as np
import cv2
from matplotlib import pyplot as plt
import matplotlib
#读取图像
img = cv2.imread('Lena.png', 0)
#傅里叶变换
dft = cv2.dft(np.float32(img), flags = cv2.DFT_COMPLEX_OUTPUT)
#将频谱低频从左上角移动至中心位置
dft_shift = np.fft.fftshift(dft)
#频谱图像双通道复数转换为0-255区间
result = 20*np.log(cv2.magnitude(dft_shift[:,:,0], dft_shift[:,:,1]))
#设置字体
matplotlib.rcParams['font.sans-serif']=['SimHei']
#显示图像
plt.subplot(121), plt.imshow(img, cmap = 'gray')
plt.title(u'(a)原始图像'), plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(result, cmap = 'gray')
plt.title(u'(b)傅里叶变换处理'), plt.xticks([]), plt.yticks([])
plt.show()
输出结果如图5所示,图5(a)为原始“Lena”图,图5(b)为转换后的频谱图像,并且保证低频位于中心位置。
在OpenCV 中,通过函数cv2.idft()实现傅里叶逆变换,其返回结果取决于原始图像的类型和大小,原始图像可以为实数或复数。其函数原型如下所示:
- dst = cv2.idft(src[, dst[, flags[, nonzeroRows]]])
– src表示输入图像,包括实数或复数
– dst表示输出图像
– flags表示转换标记
– nonzeroRows表示要处理的dst行数,其余行的内容未定义(请参阅dft描述中的卷积示例)
注意,由于输出的频谱结果是一个复数,需要调用cv2.magnitude()函数将傅里叶变换的双通道结果转换为0到255的范围。其函数原型如下:
- cv2.magnitude(x, y)
– x表示浮点型X坐标值,即实部
– y表示浮点型Y坐标值,即虚部
该函数最终输出结果为幅值,即:
下面的代码是调用cv2.idft()进行傅里叶逆变换的一个简单示例。
# -*- coding: utf-8 -*-
import numpy as np
import cv2
from matplotlib import pyplot as plt
import matplotlib
#读取图像
img = cv2.imread('Lena.png', 0)
#傅里叶变换
dft = cv2.dft(np.float32(img), flags = cv2.DFT_COMPLEX_OUTPUT)
dftshift = np.fft.fftshift(dft)
res1= 20*np.log(cv2.magnitude(dftshift[:,:,0], dftshift[:,:,1]))
#傅里叶逆变换
ishift = np.fft.ifftshift(dftshift)
iimg = cv2.idft(ishift)
res2 = cv2.magnitude(iimg[:,:,0], iimg[:,:,1])
#设置字体
matplotlib.rcParams['font.sans-serif']=['SimHei']
#显示图像
plt.subplot(131), plt.imshow(img, 'gray'), plt.title(u'(a)原始图像')
plt.axis('off')
plt.subplot(132), plt.imshow(res1, 'gray'), plt.title(u'(b)傅里叶变换处理')
plt.axis('off')
plt.subplot(133), plt.imshow(res2, 'gray'), plt.title(u'(b)傅里叶变换逆处理')
plt.axis('off')
plt.show()
输出结果如图6所示,图6(a)为原始“Lena”图,图6(b)为傅里叶变换后的频谱图像,图6©为傅里叶逆变换,频谱图像转换为原始图像的过程。
傅里叶变换的目的并不是为了观察图像的频率分布(至少不是最终目的),更多情况下是为了对频率进行过滤,通过修改频率以达到图像增强、图像去噪、边缘检测、特征提取、压缩加密等目的。
过滤的方法一般有三种:低通(Low-pass)、高通(High-pass)、带通(Band-pass)。所谓低通就是保留图像中的低频成分,过滤高频成分,可以把过滤器想象成一张渔网,想要低通过滤器,就是将高频区域的信号全部拉黑,而低频区域全部保留。例如,在一幅大草原的图像中,低频对应着广袤且颜色趋于一致的草原,表示图像变换缓慢的灰度分量;高频对应着草原图像中的老虎等边缘信息,表示图像变换较快的灰度分量,由于灰度尖锐过度造成。
高通滤波器是指通过高频的滤波器,衰减低频而通过高频,常用于增强尖锐的细节,但会导致图像的对比度会降低。该滤波器将检测图像的某个区域,根据像素与周围像素的差值来提升像素的亮度。图7展示了“Lena”图对应的频谱图像,其中心区域为低频部分。
接着通过高通滤波器覆盖掉中心低频部分,将255两点变换为0,同时保留高频部分,高通滤波器处理过程如图8所示。
其中心黑色模板生成的核心代码如下:
- rows, cols = img.shape
- crow,ccol = int(rows/2), int(cols/2)
- fshift[crow-30:crow+30, ccol-30:ccol+30] = 0
通过高通滤波器将提取图像的边缘轮廓,生成如图9所示图像。
完整代码如下所示:
# -*- coding: utf-8 -*-
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
import matplotlib
#读取图像
img = cv.imread('Lena.png', 0)
#傅里叶变换
f = np.fft.fft2(img)
fshift = np.fft.fftshift(f)
#设置高通滤波器
rows, cols = img.shape
crow,ccol = int(rows/2), int(cols/2)
fshift[crow-30:crow+30, ccol-30:ccol+30] = 0
#傅里叶逆变换
ishift = np.fft.ifftshift(fshift)
iimg = np.fft.ifft2(ishift)
iimg = np.abs(iimg)
#设置字体
matplotlib.rcParams['font.sans-serif']=['SimHei']
#显示原始图像和高通滤波处理图像
plt.subplot(121), plt.imshow(img, 'gray'), plt.title(u'(a)原始图像')
plt.axis('off')
plt.subplot(122), plt.imshow(iimg, 'gray'), plt.title(u'(b)结果图像')
plt.axis('off')
plt.show()
输出结果如图10所示,图10(a)为原始“Na”图,图10(b)为高通滤波器提取的边缘轮廓图像。它通过傅里叶变换转换为频谱图像,再将中心的低频部分设置为0,再通过傅里叶逆变换转换为最终输出图像。
低通滤波器是指通过低频的滤波器,衰减高频而通过低频,常用于模糊图像。低通滤波器与高通滤波器相反,当一个像素与周围像素的插值小于一个特定值时,平滑该像素的亮度,常用于去燥和模糊化处理。如PS软件中的高斯模糊,就是常见的模糊滤波器之一,属于削弱高频信号的低通滤波器。
下图展示了“Lena”图对应的频谱图像,其中心区域为低频部分。如果构造低通滤波器,则将频谱图像中心低频部分保留,其他部分替换为黑色0,最终得到的效果图为模糊图像。
那么,如何构造该滤波图像呢?如图12所示,滤波图像是通过低通滤波器和频谱图像形成。其中低通滤波器中心区域为白色255,其他区域为黑色0。
低通滤波器主要通过矩阵设置构造,其核心代码如下:
- rows, cols = img.shape
- crow,ccol = int(rows/2), int(cols/2)
- mask = np.zeros((rows, cols, 2), np.uint8)
- mask[crow-30:crow+30, ccol-30:ccol+30] = 1
通过低通滤波器将模糊图像的完整代码如下所示:
# -*- coding: utf-8 -*-
import cv2
import numpy as np
from matplotlib import pyplot as plt
#读取图像
img = cv2.imread('Na.png', 0)
#傅里叶变换
dft = cv2.dft(np.float32(img), flags = cv2.DFT_COMPLEX_OUTPUT)
fshift = np.fft.fftshift(dft)
#设置低通滤波器
rows, cols = img.shape
crow,ccol = int(rows/2), int(cols/2) #中心位置
mask = np.zeros((rows, cols, 2), np.uint8)
mask[crow-30:crow+30, ccol-30:ccol+30] = 1
#掩膜图像和频谱图像乘积
f = fshift * mask
print(f.shape, fshift.shape, mask.shape)
#傅里叶逆变换
ishift = np.fft.ifftshift(f)
iimg = cv2.idft(ishift)
res = cv2.magnitude(iimg[:,:,0], iimg[:,:,1])
#显示原始图像和低通滤波处理图像
plt.subplot(121), plt.imshow(img, 'gray'), plt.title('Original Image')
plt.axis('off')
plt.subplot(122), plt.imshow(res, 'gray'), plt.title('Result Image')
plt.axis('off')
plt.show()
输出结果如图13所示,图13(a)为原始“Nana”图,图13(b)为低通滤波器模糊处理后的图像。
霍夫变换(Hough Transform)是一种特征检测(Feature Extraction),被广泛应用在图像分析、计算机视觉以及数位影像处理。霍夫变换是在1959年由气泡室(Bubble Chamber)照片的机器分析而发明,发明者Paul Hough在1962年获得美国专利。现在广泛使用的霍夫变换是由Richard Duda和Peter Hart在1972年发明,并称之为广义霍夫变换。经典的霍夫变换是检测图片中的直线,之后,霍夫变换不仅能识别直线,也能够识别任何形状,常见的有圆形、椭圆形。1981年,因为Dana H.Ballard的一篇期刊论文“Generalizing the Hough transform to detect arbitrary shapes”,让霍夫变换开始流行于计算机视觉界。
霍夫变换是一种特征提取技术,用来辨别找出物件中的特征,其目的是通过投票程序在特定类型的形状内找到对象的不完美实例。这个投票程序是在一个参数空间中进行的,在这个参数空间中,候选对象被当作所谓的累加器空间中的局部最大值来获得,累加器空间是由计算霍夫变换的算法明确地构建。霍夫变换主要优点是能容忍特征边界描述中的间隙,并且相对不受图像噪声的影响。
最基本的霍夫变换是从黑白图像中检测直线,它的算法流程大致如下:给定一个物件和要辨别的形状的种类,算法会在参数空间中执行投票来决定物体的形状,而这是由累加空间里的局部最大值来决定。假设存在直线公式如(4)所示,其中m表示斜率,b表示截距。
如果用参数空间表示,则直线为(b, m),但它存在一个问题,垂直线的斜率不存在(或无限大),使得斜率参数m值接近于无限。为此,为了更好的计算,Richard O. Duda和Peter E. Hart在1971年4月,提出了Hesse normal form(Hesse法线式),如公式(5)所示,它转换为直线的离散极坐标公式。
其中r是原点到直线上最近点的距离,θ是x轴与连接原点和最近点直线之间的夹角,如图15-14所示。
对于点(x0, y0),可以将通过这个点的一族直线统一定义为公式(6)。因此,可以将图像的每一条直线与一对参数(r,θ)相关联,相当于每一对(r0,θ)代表一条通过点的直线(x0, y0),其中这个参数(r,θ)平面被称为霍夫空间。
然而在实现的图像处理领域,图像的像素坐标P(x, y)是已知的,而(r,θ)是需要寻找的变量。如果能根据像素点坐标P(x, y)值绘制每个(r,θ)值,那么就从图像笛卡尔坐标系统转换到极坐标霍夫空间系统,这种从点到曲线的变换称为直线的霍夫变换。变换通过量化霍夫参数空间为有限个值间隔等分或者累加格子。当霍夫变换算法开始,每个像素坐标点P(x, y)被转换到(r,θ)的曲线点上面,累加到对应的格子数据点,当一个波峰出现时候,说明有直线存在。
如图15所示,三条正弦曲线在平面相交于一点,该点坐标(r0,θ)表示三个点组成的平面内的直线。这就是使用霍夫变换检测直线的过程,它追踪图像中每个点对应曲线间的交点,如果交于一点的曲线的数量超过了阈值,则认为该交点所代表的参数对(r0,θ)在原图像中为一条直线。
同样的原理,可以用来检测圆,对于圆的参数方程变为如下等式:
其中(a, b)为圆的中心点坐标,r圆的半径。这样霍夫参数空间就变成一个三维参数空间。给定圆半径转为二维霍夫参数空间,变换相对简单,也比较常用。
在OpenCV中,霍夫变换分为霍夫线变换和霍夫圆变换,其中霍夫线变换支持三种不同方法——标准霍夫变换、多尺度霍夫变换和累计概率霍夫变换。
- 标准霍夫变换主要有HoughLines()函数实现。
- 多尺度霍夫变换是标准霍夫变换在多尺度下的变换,可以通过HoughLines()函数实现。
- 累计概率霍夫变换是标准霍夫变换的改进,它能在一定范围内进行霍夫变换,计算单独线段的方向及范围,从而减少计算量,缩短计算时间,可以通过HoughLinesP()函数实现。
在OpenCV 中,通过函数HoughLines()检测直线,并且能够调用标准霍夫变换(SHT)和多尺度霍夫变换(MSHT),其函数原型如下所示:
- lines = HoughLines(image, rho, theta, threshold[, lines[, srn[, stn[, min_theta[, max_theta]]]]])
– image表示输入的二值图像
– lines表示经过霍夫变换检测到直线的输出矢量,每条直线为(r,θ)
– rho表示以像素为单位的累加器的距离精度
– theta表示以弧度为单位的累加器角度精度
– threshold表示累加平面的阈值参数,识别某部分为图中的一条直线时它在累加平面中必须达到的值,大于该值线段才能被检测返回
– srn表示多尺度霍夫变换中rho的除数距离,默认值为0。粗略的累加器进步尺寸为rho,而精确的累加器进步尺寸为rho/srn
– stn表示多尺度霍夫变换中距离精度theta的除数,默认值为0,。如果srn和stn同时为0,使用标准霍夫变换
– min_theta表示标准和多尺度的霍夫变换中检查线条的最小角度。必须介于0和max_theta之间
– max_theta表示标准和多尺度的霍夫变换中要检查线条的最大角度。必须介于min_theta和π之间
下面的代码是调用HoughLines()函数检测图像中的直线,并将所有的直线绘制于原图像中。
# -*- coding: utf-8 -*-
import cv2
import numpy as np
from matplotlib import pyplot as plt
#读取图像
gray = cv2.imread('judge.png', 0)
#灰度变换
#gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#转换为二值图像
edges = cv2.Canny(gray, 50, 150)
#显示原始图像
plt.subplot(121), plt.imshow(edges, 'gray'), plt.title('Input Image')
plt.axis('off')
#霍夫变换检测直线
lines = cv2.HoughLines(edges, 1, np.pi / 180, 160)
#转换为二维
line = lines[:, 0, :]
#将检测的线在极坐标中绘制
for rho,theta in line[:]:
a = np.cos(theta)
b = np.sin(theta)
x0 = a * rho
y0 = b * rho
print x0, y0
x1 = int(x0 + 1000 * (-b))
y1 = int(y0 + 1000 * (a))
x2 = int(x0 - 1000 * (-b))
y2 = int(y0 - 1000 * (a))
print x1, y1, x2, y2
#绘制直线
cv2.line(gray, (x1, y1), (x2, y2), (255, 0, 0), 2)
#显示处理图像
plt.subplot(122), plt.imshow(gray, 'gray'), plt.title('Result Image')
plt.axis('off')
plt.show()
输出结果如图16所示,第一幅图为原始图像,第二幅检测出的直线。
使用该方法检测大楼图像中的直线如图17所示,可以发现直线会存在越界的情况。
前面的标准霍夫变换会计算图像中的每一个点,计算量比较大,另外它得到的是整条线(r,θ),并不知道原图中直线的端点。接下来使用累计概率霍夫变换,它是一种改进的霍夫变换,调用HoughLinesP()函数实现。
- lines = HoughLinesP(image, rho, theta, threshold[, lines[, minLineLength[, maxLineGap]]])
– image表示输入的二值图像
– lines表示经过霍夫变换检测到直线的输出矢量,每条直线具有4个元素的矢量,即(x1, y1)和(x2, y2)是每个检测线段的端点
– rho表示以像素为单位的累加器的距离精度
– theta表示以弧度为单位的累加器角度精度
– threshold表示累加平面的阈值参数,识别某部分为图中的一条直线时它在累加平面中必须达到的值,大于该值线段才能被检测返回
– minLineLength表示最低线段的长度,比这个设定参数短的线段不能被显示出来,默认值为0
– maxLineGap表示允许将同一行点与点之间连接起来的最大距离,默认值0
下面的代码是调用HoughLinesP()函数检测图像中的直线,并将所有的直线绘制于原图像中。
# -*- coding: utf-8 -*-
import cv2
import numpy as np
from matplotlib import pyplot as plt
import matplotlib
#读取图像
img = cv2.imread('judge.png')
#灰度转换
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#转换为二值图像
edges = cv2.Canny(gray, 50, 200)
#显示原始图像
plt.subplot(121), plt.imshow(edges, 'gray'), plt.title(u'(a)原始图像')
plt.axis('off')
#霍夫变换检测直线
minLineLength = 60
maxLineGap = 10
lines = cv2.HoughLinesP(edges, 1, np.pi/180, 30, minLineLength, maxLineGap)
#绘制直线
lines1 = lines[:, 0, :]
for x1,y1,x2,y2 in lines1[:]:
cv2.line(img, (x1,y1), (x2,y2), (255,0,0), 2)
res = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
#设置字体
matplotlib.rcParams['font.sans-serif']=['SimHei']
#显示处理图像
plt.subplot(122), plt.imshow(res), plt.title(u'(b)结果图像')
plt.axis('off')
plt.show()
输出结果如图18所示,图18(a)为原始图像,图18(b)检测出的直线,它有效地提取了线段的起点和终点。
霍夫圆变换的原理与霍夫线变换很类似,只是将线的(r,θ)二维坐标提升为三维坐标,包括圆心点(x_center,y_center,r)和半径r,其数学形式如公式(7)。
从而一个圆的确定需要三个参数,通过三层循环实现,接着寻找参数空间累加器的最大(或者大于某一阈值)的值。随着数据量的增大,导致圆的检测将比直线更耗时,所以一般使用霍夫梯度法减少计算量。在OpenCV中,提供了cv2.HoughCircles()函数检测圆,其原型如下所示:
- circles = HoughCircles(image, method, dp, minDist[, circles[, param1[, param2[, minRadius[, maxRadius]]]]])
– image表示输入图像,8位灰度单通道图像
– circles表示经过霍夫变换检测到圆的输出矢量,每个矢量包括3个元素,即(x, y, radius)
– method表示检测方法,包括HOUGH_GRADIENT值
– dp表示用来检测圆心的累加器图像的分辨率于输入图像之比的倒数,允许创建一个比输入图像分辨率低的累加器
– minDist表示霍夫变换检测到的圆的圆心之间的最小距离
– param1表示参数method设置检测方法的对应参数,对当前唯一的方法霍夫梯度法CV_HOUGH_GRADIENT,它表示传递给Canny边缘检测算子的高阈值,而低阈值为高阈值的一半,默认值100
– param2表示参数method设置检测方法的对应参数,对当前唯一的方法霍夫梯度法CV_HOUGH_GRADIENT,它表示在检测阶段圆心的累加器阈值,它越小,将检测到更多根本不存在的圆;它越大,能通过检测的圆就更接近完美的圆形
– minRadius表示圆半径的最小值,默认值为0
– maxRadius表示圆半径的最大值,默认值0
下列代码是检测图像中的圆。
# -*- coding: utf-8 -*-
import cv2
import numpy as np
from matplotlib import pyplot as plt
#读取图像
img = cv2.imread('test01.png')
#灰度转换
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#显示原始图像
plt.subplot(121), plt.imshow(gray, 'gray'), plt.title('Input Image')
plt.axis('off')
#霍夫变换检测圆
#circles1 = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1, 100,
# param1=100, param2=30, minRadius=200, maxRadius=300)
circles1 = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1, 20, param2=30)
print(circles1)
#提取为二维
circles = circles1[0, :, :]
#四舍五入取整
circles = np.uint16(np.around(circles))
#绘制圆
for i in circles[:]:
cv2.circle(img, (i[0],i[1]), i[2], (255,0,0), 5) #画圆
cv2.circle(img, (i[0],i[1]), 2, (255,0,255), 10) #画圆心
#显示处理图像
plt.subplot(122), plt.imshow(img), plt.title('Result Image')
plt.axis('off')
plt.show()
输出结果如图19所示,图19(a)为原始图像,图图19(b)检测出的圆形,它有效地提取了圆形的圆心和轮廓。
使用下面的函数能有效提取人类眼睛的轮廓,核心函数如下:
circles1 = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1, 20,
param1=100, param2=30,
minRadius=160, maxRadius=300)
输出结果如图20所示,它提取了三条圆形接近于人体的眼睛。
图20中显示了三条曲线,通过不断优化最大半径和最小半径,比如将minRadius设置为160,maxRadius设置为200,将提取更为精准的人体眼睛,如图21所示。
最终代码如下:
# -*- coding: utf-8 -*-
import cv2
import numpy as np
from matplotlib import pyplot as plt
#读取图像
img = cv2.imread('eyes.png')
#灰度转换
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#显示原始图像
plt.subplot(121), plt.imshow(gray, 'gray'), plt.title('Input Image')
plt.axis('off')
#霍夫变换检测圆
circles1 = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1, 20,
param1=100, param2=30,
minRadius=160, maxRadius=200)
print(circles1)
#提取为二维
circles = circles1[0, :, :]
#四舍五入取整
circles = np.uint16(np.around(circles))
#绘制圆
for i in circles[:]:
cv2.circle(img, (i[0],i[1]), i[2], (255,0,0), 5) #画圆
cv2.circle(img, (i[0],i[1]), 2, (255,0,255), 8) #画圆心
#显示处理图像
plt.subplot(122), plt.imshow(img), plt.title('Result Image')
plt.axis('off')
plt.show()
本文主要讲解傅里叶变换和霍夫变换。傅里叶变换主要用来进行图像除噪、图像增强处理,通过Numpy和OpenCV两种方法分别进行叙述,并结合代码加深了读者的印象;霍夫变换主要用来辨别找出物件中的特征,包括提取图像中的直线和圆,调用cv2.HoughLines()、cv2.HoughLinesP()和cv2.HoughCircles()实现。这些知识在物联网中也经常应用,比如无人驾驶、车道识别、医学图像处理、工业控制识别等。
感恩能与大家在华为云遇见!
希望能与大家一起在华为云社区共同成长。原文地址:https://blog.csdn.net/Eastmount/article/details/81461679
【拜托了,物联网!】有奖征文火热进行中:https://bbs.huaweicloud.com/blogs/296704
(By:娜璋之家 Eastmount 2021-10-03 夜于武汉)
参考文献,在此感谢这些大佬,共勉!
- [1]冈萨雷斯著. 数字图像处理(第3版)[M]. 北京:电子工业出版社,2013.
- [2]阮秋琦. 数字图像处理学(第3版)[M]. 北京:电子工业出版社,2008.
- [3]毛星云,冷雪飞. OpenCV3编程入门[M]. 北京:电子工业出版社,2015.
- [4]张铮,王艳平,薛桂香等. 数字图像处理与机器视觉——Visual C++与Matlab实现[M]. 北京:人民邮电出版社,2014.
- [5]百度百科. 傅里叶变换[EB/OL]. (2019.02.05). https://baike.baidu.com/item/傅里叶变换/7119029.
- [6]网易云课堂_高登教育. Python+OpenCV图像处理[EB/OL]. (2019-01-15). https://study.163.com/course/courseLearn.htm?courseId=1005317018.
- [7]安安zoe. 图像的傅里叶变换[EB/OL]. (2018-02-01). https://www.jianshu.com/p/89ce7fdb9e12.
- [8]daduzimama. 图像的傅里叶变换的迷思----频谱居中[EB/OL]. (2018-06-07). https://blog.csdn.net/daduzimama/article/details/80597454.
- [9]tenderwx. [数字图像处理] 傅里叶变换在图像处理中的应用[EB/OL]. (2016-03-05). https://www.cnblogs.com/tenderwx/p/5245859.html.
- [10]小小猫钓小小鱼. 深入浅出的讲解傅里叶变换(真正的通俗易懂)[EB/OL]. (2018-02-02).
- [11]https://www.cnblogs.com/h2zZhou/p/8405717.html.
- [12]百度百科. 霍夫变换[EB/OL]. (2018-11-21). https://baike.baidu.com/item/霍夫变换/4647236.
- [13]yuyuntan. 经典霍夫变换(Hough Transform)[EB/OL]. (2018-04-29). https://blog.csdn.net/yuyuntan/article/details/80141392.
- [14]g20040733. 霍夫变换[EB/OL]. (2016-12-07). https://blog.csdn.net/g200407331/article/details/53507784.
- [15]我i智能. Python下opencv使用笔记(十一)(详解hough变换检测直线与圆)[EB/OL]. (2015-07-23). https://blog.csdn.net/on2way/article/details/47028969.
- [16]ex2tron. Python+OpenCV教程17:霍夫变换[EB/OL]. (2018-01-10). https://www.jianshu.com/p/34d6dc466e81.
- [17]Daetalus. OpenCV-Python教程(9、使用霍夫变换检测直线)[EB/OL]. (2013-07-12). https://blog.csdn.net/sunny2038/article/details/9253823.
- 点赞
- 收藏
- 关注作者
评论(0)