计算机视觉 第十章OpenCV

10.1  OpenCV的Python接口

        OpenCV 是一个C++库,它包含了计算机视觉领域的很多模块。除了C++和C, Python 作为一种简洁的脚本语言,在C++代码基础上的Python接口得到了越来越 广泛的支持。

        cv2模块用到了NumPy数组,并且使用起来更加直观,可以通过以下方式导入新的cv2模块:

import cv2

10.2  OpenCV基础知识

        OpenCV 自带读取、写入图像函数以及矩阵操作和数学库。我们现在来看一些基本的组件及其使用方法。

        10.2.1  读取和写入图像

        下面的例子会载入一张图像,打印出图像大小,对图像进行转换并保存为.png格式:

import cv2

#读取图像
im = cv2.imread('xiaozhou.jpg')
#shape 属性返回一个包含图像维度的元组,前两个值分别是高度和宽度。
h,w = im.shape[:2]
print(h,w)

#保存图像
cv2.imwrite('result.png',im)

        函数imread()返回图像作为一个标准的Numpy数组,并且该函数能够处理很多不同格式的图像;函数imwrite()会根据文件后缀自动转换图像。

        10.2.2  颜色空间

        在OpenCV中,图像不是按传统的RGB颜色通道,而是按BGR顺序(即RGB的 倒序)存储的。颜色空 间的转换可以用函数cvColor()来实现。例如,可以通过下面的方式将原图像转换 成灰度图像:

import cv2
#创建灰度图像
gray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)

        在读取原图像之后,紧接其后的就是OpenCV颜色转换,有一些有用的转换代码:

        \bulletcv2.COLOR_BGR2GRAY

        \bulletcv2.COLOR_BGR2RGB

        \bulletcv2.COLOR_GRAY2BGR

        最后的cv2.COLOR_ GRAY2BGR将灰度图像转换成BGR彩色图像;如果你想在图像上绘制或覆盖有色彩 的对象,CV2.COLOR_GAY2BGR是非常有用的。

        10.2.3  显示图像及结果

        下面我们来看一些用OpenCV处理图像的例子,以及怎样利用OpenCV绘制功能和窗口管理功能来显示结果。

        例一是从文本中读取一幅图像,并创建一个整数图像表示:

import cv2
im = cv2.imread('touxiang.jpg')
#创建灰度图像
gray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
#计算积分图像
intim = cv2.integral(gray)

#归一化并保存
intim = (255.0*intim)/intim.max()
cv2.imwrite('gray_graph.png',intim)

        读取图像后,将其转化为灰度图像,函数integral()创建一幅图像,该图像的每个像素值是原图上方和左边强度值相加后的结果;这对于快速评估特征是一个非常有用的技巧。

        例二从一个种子像素泛洪填充:

import cv2
from numpy import *

#读取图像
im = cv2.imread('touxiang.jpg')
#下采样
im_lowers = cv2.pyrDown(im)
#变换成灰度图像
gray = cv2.cvtColor(im_lowers,cv2.COLOR_RGB2GRAY)
#检测特征点
s = cv2.SURF()
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('local descriptors',vis)
cv2.waitKey()

10.3  视频处理

        单纯使用Python来处理视频有些困难,因为要考虑速度、编解码器、摄像机、操作系统和文件格式。目前还没针对Python的视频库,使用OpenCV的Python接口是唯一不错的选择。

        10.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('vid_result.jpg',im)

        捕获对象VideoCapture从摄像头或文件捕获视频。read()方法解码并返回下一个视频帧,第一个变量ret是一个判断视频帧是否成功读入的标志,第二个变量则是实际读入的图像数组。函数waitKey()等待用户按键:如果按下的是Esc键(ASCII码是27),则退出应用;如果按下的是空格键,则保存该视频帧。上述代码运行结果如下图所示:

        将摄像头捕获的数据作为输入,并在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)
    key = cv2.waitKey(10)
    if key == 27:
        break

        每一视频帧都会被传递给GaussianBlur()函数,该函数会用高斯滤波器对传入的该 帧图像进行滤波。这里,我们传递的是彩色图像,所以Gaussian Blur()函数会录 入对彩色图像的每一个通道单独进行模糊。运行结果如下:

        10.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)

        上述代码将每一视频帧数组添加到列表末,直到捕获结束。最终得到的数组会有帧 数、帧高、帧宽及颜色通道数(3个),打印出的结果如下:

10.4  跟踪

        跟踪是在图像序列或视频里对其中的目标进行跟踪的过程。

        10.4.1  光流

        光流是目标、场景或摄像机在连续两帧图像间运动时造成的目标的运动。它是图像 在平移过程中的二维矢量场。作为一种经典并深入研究了的方法,它在诸如视频压 缩、运动估计、目标跟踪和图像分割等计算机视觉中得到了广泛的应用。

        光流法主要有三个假设:

        (1)亮度恒定:图像中目标的像素强度在连续帧之间不会发生变化。

        (2)时间规律:相邻帧之间的时间足够短,以至于在考虑运行变化时可以忽略它们之 间的差异。该假设用于导出下面的核心方程。

        (3)空间一致性:相邻像素具有相似的运动。

        假设一个目标像素在t时刻亮度为I(x,y,t),在t+δt时刻运 动[δx,δy] 后与t 时刻具有相同的亮度,即I(x,y,t)=I(x+δx, y+δy, t+δt). 对该约束用泰 勒公式进行一阶展开并关于t求偏导可以得到光流方程:

