第十章 OpenCV
这一章主要讲述通过Python接口使用目前流行的计算机视觉编程库OpenCV。OpenCV是一个C++库,用于实时处理计算机视觉方面的问题。
10.1 OpenCV Python接口
OpenCV是一个C++库,它涵盖了很多计算机视觉领域的模块。可以通过访问[http://opencv.willowgarage.com/ documentation/python/index.html]。
OpenCV目前最新的版本是2.4.8。实际上,OpenCV有两个Python接口,老版本的cv模块使用OpenCV内置的数据类型,新版本的cv2模块使用NumPy数组。对于新版本的模块,可以通过下面方式导入:
import cv2
而老版本的模块则通过下面方式导入:
import cv2.cv
在本章中,我们使用cv2模块,译者使用的OpenCV版本是2.4.6。
10.2 OpenCV基础
OpenCV提供了读取图像和写入图像,矩阵操作以及数学库函数。我们先来看看这些基本组件并学习怎样使用它们。
10.2.1 读取、写入图像
下面是一个简短的载入图像、打印尺寸、转换格式及保存图像为.png格斯的例子:
# -*- coding: utf-8 -*-
import cv2
# 读入图像
im = cv2.imread('../data/empire.jpg')
# 打印图像尺寸
h, w = im.shape[:2]
print h, w
# 保存原jpg格式的图像为png格式图像
cv2.imwrite('../images/ch10/ch10_P210_Reading-and-Writing-Images.png',im)
运行上面代码后,在ch10文件下保存有empire.jpg转换成.png格式的图片,即ch10P210Reading-and-Writing-Images.png,下面是转换格式后保存的.png的图像: 函数imread()将图像返回为一个标准的NumPy数组,如果你喜欢的话,你可以将该函数用于PIL图像读取的备选函数。函数imwrite()能够根据文件后缀自动的进行格式转换。
10.2.2 颜色空间
在OpenCV中,图像不是用常规的RGB颜色通道来存储的,它们用的是BGR顺序。当读取一幅图像后,默认的是BGR,不过有很多转换方式是可以利用的。颜色空间转换可以用函数cvtColor()函数。比如,下面是一个转换为灰度图像的例子:
import cv2
im = cv2.imread('../data/empire.jpg')
# create a grayscale version
gray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
10.2.3 显示图像和结果
下面我们看一些用OpenCV进行图像处理并用OpenCV绘图及窗口管理功能显示图像后的结果的示例。
第一个例子是从文件中读取一幅图像,并创建积分图像表示:
# -*- coding: utf-8 -*-
import cv2
from pylab import *
# 添加中文字体支持
from matplotlib.font_manager import FontProperties
font = FontProperties(fname=r"c:\windows\fonts\SimSun.ttc", size=14)
# 读入图像
im = cv2.imread('../data/fisherman.jpg')
# 转换颜色空间
gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
# 显示积分图像
fig = plt.figure()
subplot(121)
plt.gray()
imshow(gray)
title(u'灰度图', fontproperties=font)
axis('off')
# 计算积分图像
intim = cv2.integral(gray)
# 归一化
intim = (255.0*intim) / intim.max()
#显示积分图像
subplot(122)
plt.gray()
imshow(intim)
title(u'积分图', fontproperties=font)
axis('off')
show()
# 用OpenCV显示图像
#cv2.imshow("Image", intim)
#cv2.waitKey()
# 用OpenCV保存积分图像
#cv2.imwrite('../images/ch10/ch10_P211_Displaying-Images-and-Results-cv2.jpg',intim)
# 保存figure中的灰度图像和积分图像
fig.savefig("../images/ch10/ch10_P211_Displaying-Images-and-Results.png")
运行上面代码,显示如下结果,并在/images/ch10/目录下生成一幅保存有灰度图像和积分图像的图片: 第二个例子从种子像素开始应用泛洪(漫水)填充:
# -*- coding: utf-8 -*-
import cv2
import numpy
from pylab import *
# 添加中文字体支持
from matplotlib.font_manager import FontProperties
font = FontProperties(fname=r"c:\windows\fonts\SimSun.ttc", size=14)
# 读入图像
filename = '../data/fisherman.jpg'
im = cv2.imread(filename)
# 转换颜色空间
rgbIm = cv2.cvtColor(im, cv2.COLOR_BGR2RGB)
# 显示原图
fig = plt.figure()
subplot(121)
plt.gray()
imshow(rgbIm)
title(u'原图', fontproperties=font)
axis('off')
# 获取图像尺寸
h, w = im.shape[:2]
# 泛洪填充
diff = (6, 6, 6)
mask = zeros((h+2, w+2), numpy.uint8)
cv2.floodFill(im, mask, (10, 10), (255, 255, 0), diff, diff)
# 显示泛洪填充后的结果
subplot(122)
imshow(im)
title(u'泛洪填充', fontproperties=font)
axis('off')
show()
#fig.savefig("../images/ch10/floodFill.png")
# 在OpenCV窗口中显示泛洪填充后的结果
# cv2.imshow('flood fill', im)
# cv2.waitKey()
# 保存结果
# cv2.imwrite('../images/ch10/floodFill.jpg',im)
译者使用的是matplotlib显示泛洪填充后的结果,上面代码底下的注释部分是用OpenCV显示泛洪填充的结果。如果你用OpenCV窗口显示上面运行的结果,可以反注释。下面是上面泛洪填充后的结果: 作为最后一个例子,我们看一下提取图像的SURF(加速稳健特征)特征。SURF是SIFT特征的一个快速版本。这里我们也会展示一些OpenCV绘制命令。
# -*- coding: utf-8 -*-
import cv2
import numpy
from pylab import *
# 读入图像
im = cv2.imread('../data/empire.jpg')
# 下采样
im_lowres = cv2.pyrDown(im)
# 转化为灰度图像
gray = cv2.cvtColor(im_lowres, cv2.COLOR_RGB2GRAY)
# 检测特征点
s = cv2.SURF()
mask = numpy.uint8(ones(gray.shape))
keypoints = s.detect(gray, mask)
# 显示图像及特征点
vis = cv2.cvtColor(gray, cv2.COLOR_GRAY2BGR)
for k in keypoints[::10]:
cv2.circle(vis, (int(k.pt[0]), int(k.pt[1])), 2, (0, 255, 0), -1)
cv2.circle(vis, (int(k.pt[0]), int(k.pt[1])), int(k.size), (0, 255, 0), 2)
cv2.imshow('local descriptors', vis)
cv2.waitKey()
cv2.imwrite('../images/ch10/ch10_P261_Fig10-3.jpg',vis)
上面代码先读入一幅图像,用pyrDown
下采样,得到的一幅尺寸是原图像尺寸一半的降采样图像,即im_lowres,然后将图像转换为灰度图像,并将它传递给SURF关键点检测对象。运行上面代码,可得下面SURF特征点检测结果:
10.3 视频处理
单纯利用Python处理视频是比较困难的,因为要考虑到速度、编解码器、摄像机、操作系统已经文件格式等问题。目前Python还没有视频处理库。Python处理视频的接口仅有的较好的就是OpenCV。在这一节,我们会展示一些对视频进行处理的基本例子。
10.3.1 视频输入
OpenCV能够很好地支持视频的读入。下面的例子展示了捕获视频帧,并在OpenCV窗口中显示它们:
import cv2
# setup video capture
cap = cv2.VideoCapture(0)
while True:
ret,im = cap.read()
cv2.imshow('video test',im)
key = cv2.waitKey(10)
if key == 27:
break
if key == ord(' '):
cv2.imwrite('vid_result.jpg',im)
上面VideoCapture从摄像头或文件中捕获视频。这里我们给它传递了一个整数作为初始化参数,它实际是视频设备的ID号,对于单个连着的摄像头,其ID号为0。read()方法解码并返回下一视频帧。waitKey()函数等待用户按下“Esc”(对应的ASCII码为27)终止应用,或按“space”将当前帧保存起来。
我们将上面例子进行拓展,使其能够在OpenCV窗口中对输出的视频进行模糊。对上面的例子进行稍微的修改便可实现该功能:
import cv2
# setup video capture
cap = cv2.VideoCapture(0)
# get frame, apply Gaussian smoothing, show result
while True:
ret,im = cap.read()
blur = cv2.GaussianBlur(im,(0,0),10)
cv2.imshow('camera blur',blur)
if cv2.waitKey(10) == 27:
break
上面对每一帧图像,将其传给GaussianBlur()函数,该函数实现对图像进行高斯滤波。在这个实例中,我们传递的是彩色图像,每一个颜色通道可以分别对其进行模糊。该函数以一个滤波器大小元组及高斯函数的标准差作为输入,如果滤波器大小设置为0,则它自动赋为标准差。运行上面的结果如下:
10.3.2 读取视频到NumPy数组
OpenCV可以从一个文件中读取视频帧序列,并将它们转换成NumPy数组。下面给出了一个从摄像头捕获视频,并将它们存储在NumPy数组中的例子。
import cv2
from pylab import *
# setup video capture
cap = cv2.VideoCapture(0)
frames = []
# get frame, store in array
while True:
ret,im = cap.read()
cv2.imshow('video',im)
frames.append(im)
if cv2.waitKey(10) == 27:
break
frames = array(frames)
# check the sizes
print im.shape
print frames.shape
上面每一帧数组会被添加到列表的末尾直到捕获终止。打印出的数组大小表示的帧数、高度、宽度、3。运行上面代码打印出的结果为:
(480, 640, 3)
(40, 480, 640, 3)
这里,记录了40帧。类似如这样的视频数据数组非常适合视频处理,比兔计算视频帧差异以及跟踪。
10.4 跟踪
跟踪是对视频帧序列中的物体进行跟踪。
10.4.1 光流法
10.4.2 Lucas-Kanade算法
Lucas-Kanade算法原理这里略,具体可以参阅中译本。
import lktrack
imnames = ['../data/bt/bt.003.pgm', '../data/bt/bt.002.pgm', '../data/bt/bt.001.pgm', '../data/bt/bt.000.pgm']
# create tracker object
lkt = lktrack.LKTracker(imnames)
# detect in first frame, track in the remaining
lkt.detect_points()
lkt.draw()
for i in range(len(imnames)-1):
lkt.track_points()
lkt.draw()
import lktrack
from pylab import *
imnames = ['../data/viff/viff.000.ppm', '../data/viff/viff.001.ppm',
'../data/viff/viff.002.ppm', '../data/viff/viff.003.ppm', '../data/viff/viff.004.ppm']
# track using the LKTracker generator
lkt = lktrack.LKTracker(imnames)
for im,ft in lkt.track():
print 'tracking %d features' % len(ft)
# plot the tracks
figure()
imshow(im)
for p in ft:
plot(p[0],p[1],'bo')
for t in lkt.tracks:
plot([p[0] for p in t],[p[1] for p in t])
axis('off')
show()