Python计算机视觉编程

Programming Computer Vision with Python

第十章 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的图像: ch10_P210_Reading-and-Writing-Images 函数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/目录下生成一幅保存有灰度图像和积分图像的图片: ch10_P211_Displaying-Images-and-Results 第二个例子从种子像素开始应用泛洪(漫水)填充:

    # -*- 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窗口显示上面运行的结果,可以反注释。下面是上面泛洪填充后的结果: floodFill 作为最后一个例子,我们看一下提取图像的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特征点检测结果: ch10_P261_Fig10-3

    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,则它自动赋为标准差。运行上面的结果如下: camera-blur

    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()
    

    LKtrack-bt

    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()
    

    ch10_fig10-6_generator