\triangledown I^Tv=-I_t

v=[u,v] 是运动矢量,It 是时间偏导。对于图像中那些单个的点,该方程是线性方程 组。由于v包含两个未知变量,所以该方程是不可解的。通过强制加入空间一致性 约束,则有可能获得该方程的解。OpenCV 包含了一些光流实现:

import cv2
from numpy import *
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()和circle(),并用变量step控制流样本的间距。

遇到的问题分别是:不是整型变量以及给定的值超出范围

修改为:

y,x = mgrid[step/2:h:step,step/2:w:step].reshape(2,-1).astype(int)
cv2.line(vis,(x1,y1),(x2,y2),(0,255,0),1)

        10.4.2  Lucas-Kanade算法

        跟踪最基本的形式是跟随感兴趣点,比如角点。对此,一次流行的算法是Lucas Kanade跟踪算法,它利用了稀疏光流算法。角点是结构张量(Harris矩阵)中有两个较大特征值的那些 点,且更小的特征值要大于某个阈值。

        如果基于每一个像素考虑,该光流方程组是欠定方程,即每个方程中含很多未知变 量。利用相邻像素有相同运动这一假设,对于n个相邻像素,可以将这些方程写成 如下系统方程:

\begin{bmatrix}\nabla I^T(\mathbf{x}_1)\\\nabla I^T(\mathbf{x}_2)\\\vdots\\\nabla I^T(\mathbf{x}_n)\end{bmatrix}\boldsymbol{v}=\begin{bmatrix}I_x(\mathbf{x}_1)&I_y(\mathbf{x}_1)\\I_x(\mathbf{x}_2)&I_y(\mathbf{x}_2)\\\vdots&\vdots\\I_x(\mathbf{x}_n)&I_y(\mathbf{x}_n)\end{bmatrix}\begin{bmatrix}\boldsymbol{u}\\\boldsymbol{v}\end{bmatrix}=-\begin{bmatrix}I_t(\mathbf{x}_1)\\I_t(\mathbf{x}_2)\\\vdots\\I_t(\mathbf{x}_n)\end{bmatrix}

        将上面的矩阵变换成结构张量形式,可以得出以下关系:

\overline{M_l}v=-\begin{bmatrix}I_t(\mathbf{x}_1)\\I_t(\mathbf{x}_2)\\\vdots\\I_t(\mathbf{x}_n)\end{bmatrix},或简记为Av=b

        该超定方程组可以用最小二乘法求解,得出运动矢量:

v=(A^TA)^{-1}A^Tb

        标准的Lucas-Kanade 跟踪适用于小位移;为了能够处理较大位移,需采用分层方法。在该情形下,光流可以通过对图像由粗到精采样计算得到。这就是OpenCV 函数calcOpticalFlowPyrLK() 要做的事。让我们看看怎样利用这些函数建立一个 Python 跟踪类。创建名为lktrack.py的文件,向其添加下面的类和构造函数:

import cv2
from numpy import *
#一些常数和默认参数
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):
    """用金字塔光流Lucas-Kanade跟踪类"""
    def __init__(self,imnames):
       """使用图像名称列表初始化"""
       self.imnames = imnames
       self.features = []
       self.tracks = []
       self.current_frame = 0
    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


    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)

            #reshape()操作,以适应输入格式
            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

    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)

    def draw(self):
        """用OpenCV自带的画图函数画出当前图像及跟踪点,按任意键关闭窗口"""
        #用绿色圆圈画出跟踪点
        for point in self.features:
            cv2.circle(self.image,(int(point[0][0]),int(point[0][0])),3,(0,255,0),-1)

        cv2.imshow('LKtrack',self.image)
        cv2.waitKey()

    def track(self):
        """发生器,用于遍历整个序列"""
        for i in range(len(self.imnames)):
            if self.features == []:
                self.detect_points()
            else:
                self.track_points()
        #创建一份RGB副本
        f = array(self.features).reshape(-1,2)
        im = cv2.cvtColor(self.image,cv2.COLOR_BGR2RGB)
        yield im,f



        用一个文件名列表对跟踪对象进行初始化,变量features和tracks分别保存角点和 对这些角点进行跟踪的位置,同时,我们也利用一个变量对当前帧进行跟踪。

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

import lktrack
from pylab import *
imnames = ['viff.000.ppm','viff.001.ppm','viff.002.ppm','viff.003.ppm','viff.004.ppm']
#用LKTracker发生器进行跟踪
lkt = lktrack.LKTracker(imnames)
for im,ft in lkt.track():
    print('tracking %d features' % len(ft))

#画出轨迹
figure()
imshow()
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()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值