Python图像处理【2】探索Python图像处理库

0. 前言

和多数编程任务类似,在编写图像处理应用程序时,我们无需重复“造轮子”的过程,利用 Python 强大且丰富的第三方库能够大量缩减应用程序的编写时间,达到事半功倍的效果。在本节中,我们将学习使用不同的 Python 库实现一些常见的图像处理、变换和可视化技术,这些技术通常可以用作更复杂的图像处理任务的基本预处理/后处理步骤。

1. 利用 scikit-image 绘制图像

scikit-image 是基于 scipy 的图像处理库,它将图片作为 Numpy 数组进行处理。在本小节中,我们将介绍如何在图像中添加随机噪声添加到图像(具有不同的方差 sigma σ \sigma σ )以创建带有噪声的图像,然后创建图像蒙太奇效果。

(1) 首先导入所需的库,并使用 imread() 函数读取输入 RGB 图像:

from skimage.io import imread
from skimage.util import random_noise, montage
import matplotlib.pyplot as plt
import numpy as np
im = imread("1.png")

(2) 使用 scikit-image 模块的函数 random_noise(),通过添加具有给定方差的高斯随机噪声,利用输入图像创建带有噪声的图像。根据 scikit-image 文档所示,函数 random_noise() 函数的用法如下:

skimage.util.random_noise(image, mode='gaussian', seed=None, clip=True, **kwargs)

使用以上函数通过向输入图像添加具有不同方差的随机高斯噪声来生成噪声图像。

(3) 使用 NumPy 函数 np.linspace()01 (按值的递增顺序)生成 9 个不同的 σ \sigma σ 值:

# 在 0 到 1 区间内创建 包含9 个点的等差序列,作为标准差
sigmas = np.linspace(0, 1, 9)
noisy_images = np.zeros((9, im.shape[0], im.shape[1], im.shape[2]))
for i in range(len(sigmas)):
    # 用不同的 Sigma 值在图像中添加高斯随机噪声
    noisy_images[i,:,:,:] = random_noise(im, var=sigmas[i]**2)

(4) 使用 scikit image-util 模块的函数 montage() 创建含噪图像的蒙太奇效果图像。montage() 函数接受有噪图像 ndarray 作为输入参数,并在网格中显示图像。scikit-image 函数 montage() 的调用方法如下,利用该函数可以创建多个单通道或多通道图像的蒙太奇效果图像:

noisy_images_montage = montage(noisy_images, rescale_intensity=True, multichannel=True) # 创建蒙太奇

(5) 最后,绘制噪声图像的蒙太奇效果图像:

plt.figure(figsize=(15,15))
plt.imshow(noisy_images_montage)
plt.title('Noisy montage', size=15)
plt.axis('off')
plt.show()

图像蒙太奇效果

2. 使用 SciPy 模块裁剪/调整图像大小

调整图像大小/裁剪图像是一项常见且重要的预处理步骤,例如,深度学习模型需要将输入图像调整为同一大小。在本小节中,我们将学习如何使用 scipy.ndimage 模块的 zoom() 函数缩放图像,然后介绍如何使用 Numpy ndarray 切片语法裁剪图像。

(1) 首先,从 Python 库模块导入所需的函数:

from scipy import ndimage
import matplotlib.pyplot as plt
from skimage.io import imread

(2) 读取输入图像并使用 scipy.ndimage.zoom() 函数缩放图像。根据 SciPy 文档所述,zoom() 函数的调用方式如下:

scipy.ndimage.zoom(input, zoom, output=None, order=3, mode='constant', cval=0.0, prefilter=True)

zoom() 函数默认使用样条插值缩放阵列,在函数中通过指定 “nearest” 模式执行样条插值,样条插值将通过复制最近的像素来扩展输入图像。我们可以分别指定每个轴的缩放因子,由于我们不需要在颜色通道上缩放,所以通道上的缩放因子指定为 1,而宽度和高度维度的缩放因子指定为 2,样条插值的阶 (order),默认为 3,阶是 [0, 5] 范围内的整数:

im = imread('1.png') / 255
zoomed_im = ndimage.zoom(im, (2,2,1), mode='nearest', order=1) # 缩放图像
print(im.shape, zoomed_im.shape)

(3) 最后,使用 NumPy ndarray 切片语法通过裁剪图像显示原始图像和缩放后的图像:

plt.figure(figsize=(20,10))
plt.subplot(121)
plt.imshow(im)
plt.title('Original Image', size=25)
plt.subplot(122)
plt.imshow(zoomed_im[125:325,375:550,:]) # 裁剪图像
plt.title('Zoomed and Cropped Image', size=25)
plt.show()

裁剪/缩放图像

3. 使用 OpenCV 绘制轮廓

3.1 轮廓简介

