目录
①cv2.filter2D(image, cv2.CV_32F, kernel_x) 中 cv2.CV_32F 表示32 位浮点数类型
作用:选择归一化的计算方式。NORM_MINMAX 是最常用的方式之一,它的逻辑是:
一.边缘检测
1.边缘检测概念
边缘检测:识别图像中亮度或颜色发生显著变化的区域(即边缘)。这些边缘对应着物体的边界、表面法线的突然改变或光照条件的变化。边缘构成了物体的轮廓,有助于快速定位和分离图像中的不同物体。
2.不同算子简介
Sobel算子:
Sobel算子是一种基于梯度的边缘检测方法。它使用两个3x3的卷积核,一个用于检测水平边缘,另一个用于检测垂直边缘。这两个卷积核分别计算图像中每个像素的水平和垂直梯度。然后,通过组合这两个梯度,可以获得每个像素的梯度幅值和方向。梯度值大的像素通常表示边缘。
Laplacian算子:
Laplacian算子是一种二阶导数算子,用于检测图像中的边缘。它对图像进行二次卷积,以查找像素值的变化率。Laplacian算子通常会增强边缘的轮廓,但它不提供边缘的方向信息。因此,它常常需要与其他方法一起使用,如Sobel算子,以获得更精确的边缘信息。
Canny边缘检测:
Canny边缘检测是一种多步边缘检测算法,它具有良好的边缘定位和噪声抑制能力。它的步骤包括:
a. 高斯滤波:首先,对图像进行高斯平滑以减少噪声。
b. 梯度计算:使用Sobel算子计算图像的梯度幅值和方向。
c. 非极大值抑制:在梯度方向上,只保留局部梯度最大的像素,以细化边缘。
d. 滞后阈值:应用双阈值,将像素分类为强边缘、弱边缘和非边缘。
e. 边缘跟踪:通过连接强边缘像素,形成连续的边缘。
二.Robert算子
图像可看作二维函数 f(x,y)((x,y) 是坐标,f 是灰度值 )
1.概念
Robert 算子是最早的一种边缘检测算子,它的核心思想是用交叉差分(Cross Difference)来近似图像中的梯度变化,从而检测出灰度突变的位置(即边缘)。将卷积结果对应到图像中卷积核左上角对齐的像素位置 ,来表示该位置的梯度。常用来处理具有陡峭的低噪声图像,当图像边缘接近于正45度或负45度时,该算法处理效果更理想。其缺点是对边缘的定位不太准确,提取的边缘线条较粗。
2.整体算法思想
- Robert 算子使用两个2×2的卷积核,分别检测水平和垂直方向的边缘
- 将两个卷积核分别与图像进行卷积,得到水平梯度
和垂直梯度
卷积后突出水平方向的灰度变化(如从左上到右下的边缘)。
卷积后突出垂直方向的灰度变化(如从右上到左下的边缘)。
- 将两个方向的梯度合成,得到边缘强度(梯度幅值)公式:Gradient Magnitude=
- 二值化(可选):将梯度幅值与阈值比较,大于阈值的点视为边缘(通常设为白色,值为 255),小于阈值的点视为非边缘(通常设为黑色,值为 0)。
3.计算步骤
(1)梯度计算:
Robert算子通过计算图像中灰度值的局部变化来估计梯度。边缘通常表示为灰度值的突变,因此,边缘检测算法的核心是计算图像中灰度变化的大小和方向。
Robert算子使用两个简单的卷积核(或掩模)来计算图像在水平和垂直方向上的梯度。这两个卷积核分别是
、
就是 Robert 算子的卷积核,它们是 预先定义好的小矩阵 ,作用是和图像做卷积运算,用来 “探测” 图像里水平、垂直方向的灰度变化。
- 比如
这个核,会和图像每个像素周围区域做乘法 + 求和,突出水平方向的灰度差异;
则突出垂直方向的差异。
梯度幅值:用上面这两个卷积核分别和图像卷积后,会得到水平方向梯度 、垂直方向梯度
。但边缘检测要看 “变化有多明显”,所以需要把这两个方向的梯度 合成一个 “强度值” ,也就是梯度幅值(常见计算方式:
,或者简化为
)
(2)边缘检测:
- 根据梯度幅值进行边缘检测。通常会应用一个阈值,将幅值高于阈值的像素标记为边缘像素,从而得到二值化的边缘图像。
4.Robert算子 代码1
代码1(用公式)
import cv2
import numpy as np
# 读取图像(以灰度模式读取)
image = cv2.imread('example.jpg', cv2.IMREAD_GRAYSCALE)
# 检查图像是否成功加载
if image is None:
print("无法读取图像,请检查文件路径!")
else:
# 定义Robert算子的卷积核
kernel_x = np.array([[1, 0], [0, -1]], dtype=np.float32) # 水平方向
kernel_y = np.array([[0, 1], [-1, 0]], dtype=np.float32) # 垂直方向
# 应用卷积核
grad_x = cv2.filter2D(image, cv2.CV_32F, kernel_x)
grad_y = cv2.filter2D(image, cv2.CV_32F, kernel_y)
# 计算梯度幅值(边缘强度)对矩阵每个元素进行 平方和开根号运算
grad_magnitude = cv2.magnitude(grad_x, grad_y)
#4. 转换为8位图像(便于显示)
grad_magnitude_8u = np.uint8(grad_magnitude) # 直接截断转成uint8(可能丢失信息)
# 先不要管这个
# 更好的方法:归一化到0-255范围
# grad_magnitude_8u = cv2.normalize(grad_magnitude, None, 0, 255, cv2.NORM_MINMAX, dtype=cv2.CV_8U)
# 显示原始图像和处理后的边缘图像
cv2.imshow('Original Image', image)
cv2.imshow('Robert Edge Detection', grad_magnitude_8u)
# 等待按键关闭窗口
cv2.waitKey(0)
cv2.destroyAllWindows()
细节解释:
①cv2.filter2D(image, cv2.CV_32F, kernel_x)
中 cv2.CV_32F 表示
32 位浮点数类型
cv2.CV_32F
是 OpenCV 里用来 指定图像数据类型 的标识,代表 “32 位浮点数类型(32 - bit float)” 。如果这里用-1代替cv2.CV_32F,
则输出与输入图像image类型相同,默认是uint8,而cv2.magnitude参数类型通常为float32
或float64,
则会导致参数类型错误。
像边缘检测、卷积运算这类操作,计算过程中可能产生小数(比如梯度值、卷积核加权和),用 float
类型(32 位浮点)存储能保留这些小数,避免截断误差。如果用 uint8
(8 位整数),计算时小数会直接丢失,导致结果错误或精度下降。
②uint8类型解释
它是 “无符号 8 位整数类型” 的简称,占用 1 个字节(8 个二进制位) ,能表示的数值范围是 0 ~ 255(因为 8 位二进制最大是 11111111
,对应十进制 255 )。
③uint8截断的缺点
grad_magnitude_8u = np.uint8(grad_magnitude) # 直接截断转成uint8(可能丢失信息)
不管浮点数原本的范围,直接把小数部分砍掉(取整),然后把整数部分硬塞进 uint8
的表示范围(0 - 255)。一旦整数部分超过 255,就会 “取模 256”(或理解为 “循环截断” ),导致数值失真。假设 grad_magnitude
里有个像素值是 258.5
,它的整数部分是 258
。但 uint8
最大只能存 255
,直接截断时,会用 258 % 256 = 2
(取模运算),最终这个值就变成 2
存入 uint8
变量。
④cv2.normalize 等比例归化函数
grad_magnitude_8u = cv2.normalize(
grad_magnitude, # 输入:要处理的矩阵(浮点型梯度幅值)
None, # 输出:默认填 None,让函数自动创建输出矩阵
0, 255, # 归一化目标范围:把数据缩放到 0~255
cv2.NORM_MINMAX, # 归一化方式:按最小-最大值缩放
dtype=cv2.CV_8U # 输出数据类型:转成 8 位无符号整数(uint8)
)
作用:选择归一化的计算方式。NORM_MINMAX
是最常用的方式之一,它的逻辑是:
NORM_MINMAX
是最常用的方式之一,它的逻辑是:先找到输入矩阵的最小值(min)和最大值(max)。然后把每个元素x
按公式缩放:这样就能把原始数据均匀 “挤压” 到 0~255 范围,保留数据的相对差异。
举例:
6.Robert算子 卷积核的设计理念
在图像处理中,边缘方向定义为灰度变化最剧烈的方向(如从黑到白的过渡方向),而梯度方向则是与边缘方向垂直的方向(指向灰度增加最快的方向)。
水平梯度核的目标是检测垂直边缘(即边缘方向为水平,如 | )。此时灰度变化发生在水平方向(如左黑右白),梯度方向为水平(从左向右)。
Robert 算子使用两个 2×2 的卷积核,分别检测图像中不同方向上的灰度变化。这两个卷积核的设计是为了近似计算图像在 45° 和 135° 方向上的一阶导数。通过计算这两个方向上的灰度差值(即梯度),可以判断图像中是否存在边缘。如果在某个位置处,这两个方向上的灰度变化较大,那么该位置就可能存在边缘。
简洁Robert算子代码1:
(用公式)
import cv2
import numpy as np
import matplotlib.pyplot as plt
img=cv2.imread('Y:\\pycharm\\lion.png',cv2.IMREAD_GRAYSCALE)
if img is None:
print("无法读取")
else:
kernel_x=np.array([[1,0],[0,-1]],dtype=np.float32)
kernel_y=np.array([[0,-1],[1,0]],dtype=np.float32)
grad_x=cv2.filter2D(img,cv2.CV_32F,kernel_x)
grad_y=cv2.filter2D(img,cv2.CV_32F,kernel_y)
grad_magnitude=cv2.magnitude(grad_x,grad_y)
grad_magnitude_uint8=np.uint8(grad_magnitude)
plt.subplot(121),plt.imshow(img,cmap='gray')
plt.title('Origin'),plt.xticks([]),plt.yticks([])
plt.subplot(122),plt.imshow( grad_magnitude_uint8,cmap='gray')
plt.title('new'),plt.xticks([]),plt.yticks([])
plt.show()
dst = cv2.addWeighted(src1, alpha, src2, beta, gamma)函数
参数 | 含义 |
---|---|
src1 |
第一个输入数组(如图像),必须与 src2 大小和通道数相同。 |
alpha |
src1 的权重系数,取值范围通常为 [0, 1] 。例如:- 0.5 表示保留 50% 的 src1 。 |
src2 |
第二个输入数组,与 src1 类型相同。 |
beta |
src2 的权重系数,取值范围通常为 [0, 1] 。例如:- 0.5 表示保留 50% 的 src2 。 |
gamma |
加到每个总和的标量值(亮度调整)。例如: 示不对加权求和后的结果进行额外的亮度调整 |
简洁Robert算子代码2:
用公式
import cv2
import numpy as np
import matplotlib.pyplot as plt
img=cv2.imread('Y:\\pycharm\\lion.png',cv2.IMREAD_GRAYSCALE)
if img is None:
print("无法读取")
else:
kernel_x=np.array([[1,0],[0,-1]],dtype=np.float32)
kernel_y=np.array([[0,-1],[1,0]],dtype=np.float32)
grad_x=cv2.filter2D(img,cv2.CV_32F,kernel_x)
grad_y=cv2.filter2D(img,cv2.CV_32F,kernel_y)
grad_x=cv2.convertScaleAbs(grad_x)
grad_y=cv2.convertScaleAbs(grad_y)
grad_xy=cv2.addWeighted(grad_x,1,grad_y,1,0)
plt.subplot(121),plt.imshow(img,cmap='gray')
plt.title('Origin'),plt.xticks([]),plt.yticks([])
plt.subplot(122),plt.imshow(grad_xy,cmap='gray')
plt.title('new'),plt.xticks([]),plt.yticks([])
plt.show()
三.Sobel算子
1.概念
Sobel 算子是一个主要用作边缘检测的离散微分算子 (discrete differentiation operator)。 它Sobel算子结合了高斯平滑和微分求导,用来计算图像灰度函数的近似梯度。
2.计算
水平变化: 将 I 与一个奇数大小的内核进行卷积。比如,当内核大小为3时,
的计算结果为:
垂直变化: 将: I 与一个奇数大小的内核进行卷积。比如,当内核大小为3时,
的计算结果为:
在图像的每一点,结合以上两个结果求出近似梯度:
另外有时,也可用下面更简单公式代替:
4.代码
用公式
网上的例程模版:
import cv2
import numpy as np
# 读取图像
zhaoyun = cv2.imread('zhaoyun1.jpg',0)
# Sobel边缘检测
zhaoyun_x_64 = cv2.Sobel(zhaoyun,cv2.CV_64F,dx=1,dy=0)# 水平方向
zhaoyun_x_full = cv2.convertScaleAbs(zhaoyun_x_64) #取绝对值并转为
zhaoyun_y_64 = cv2.Sobel(zhaoyun,cv2.CV_64F,dx=0,dy=1)# 垂直方向
zhaoyun_y_full = cv2.convertScaleAbs(zhaoyun_y_64)
zhaoyun_xy_sobel_full = cv2.addWeighted(zhaoyun_x_full,1,zhaoyun_y_full,1,0)
cv2.imshow('zhaoyun',zhaoyun)
cv2.imshow('zhaoyun_xy_sobel_full',zhaoyun_xy_sobel_full)
cv2.waitKey(0)
soble函数
sobel算子
cv2.Sobel(src, ddepth, dx, dy[, ksize[, scale[, delta[, borderType]]]])
参数:
src:输入图像
ddepth: 输出图像的深度(可以理解为数据类型),-1表示与原图像相同的深度
这里填CV_32F,CV_64F都行
dx,dy:当组合为dx=1,dy=0时求x方向的一阶导数,
当组合为dx=0,dy=1时求y方向的一阶导数(如果同时为1,通常效果不佳)
ksize:(可选参数)Sobel算子的大小,必须是1,3,5或者7,默认为3。
简洁Sobel算子代码:
import cv2
import numpy as np
import matplotlib.pyplot as plt
img=cv2.imread('Y:\\pycharm\\lion.png',cv2.IMREAD_GRAYSCALE)
if img is None:
print("无法读取")
else:
soblex=cv2.Sobel(img,cv2.CV_64F,1,0,ksize=3)
soblex_full=cv2.convertScaleAbs(soblex)
sobley=cv2.Sobel(img,cv2.CV_64F,0,1,ksize=3)
sobley_full=cv2.convertScaleAbs(sobley)
new=cv2.addWeighted(soblex_full,1,sobley_full,1,0)
plt.subplot(121),plt.imshow(img,cmap='gray')
plt.title('origin'),plt.xticks([]),plt.yticks([])
plt.subplot(122), plt.imshow(new, cmap='gray')
plt.title('new'), plt.xticks([]), plt.yticks([])
plt.show()
5.Sobel算子的模拟实现
mySobel函数模拟实现了cv2.Sobel函数的逻辑
# sobel模拟实现
import cv2
import numpy as np
import matplotlib.pyplot as plt
#sobel模拟函数
def mySobel(img, kernel, height, width):
#储存卷积操作后新图的列表
mySobel=np.zeros((height,width),dtype=np.float32)
# 零填充法
arr = np.zeros((height + 2, width + 2), dtype=np.float32)
for i in range(height):
for j in range(width):
arr[i + 1, j + 1] = img[i, j]
#卷积操作
for i in range(1, height + 1):
for j in range(1, width + 1):
mySobel[i-1][j-1]=(#这里格式务必从新行开始
arr[i - 1][j - 1] * kernel[0][0]
+ arr[i - 1][j] * kernel[0][1]
+arr[i - 1][j + 1] * kernel[0][2]
+arr[i][j - 1] * kernel[1][0]
+arr[i][j] * kernel[1][1]
+arr[i][j + 1] * kernel[1][2]
+arr[i + 1][j - 1] * kernel[2][0]
+arr[i + 1][j] * kernel[2][1]
+arr[i + 1][j + 1] * kernel[2][2]
)
return mySobel
img = cv2.imread('Y:\\pycharm\\lion.png', cv2.IMREAD_GRAYSCALE)
height, width = img.shape[:2]
if img is None:
print("无法读取")
else:
kernel_x = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]], dtype=np.float32)
kernel_y = np.array([[-1, -2, -1], [0, 0, 0], [1, 2, 1]], dtype=np.float32)
sobel_x = mySobel(img, kernel_x, height, width)
sobel_x_full = cv2.convertScaleAbs(sobel_x)
sobel_y = mySobel(img, kernel_y, height, width)
sobel_y_full = cv2.convertScaleAbs(sobel_y)
new = cv2.addWeighted(sobel_x_full, 1, sobel_y_full, 1, 0)
plt.subplot(121), plt.imshow(img, cmap='gray')
plt.title('origin'), plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(new, cmap='gray')
plt.title('new'), plt.xticks([]), plt.yticks([])
plt.show()
结果:
6.均值滤波的模拟实现
与上面sobel模拟实现差不多,都是设置相应的卷积核,然后都执行卷积操作,这里的myfilter实际就是上面的mySobel。
import cv2
import numpy as np
import matplotlib.pyplot as plt
def add_peppersalt_noise(img,n=10000):
result=img.copy()
h,w=img.shape[:2]
for i in range(n):
x=np.random.randint(1,w)
y=np.random.randint(1,h)
if np.random.randint(0,2)==0:
result[x,y]=0
else:
result[x,y]=255
return result
def myfilter(img,kernel,height,width):
dst = np.zeros((height, width), dtype=np.float32)
# 零填充法
arr = np.zeros((height + 2, width + 2), dtype=np.float32)
for i in range(height):
for j in range(width):
arr[i + 1, j + 1] = img[i, j]
# 卷积操作
for i in range(1, height + 1):
for j in range(1, width + 1):
dst[i - 1][j - 1] = ( # 这里格式务必从新行开始
arr[i - 1][j - 1] * kernel[0][0]
+ arr[i - 1][j] * kernel[0][1]
+ arr[i - 1][j + 1] * kernel[0][2]
+ arr[i][j - 1] * kernel[1][0]
+ arr[i][j] * kernel[1][1]
+ arr[i][j + 1] * kernel[1][2]
+ arr[i + 1][j - 1] * kernel[2][0]
+ arr[i + 1][j] * kernel[2][1]
+ arr[i + 1][j + 1] * kernel[2][2]
)
return dst
img=cv2.imread('Y:\\pycharm\\lion.png',cv2.IMREAD_GRAYSCALE)
height, width = img.shape[:2]
if img is None:
print("无法读取图像")
else:
img1 = add_peppersalt_noise(img)
kernel=np.ones((3,3),dtype=np.float32)/25
dst=myfilter(img1,kernel,height,width)
plt.subplot(121),plt.imshow(img1,cmap='gray')
plt.title("origin"),plt.xticks([]),plt.yticks([])
plt.subplot(122), plt.imshow(dst, cmap='gray')
plt.title("dst"), plt.xticks([]), plt.yticks([])
plt.show()