计算机视觉是人工智能最热门的应用领域之一。人工智能技术推动了汽车自动驾驶、机器人以及各种照片处理类软件的巨大发展。目标检测技术也在稳步推进。生成对抗网络(GANs)同样也是人们最近比较关注的一个问题。这些都在向我们展示未来计算机视觉领域的发展前景是多么的不可限量。
让我们一起登上人工智能发展的高速列车。从本文开始,我们将有一系列关于图像处理和目标检测基础知识的教程。本篇是OpenCV入门教程第一部分,完整的系列教程如下:
1. 理解颜色模型与在图像上绘制图形(图像处理基本操作)。
2. 基本的图像处理与过滤。
3. 从特征检测到人脸检测(TBU)
本系列的第一部分将从Opencv的安装,结合代码实战讲解颜色模型与图形绘制讲起。本教程的完整代码已经放在Github上,方便大家使用。
一、OpenCV简介
图像处理是指对图像执行一些操作以达到预期效果的过程。可以类比数据分析工作,在数据分析时我们需要做一些数据预处和特征工程。图像处理也是一样的。我们通过图像处理来处理图片从而可以从中提取处一些更加有用的特征。我们可以通过图像处理减少图像噪声,调整图像亮度、颜色或者对比度等等。想要进一步系统了解图像处理基础知识,参看(https://www.youtube.com/watch?v=QMLbTEQJCaI)。
OpenCV是Open Source Computer Vision的缩写,由英特尔公司于1999年推出。它最初是用C/ C++编写的,所以你可能会看到更多用C语言而不是Python编写的教程。但现在它在Python中也被广泛用于计算机视觉。首先,让我们为使用OpenCV配置环境。安装过程如下,详细安装描述参看(https://pypi.org/project/opencv-python/)。
pip install opencv-python==3.4.2
pip install opencv-contrib-python==3.3.1
安装完成后,可以通过下方两条命令测试其是否正常工作。如果没有任何报错,那么就可以开始使用了!
import cv2
cv2.__version__
我们使用用OpenCV做的第一步就是导入一个图像,如下方所示。
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
# Import the image
img = cv2.imread('burano.jpg')
plt.imshow(img)
上图是在意大利最美丽的岛屿之一布拉诺所拍摄的。如果你去过这个地方,你可能会注意到这幅图里有些不同。这确实和我们通常看到的布拉诺的照片有点不同。这是因为OpenCV中颜色模式的默认设置顺序是BGR,不同与Matplotlib。因此,要在RGB模式下查看图像,我们需要将它从BGR转换为RGB,如下所示。
# Convert the image into RGB
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
plt.imshow(img_rgb)
这张图就是真正的布拉诺了,多么美丽!
二、不只是RGB
我们再来谈谈颜色模型。颜色模型是一个使用原色构建全系列颜色的系统。这里先介绍这两种不同的颜色模型:“加色模型”和”减色模型”。加色模型使用光代表计算机屏幕上的颜色,而减色模型使用墨水在纸上打印这些数字图像。前者的原色由红色、绿色和蓝色(RGB)组成,后者有蓝色、品红、黄色和黑色(CMYK)四种原色组成。我们在图像上看到的所有其他颜色都是由这些原色组合或混合而成的。所以当分别用RGB、CMYK表示一张图像时,图像可以有着略微不同地表达。如下图所示。
日常生活中见到最多的就是这两种颜色模型。然而,在彩色模型的世界里不仅仅只有这两种颜色模型。众多的颜色模型中,灰度(grayscale)、HSV和HLS也是你会在计算机视觉任务中经常看到的。
灰度(grayscale)很简单。它通过黑白的强度来表示图像和形态,这也意味着它只有一个通道。要查看灰度图像,我们需要将颜色模型转换为灰色,就像前面对BGR图像所做的操作那样。
# Convert the image into gray scale
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
plt.imshow(img_gray, cmap = 'gray')
实际上,RGB图像是由三个通道叠加而成的:R, G, b。所以如果我们把每个通道一个一个的描绘出来,我们就可以理解颜色通道是如何构成的了!
# Plot the three channels of the image
fig, axs = plt.subplots(nrows = 1, ncols = 3,figsize = (20, 20))
for i in range(0, 3):
ax =axs[i]
ax.imshow(img_rgb[:, :, i], cmap = 'gray')
plt.show()
观察上面的图片。这三幅图像展示了每个通道是如何组成的。在R通道图中,红色饱和度高的部分看起来是白色的。这是由于红色部分中的值接近255。在灰度模式下,值越高颜色就越白。你还可以使用G或B通道来检查这一点,并比较某些部分之间的差异。
HSV和HLS有一些不同。正如在上图看到的那样,他们有一个三维的表达,更类似于人类的感知方式。HSV代表色调、饱和度和色值。HSL代表色调、饱和度和亮度。HSV的中轴是色值,HSL的中轴是光量。沿着中心轴的角度,有色调和实际的颜色。与中心轴的距离属于饱和度。转换颜色模型的方法如下。
# Transform the image into HSV and HLS models
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
img_hls = cv2.cvtColor(img, cv2.COLOR_BGR2HLS)
# Plot the converted images
fig, (ax1, ax2) = plt.subplots(nrows = 1, ncols =2, figsize = (20, 20))
ax1.imshow(img_hsv)
ax2.imshow(img_hls)
plt.show()
但是为什么要变换颜色?这些有什么用处?一个很典型的例子--车道检测。观察下图,不同颜色模式下的车道线。在计算机视觉任务中,我们利用掩膜(masking)进行多色模式转换。如果你想了解更多关于图像处理在车道检测任务中的应用,可参看这篇文章。
(https://towardsdatascience.com/finding-lane-lines-simple-pipeline-for-lane-detection-d02b62e7572b)
图像处理是就是对图像数据进行预处理。它可以减少噪音,提取有用的颜色模型,从而简化分类和检测任务。因此,所有上述技术,包括我们稍后将讨论的技术,都是为了帮助模型更容易地实现检测。
三、在图像上绘制图形
让我们在图像上添加一些图形。咱们这次代码示例中使用的图片来自巴黎“爱的墙”。上面用各种国际语言写满了“我爱你”。我们要做的是找到语言中的单词并用矩形标记它们。假如我们要定位韩语版本“我爱你”。首先,复制原始图像并用cv2.rectangle()函数绘制一个矩形,同时给出左上角和右下角的坐标值。如下:
# Copy the image
img_copy = img.copy()
# Draw a rectangle
cv2.rectangle(img_copy, pt1 = (800, 470), pt2 =(980, 530),
color = (255, 0, 0), thickness = 5)
plt.imshow(img_copy)
使用cv2.circle()函数,画一个圆,圈出更多的韩语单词。我们需要指定它的圆心的点和半径的长度。
# Draw a circle
cv2.circle(img_copy, center = (950, 50), radius =50,
color = (0, 0, 255), thickness = 5)
plt.imshow(img_copy),
我们还可以将文本数据放在图像上。使用cv2.putText()函数,我们可以指定文本的位置、字体样式和大小。
# Add text
cv2.putText(img_copy, text = "the Wall ofLove",
org = (250, 250),
fontFace = cv2.FONT_HERSHEY_DUPLEX,
fontScale = 2,
color = (0, 255, 0),
thickness = 2,
lineType = cv2.LINE_AA)
plt.imshow(img_copy)
四、不止是图像
在上边的介绍中,我们选取意大利和法国的两张风景图作为示例。假如,我们想要画张地图把这些地方标出来。那么,首先我们要创建一个窗口并绘制图形。不一样的是这里不是通过指定点绘制图形而是通过点击响应。先试试绘制圆圈。首先创建一个函数,它将用位置和鼠标点击的数据绘制一个圆圈。
# Step 1. Define callback function
def draw_circle(event, x, y, flags, param):
ifevent == cv2.EVENT_LBUTTONDOWN:
cv2.circle(img, center = (x, y), radius = 5,
color = (87, 184, 237), thickness = -1)
elifevent == cv2.EVENT_RBUTTONDOWN:
cv2.circle(img, center = (x, y), radius = 10,
color = (87, 184, 237),thickness = 1)
当按下鼠标点击按钮时,使用cv2.EVENT_LBUTTONDOWN或
cv2.EVENT_RBUTTONDOWN记录位置数据。把鼠标的位置设置为圆心(x, y),并绘制圆圈。
# Step 2. Call the window
img = cv2.imread('map.png')
cv2.namedWindow(winname = 'my_drawing')
cv2.setMouseCallback('my_drawing', draw_circle)
设置一个地图作为窗口的背景,并将窗口命名为my_drawing。使用cv2.setMouseCallback()函数,在窗口和我们在步骤1中创建的函数draw_circle之间建立了一个连接。
# Step 3. Execution
while True:
cv2.imshow('my_drawing',img)
ifcv2.waitKey(10) & 0xFF == 27:
break
cv2.destroyAllWindows()
现在我们使用while循环执行窗口。if子句的执行条件是,当我们按下键盘上的ESC时,将窗口设置为关闭。
接下来尝试绘制一个矩形。由于在cv2.rectangle()函数中,矩形需要两个点来表示pt1和pt2,所以我们需要一个额外的步骤来设置第一个点击点为pt1,最后一个点击点为pt2。我们要用cv2.EVENT_MOUSEMOVE和cv2.EVENT_LBUTTONUP来检测鼠标的移动。
我们首先将drawing = False定义为默认值。当按下左键时,绘图变为true,我们将第一个位置设为pt1。如果正在绘图,它将以当前点为pt2,并在移动鼠标时继续绘制矩形。就像数字重叠一样。当左键打开时,绘图变为false,它将鼠标的最后一个位置作为pt2的最后一个点。
# Initialization
drawing = False
ix = -1
iy = -1
# create a drawing function
def draw_rectangle(event, x, y, flags, params):
globalix, iy, drawing
ifevent == cv2.EVENT_LBUTTONDOWN:
drawing = True
ix,iy = x, y
elifevent == cv2.EVENT_MOUSEMOVE:
ifdrawing == True:
cv2.rectangle(img, pt1=(ix, iy), pt2=(x, y),
color = (87, 184,237), thickness = -1)
elifevent == cv2.EVENT_LBUTTONUP:
drawing = False
cv2.rectangle(img, pt1=(ix, iy), pt2=(x, y),
color = (87, 184, 237),thickness = -1)
在步骤1中将draw_circle函数替换为draw_rectangle。请不要忘记在回调函数cv2.setMouseCallback()中进行更改。因此,整个代码脚本将如下所示。
import cv2
import numpy as np
# Step 1. Define callback function
drawing = False
ix = -1
iy = -1
def draw_rectangle(event, x, y, flags, params):
globalix, iy, drawing
ifevent == cv2.EVENT_LBUTTONDOWN:
drawing = True
ix,iy = x, y
elifevent == cv2.EVENT_MOUSEMOVE:
ifdrawing == True:
cv2.rectangle(img, pt1 = (ix, iy), pt2 = (x, y),
color = (87, 184,237), thickness = -1)
elifevent == cv2.EVENT_LBUTTONUP:
drawing = False
cv2.rectangle(img, pt1 = (ix, iy), pt2 = (x, y),
color = (87, 184, 237),thickness = -1)
# Step 2. Call the window
img = cv2.imread('map.png')
cv2.namedWindow(winname = 'my_drawing')
cv2.setMouseCallback('my_drawing',draw_rectangle)
# Step 3. Execution
while True:
cv2.imshow('my_drawing', img)
ifcv2.waitKey(10) & 0xFF == 27:
break
cv2.destroyAllWindows()
五、总结与展望
本篇文章介绍了Opencv的安装、图像颜色模型的转换与图形绘制。下次,将介绍图像轮廓提出与目标检测等技术。敬请期待!