文章目录(Table of Contents)
简介
在本文中,我们将会介绍 Python 中的 OpenCV 库来介绍计算机视觉的各个方面。OpenCV
是一个旨在解决计算机视觉问题的 Python 库。
OpenCV 最初由 Intel 在 1999 年开发,但是后来由 Willow Garage 资助。它支持很多编程语言,如C++,Python,Java 等等。它也支持多种平台,包括 Windows,Linux 和 MacOS。
OpenCV Python 只是一个与 Python 一起使用的原始 C++ 库的包装类。通过使用它,所有OpenCV 数组结构都能被转化为 NumPy 数组或从 NumPy 数组转化而来。这样就可以轻松地将其与其他使用 NumPy 的库集成。例如,SciPy 和 Matplotlib 等库。
计算机如何读取图像
在详细介绍 Python OpenCV 之前,我们首先看一下「计算机是如何读取图片」的。下面是一张「纽约天际线」的图像:
对于计算机来说,他将任何图片都读取为一组 0 到 255 之间的值。对于任何一张彩色图片,有三个主通道——红色(R),绿色(G)和蓝色(B)。对于黑白照片就只有一个通道了。
计算机对每个原色(R,G,B)创建一个矩阵;然后,组合这些矩阵以提供 R, G 和 B 各个颜色的像素值。每一个矩阵的元素提供与像素的亮度强度有关的数据。
下图是计算机读取上面「纽约天际线」的过程,会分别创建三个矩阵,分别表示每个像素 R,G,B的强度:
参考资料
- Python中Pillow使用介绍,Pillow 库的使用,也是对图像进行处理;
- 手把手教你使用OpenCV库(附实例、Python代码解析),机器之心中一篇介绍 python-opencv 的文章(文章中代码的格式不好);
OpenCV 基础操作
我们首先需要按照下面的方式安装 opencv-python
的库:
- pip install opencv-python
后面就可以按照下面的方式导入 opencv-python
库了。
- import cv2
如果在安装过程中出现报错:ModuleNotFoundError: No module named 'skbuild'
,则更新 pip
即可,如下所示:
- pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple --upgrade pip
使用 OpenCV 加载图像-imread
我们使用下面的图像来作为例子:
我们使用 cv2.imread
来读取图像,后面的参数 1
表示「彩色图像」,最终是一个三维的矩阵,说明 RGB 三个通道都有值:
- import cv2
- img = cv2.imread("kenan.png", 1) # 彩色图像
- print(img.shape)
- # >> (188, 329, 3)
如果想要读取为黑白的图像,只需要将参数设置为 0 即可:
- import cv2
- img = cv2.imread("kenan.png", 0) # 黑白图像
- print(img.shape)
- # >> (188, 329)
使用 OpenCV 保存图像-imwrite
我们可以使用 cv2.imwrite
来进行图像的保存。下面的例子中我们打开图像后再进行保存:
- import cv2
- # 读取图像
- im = cv2.imread('data/src/lena.jpg')
- # 保存图形
- cv2.imwrite('data/dst/lena_opencv_red.jpg', im)
使用 OpenCV 显示图像-imshow
在加载完毕之后,我们想要将图像显示出来,这里可以使用 cv2.imshow
来进行图像的显示:
- import cv2
- img = cv2.imread("kenan.png", 0) # 黑白图像
- cv2.imshow("KeNan", img)
- cv2.waitKey(20000) # 等待一段时间
- cv2.destroyAllWindows() # 关闭窗口
最后需要加上 waitkey
,来使得图像可以显示一段时间。最终的效果如下所示:
创建图像
有的时候我们需要创建纯白色或是纯黑色的图像,可以使用 numpy 来定义一个矩阵即可:
- import numpy as np
- blank_image = np.zeros((height,width,3), np.uint8) # 纯黑色
- blank_image = np.zeros((height,width,3), np.uint8)*255 # 纯白色
上面是定义了纯黑色与纯白色的图像,当然我们也可以对图片颜色进行修改。修改为「左侧是蓝色」,「右侧是绿色」:
- blank_image[:,0:width//2] = (255,0,0) # (B, G, R)
- blank_image[:,width//2:width] = (0,255,0)
最终的效果如下图所示:
参考资料,Create a new RGB OpenCV image using Python?
转换图像的颜色空间-cvtColor
颜色空间转换,例如将彩色的图片转换为灰度图。同时需要注意的是,opencv 的彩色图片是 BGR 颜色空间的,如果希望 matplotlib 可以正常显示,需要转换为 RGB 颜色空间(图像处理-matplotlib显示opencv图像)。我们使用 cv2.cvtColor
来转换颜色空间。
例如下面的例子,我们将彩色图像转换为灰度图,使用了 cv2.COLOR_BGR2GRAY
:
- img = cv2.imread("kenan.png", 1) # 彩色图像
- img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 转换为灰度图
关于 opencv 中 color space 的介绍可以参考,OpenCV Color Conversions。
OpenCV 格式与 Pillow 格式互相转换
有一点需要牢记:
OpenCV
中图像的颜色空间是BGR
;Pillow
中的颜色空间是RGB
;
OpenCV 格式到 Pillow 格式
图片通过 OpenCV
读取是 numpy
的格式,通过 Image.fromarray()
将其转换为 pillow
的格式:
- img = cv2.imread("./cat.png")
- img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
- print(type(img)) # <class 'numpy.ndarray'>
- im_pil = Image.fromarray(img) # 转换为 pillow 格式
- print(type(im_pil)) # <class 'PIL.Image.Image'>
参考资料,Convert opencv image format to PIL image format?
Pillow 格式到 OpenCV 格式
将 pillow 格式转换为 OpenCV 格式其实就是转换为 numpy 的格式。我们只需要使用 np.array(或是 np.asarray,这个节约内存) 即可。注意下面进行了颜色空间的转换(转换到了 OpenCV 使用的 BGR 颜色空间)。
- pil_image = Image.open('./cat.png').convert('RGB')
- open_cv_image = cv2.cvtColor(np.array(pil_image), cv2.COLOR_RGB2BGR) # 转换为 opencv 的格式
参考资料,Convert image from PIL to openCV format
阈值处理
全局阈值处理-threshold
之前我们介绍了将图像转换为了「灰度图」,但是有时我们还希望可以将图像只保留「两种颜色」,即非黑即白,于是可以使用 cv2.threshold
。
- cv2.threshold (源图片, 阈值, 填充色, 阈值类型)
这里的「阈值」是「全局阈值」,也就是整个图片都会使用这个阈值。而「阈值类型」有以下的五种:
- cv.THRESH_BINARY:当小于「阈值」则为 0,大于「阈值」为「填充色」;
- cv.THRESH_BINARY_INV:当小于「阈值」则为「填充色」,大于「阈值」为 0;
- cv.THRESH_TRUNC:当小于「阈值」则不变,大于「阈值」为「阈值」;
- cv.THRESH_TOZERO:当小于「阈值」则为 0,大于「阈值」不变;
- cv.THRESH_TOZERO_INV:当小于「阈值」则不变,大于「阈值」为 0;
下图是五种情况的效果图:
我们还是对上面柯南的图片进行二值化:
- img = cv2.imread("kenan.png", 1) # 彩色图像
- img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 转换为灰度图
- ret, thresh = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY) # 转换为「二值图」
关于 cv2.threshold
详细的介绍可以查看链接,OpenCV Thresholding。但是有的时候直接使用上面的方式进行全局的二值化效果不会很好,于是会出现自适应阈值。
自适应阈值-adaptiveThreshold
在上面我们介绍了使用全局值作为阈值,但是这并非在所有情况都是好的。例如一副图像在不同的地区的照明条件是不一样的。于是在这种情况下自适应阈值是有作用的。
这里「自适应阈值」会根据一小个区域来确定阈值,相当于一个图片的不同区域的阈值是不同的。我们可以使用 adaptiveMethod
决定阈值是如何计算的(有两种方式):
cv.ADAPTIVE_THRESH_MEAN_C
:阈值是邻域面积的平均值减去常数Ccv.ADAPTIVE_THRESH_GAUSSIAN_C
:阈值是邻域值减去常数C的高斯加权总和。
还有其他两个参数,分别是:
BLOCKSIZE
,确定附近区域的大小和;C
,是从邻域像素的平均或加权总和中减去一个常数;
下面是一个例子,分别是不同的「阈值处理」的效果:
我们还是使用上面的柯南来作为例子,看一下「自适应阈值」的效果(注意我们将图片进行了平滑,在第三行代码):
- img = cv2.imread("kenan.png", 1) # 彩色图像
- img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 转换为灰度图
- img_gray = cv2.medianBlur(img_gray, 3)
- thresh = cv2.adaptiveThreshold(img_gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, blockSize=11, C=2)
最终的效果如下所示:
关于「自适应阈值」的方式可以查看链接,OpenCV Adaptive Thresholding
关于图像的轮廓
轮廓可以简单的理解为「链接所有连续点的曲线」,具有相同的颜色。为了获得更好的准确率。输入图像应该是「二进制图像」(即需要先使用阈值处理)。
我们看下面的例子,将上面「柯南」的图像二值化之后,我们使用 cv2.findContours
来寻找轮廓(这里是寻找白色部分的轮廓),使用 cv2.drawContours
来绘制轮廓:
- img = cv2.imread("kenan.png", 1) # 彩色图像
- img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 转换为灰度图
- img_gray = cv2.medianBlur(img_gray, 5) # 图像进行平滑
- thresh = cv2.adaptiveThreshold(img_gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, blockSize=11, C=2) # 二值化
- contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) # 寻找区域
- thresh_draw = cv2.drawContours(img, contours, -1, (0,255,0), 1)
最终的效果如下所示:
上面返回的 contours
是轮廓的坐标。我们可以将 contours
与循环结合,逐步绘制轮廓。下面看一个例子:
- import cv2
- img = cv2.imread("kenan.png", 1) # 彩色图像
- img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 转换为灰度图
- img_gray = cv2.medianBlur(img_gray, 5) # 图像进行平滑
- thresh = cv2.adaptiveThreshold(img_gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, blockSize=11, C=2) # 二值化
- contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) # 寻找区域
- for i in contours:
- thresh_draw = cv2.drawContours(img, i, -1, (0,255,0), 1)
- cv2.imshow("1.jpg", thresh_draw)
- cv2.waitKey(100) # 等待一段时间
- cv2.destroyAllWindows() # 关闭窗口
最终的结果如下所示,可以看到绿色的轮廓线逐渐出现:
我们把轮廓和上面阈值处理的结果放在一起:
关于图像的轮廓可以参考链接,OpenCV Contours。
- 微信公众号
- 关注微信公众号
- QQ群
- 我们的QQ群号
评论