01
前言
一切看似复杂的计算机视觉项目,其基础都会回归到单张图片上。能够理解 灰度/彩色图像
的基本原理并将代码用于实际案例是本文的目标。下文将详细介绍如何利用 Python 实现 灰度/彩色图像
的基本处理,主要分为两个部分:
- 详细原理介绍
- Python 代码实战
02
原理介绍
计算机实际上是怎么”看“图像数据的呢?图像只是三维现实场景的二维表示,比如现实中的一辆汽车是三维物体,但如果你给汽车拍张照片,我们就得到了它的二维图像。
这张图像包含的信息有:汽车的颜色、形状、随照明条件不同而不同的阴影,以及表观大小(随摄影距离的远近,物体表现得更大还是更小)这是计算机“看”图的第一步。
接下来,我们还要将数字图像打散,使之成为一个由色彩和强度小单元组成的网络,也就是我们常说的像素。
因为在我们编写程序来处理并判读图像的过程中,这个网格至关重要。(直接读入一整张图片对于目前的计算机技术来说还是太难了,所以得拆分成像素网格)
2.1 像素网络
我们先来看灰度图像,这样避免了彩色带来的复杂性。放大图片中的某一小部分,会发现它是一个二维网络值,亦被称之为具有宽度和高度的数组(单个颜色强度很小的单位)
这个网格中每个像素颜色都有一个对应的数值,每个像素的值范围是0~255。0 表示黑色 255 表示白色,我们可以通过定位像素网格的横纵坐标来获取某一特定位置的像素值。
2.2 彩色图像
毋庸置疑,彩色图像比灰度图像拥有更多的信息,但维度也高了一层。灰度图像是只有长和宽的二维,而彩色图像是三维的。
彩色图像被解析为具有宽高和深的三维立方体。深是指颜色通道的数量:大多数彩色图像可以仅通过三种颜色组合来表示,即红绿蓝(red,green,blue;组合起来便是我们经常见到的 rgb)
可以将深度看做三个堆叠的二维色彩图层堆叠到一起形成的完整的彩色图像。蓝,绿,红色各一层。
问:灰度图像网格处理已经够用大多数场景了,为什么还需要彩色图像?
答:彩色图像虽然带来了不必要的复杂性且占用了更多的内存空间,但也不能一棒子打死,在某些分类任务中,彩色图像会非常有用。比如对下图的交通道路线(灰色图像)进行分类,该如何区分黄白线呢?
尽管能够勉强猜出虚线大概率是白色,毕竟深一点。但这样的分类方法实在太过草率,如果再加上角度不一的日照,雨雪等天气,那就更无从下手了。这样一来,直接看彩色图像是不是就舒服多了
通常在计算机视觉应用中,识别车道线,汽车或行人时,可以通过人眼的观察习惯来判断颜色信息和彩色图像是否有用。如果对人眼来说,彩色图像识别起来更轻松,那么彩色图像对算法来说也更轻松些。一言以蔽之,如果色彩的存在对最终的结果非常有帮助,那就用吧!
03
代码实战
本次代码实战将包含以下知识点:
- 彩色图片的读入(cv2 库与matplotlib 库两种方式)
- 彩色图像转灰度图像
- 通过位置访问单个像素
import numpy as np
import matplotlib.image as mpimg # matplotlib中读取图片
import matplotlib.pyplot as plt
import cv2 # 计算机视觉库
# 设置中文字体的支持
plt.rc('font', **{'family': 'Microsoft YaHei, SimHei'})
# 解决保存图像是负号'-'显示为方块的问题
plt.rcParams['axes.unicode_minus'] = False
以上为基础库的导入和细节配置
3.1 读入图片的两种方式
代码语言:javascript复制# matploblib 读入图片:正常的 RGB 格式
car = plt.imread('car.jpg')
# cv2 库读入,会变成 BGR 的形式
## 也就是 R(红色) 和 B(蓝色) 的部分反过来了
## 后续需要用 cv2 库的函数处理
car_cv2 = cv2.imread('car.jpg')
# cv2.cvtColor 传入参数:需要转换的图片;转换成什么形式
car_cv2_correct = cv2.cvtColor(car_cv2, cv2.COLOR_BGR2RGB)
print(f'图片的数据类型:{type(car)},图片规格:{car.shape}')
f, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(20, 15))
ax1.imshow(car); ax2.imshow(car_cv); ax3.imshow(car_cv2_correct)
ax1.set_title('matplotlib 读入')
ax2.set_title('cv2 读入')
ax3.set_title('cv2 读入并经过处理')
# 可知图片是由一个 ’宽×长 = 515*800‘ 的矩阵组成的,矩阵中的每个元素就是一个像素
# 3 表示的是图像的颜色通道数量,将在后续章节进行详解
使用 cv2 库读入图片时,图像是以 BGR 的形式存储在数组中,所以蓝色和红色的部分会相反,需要用 cv2 函数来显式转换一下格式.
3.2 转化为灰度图像
代码语言:javascript复制car_copy = np.copy(car)
# 因为图像是以矩阵形式存储,所以可以用 numpy 的 copy 函数
# 对函数 cv2.cvtColor 传入需要被处理的图像以及处理函数
## RGB 格式转灰度图像 GRAY
car_gray = cv2.cvtColor(car_copy, cv2.COLOR_RGB2GRAY)
需要注意的是,如果直接输出转换后的灰度图像,可能不会得到我们想要的效果,还需要往函数 imshow 中添加参数 cmap='gray'
代码语言:javascript复制f, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(20, 15))
ax1.imshow(car, cmap='gray') # 如果不是子图,就直接 plt.imshow()
ax2.imshow(car_gray)
ax3.imshow(car_gray, cmap='gray')
ax1.set_title('未经cv2处理的原图,添加cmap参数')
ax2.set_title('cv2处理,添加了cmap参数')
ax3.set_title('cv2处理,imshow未加cmap参数')
# cv2处理以后,imshow输出图片时还要添加 cmap 参数才完整
## 输出原汁原味的灰度图像
## 至于如何使用 matplotlib 将原始图像转化为灰度图像,
## 以及 cmap 参数的含义,可参考网络
3.3 通过位置访问单个像素
在原理介绍环节,我们提到:将数字图像打散后,会使之成为一个由色彩和强度小单元组成的网络,也就是我们常说的像素。
网格中每个像素颜色都有一个对应的数值,我们可以通过定位像素网格的横纵坐标来获取某一特定位置的像素值。
所以只需要将 x,y 坐标传入图像矩阵即可,不过值得注意的是,要先传入 y 再传入 x,因为图像的坐标是反着来的。
这里我们传入两个坐标:图片中的非汽车区域和汽车的前挡风玻璃区域,顺便比较一下像素值。
代码语言:javascript复制x, y = 100, 200 # 这个坐标对应的是图片里的非汽车区域
x1, y1 = 365, 168 # 对应汽车的前挡风玻璃
# 注意:要先传入y再传入x,因为图像坐标是反着来的
pixel_value = car_gray[y, x]
window_pixel_value = car_gray[y1, x1]
print(f'非汽车区域的像素值 {round(pixel_value,2)}')
print(f'前挡风玻璃区域的像素值 {round(window_pixel_value,2)}')
# 也符合人眼的观察,灰度图像中,
## 前挡风玻璃那部分的亮度确实比非汽车区域要亮一些
结果也符合人眼的观察,灰度图像中,前挡风玻璃区域的亮度确实比非汽车区域要亮一些。
小结
总结一下本文提到的内容:
- 彩色图像的读取:matplotlib 与 cv2 两种方,cv2 需显式转换 RGB 格式
- 灰度图像转换:cv2.cvt(需要转换的图像, cv2.COLOR_RGB2GRAY)
- 访问单个像素:传入 xy 坐标,不过要注意是先传入 y 再传入 x
灰度图像是我们帮助计算机 “看” 和 “理解” 图像的第一步,把图像转为灰度图像的像素网格以及 x 和 y 的函数来处理以后,我们还需要学会如何利用这些信息,例如如何用图像信息来分离特定区域。这也是我们第二节将会学习的内容:蓝幕与颜色阈值