图像处理技术是计算机视觉项目的核心,通常是计算机视觉项目中的关键工具,可以使用它们来完成各种计算机视觉任务。因此,如果要构建计算机视觉项目,就需要对图像处理有足够的了解。图像运算也是图像处理技术的一种,在本文中,将介绍可以对图像执行的常见算术运算,例如按位运算、加减法、形态变换等。
饱和运算( saturated operation
)是一种算术运算,其通过限制运算可以采用的最大值和最小值将运算限制在固定范围内。例如,对图像的某些操作(例如插值等)可能会产生超出可用范围的值,使用饱和运算就可以解决这个问题。
例如,要存储图像 img,它是对 8 位图像(值范围为 0 到 255 )执行特定操作的结果,则饱和运算计算公式如下:
r e s u l t ( x , y ) = m i n ( m a x ( r o u n d ( i m g ) , 0 ) , 255 ) result(x,y)=min(max(round(img),0),255) result(x,y)=min(max(round(img),0),255)
通过以下简单示例以更好的理解:
x = np.uint8([250])
y = np.uint8([50])
# OpenCV中加法:250+50 = 300 => 255:
result_opencv = cv2.add(x, y)
print("cv2.add(x:'{}' , y:'{}') = '{}'".format(x, y, result_opencv))
# Numpy中加法:250+50 = 300 % 256 = 44:
result_numpy = x + y
print("x:'{}' + y:'{}' = '{}'".format(x, y, result_numpy))
在 OpenCV
中,这些值会被裁剪至 [0, 255]
范围内,这种运算就称为饱和操作。而在 NumPy
中,值是环绕的,这也称为模运算。
图像加法和减法可以分别使用 cv2.add()
和 cv2.subtract()
函数执行。这些函数两个数组执行逐元素求和/相减,也可用于对数组和标量求和/相减。例如,对图像的所有像素添加 60,首先要构建图像以添加到原始图像:
M = np.ones(image.shape, dtype="uint8") * 60
然后,使用以下代码执行加法或减法:
added_image = cv2.add(image, M)
subtracted_image = cv2.subtract(image, M)
也可以创建一个标量并将其添加到原始图像中。例如,要给图像的所有像素加上 110,首先构建标量:
scalar = np.ones((1, 3), dtype="float") * 110
然后,使用以下代码执行加法或减法:
added_image_2 = cv2.add(image, scalar)
subtracted_image_2 = cv2.subtract(image, scalar)
代码的执行结果如下图所示:
自上图中,可以清楚地看到加减预定义值的效果。
图像混合也是图像相加的一种,只是其可以赋予相加以图像不同的权重,可以得到类似透明的效果,可以使用 cv2.addWeighted()
函数进行图像混合。
接下来,结合 Sobel
算子来观察 cv2.addWeighted()
函数效果。
Sobel 算子用于边缘检测,它创建一个检测到图中边缘的图像。 Sobel 算子使用两个 3 × 3 核,它们与原始图像卷积以计算导数的近似值,捕获水平和垂直梯度:
# 输出深度设置为CV_16S,以避免溢出
# CV_16S是由2字节有符号整数(16位有符号整数)组成的通道
gradient_x = cv2.Sobel(gray_image, cv2.CV_16S, 1, 0, 3)
gradient_y = cv2.Sobel(gray_image, cv2.CV_16S, 0, 1, 3)
在计算出水平和垂直梯度后,可以使用函数 cv2.addWeighted()
将它们混合成图像,如下所示:
abs_gradient_x = cv2.convertScaleAbs(gradient_x)
abs_gradient_y = cv2.convertScaleAbs(gradient_y)
# 使用相同的权重混合两个图像
sobel_image = cv2.addWeighted(abs_gradient_x, 0.5, abs_gradient_y, 0.5, 0)
最后绘制图像:
def show_with_matplotlib(color_img, title, pos):
img_RGB = color_img[:, :, ::-1]
ax = plt.subplot(1, 4, pos)
plt.imshow(img_RGB)
plt.title(title, fontsize=8)
plt.axis('off')
plt.figure(figsize=(10, 4))
plt.suptitle("Sobel operator and cv2.addWeighted() to show the output", fontsize=14, fontweight='bold')
show_with_matplotlib(image, "Image", 1)
show_with_matplotlib(cv2.cvtColor(abs_gradient_x, cv2.COLOR_GRAY2BGR), "Gradient x", 2)
show_with_matplotlib(cv2.cvtColor(abs_gradient_y, cv2.COLOR_GRAY2BGR), "Gradient y", 3)
show_with_matplotlib(cv2.cvtColor(sobel_image, cv2.COLOR_GRAY2BGR), "Sobel output", 4)
# Show the Figure:
plt.show()
代码的运行结果如下图所示:
OpenCV
中包含一些操作可以使用按位运算符在位级别执行,这些按位运算很简单,计算速度很快,因此,它们也是处理图像时的有用工具。
按位运算包括 AND
、OR
、NOT
和 XOR
。
为了进行演示按位运算,我们首先创建一些图像:
img_1 = np.zeros((300, 300), dtype='uint8')
cv2.rectangle(img_1, (10, 10), (110, 110), (255, 255, 255), -1)
cv2.circle(img_1, (200, 200), 50, (255, 255, 255), -1)
img_2 = np.zeros((300, 300), dtype='uint8')
cv2.rectangle(img_2, (50, 50), (150, 150), (255, 255, 255), -1)
cv2.circle(img_2, (225, 200), 50, (255, 255, 255), -1)
image = cv2.imread('sigonghuiye.jpeg')
image = cv2.resize(image,(300, 300))
img_3 = np.zeros((300, 300), dtype="uint8")
cv2.circle(img_3, (150, 150), 150, (255, 255, 255), -1)
然后对所创建的图像执行按位运算:
# OR
bitwise_or = cv2.bitwise_or(img_1, img_2)
# AND
bitwise_and = cv2.bitwise_and(img_1, img_2)
# XOR
bitwise_xor = cv2.bitwise_xor(img_1, img_2)
# NOT
bitwise_not_1 = cv2.bitwise_not(img_1)
bitwise_not_2 = cv2.bitwise_not(img_2)
# AND with mask
bitwise_and_example = cv2.bitwise_and(image, image, mask=img_3)
显示运算结果:
接下来,我们使用真实图像,进一步使用按位运算,需要注意的是,加载的真实图像应该具有相同的形状:
image = cv2.imread('8.png')
binary_image = cv2.imread('250.png')
image = image[250:500,170:420]
bitwise_and = cv2.bitwise_and(image, binary_image)
bitwise_or = cv2.bitwise_or(image, binary_image)
bitwise_xor = cv2.bitwise_xor(image, binary_image)
形态变换( Morphological transformations
)通常是在二值图像上执行、基于图像形状的操作。其具体的操作由核结构元素决定,它决定了操作的性质。膨胀和腐蚀是形态变换领域的两个基本算子。此外,开运算和闭运算是两个重要的运算,它们可以通过上述两个运算(膨胀和腐蚀)获得。最后,还有其他三个常用的变换操作,是基于之前的一些操作的变体或结合。
二值图像的膨胀运算的主要作用是逐渐扩大前景对象的边界区域。这意味着前景对象的区域会变大,而这些区域内的孔会缩小:
dilation = cv2.dilate(image, kernel, iterations=1)
腐蚀操作对二值图像的主要作用是逐渐侵蚀掉前景对象的边界区域。这意味着前景对象的区域会变小,而这些区域内的空洞会变大:
erosion = cv2.erode(image, kernel, iterations=1)
接下来,使用膨胀运算与腐蚀运算:
image_names = ['test1.png', 'test2.png', 'test3.png']
path = 'morpho_test_imgs'
kernel_size_3_3 = (3, 3)
kernel_size_5_5 = (5, 5)
def load_all_test_images():
test_morph_images = []
for index_image, name_image in enumerate(image_names):
image_path = os.path.join(path, name_image)
test_morph_images.append(cv2.imread(image_path))
return test_morph_images
def show_with_matplotlib(color_img, title, pos):
img_RGB = color_img[:, :, ::-1]
ax = plt.subplot(3, 3, pos)
plt.imshow(img_RGB)
plt.title(title, fontsize=8)
plt.axis('off')
def erode(image, kernel_type, kernel_size):
kernel = build_kernel(kernel_type, kernel_size)
erosion = cv2.erode(image, kernel, iterations=1)
return erosion
def dilate(image, kernel_type, kernel_size):
kernel = build_kernel(kernel_type, kernel_size)
dilation = cv2.dilate(image, kernel, iterations=1)
return dilation
test_images = load_all_test_images()
for index_image,image in enumerate(test_images):
show_with_matplotlib(image, 'test img_{}'.format(index_image + 1), index_image * 3 + 1)
img_1 = erode(image, cv2.MORPH_RECT, (3,3))
show_with_matplotlib(img_1, 'erode_{}'.format(index_image + 1), index_image * 3 + 2)
img_2 = dilate(image, cv2.MORPH_RECT, (3,3))
show_with_matplotlib(img_2, 'dilate_{}'.format(index_image + 1), index_image * 3 + 3)
开运算先执行腐蚀,然后使用相同的结构元素(或核)进行膨胀。通过这种方式,可以应用腐蚀来消除一小组不需要的像素(例如,椒盐噪声)。
腐蚀会不分青红皂白地影响图像的所有区域。通过在腐蚀后执行扩张操作,可以减少腐蚀过度的一些影响:
opening = cv2.morphologyEx(image, cv2.MORPH_OPEN, kernel)
闭预算同样也可以从腐蚀和膨胀操作中推导出来,该操作先执行膨胀,然后执行腐蚀。膨胀操作通常用于填充图像中的小孔。然而,膨胀操作也会使一小群噪声像素变大。通过在膨胀后对图像应用腐蚀操作,将减少膨胀带来的这种影响:
closing = cv2.morphologyEx(image, cv2.MORPH_CLOSE, kernel)
接下来,实际使用开运算与闭运算:
# build_kernel() 和 show_with_matplotlib() 函数与4.1中相同
def closing(image, kernel_type, kernel_size):
kernel = build_kernel(kernel_type, kernel_size)
clos = cv2.morphologyEx(image, cv2.MORPH_CLOSE, kernel)
return clos
def opening(image, kernel_type, kernel_size):
kernel = build_kernel(kernel_type, kernel_size)
ope = cv2.morphologyEx(image, cv2.MORPH_OPEN, kernel)
return ope
for index_image,image in enumerate(test_images):
show_with_matplotlib(image, 'test img_{}'.format(index_image + 1), index_image * 3 + 1)
img_1 = closing(image, cv2.MORPH_RECT, (3,3))
show_with_matplotlib(img_1, 'closing_{}'.format(index_image + 1), index_image * 3 + 2)
img_2 = opening(image, cv2.MORPH_RECT, (3,3))
show_with_matplotlib(img_2, 'opening_{}'.format(index_image + 1), index_image * 3 + 3)
plt.show()
形态梯度运算定义为输入图像的膨胀和腐蚀之间的差异:
morph_gradient = cv2.morphologyEx(image, cv2.MORPH_GRADIENT, kernel)
形态梯度运算的用法:
# build_kernel() 函数与4.1中相同
def show_with_matplotlib(color_img, title, pos):
img_RGB = color_img[:, :, ::-1]
ax = plt.subplot(2, 3, pos)
plt.imshow(img_RGB)
plt.title(title, fontsize=8)
plt.axis('off')
def morphological_gradient(image, kernel_type, kernel_size):
kernel = build_kernel(kernel_type, kernel_size)
morph_gradient = cv2.morphologyEx(image, cv2.MORPH_GRADIENT, kernel)
return morph_gradient
for index_image,image in enumerate(test_images):
print(index_image)
show_with_matplotlib(image, 'test img_{}'.format(index_image + 1), index_image + 1)
img = morphological_gradient(image, cv2.MORPH_RECT, (3,3))
show_with_matplotlib(img, 'gradient_{}'.format(index_image + 1), index_image + 4)
顶帽运算被定义为输入图像和图像开运算之间的差:
top_hat = cv2.morphologyEx(image, cv2.MORPH_TOPHAT, kernel)
黑帽操作被定义为输入图像和输入图像闭运算的差:
black_hat = cv2.morphologyEx(image, cv2.MORPH_BLACKHAT, kernel)
顶帽运算与低帽运算的用法:
# build_kernel() 和 show_with_matplotlib() 函数与4.1中相同
def black_hat(image, kernel_type, kernel_size):
kernel = build_kernel(kernel_type, kernel_size)
black = cv2.morphologyEx(image, cv2.MORPH_BLACKHAT, kernel)
return black
def opening_and_closing(image, kernel_type, kernel_size):
opening_img = opening(image, kernel_type, kernel_size)
closing_img = closing(opening_img, kernel_type, kernel_size)
return closing_img
for index_image,image in enumerate(test_images):
show_with_matplotlib(image, 'test img_{}'.format(index_image + 1), index_image * 3 + 1)
img_1 = top_hat(image, cv2.MORPH_RECT, (3,3))
show_with_matplotlib(img_1, 'top_hat_{}'.format(index_image + 1), index_image * 3 + 2)
img_2 = black_hat(image, cv2.MORPH_RECT, (3,3))
show_with_matplotlib(img_2, 'black_hat_{}'.format(index_image + 1), index_image * 3 + 3)
plt.show()
OpenCV
提供了 cv2.getStructuringElement()
函数用于构造结构元素。此函数输出所需的核( uint8
类型的 NumPy
数组),该函数接收两个参数——核的形状和大小。 OpenCV
中提供了以下三种核形状:
核形状 | 说明 |
---|---|
cv2.MORPH_RECT | 矩形核 |
cv2.MORPH_ELLIPSE | 椭圆核 |
cv2.MORPH_CROSS | 十字形核 |
可以使用不同的核大小和形状、形态变换和图像。测试不同的核形状和大小。例如,下图是使用核大小 (3, 3
) 和矩形核 (cv2.MORPH_RECT
) 时的输出:
使用核大小 (5, 5
) 和矩形核 (cv2.MORPH_RECT
) 时的输出:
使用核大小 (3, 3
) 和十字形核 (cv2.MORPH_CROSS
) 时的输出:
使用核大小 (5, 5
) 和十字形核 (cv2.MORPH_CROSS
) 时的输出:
在预处理图像时,形态学操作是一种非常有用的技术,可以使用形态学操作消除一些干扰图像正确处理的噪声,或者处理图像结构中的缺陷。
图像运算也是一种常见的图像处理技术,在本文中,介绍了可用于 对图像执行的常见算术运算,例如按位运算、加减法、形态变换等。
OpenCV-Python实战(1)——OpenCV简介与图像处理基础(内含大量示例,建议收藏)
OpenCV-Python实战(2)——图像与视频文件的处理(两万字详解,️建议收藏)
OpenCV-Python实战(3)——OpenCV中绘制图形与文本(万字总结,️建议收藏)
OpenCV-Python实战(4)——OpenCV常见图像处理技术(❤️万字长文,含大量示例❤️)
OpenCV-Python实战(6)——OpenCV中的色彩空间和色彩映射(❤️万字长文,含大量示例❤️)
OpenCV-Python实战(7)——直方图详解(❤️含大量示例,建议收藏❤️)