有趣的 Python 图像处理

849 阅读9分钟

这是我参与8月更文挑战的第29天,活动详情查看:8月更文挑战

前言

图像处理是一门很高的学问,也非常有趣。要掌握好图像处理需要了解很多知识,但是作为娱乐,对这方面知识的要求就低了许多。今天就带大家来了解一下图像处理的知识,Python 图像处理通常使用 Pillow 库,其中提供了大量的操作,像灰度转换、高斯模糊、色道分离等,另外 OpenCV 中也提供了一些图像处理的方法,我们可以结合两者进行有效的图像处理。

一、PIL 简介

PIL 的全称是(Python Imaging Library)也就是 Python 图像库的意思,实际上 PIL 只支持到 Python 2.7,现在我们大多数人使用的都是 Python 3.x,在 3.x 中有兼容版的 Pillow 模块,有了 PIL 的基本操作,同时也加入了一些新的操作。

1.1 安装

我们使用 pip 安装:

pip install pillow

安装完成后我们就可以使用了,虽然在安装是我们时使用 Pillow,但是在实际使用中,我们导入的是 PIL:

from PIL import Image

其中,Image 类是我们最常使用的一个类。导入模块后,我们来看看最简单的操作:

from PIL import Image

# 打开图片
img = Image.open('0_img/q.jpg')

# 显示图片
img.show()

这样我们就完成了图片读取和图片显示的操作了。当然,我们获取图像的对象之后,可以获取它相应的属性:

from PIL import Image

# 打开图片
img = Image.open('0_img/q.jpg')

print('图像格式:', img.format)
print('图像大小:',img.size)
print('图像宽度:', img.width)
print('图像高度:', img.height)

# 显示图片
img.show()

输出如下:

图像格式: JPEG
图像大小: (1280, 720)
图像宽度: 1280
图像高度: 720

二、使用 PIL 处理图像

通常处理图像需要我们掌握许多和图像相关的知识,但是我们使用模块可以很简单地实现图像处理。我们不需要知道底层实现,只需要知道这些处理的作用就可以了。

2.1 图像数组

我们使用 PIL 读取图片,它的返回值是一个 PIL.JpegImagePlugin.JpegImageFile 对象,而我们在使用 OpenCV 处理时,处理的是 ndarray 对象。我们可以使用如下语句将其转换成 ndarray 对象:

from PIL import Image
import numpy as np
# 读取图像
img = Image.open('0_img/q.jpg')
# 使用np将其转换成ndarray对象
img_array = np.array(img)

而转换成的 ndarray 对象有 RGB 三个色道,图像的每个像素点都有三个值,值的大小为(0~255)。

2.2 灰度转换

灰度变换是指根据某种目标条件按一定变换关系逐点改变源图像中每一个像素灰度值的方法,灰度转换可以改善图像的画质。我们通常说的黑白照片就是灰度照片,而真正意义上的黑白图像中的像素值只有 0 和 255,而 PIL 实现灰度转换也非常简单:

from PIL import Image
# 读取图像
img = Image.open('0_img/q.jpg')
# 灰度转换
grey_img = img.convert('L')
# 保存图像
grey_img.save('0_img/grey.jpg')

原图和灰度图对比如下:

在这里插入图片描述

2.3 图像混合

图像混合就是将两张图片混合在一起,我们可以创建一张单色的图像,然后再进行混合:

from PIL import Image 
# 读取图像
img1 = Image.open('0_img/q.jpg')
# 创建一个图像
img2 = Image.new('RGB', img1.size, (255, 0, 0))
# 混合图像
Image.blend(img1, img2, 0.5).show()

我们使用 Image.new 创建一个图像,该方法传入三个参数,第一个是图像的模式通常有“RGB”、“L”(灰度)等。第二个参数为图像大小,第三个参数则是颜色值。我们可以直接传入颜色字符“red”、“blue”等。

混合图像的方法为 Image.blend,该方法接收三个参数,第一个和第二个都是图片对象,第三个为 img2 的透明度。混合后效果如下:

在这里插入图片描述

2.4 图像缩放

图像缩放即按照尺寸放大或缩小图像,通常我们可以使用该方法生成图像的缩略图。图像缩放使用到 thumbnail 方法,该方法接收一个元组,元组包含两个元素,宽和高:

