OpenCV-Python实战(5)——OpenCV图像运算

0. 前言

图像处理技术是计算机视觉项目的核心,通常是计算机视觉项目中的关键工具,可用于完成各种计算机视觉任务。因此,如果你想构建一个计算机视觉项目,你需要对图像处理有一个很好的了解。图像运算也是一种图像处理技术。本文将介绍可对图像进行的常见算术运算,如按位运算、加减法、形态变换等。

1. 饱和运算

饱和运算(saturated operation)是一种算术运算,其通过限制运算可以采用的最大值和最小值将运算限制在固定范围内。例如,对图像的某些操作(例如插值等)可能会产生超出可用范围的值,使用饱和运算就可以解决这个问题。
例如,要存储图像 img,它是对 8 位图像(值范围为 0 到 255 )执行特定操作的结果,则饱和运算计算公式如下:
result%28x%2Cy%29%3Dmin%28max%28round%28img%29%2C0%29%2C255%29
通过以下简单示例更好地理解:

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中,值是环绕的,这也称为模运算。

2. 图像加减法与图像混合

图像加法和减法可以分别使用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()

运行代码的结果如下图所示:

图像混合

3. 按位运算

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)

运行代码的结果如下,可以看到按位运算后生成的图像:
按位运算

4. 形态变换

形态变换(Morphological transformations)通常是在二值图像上执行、基于图像形状的操作。其具体的操作由核结构元素决定,它决定了操作的性质。膨胀和腐蚀是形态变换领域的两个基本算子。此外,开运算和闭运算是两个重要的运算,它们可以通过上述两个运算(膨胀和腐蚀)获得。最后,还有其他三个常用的变换操作,是基于之前的一些操作的变体或结合。

4.1 膨胀运算与腐蚀运算

二值图像的膨胀操作的主要作用是逐渐扩大前景物体的边界区域。这意味着前景物体的区域会变大,而这些区域内的洞会缩小:

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)

膨胀运算与腐蚀运算

4.2 开运算与闭运算

开运算先执行腐蚀,然后使用相同的结构元素(或核心)执行膨胀。通过这种方式,可以应用侵蚀来去除一小组不需要的像素(例如,椒盐噪声)。
腐蚀不加选择地影响图像的所有区域。通过在蚀刻后执行膨胀操作可以减少过度蚀刻的一些影响:

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()

开运算与闭运算

4.3 形态梯度运算

形态梯度操作定义为输入图像的膨胀和腐蚀之间的差异:

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)

形态梯度运算

4.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()

顶帽运算与低帽(黑帽)运算

4.5 结构元素

OpenCV提供了cv2.getStructuringElement()函数用于构造结构元素。此函数输出所需的核(uint8类型的NumPy数组),该函数接收两个参数——核的形状和大小。OpenCV中提供了以下三种核形状:

核形状 说明
cv2.MORPH_RECT 矩形核
cv2.MORPH_ELLIPSE 椭圆核
cv2.MORPH_CROSS 十字形核

4.6 应用形态变换

可以使用不同的核大小和形状、形态变换和图像。测试不同的核形状和大小。例如,下图是使用核大小 (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)——直方图详解
OpenCV-Python实战(8)——直方图均衡化
OpenCV-Python实战(9)——OpenCV用于图像分割的阈值技术
OpenCV-Python实战(10)——OpenCV轮廓检测
OpenCV-Python实战(11)——OpenCV轮廓检测相关应用
OpenCV-Python实战(12)——一文详解AR增强现实
OpenCV-Python实战(13)——OpenCV与机器学习的碰撞
OpenCV-Python实战(14)——人脸检测详解

版权声明:本文为博主盼小辉丶原创文章,版权归属原作者,如果侵权,请联系我们删除!

原文链接:https://blog.csdn.net/LOVEmy134611/article/details/120069198

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
扎眼的阳光的头像扎眼的阳光普通用户
上一篇 2022年2月18日 下午1:21
下一篇 2022年2月18日 下午1:45

相关推荐