轮廓可以简单地理解为连接图像中对象所有边界连续点的曲线,轮廓通常具有相同的颜色或强度。轮廓是形状分析、物体检测等应用中的重要工具。为了得到更好的轮廓精度,可以使用二值图像检测对象轮廓。因此,在检测图像轮廓之前,通常需要对图像应用阈值处理或 Canny 边缘检测算法。
OpenCV 中,可以使用 cv2.threshold() 函数使用固定阈值创建二值图像:

cv2.threshold(img, thresh, maxval, type)

使用 Canny 算法检测图像边缘:

Canny(img, threshold1, threshold2, apertureSize = 3, L2gradient = false)

Canny() 函数将输入灰度图像和两个滞后阈值(最大阈值和最小阈值)作为输入,其核心思想在于,当像素值>最大阈值时,其被视为强边缘像素,而在最小阈值和最大阈值之间的像素值被认为是弱边缘像素。强边将包含在边缘图中,而弱边仅当它们连接到强边时才会包含在边缘图中,通常,上阈值的大小为下阈值的 1.5–2 倍。
使用 findContours() 函数可以计算灰度图像的轮廓。首先,需要使用以上两个函数将图像转换为二值图像,然后计算图像轮廓。此外,需要注意的是,使用 OpenCV 函数查找轮廓需要从黑色背景中查找白色对象,要检测的对象是白色的,背景是黑色的。findContours() 函数接受三个参数,用于查找二值图像中的轮廓:

cv2.findContours(img, mode, method)

3.2 绘制轮廓

(1) 首先,导入所需的库。使用 OpenCVimread() 函数从磁盘读取输入图像,OpenCVBGR 格式存储 RGB 图像,如果想要使用 matplotlib.pyplot 正确显示,我们需要使用 cv2.cvtClor() 函数将其转换为 RGB 格式,然后绘制图像:

import cv2
import numpy as np
import matplotlib.pyplot as plt

image = cv2.imread("1.png")
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # 将 BGR 模式转换为 RGB

(2) 将图像转换为灰度图像,并使用具有合适滞后阈值的 Canny 边缘检测器来查找图像中的边缘,然后从二值边缘图像中检测轮廓:

gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
edged = cv2.Canny(gray, 125, 250)
contours_edged, _ = cv2.findContours(edged, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
print("Number of Contours found with Canny edges = " + str(len(contours_edged)))

或者使用阈值函数将灰度图像转换为二值图像,然后根据阈值图像中查找轮廓:

ret, thresh = cv2.threshold(gray, 127, 255, 0)
contours_thresh, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
print("Number of Contours found with threshold = " + str(len(contours_thresh)))

(3) 使用 matplotlib.pyplot 模块的 imshow() 函数显示原始图像、阈值图像和轮廓图像:

plt.figure(figsize=(20,15))
plt.subplot(221), plt.imshow(image), plt.title('Original Image', size=10), plt.axis('off')
plt.subplot(222), plt.imshow(thresh, cmap='gray'), plt.title('Threshold Image', size=10), plt.axis('off')
plt.subplot(223), plt.imshow(edged, cmap='gray'), plt.title('Canny Edges Image', size=10), plt.axis('off')
plt.subplot(224), plt.imshow(cv2.drawContours(np.copy(image), contours_thresh, -1, (0,255,0), 3))
plt.title('Contour Lines with Threshold Image', size=10), plt.axis('off')

轮廓图像
(4) 最后,从边缘二值图像中绘制前 n=200 个轮廓:

n = 200
plt.figure(figsize=(7,7))
colors = plt.cm.coolwarm(np.linspace(0, 1, n))
for i in range(n):
    image = cv2.drawContours(image, contours_edged, i, 255*colors[i], 3)
plt.imshow(image)
plt.title('First ' + str(n) + ' Contour lines with Canny Edges', size=10), plt.axis('off')
plt.tight_layout()
plt.show()

前200个轮廓

4. 使用 OpenCV 统计图像中的对象数量

接下来,我们学习使用 OpenCVfindContours() 函数来统计图像中的对象数,我们以从输入图像中计算孟加拉语字母数量为例。

(1) 导入所需的库后,将图像转换为灰度图像,并使用 Canny 边缘检测器:

import cv2
import numpy as np
import matplotlib.pylab as plt

image = cv2.imread('2.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
edged = cv2.Canny(gray, 75, 150)

(2) 使用形态学闭运算函数 morphologyEx() 删除图像中的小孔,改进检测质量:

thresh = cv2.threshold(gray, 215, 255, cv2.THRESH_BINARY_INV)[1]
kernel = np.ones((2,2),np.uint8)
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)

(3) 最后,使用 findContours() 函数查找所有对象轮廓,并使用 drawContours() 函数迭代绘制这些轮廓:

cnts, _ = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
output = image.copy()
for c in cnts:
    cv2.drawContours(output, [c], -1, (0, 0, 255), 2) 

(4) 绘制原始图像、二值图像和输出图像:

text = "Found {} objects".format(len(cnts))
cv2.putText(output, text, (50, 220),  cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)
plt.figure(figsize=(20,7))
plt.subplot(131), plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)), plt.axis('off'), plt.title('Original image', size=10)
plt.subplot(132), plt.imshow(thresh, cmap='gray'), plt.axis('off'), plt.title('Binary image', size=10)
plt.subplot(133), plt.imshow(cv2.cvtColor(output, cv2.COLOR_BGR2RGB)), plt.axis('off'), plt.title('Counting objects', size=10)
plt.show()