from PIL import Image
# 读取图像
img1 = Image.open('0_img/q.jpg')
# 输出元素图像的尺寸
print(img1.size)
# 将img1的宽和高都缩小到原来的1/2
img1.thumbnail((img1.size[0]//2, img1.size[1]//2))
# 输出缩放后的尺寸
print(img1.size)

输出结果如下:

(1280, 720)
(640, 360)

这里我们需要注意一下,上面接触到的 open、new 和 blend 方法都是 Image 类的静态方法,而 thumbnail 是是需要用实际对象执行的方法。

2.5 图像裁剪和粘贴

裁剪和粘贴是图像的基本操作,在 PIL 中实现起来非常简单。裁剪使用的是 crop 方法,该方法传入一个元组,该元组有四个参数(左上角的 x,左上角的 y,右下角的 x,右下角的 y),裁剪是在原图的基础上进行的。粘贴同样是在原图的基础上进行的,粘贴是通过 paste 方法实现,该方法传入两个参数,图像对象和左上角坐标元组。代码如下:

from PIL import Image
# 读取图像
img = Image.open('0_img/q.jpg')
# 拷贝图片
img1 = img.copy()
img2 = img.copy()
# 裁剪图像,
crop_im = img1.crop((200, 200, 400, 400))
# 粘贴图像
img2.paste(crop_im, (0, 0))
img2.show()

效果图如下:

在这里插入图片描述

2.6 色道分离和合并

实现色道分离只需要调用 split 方法,该方法返回 3 个灰度的 Image 对象。色道的合并 Image.merge 方法,该方法传入两个参数,第一个是图像模式,第二个是色道的列表:

from PIL import Image
# 读取图像
img1 = Image.open('0_img/q.jpg')
img2 = Image.open('0_img/s.jpg')
# 色道分离
r1, g1, b1 = img1.split()
r2, g2, b2 = img2.split()
# 色道合并
img3 = Image.merge('RGB', [r1, g2, b1])
img3.show()

效果图和原图对比如下:

在这里插入图片描述

左为 s.jpg,中间为 q.jpg,最右边为合并后的图像。

2.7 高斯模糊

高斯模糊也叫高斯平滑,直观来看的话就是会使图片模糊,但是它实际的作用是对图片降噪。有许多处理需要预先用到高斯模糊,在 PIL 中通过调用 filter 实现:

from PIL import Image,ImageFilter

# 读取图像
img = Image.open('0_img/q.jpg')
# 高斯模糊
g_img = img.filter(ImageFilter.GaussianBlur)
g_img.show()

filter 方法是给图片添加滤镜,在 ImageFilter 类中提供了许多预设好的滤镜。

2.8 色彩亮度调节

在 ImageEnhance 中提供了许多调节图像的类,如下:

类名方法作用
ImageEnhance.Color获取颜色调整器
ImageEnhance.Contrast获取对比度调整器
ImageEnhance.Brightness获取亮度调整器
ImageEnhance.Sharpness获取清晰度调整器

要调节图像我们需要先生成相应的调节器,然后再进行调节,下面我们用颜色为例:

from PIL import Image,ImageEnhance
# 读取图像
img = Image.open('0_img/q.jpg')
# 获取颜色调节器
color_enhance = ImageEnhance.Color(img)
# 调节颜色,传入值小于1就是减弱,大于1为增强
enhance_img = color_enhance.enhance(0.5)
# 显示图像
enhance_img.show()

其它属性的调节同颜色一样。

三、OpenCV 简介

OpenCV 同 PIL 一样,也可以进行图像处理。但是 OpenCV 读取图像是以 ndarray 形式,且模式为 BGR。我们先安装 OpenCV:

pip3 install opencv-python

然后我们就可以开始使用了。

3.1 OpenCV 读取图像

OpenCV 读取图像是通过 cv2.imread 方法实现,该方法返回的是 ndarray 对象,且模式为 BGR。

import cv2
# 读取图像
img = cv2.imread('jt.jpg')
# 显示图像,传入两个参数,第一个是窗口名,第二个是ndarray对象
cv2.imshow('img', img)
# 等待键盘输入
cv2.waitKey(0)
# 销毁所有窗口
cv2.destroyAllWindows()

调用 imshow 方法图像只会显示一瞬间,所以需要调用 waitkey 方法,该方法接收等待的毫秒数,如果传入 0 则是无限等待。因为 OpenCV 的底层是 C/C++ 所以,需要释放窗口内存。

3.1 OpenCV 读取的图像和 PIL 读取的图像互相转换

我们上面已经知道如何将 Image 对象转换成 ndarray 对象,但是因为图像模式不同,显示的时候是不一样的,所以我们需要将 RGB 转成 BGR 或者 BRG 转换成 RGB。

RGB 转 BGR:

import cv2
import numpy as np
from PIL import Image

# 用PIL读取图像
pil_img = Image.open('jt.jpg')
# 转换成ndarray对象,该对象模式为rgb
rgb = np.array(pil_img)
# 将rgb转换成bgr
bgr = rgb[...,::-1]
# 使用opencv显示图像
cv2.imshow('pil_im', bgr)
cv2.waitKey(0)
cv2.destroyAllWindows()

BGR 转 RGB:

import cv2
from PIL import Image
# 读取图像
bgr = cv2.imread('jt.jpg')
# bgr转rgb
rgb = bgr[...,::-1]
# 将ndarray对象转换成Image对象
cv_im = Image.fromarray(rgb)
cv_im.show()

执行后发现显示效果都同原图一样。

四、综合操作

前面我们一直在讲简单的图像处理,下面我们来一些综合的操作。

4.1 边缘检测

在 OpenCV 中提供了许多边缘检测的算法,其中 Canny 是当前最优算法,我们可以很快地实现边缘检测,代码如下:

import cv2
# 读取图片
img = cv2.imread('jt.jpg')
# 灰度转换
grey = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# canny算法,该算法
canny_im = cv2.Canny(grey, 50, 200)
# 显示图片
cv2.imshow('canny', canny_im)
cv2.waitKey(0)
cv2.destroyAllWindows()

我们看看原图和效果图:

在这里插入图片描述

大致轮廓都出来了,但是浅色部分没有识别出,于是我们就想到颜色增强。修改后代码如下:

import cv2
import numpy as np
from PIL import Image, ImageEnhance
# 读取图像
img = Image.open('jt.jpg')
# 增强颜色
color_enhance = ImageEnhance.Color(img)
en_im = color_enhance.enhance(5)
# 转换成ndarray对象
img_array = np.array(en_im)
# 转换成bgr
bgr = img_array[...,::-1]
# canny算法,该算法传入三个值,第一个是ndarray对象,第二个和第三个为阈值
canny_im = cv2.Canny(bgr, 150, 200)
cv2.imshow('canny', canny_im)
cv2.imwrite('canny.jpg', canny_im)
cv2.waitKey(0)
cv2.destroyAllWindows()

效果图如下:

在这里插入图片描述

大家可以根据不同的图片进行调节。

4.2 图片文字结合效果

我们先准备一张文字图片,尽量小一点,文字图片可以用 Windows 自带的画图工具制作。然后再另外准备一张图片。制作思路如下:

  1. 准备文字图片和背景图片
  2. 我们根据背景图片的大小,创建一个白色图片
  3. 我们将文字图片从左到右,从上到下粘贴到白色图像上
  4. 让粘贴了问题的图像和背景图像混合

代码如下:

from PIL import Image
# 读取背景图像和文字图像
im = Image.open('zxz.jpg')
source = Image.open('zack.png')
# 获取背景图像和文字图像的大小
im_size = im.size
source_size = source.size
# 计算一行可以摆放多少文字图片
width_num = im_size[0] // source_size[0]
# 计算一列可以摆放多少文字图片
height_num = im_size[1] // source_size[1]
# 创建一个同背景图片大小一致的白色图片
bg_im = Image.new('RGB', im_size, (255, 255, 255))
# 循环变量
x, y = 0, 0
# 循环,将文字图片从左到右从上到下铺满白色图片
for i in range(width_num+1):
    x = i * source_size[0]
    for j in range(height_num+1):
        y = j * source_size[1]
        bg_im.paste(source, (x, y))
# 将粘贴了文字图像的图像和背景图像混合
Image.blend(im, bg_im, 0.5).save('res.png')

上述代码实现如下效果:

在这里插入图片描述

大家可以根据自己学习的知识,制作一些有趣的混合图像。