计算机视觉 第十章OpenCV

10.1 OpenCV的Python接口

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

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

python 复制代码
import cv2

10.2 OpenCV基础知识

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

10.2.1 读取和写入图像

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

python 复制代码
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()来实现。例如,可以通过下面的方式将原图像转换 成灰度图像:

python 复制代码
import cv2
#创建灰度图像
gray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)

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

cv2.COLOR_BGR2GRAY

cv2.COLOR_BGR2RGB

cv2.COLOR_GRAY2BGR

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

10.2.3 显示图像及结果

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

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

python 复制代码
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()创建一幅图像,该图像的每个像素值是原图上方和左边强度值相加后的结果;这对于快速评估特征是一个非常有用的技巧。

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

python 复制代码
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窗口中显示这些视频帧的完整例子:

python 复制代码
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窗口中实时显示经 模糊的(彩色)图像,我们只需对上面的例子做简单的修改:

python 复制代码
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数组中的例子:

python 复制代码
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求偏导可以得到光流方程:

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

python 复制代码
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控制流样本的间距。

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

修改为:

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

10.4.2 Lucas-Kanade算法

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

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

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

,或简记为

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

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

python 复制代码
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分别保存角点和 对这些角点进行跟踪的位置,同时,我们也利用一个变量对当前帧进行跟踪。

python 复制代码
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()
python 复制代码
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()
相关推荐
weixin_4374977711 小时前
读书笔记:Context Engineering 2.0 (上)
人工智能·nlp
喝拿铁写前端11 小时前
前端开发者使用 AI 的能力层级——从表面使用到工程化能力的真正分水岭
前端·人工智能·程序员
goodfat11 小时前
Win11如何关闭自动更新 Win11暂停系统更新的设置方法【教程】
人工智能·禁止windows更新·win11优化工具
北京领雁科技11 小时前
领雁科技反洗钱案例白皮书暨人工智能在反洗钱系统中的深度应用
人工智能·科技·安全
落叶,听雪11 小时前
河南建站系统哪个好
大数据·人工智能·python
清月电子12 小时前
杰理AC109N系列AC1082 AC1074 AC1090 芯片停产替代及资料说明
人工智能·单片机·嵌入式硬件·物联网
Dev7z12 小时前
非线性MPC在自动驾驶路径跟踪与避障控制中的应用及Matlab实现
人工智能·matlab·自动驾驶
七月shi人12 小时前
AI浪潮下,前端路在何方
前端·人工智能·ai编程
橙汁味的风12 小时前
1隐马尔科夫模型HMM与条件随机场CRF
人工智能·深度学习·机器学习
itwangyang52012 小时前
AIDD-人工智能药物设计-AI 制药编码之战:预测癌症反应,选对方法是关键
人工智能