请添加图片描述

5. 使用 PIL 将彩色图像转换为灰度图像

在本小节中,我们将学习 PIL 图像模式以及如何将带有调色板的 PNG 彩色图像转换为灰度图像。要完成图像转换任务,最简单的方法是使用 convert() 函数将图像的色彩模式从彩色图像转换为灰度图像。

5.1 使用 convert() 函数

(1) 首先,导入所需的库,并使用 PIL 图像类的方法将图像从磁盘读取到图像对象,使用 matplotlib 绘制原始 PNG 图像:

import numpy as np
from PIL import Image
import matplotlib.pyplot as plt

img = Image.open('1.png')
print(img.mode)

plt.imshow(img)
plt.axis('off')
plt.title('Original Image')
plt.show()

原始图像
(2) 接下来,在 image 对象上,需要调用方法 convert() 转换图像色彩模式,将其转换为灰度图像,然后绘制输出图像:

img = img.convert('RGB').convert('L')
print(img.mode)
plt.imshow(img, cmap='gray')
plt.axis('off')
plt.title('Grayscale Image')
plt.show()

输出灰度图像

5.2 将带有调色板的 PNG 图像转换为灰度图像

还有另一种使用 PIL 库将图像转换为灰度图像的方法,相较于上一种方法,这是一种更复杂的方式。但使用这种方法可以令我们明确理解图像如何以 PIL 格式进行存储。

(1) 首先导入所有所需的库,并定义函数 rgb2gray() 以从 RGB 彩色图像计算灰度图像强度值,读取 PNG 图像并提取图像调色板:

import numpy as np
from PIL import Image
import matplotlib.pyplot as plt

def rgb2gray(R, G, B):
    return 0.2989 * R + 0.5870 * G + 0.1140 * B

img = Image.open('2.png')   # 读取 png 图像
pal = img.getpalette()      # 获取调色板

(2) 使用 PIL image 类的 getpixel() 方法计算图像中每个像素值的调色板索引。使用计算的索引从图像调色板中获取每个像素的 RGB 值。最后,使用自定义 rgb2gray() 函数(或使用 scikit-image.color 模块中的相同函数)计算与图像中相同位置对应的输出像素的灰度强度值:

arr = np.zeros((img.height, img.width))     # 初始化输出图像
for i in range(arr.shape[0]):
    for j in range(arr.shape[1]):
        idx = img.getpixel((j,i))           # 获取调色板中像素的索引
        R, G, B = pal[3*idx], pal[3*idx+1], pal[3*idx+2] # 获取像素 R,G,B 值
        arr[i,j] = rgb2gray(R, G, B)        # 转换为灰度图像

(3) 绘制输出图像:

plt.subplot(121)
plt.imshow(img)
plt.title('Origial Image')
plt.axis('off')
plt.subplot(122)
plt.imshow(arr, cmap='gray')
plt.title('Grayscale Image')
plt.axis('off')
plt.show()

输出图像

6. 使用 SciPy 旋转图像

有时,我们可能需要几何变换作为图像处理任务中的预处理步骤。在本小节中,我们将学习如何使用 scipy.ndimage 模块中的 rotate() 函数旋转图像。接下来,我们将逆时针旋转图像一定的角度,并使用样条插值。

(1) 从相应的 Python 库模块导入所需的函数,读取彩色输入图像:

from scipy.ndimage import rotate
from skimage.io import imread
from matplotlib import pyplot as plt
im = imread('1.png')

(2) 应用 SciPy 库的 rotate() 函数,ndimage 模块根据输入图像与旋转值执行旋转变换,逆时针旋转按照惯例应当以正角度表示,而顺时针则以负角度表示:

im = rotate(im, -45)
plt.figure(figsize=(5,5))
plt.imshow(im)
plt.axis('off') 
plt.show()

输出图像

小结

在本节中,我们介绍了多个用于图像处理的流行 Python 第三方库,利用这些图像处理库我们可以轻松的执行基本的图像变换操作。本节我们重点介绍了如何利用 scikit-image 绘制图像、使用 PIL 库修改图像模式、利用 SciPy 库调整图像大小以及进行简单的几何变换、同时介绍了轮廓的基本概念并学习了如何提取对象轮廓、进一步我们可以根据轮廓可以统计图像中的目标数量。

系列链接

Python图像处理【1】图像与视频处理基础
Python图像处理【3】探索Python图像处理库
Python图像处理【4】图像线性变换
Python图像处理【5】图像扭曲与逆扭曲详解
Python图像处理【6】通过哈希查找重复和类似的图像

猜你喜欢

转载自blog.csdn.net/qq_30167691/article/details/128072716