本文主要是根据《OpenCV 3 计算机视觉 Python语言实现》第3章 使用OpenCV 3 处理图像 和 冈萨雷斯《数字图像处理(第三版)》10.2 点、线和边缘检测 两部分,针对自己薄弱的基础,结合网上资源,进行整理的个人笔记。
傅里叶变换:所有的波形都可以由一系列简单且频率不同的正弦曲线叠加得到。
对一个时域信号进行傅里叶变换,就可以得到的信号的频谱,信号的频谱由两部分构成:幅度谱和相位谱
幅度谱:把一幅图像中最明亮的像素放在图像中央,然后逐渐变暗,在边缘上的像素最暗。
从频谱图上可以看出,当将频谱移频到原点以后,图像中心比较亮。在频谱图中,一个点的亮暗主要与包含这个频率的数目有关,也就是说在空间域中包含这种频率的点越多,频谱图中对应的频率的位置越亮。而经过频移后,频率为0的部分,也就是傅里叶变换所得到的常量分量在图像中心,由内往外扩散,点所代表的频率越来越高。
相关:滤波器模版移过图像并计算每个位置乘积之和的处理
一个函数与离散单位冲激相关,在该冲激位置产生这个函数的一个翻转的版本
卷积:滤波器首先旋转180°,再移过图像并计算每个位置乘积之和的处理
一个函数与离散单位冲激卷积,在该冲激位置产生这个函数的一个拷贝的版本
如果滤波器模版是对称的,相关和卷积将得到相同的结果。
用Matlab fft2与fftshift 绘制频谱图,得不到与链接1相同的结果。所以对频谱图的概念还是不理解。以后如再遇到,再仔细研究这一问题。
平时其实所谓的卷积,一直是用相关来理解的,这是错误的,所以在这特地提一下。
之所以没发现,是因为用到的滤波器模版都是对称的,不影响结果。
以后如再遇到频谱图的问题,再回来看链接1。
本章图片出自链接2。链接2和3足以理解相关与卷积。
一阶导数通常在图像中产生较粗的边缘
二阶导数对精细细节,如细线、孤立点和噪声有较强的响应
二阶导数在灰度斜坡和灰度台阶过渡处会产生双边缘响应
二阶导数的符号可用于确定边缘的过渡是从亮到暗还是从暗到亮
拉普拉斯模版,响应绝对值超过指定阈值即为孤立点。
注:对于一个导数模版,系数之和为零表明在恒定灰度区模版响应将是零。
拉普拉斯模版
边缘检测是基于灰度突变来分割图像的最常用的方法。
实际中,数字图像都存在被模糊且带有噪声的边缘,模糊的程度主要取决于聚焦机理(如光学成像中的镜头)中的限制,且噪声水平主要取决于成像系统的电子元件。
零灰度轴和二阶导数极值间的连线的交点称为该二阶导数的零交叉点,可用于定位粗边缘的中心。
微弱的可见噪声对检测边缘所用的两个关键导数有严重的影响,所以图像的平滑处理是检测之前必须仔细考虑的问题。
图像梯度及其性质:
图像坐标系统的原点位于左上角,正x轴向下延伸,正y轴向右延伸。
任意点处一个边缘的方向与该点处梯度向量的方向正交。
梯度算子:
用于计算梯度偏导数的滤波器模版,通常称为梯度算子、差分算子、边缘算子或边缘检测子。
罗伯特交叉梯度算子(Roberts)、Prewitt算子、Sobel算子、Laplace算子、Scharr滤波器
Sobel模版能较好地抑制(平滑)噪声
注:各梯度算子和滤波器在这就不单独介绍了,定义及性质可以见本章相关链接1,2,4.
用绝对值来近似梯度的幅值:M=|gx|+|gy|
Marr-Hildreth边缘检测器(LoG滤波器)算法步骤:
1.使用高斯滤波器平滑图像,并计算其拉普拉斯算子
2.寻找步骤2所得图像的零交叉
坎尼边缘检测器(Canny)算法步骤:
1. 用一个高斯滤波器平滑输入图像
2. 计算梯度幅值图像和角度图像
3. 对梯度幅值图像应用非最大限制
本程序主要参考链接2.
除去Scharr滤波器,3.5 提到所有的梯度算子都有仿真。
Scharr滤波器与Sobel算子相似,就不单独列出了。
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 读入图像并灰度化
image = cv2.imread("1.jpg", cv2.IMREAD_UNCHANGED)
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 自定义卷积核
# Roberts边缘算子
kernel_Roberts_x = np.array([
[1, 0],
[0, -1]
])
kernel_Roberts_y = np.array([
[0, -1],
[1, 0]
])
# Sobel边缘算子
kernel_Sobel_x = np.array([
[-1, 0, 1],
[-2, 0, 2],
[-1, 0, 1]])
kernel_Sobel_y = np.array([
[1, 2, 1],
[0, 0, 0],
[-1, -2, -1]])
# Prewitt边缘算子
kernel_Prewitt_x = np.array([
[-1, 0, 1],
[-1, 0, 1],
[-1, 0, 1]])
kernel_Prewitt_y = np.array([
[1, 1, 1],
[0, 0, 0],
[-1, -1, -1]])
# 拉普拉斯卷积核
kernel_Laplacian_1 = np.array([
[0, 1, 0],
[1, -4, 1],
[0, 1, 0]])
kernel_Laplacian_2 = np.array([
[1, 1, 1],
[1, -8, 1],
[1, 1, 1]])
#下面两个卷积核不具有旋转不变性
kernel_Laplacian_3 = np.array([
[2, -1, 2],
[-1, -4, -1],
[2, 1, 2]])
kernel_Laplacian_4 = np.array([
[-1, 2, -1],
[2, -4, 2],
[-1, 2, -1]])
# 5*5 LOG卷积模板
kernel_LoG = np.array([
[0, 0, -1, 0, 0],
[0, -1, -2, -1, 0],
[-1, -2, 16, -2, -1],
[0, -1, -2, -1, 0],
[0, 0, -1, 0, 0]])
# Canny边缘检测 k为高斯核大小,t1,t2为阈值大小
def Canny(image, k, t1, t2):
img = cv2.GaussianBlur(image, (k, k), 0)
canny = cv2.Canny(img, t1, t2)
return canny
# 卷积
output_1 = cv2.filter2D(image, -1, kernel_Roberts_x)
output_2 = cv2.filter2D(image, -1, kernel_Sobel_x)
output_3 = cv2.filter2D(image, -1, kernel_Prewitt_x)
output_4 = cv2.filter2D(image, -1, kernel_Laplacian_1)
output_5 = cv2.filter2D(image, -1, kernel_LoG)
output_6 = Canny(image,3,50,150)
# 显示处理后的图像
plt.figure("Original Image") # 图像窗口名称
plt.imshow(image,cmap='gray') # 显示灰度图要加cmap
plt.axis('off') # 关掉坐标轴为 off
plt.title('Original Image') # 图像题目
plt.show()
plt.figure("Multi Image") # 图像窗口名称
plt.suptitle('Multi_Image') # 图片名称
plt.subplot(2,3,1), plt.title('Roberts')
plt.imshow(output_1, cmap='gray'), plt.axis('off')
plt.subplot(2,3,2), plt.title('Sobel')
plt.imshow(output_2, cmap='gray'), plt.axis('off')
plt.subplot(2,3,3), plt.title('Prewitt')
plt.imshow(output_3, cmap='gray'), plt.axis('off')
plt.subplot(2,3,4), plt.title('Laplacian')
plt.imshow(output_4, cmap='gray'), plt.axis('off')
plt.subplot(2,3,5), plt.title('LOG')
plt.imshow(output_5, cmap='gray'), plt.axis('off')
plt.subplot(2,3,6), plt.title('Canny')
plt.imshow(output_6, cmap='gray'), plt.axis('off')
plt.show()
# 等待按键并销毁窗口
cv2.waitKey()
cv2.destroyAllWindows()
代码思路:
1. 根据输入的标准差确定LoG窗口的大小,半径为3 * sigma占99.7%,所以窗口大小为2*向上取整(sigma)+1。
参考本章链接3.
2. 在(-size/2+1, size/2)内生成步进为1的x和y.
3. 在对应的网格上计算对应的LoG系数。
4. 应用LoG核,经处理后的数据存储于log
5. 找到log内满足零交叉的元素
6. 输出结果
此代码参考本章相关链接4,但是其LoG核系数有点问题。更改后的代码如下。
import numpy as np
import matplotlib.pyplot as plt
import cv2
def edgesMarrHildreth(img, sigma):
"""
finds the edges using MarrHildreth edge detection method...
:param im : input image
:param sigma : sigma is the std-deviation and refers to the spread of gaussian
:return:
a binary edge image...
"""
# 根据输入的高斯标准差确定窗口大小,3 * sigma占99.7%
size = int(2 * (np.ceil(3 * sigma)) + 1)
# 生成(-size / 2 + 1, size / 2 )的网格
x, y = np.meshgrid(np.arange(-size / 2 + 1, size / 2 + 1), np.arange(-size / 2 + 1, size / 2 + 1))
# 计算LoG核
kernel = ((x ** 2 + y ** 2 - (2.0 * sigma ** 2)) / sigma ** 4) * np.exp(
-(x ** 2 + y ** 2) / (2.0 * sigma ** 2)) # LoG filter
kern_size = kernel.shape[0]
# 生成与输入图像相同大小的全零矩阵log
log = np.zeros_like(img, dtype=float)
# 应用LoG核
for i in range(img.shape[0] - (kern_size - 1)):
for j in range(img.shape[1] - (kern_size - 1)):
window = img[i:i + kern_size, j:j + kern_size] * kernel
log[i, j] = np.sum(window)
# 将log由float转换为int64
log = log.astype(np.int64, copy=False)
# 生成与log相同大小的全零矩阵zero_crossing
zero_crossing = np.zeros_like(log)
# 判断零交叉点
for i in range(log.shape[0] - (kern_size - 1)):
for j in range(log.shape[1] - (kern_size - 1)):
if log[i][j] == 0:
if (log[i][j - 1] < 0 and log[i][j + 1] > 0) or (log[i][j - 1] < 0 and log[i][j + 1] < 0) or (
log[i - 1][j] < 0 and log[i + 1][j] > 0) or (log[i - 1][j] > 0 and log[i + 1][j] < 0):
zero_crossing[i][j] = 255
if log[i][j] < 0:
if (log[i][j - 1] > 0) or (log[i][j + 1] > 0) or (log[i - 1][j] > 0) or (log[i + 1][j] > 0):
zero_crossing[i][j] = 255
# 绘制输出图像
fig = plt.figure()
a = fig.add_subplot(1, 2, 1)
plt.imshow(log, cmap='gray')
plt.axis('off')
a.set_title('Laplacian of Gaussian')
a = fig.add_subplot(1, 2, 2)
plt.imshow(zero_crossing, cmap='gray')
plt.axis('off')
string = 'Zero Crossing sigma = '
string += (str(sigma))
a.set_title(string)
plt.show()
return zero_crossing
img = cv2.imread('1.jpg',0)
zero_crossing = edgesMarrHildreth(img,4)
参考本章链接5.
1. 用高斯滤波器对图像进行去噪。
2. 计算梯度。
3. 在边缘上使用非最大抑制(NMS)。
4. 在检测到的边缘上使用双阈值去除假阳性。
5. 分析所有的边缘及其之间的连接。
Canny边缘检测算法在这就先不详细解释了。
参考本章链接1,2.
在原始图像坐标系下的一个点对应了参数坐标系中的一条直线,同样参数坐标系的一条直线对应了原始坐标系下的一个点,然后,原始坐标系下呈现直线的所有点,它们的斜率和截距是相同的,所以它们在参数坐标系下对应于同一个点。这样在将原始坐标系下的各个点投影到参数坐标系下之后,看参数坐标系下有没有聚集点,这样的聚集点就对应了原始坐标系下的直线。
个人理解:
r=xcosθ+ysinθ在θ—r平面相交,能确定θ和r,即可得到在x—y平面的表达式,即确定直线表达式。
Hough变换检测圆和椭圆并未理解。
把《OpenCV 3 计算机视觉 Python语言实现》第3章 使用OpenCV 3 处理图像 的内容巩固了一遍。
但是还是有不足,比如Canny没有详解,Hough变换理解也很不到位。
暂时就这样吧。
个人水平有限,有问题欢迎各位大神批评指正!