目录
1 OpenCV的Python接口
OpenCV是一个C++库,它包含了计算机视觉领域的很多模块。除了C++和C,Python作为一种简洁的脚本语言,在C++代码基础上的Pyhton接口得到了越来越广泛的支持。
2 OpenCV基础知识
OpenCV自带读取、写入图像函数以及矩阵操作和数学库,在这部分介绍一些基本的组件及其使用方法。
2.1 读取和写入图像
下面的例子会载入一幅图像,打印出图像大小,对图像进行转换并保存为.png格式:
import cv2
im = cv2.imread('empire.jpg')
h, w = im.shape[:2]
print(h, w)
cv2.imwrite('result_empire.png', im)
函数imread()返回图像为一个标准的NumPy数组,并且该函数能够处理很多不同格式的图像。函数imwrite()会根据文件后缀自动转换图像。
运行结果:
2.2 颜色空间
在OpenCV中,图像不是按照传统的RGB颜色通道,而是按照BGR顺序存储的。读取图像时默认的是BGR,但是还有一些可用的转换函数。颜色空间的转换使用函数cvColor()来实现。可以使用下面的代码将原图像转为灰度图像:
import cv2
im = cv2.imread('empire.jpg')
gray = cv2.cvColor(im, cv2.COLOR_BGR2GRAY)
运行结果:
在读取图像之后,紧接其后的是OpenCV颜色转换代码,其中最有用的一些转换代码如下:
- cv2.COLOR_BGR2GRAY
- cv2.COLOR_BGR2RGB
- cv2.COLOR_GRAY2BGR
上面的每个转换代码中,转换后的图像颜色通道数与对应的转换代码相匹配。例如,对于灰度图像只有一个通道,对于RGB和BGR图像则有三个通道。最后的cv2.COLOR_GRAY2BGR将灰度图像转换为BGR彩色图像。
2.3 显示图像及结果
下面这个例子是从文件中读取一幅图像,并创建一个整数图像表示:
import cv2
im = cv2.imread('fisherman.jpg')
gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
intim = cv2.integral(gray)
intim = (255.0 * intim) / intim.max()
cv2.imwrite('result_fisherman.jpg', intim)
运行结果:
读取图像后,将其转化为灰度图像,函数integral()创建一幅图像,该图像每个像素值是原图上方和左边强度值相加后的结果。在保存图像前,通过除以图像中的像素最大值将其归一化到0至255之间。
下面这个例子是从一个种子像素进行泛洪填充:
import cv2
from numpy import *
filename = 'fisherman.jpg'
im = cv2.imread(filename)
h, w = im.shape[:2]
diff = (6, 6, 6)
mask = zeros((h + 2, w + 2), uint8)
cv2.floodFill(im, mask, (10, 10), (255, 255,0),diff, diff)
cv2.imshow('flood fill', im)
cv2.waitKey()
cv2.imwrite('result_fisherman_1.jpg', im)
运行结果:
在这个例子中,对图像应用泛洪填充并在OpenCV窗口显示。waitKey()函数一直处于暂停状态,直到有按键按下,此时窗口才会自动关闭。这里的floodFill()函数获取(灰色或彩色)图像、一个掩模、一个种子像素以及新的颜色代替下限和上限阈值差的泛洪像素。泛洪填充以种子像素为起始,只要能在阈值的差异范围内添加新的像素,泛洪填充就会持续扩展,
最后一个例子为SURF特征提取, SURF 特征是 SIFT 特征的一个更快特征提取版,其实现代码为:
import cv2
from pylab import *
im=cv2.imread('util/1.jpg')
# h,w=im.shape[:2]
# image=cv2.cvtColor(im,cv2.COLOR_RGB2GRAY)
im_lowers=cv2.pyrDown(im)
gray=cv2.cvtColor(im_lowers,cv2.COLOR_RGB2GRAY)
s= cv2.SIFT_create()
mask=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('Image',vis)
cv2.waitKey()
cv2.destroyAllWindows()
运行结果:
3 处理视频
单纯使用Python处理视频有些困难,因为需要考虑速度、编码器、摄像机、操作系统和文件格式。使用OpenCV的Python接口是一个很好的选择。
3.1 视频输入
OpenCV可以很好地支持从摄像头读取视频。下面给出一个捕获视频帧并在OpenCV窗口中显示这些视频帧的完整例子:
import cv2
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('video_result.jpg',im)
捕获对象VideoCapture从摄像头或文件捕获视频。通过一个整数进行初始化,该整数为视频设备的id。如果仅有一个摄像头和计算机相连,那么该摄像头的id为0。read()方法解码并返回下一视频帧,第一个变量ret是一个判断视频帧是否成功读入的标志,第二个变量则是实际读入的图像数组。函数waitKey()等待用户按键:若按下Esc则退出,若按下空格则保存该视频帧。
运行结果:
拓展上面的例子,将摄像头捕获的数据作为输入,并在 OpenCV 窗口中实时显示经模糊的(彩色)图像:
import cv2
cap = cv2.VideoCapture(0)
while True:
ret,im = cap.read()
blur = cv2.GaussianBlur(im, (0, 0), 5)
cv2.imshow('camera blur', blur)
if cv2.waitKey(10) == 27:
break
运行结果:
每一视频帧都会被传递给 GaussianBlur() 函数,该函数会用高斯滤波器对传入的该帧图像进行滤波。这里,我们传递的是彩色图像,所以 Gaussian Blur() 函数会录入对彩色图像的每一个通道单独进行模糊。该函数需要为高斯函数设定滤波器尺寸 (保存在元组中)及标准差;在本例中标准差设为 5。如果该滤波器尺寸设为 0,则它由标准差自动决定,显示出的结果与上图相似。
3.2 将视频读取到NumPy数组
使用 OpenCV 可以从一个文件读取视频帧,并将其转换成 NumPy 数组。下面是一个从摄像头捕获视频并将视频帧存储在一个 NumPy 数组中的例子:
import cv2
from numpy import *
cap = cv2.VideoCapture(0)
frames = []
while True:
ret,im = cap.read()
cv2.imshow('video',im)
frames.append(im)
if cv2.waitKey(10) == 27:
break
frames = array(frames)
print (im.shape)
print (frames.shape)
运行结果:
4 跟踪
跟踪是在图像序列或视频里对其中的目标进行跟踪的过程。
4.1 光流
光流是目标、场景或摄像机在连续两帧图像间运动时造成的目标的运动。它是图像在平移过程中的二维矢量场。
光流法主要依赖于三个假设。
- 亮度恒定 (图像中目标的像素强度在连续帧之间不会发生变化)。
- 时间规律 (相邻帧之间的时间足够短,以至于在考虑运行变化时可以忽略它们之 间的差异。该假设用于导出下面的核心方程)。
- 空间一致性 (相邻像素具有相似的运动)。
在很多情况下这些假设并不成立,但是对于相邻帧间的小运动以及短时间跳跃,它还是一个非常好的模型。
下面就是一个利用calcOpticalFlowFarneback()在视频中寻找运动矢量的例子:
import cv2
import numpy as np
from pylab import *
from PIL import Image
def draw_flow(im,flow,step=16):
h,w = im.shape[:2]
y,x = mgrid[step/2:h:step,step/2:w:step].reshape(2,-1)
fx, fy = flow[y, x].T
lines = vstack([x,y,x+fx,y+fy]).T.reshape(-1,2,2)
lines = int32(lines)
vis = cv2.cvtColor(im, cv2.COLOR_GRAY2BGR)
for (x1, y1), (x2, y2) in lines:
cv2.line(vis, (x1, y1), (x2, y2), (0, 255, 0), 1)
cv2.circle(vis, (x1, y1), 1, (0, 255, 0), -1)
return vis
cap = cv2.VideoCapture(0)
ret,im = cap.read()
prev_gray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
while True:
ret,im = cap.read()
gray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
flow = cv2.calcOpticalFlowFarneback(prev_gray,gray,None,0.5,3,15,3,5,1.2,0)
prev_gray = gray
cv2.imshow('Optical flow',draw_flow(gray,flow))
if cv2.waitKey(10) == 27:
break
运行结果:
在上述例子中,利用摄像头捕获图像,并对每个连续图像进行光流估计。由calcOpticalFlowFarneback()返回运动光流矢量保存在双通道图像变量flow中。辅助函数draw_flow()会在图像均匀间隔的点处绘制光流矢量,利用到了OpenCV的绘图函数line()和cirle(),并用变量step控制流样本的间距。
4.2 Lucas-Kanade算法
跟踪最基本的形式是跟随感兴趣点,比如角点。对此,一个流行的算法是Lucas-Kanade算法,它利用了稀疏光流算法。
Lucas-Kanade算法可以应用于任何一种特征,不过通常使用一些角点,例如Harris角点。如果基于每一个像素考虑,该光流方程是欠定方程,即每个方程中含很多未知变量。利用相邻像素有相同运动这一假设,对于n个相邻像素,可以写成一个系统方程,并用最小二乘法求解。对于周围像素的贡献可以进行加权处理,使越远的像素影响越小。最后求解超定方程组得出运动矢量。这些Lucas-Kanade包含在OpenCV中,接下来创建一个Python跟踪类:
from numpy import *
import cv2
lk_params = dict(winSize=(15,15),maxLevel=2,
criteria=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT,10,0.03))
subpix_params = dict(zeroZone=(-1,-1),winSize=(10,10),
criteria = (cv2.TERM_CRITERIA_COUNT | cv2.TERM_CRITERIA_EPS,20,0.03))
feature_params = dict(maxCorners=500,qualityLevel=0.01,minDistance=10)
class LKTracker(object):
def __init__(self,imnames):
self.imnames = imnames
self.features = []
self.tracks = []
self.current_frame = 0
用一个文件名列表对跟踪对象进行初始化,变量features和tracks分别保存角点和对这些角点进行跟踪的位置,同时利用一个变量对当前帧进行跟踪。在开始检测角点时,需要载入实际图像并转换为灰度图像,提取“利用跟踪好的特征”点。OpenCV的函数goodFeaturesToTrack()方法可以完成这一主要工作:
def detect_points(self):
self.image = cv2.imread(self.imnames[self.current_frame])
self.gray = cv2.cvtColor(self.image,cv2.COLOR_BGR2GRAY)
features = cv2.goodFeaturesToTrack(self.gray, **feature_params)
cv2.cornerSubPix(self.gray,features, **subpix_params)
self.features = features
self.tracks = [[p] for p in features.reshape((-1,2))]
self.prev_gray = self.gray
上述代码中的cornerSubPix()提炼角点位置,并保存在成员变量features和tracks中。需要注意的是,运行该函数会清楚跟踪历史。检测角点之后,还要对其进行跟踪。首先要获取一幅图像,然后应用OpenCV中的calcOpticalFlowPyrLK()找出这些点运动到哪里,最后清除这些包含跟踪点的列表。下面的函数track_points()可以完成该过程:
def track_points(self):
if self.features != []:
self.step()
self.image = cv2.imread(self.imnames[self.current_frame])
self.gray = cv2.cvtColor(self.image,cv2.COLOR_BGR2GRAY)
tmp = float32(self.features).reshape(-1, 1, 2)
features,status,track_error = cv2.calcOpticalFlowPyrLK(self.prev_gray,self.gray,tmp,None,**lk_params)
self.features = [p for (st,p) in zip(status,features) if st]
features = array(features).reshape((-1,2))
for i,f in enumerate(features):
self.tracks[i].append(f)
ndx = [i for (i,st) in enumerate(status) if not st]
ndx.reverse()
for i in ndx:
self.tracks.pop(i)
self.prev_gray = self.gray
下面定义一个辅助函数step()用于移动下一视频帧:
def step(self,framenbr=None):
if framenbr is None:
self.current_frame = (self.current_frame + 1) % len(self.imnames)
else:
self.current_frame = framenbr % len(self.imnames)
该方法会跳转到一个给定的视频帧,如果没有参数则直接跳转到下一帧。
最后,添加draw()方法绘出跟踪的结果:
def draw(self):
for point in self.features:
cv2.circle(self.image,(int(point[0][0]),int(point[0][1])),3,(0,255,0),-1)
cv2.imshow('LKtrack',self.image)
cv2.waitKey()
4.2.1 使用跟踪器
我们将该跟踪类应用于真是的场景中。下面的脚本初始化一个跟踪对象,对视频序列进行角点检测、跟踪,并画出跟踪结果:
import lktrack
imnames = ['bt.003.pgm', 'bt.002.pgm', 'bt.001.pgm', 'bt.000.pgm']
lkt = lktrack.LKTracker(imnames)
lkt.detect_points()
lkt.draw()
for i in range(len(imnames)-1):
lkt.track_points()
lkt.draw()
这里有个错误,features在detect后类型会发生变化,不能使用list数组的判断方式,可以添加numpy数组的判断,后可以正常运行
不修改代码的话,只会运行一遍,出只有绿点的图
运行结果:
4.2.2 使用发生器
将下面的方法添加到LKTracker类:
def track(self):
for i in range(len(self.imnames)):
if self.features == []:
self.detect_points()
else:
self.track_points()
f = array(self.features).reshape(-1,2)
im = cv2.cvtColor(self.image,cv2.COLOR_BGR2RGB)
yield im,f
上面的方法创建一个发生器,可以使遍历整个序列并将获得的跟踪点和这些图像以RGB数组保存,以便画出跟踪结果。
import lktrack
import cv2
import numpy as np
from pylab import *
from PIL import Image
imnames = ['bt.003.pgm', 'bt.002.pgm', 'bt.001.pgm', 'bt.000.pgm']
lkt = lktrack.LKTracker(imnames)
for im,ft in lkt.track():
print ('tracking %d features' % len(ft))
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()
运行结果: