Python计算机视觉编程——第10章 OpenCV

目录

10.1 OpenCV的Python接口

10.2 OpenCV基础知识

10.2.1 读取和写入图像

10.2.2 颜色空间

10.2.3 显示图像及结果

10.3 处理视频

10.3.1 视频输入

10.3.2 将视频读取到NumPy数组中

10.4 跟踪

10.4.2 Lucas-Kanade算法

1.使用跟踪器

2.使用发生器


10.1 OpenCV的Python接口

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

在之后的示例代码中,若 import cv2 语句报错,请参考下面这篇文章:https://blog.csdn.net/weixin_44468210/article/details/109031178

10.2 OpenCV基础知识

10.2.1 读取和写入图像

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

import cv2
# 读取图像
im = cv2.imread('5.jpg')
h,w = im.shape[:2]
print (h,w)
# 保存图像
cv2.imwrite('5-1.png',im)

 Python计算机视觉编程——第10章 OpenCV_第1张图片

函数 imread() 返回图像为一个标准的 Numpy 数组,并且该函数能够处理很多不同格式的图像。 

10.2.2 颜色空间

在OpenCV中,图像不是按传统的RGB颜色通道,而是按BGR顺序(即RGB的倒叙)存储的。读取图像时默认的是BGR,但是还有一些可用的转换函数。颜色空间的转换可以用函数cvtColor()来实现。

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

  1. cv2.COLOR_BGR2GRAY (原图像转化为灰度图像)
  2. cv2.COLOR_BGR2RGB (原图像转化为RGB图像)
  3. cv2.COLOR_GRAY2BGR (灰度图像图像转化为BGR图像)
# -*- coding: utf-8 -*-
import cv2
import numpy as np
from pylab import  *
from PIL import Image


# 添加中文字体支持
from matplotlib.font_manager import FontProperties
font = FontProperties(fname=r"c:\windows\fonts\SimSun.ttc", size=14)

# 读取图像
im = imread('18.jpg')
im1 = cv2.imread('18.jpg')
gray = cv2.cvtColor(im1,cv2.COLOR_BGR2GRAY)
BGR = cv2.cvtColor(gray,cv2.COLOR_GRAY2BGR)
figure()
subplot(131)
title(u'(a)原始图像', fontproperties=font)
axis('off')
imshow(im)
subplot(132)
title(u'(b)COLOR_BGR2GRAY', fontproperties=font)
axis('off')
imshow(gray)
subplot(133)
title(u'(c)COLOR_GRAY2RGB', fontproperties=font)
axis('off')
imshow(BGR)
show()

Python计算机视觉编程——第10章 OpenCV_第2张图片

10.2.3 显示图像及结果

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

# -*- coding: utf-8 -*-
import cv2
import numpy as np
from pylab import  *
from PIL import Image
# 添加中文字体支持
from matplotlib.font_manager import FontProperties
font = FontProperties(fname=r"c:\windows\fonts\SimSun.ttc", size=14)

# 读取图像
im = cv2.imread('18.jpg')
gray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
# 计算积分图像
intim = cv2.integral(gray)
# 归一化并保存
intim = (255.0*intim) / intim.max()

figure()
#subplot(121)
title(u'归一化后图像', fontproperties=font)
axis('off')
imshow(intim)
show()

Python计算机视觉编程——第10章 OpenCV_第3张图片

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

从一个种子像素进行泛洪填充:

# -*- 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 = '18.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)

 Python计算机视觉编程——第10章 OpenCV_第4张图片

若使用书上示例代码的OpenCV窗口中显示结果,尺寸过大的图片会显示不完全。

下面是一个SURF特征提取的例子:

# -*- coding: utf-8 -*-
import cv2
import numpy
from pylab import *

# 读入图像
im = cv2.imread('18.jpg')
# 下采样
im_lowres = cv2.pyrDown(im)
# 转化为灰度图像
gray = cv2.cvtColor(im_lowres, cv2.COLOR_RGB2GRAY)
# 检测特征点
s = cv2.xfeatures2d.SURF_create()
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()

xfeatures2d函数在python3.7已经因为专利而无法使用,若要使用需要降级python版本,在此援引他人实验结果:

(30条消息) AttributeError: module 'cv2' has no attribute 'xfeatures2d'_AllyLi0224的博客-CSDN博客

(30条消息) Python计算机视觉编程第十章——OpenCV基础知识_橘子果酱CV的博客-CSDN博客

 

10.3 处理视频

10.3.1 视频输入

OpenCV 能够很好地支持从摄像头读取视频。下面给出了一个捕获视频帧并在 OpenCV 窗口中显示这些视频帧的完整例子:

# -*- coding: utf-8 -*-
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('videoframe.jpg',im)

Python计算机视觉编程——第10章 OpenCV_第5张图片 

捕获对象 VideoCapture 从摄像头或文件捕获视频。通过一个整数进行初始化,该整数为视频设备的 id;如果仅有一个摄像头与计算机相连接,那么该摄像头的 id 为 0。read() 方法解码并返回下一视频帧,第一个变量 ret 是一个判断视频帧是否 成功读入的标志,第二个变量im则是实际读入的图像数组。函数 waitKey() 等待用户 按键:如果按下的是 Esc 键(ASCII 码是 27)键,则退出应用;如果按下的是空格键,就保存该视频帧。

拓展上面的例子,将摄像头捕获的数据作为输入,并在 OpenCV 窗口中实时显示经模糊的(彩色)图像:

# -*- coding: utf-8 -*-
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

Python计算机视觉编程——第10章 OpenCV_第6张图片

每一视频帧都会被传递给 GaussianBlur() 函数,该函数会用高斯滤波器对传入的该帧图像进行滤波。这里,我们传递的是彩色图像,所以 Gaussian Blur() 函数会录入对彩色图像的每一个通道单独进行模糊。该函数需要为高斯函数设定滤波器尺寸 (保存在元组中)及标准差;在本例中标准差设为 5。如果该滤波器尺寸设为 0,则它由标准差自动决定,显示出的结果与上图相似。

10.3.2 将视频读取到NumPy数组中

使用 OpenCV 可以从一个文件读取视频帧,并将其转换成 NumPy 数组。下面是一个从摄像头捕获视频并将视频帧存储在一个 NumPy 数组中的例子:

# -*- coding: utf-8 -*-
import cv2
import numpy as np
from numpy import array
# 设置视频捕获
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 跟踪

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

光流法主要依赖于三个假设。

  1. 亮度恒定 图像中目标的像素强度在连续帧之间不会发生变化。
  2. 时间规律 相邻帧之间的时间足够短,以至于在考虑运行变化时可以忽略它们之间的差异。该假设用于导出下面的核心方程。
  3. 空间一致性 相邻像素具有相似的运动。

在很多情况下这些假设并不成立,但是对于相邻帧间的小运动以及段时间跳跃,他还是一个非常好的模型。

Python计算机视觉编程——第10章 OpenCV_第7张图片

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

运行下面的脚本:

# -*- coding: utf-8 -*-
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).astype(int)
    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

Python计算机视觉编程——第10章 OpenCV_第8张图片

Python计算机视觉编程——第10章 OpenCV_第9张图片

在该例子中,利用摄像头捕获图像,并对每个连续图像对进行光流估计。由 calcOpticalFlowFarneback() 返回的运动光流矢量保存在双通道图像变量 flow 中。辅助函数 draw_flow() 会在图像均匀间隔的点处绘制光流矢量, 它利用 OpenCV 的绘图函数 line() 和 circle(),并用变量 step 控制流样本的间距。 最终的结果上图所示:圆环网格表示流样本的位置,带有线条的流矢量显示了每个样本点是怎样运动的。

10.4.2 Lucas-Kanade算法

跟踪最基本的形式是跟随感兴趣点,比如角点。对此,一次流行的算法是 Lucas-Kanade 跟踪算法,它利用了稀疏光流算法。

Lucas-Kanade 跟踪算法可以应用于任何一种特征,不过通常使用一些角点,角点是结构张量(Harris 矩阵)中有两个较大特征值的那些点,且更小的特征值要大于某个阈值。

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

\left[\begin{array}{c} \nabla I^{T}\left(x_{1}\right) \\ \nabla I^{T}\left(x_{2}\right) \\ \cdot \\ \cdot \\ \cdot \\ \nabla I^{T}\left(x_{n}\right) \end{array}\right] v=\left[\begin{array}{cc} I_{x}\left(x_{1}\right) & I_{y}\left(x_{1}\right) \\ I_{x}\left(x_{2}\right) & I_{y}\left(x_{2}\right) \\ \cdot & \cdot \\ \cdot & \cdot \\ \cdot & \cdot \\ I_{x}\left(x_{n}\right) & I_{y}\left(x_{n}\right) \end{array}\right]\left[\begin{array}{l} u \\ v \end{array}\right]=-\left[\begin{array}{c} I_{t}\left(x_{1}\right) \\ I_{t}\left(x_{2}\right) \\ \cdot \\ \cdot \\ \cdot \\ I_{t}\left(x_{n}\right) \end{array}\right]

该系统方程的优势是,现在方程的数目多于未知变量,并且可以用最小二乘法解出该系统方程。对于周围像素的贡献可以进行加权处理,使越远的像素影响越小;高斯权重是一种最常见的选择。可以得出以下关系:

Python计算机视觉编程——第10章 OpenCV_第10张图片

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

\boldsymbol{v}=\left(\boldsymbol{A}^{T} \boldsymbol{A}\right)^{-1} \boldsymbol{A}^{T} \boldsymbol{b}

标准的 Lucas-Kanade 跟踪适用于小位移;为了能够处理较大位移,需要采用分层的方法。在该情形下,光流可以通过对图像由粗到精采样计算得到。这就是 OpenCV 函数 calcOpticalFlowPyrLK() 要做的事。

这些 Lucas-Kanade 函数包含在 OpenCV 中,建立一个 Python 跟踪类。创建名为 lktrack.py  的文件,向其添加下面的类和构造函数:

# -*- coding: utf-8 -*-
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):
    """  用金字塔光流 Lucas-Kanade 跟踪类"""

    def __init__(self,imnames):
        """  使用图像名称列表初始化 """

        self.imnames = imnames
        self.features = []
        self.tracks = []
        self.current_frame = 0

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

    def draw(self):
        """  用 OpenCV 自带的画图函数画出当前图像及跟踪点,按任意键关闭窗口 """

        # 用绿色圆圈画出跟踪点
        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()

1.使用跟踪器

将该跟踪类用于真实的场景中。下面的脚本初始化一个跟踪对象,对视频序列进行角点检测、跟踪,并画出跟踪结果:

# -*- coding: utf-8 -*-
import lktrack
imnames = ['   .pgm', '   .pgm', '   .pgm', '   .pgm']
# 创建跟踪对象
lkt = lktrack.LKTracker(imnames)
# 在第一帧进行检测,跟踪剩下的帧
lkt.detect_points()
lkt.draw()
for i in range(len(imnames)-1):
    lkt.track_points()
    lkt.draw()

Python计算机视觉编程——第10章 OpenCV_第11张图片

每次画出一帧,并显示当前跟踪到的点,按任意键会转移到序列的下一帧。上图显示了牛津corridor 序列的前 4 幅图像的跟踪结果。

2.使用发生器

将下面的方法添加到 LKTracker 类:

 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

上面的方法创建一个发生器,可以使遍历整个序列并将获得的跟踪点和这些图像以 RGB 数组保存,以方便画出跟踪结果。将它用于经典的牛津“dinosaur”序列,并画出这些点及这些点的跟踪结果,代码如下:

# -*- coding: utf-8 -*-
import lktrack
import cv2
import numpy as np
from pylab import  *
from PIL import Image
imnames = ['   .pgm', '   .pgm', '   .pgm', '   .pgm']
# 用 LKTracker 发生器进行跟踪
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()

Python计算机视觉编程——第10章 OpenCV_第12张图片

 

你可能感兴趣的:(计算机视觉,python,opencv)