OpenCV Tutorials 03 - 简单物体追踪和几何转换

2,659 阅读7分钟

简单物体追踪和几何转换

一、更改颜色空间

有时候,我们需要对图像的颜色空间进行转换,比如说在图像进行阈值处理、边缘检测时,三个通道的图像难以进行梯度检测,所以我们进行色域转换:BGR <-> gray, BGR <-> HSV

另外,接下来我们将会尝试将一个视频中的有色物体提取出来。

下面我们将会学习两个函数:cv.cvtColor()转换颜色空间,cv.inRange()等

1. 改变图像色域

OpenCV里面包含了150多种色域转换的方式,但是我们只需关注两种:BGR <-> gray, BGR <-> HSV,色域转换函数如下:

  • cv.cvtColor(input_image, flag) ,其中flag决定了转换类型,有如下取值:
    1. cv.COLOR_BGR2GRAY,实现BGR -> gray 转换
    2. cv.COLOR_BGR2HSV,实现BGR -> HSV 转换
import cv2 as cv
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
%matplotlib inline
def cv_show(name, img):
    cv.imshow(name, img)
    cv.waitKey(0)
    cv.destroyAllWindows()
def compare(imgs):
  #  for i in range(len(imgs)):
 #       imgs[i][:,-3:-1,:] = [255,255,255]
    res = np.hstack(imgs)
    cv_show('Compare', res)
# 打印查看flag的取值
flags = [i for i in dir(cv) if i.startswith('COLOR_')]
flags
['COLOR_BAYER_BG2BGR', 'COLOR_BAYER_BG2BGRA', 'COLOR_BAYER_BG2BGR_EA', 'COLOR_BAYER_BG2BGR_VNG', 'COLOR_BAYER_BG2GRAY', ... 'COLOR_YUV420sp2GRAY', 'COLOR_YUV420sp2RGB', 'COLOR_YUV420sp2RGBA', 'COLOR_mRGBA2RGBA']
  • 注意:对于 HSV,色相范围是[0,179] ,饱和度范围是[0,255] ,值范围是[0,255]。不同的软件使用不同的尺度。因此,如果在OpenCV中使用HSV ,则需规范化这些取值范围

2. 物体跟踪

在了解到BGR图像转化为HSV图像的方法之后,我们可以使用它来提取有色对象。相比较BGR图像爱,在HSV图像中,我们更易于表示一种颜色。接下来的应用中,我们将使用以下步骤来提取有色物体:

  1. 获取到视频的每一帧
  2. 将帧图像的色域从BGR转换到HSV
  3. 将HSV图像阈值处理为蓝色范围
  4. 单独提取出蓝色,便于我们之后处理
  • cv.inRange的用法: cv.inRange( src, lowerb, upperb[, dst] ) -> dst,得到一副二值图像
  • OpenCV中HSV的BGR角度范围:
    1. 红色:0
    2. 绿色:60
    3. 蓝色:120
    4. 黄色:30
    5. 青色:90
    6. 紫色:150
cap = cv.VideoCapture(0)

if not cap.isOpened():
    print('打开文件失败!')
    exit()
    
while(1):
    # 逐帧提取
    _, frame = cap.read()
    
    # 转换色域

    hsv = cv.cvtColor(frame, cv.COLOR_BGR2HSV)
    
    # 设定蓝色色域的上下限,参数分别为:颜色角度、饱和度、明度
    lower_blue = np.array([0,50,50])
    upper_blue = np.array([20,255,255])
    
    # 在对应颜色范围就置为255,不在的话就归0
    mask = cv.inRange(hsv, lower_blue, upper_blue)
    
    # 滤出对应的颜色范围图像
    res = cv.bitwise_and(frame,frame, mask= mask)
    
    
    cv.imshow('frame',frame)
    cv.imshow('mask',mask)
    cv.imshow('res',res)
    k = cv.waitKey(1) & 0xFF
    if k == 27:
        break
cv.destroyAllWindows()

图像追踪.PNG

  • 注意: 可以看见图像中存在一些噪声,稍后我们会讨论解决办法,这是图像追踪的最最简单的一种方式,若是学写了轮廓函数,我们就能做很多事情,比如找到物体的质心,然后用它来跟踪物体,仅仅通过在摄像机前移动你的手绘制图表,还有其他有趣的事情。

3. 怎样了解我们需要跟踪的HSV值?

我们可以在网站:www.stackoverflow.com/ 寻求相关答案。另外,我们也可以使用 cv.cvtColor()函数来进行BGR到HSV的转换。不需要传入图像,只需要传入对应的BGR值便可。

# 查看Green的HSV值

# 现将BGR格式的纯绿色定义出来
green_BGR = np.uint8([[[0, 255, 0]]])
green_BGR.shape
(1, 1, 3)

一行一列三通道,定义多行多列可以直接使用np.zeros

red_BGR = np.zeros((255, 400, 3))
red_BGR[...,2] = 255
cv_show('Red', red_BGR)

定义纯色图像.PNG

# 进行BGR2HSV转化
green_HSV = cv.cvtColor(green_BGR, cv.COLOR_BGR2HSV)

green_HSV
array([[[ 60, 255, 255]]], dtype=uint8)

现在你把[ H-10,100,100] 和 [ H + 10,255,255]分别作为下界和上界。除了这个方法,你可以使用任何图像编辑工具,如 GIMP 或任何在线转换器来找到这些值,但不要忘记调整 HSV 范围(主要是第一个角度的范围,要除二)。

4. 练习

尝试提取出多种颜色的物体,如蓝色、绿色一起提取

cap = cv.VideoCapture('Tg.mp4')

if not cap.isOpened():
    print('打开文件失败!')
    exit()
    
while(1):
    # 逐帧提取
    _, frame = cap.read()
    
    # 转换色域

    hsv = cv.cvtColor(frame, cv.COLOR_BGR2HSV)
    
    # 设定黄色色域的上下限,参数分别为:颜色角度、饱和度、明度
    lower_yellow = np.array([20,100,100])
    upper_yellow = np.array([40,255,255])
    
    # 设定红色色域的上下限,参数分别为:颜色角度、饱和度、明度
    lower_red = np.array([0,100,100])
    upper_red = np.array([20,255,255])
    
    # 在对应颜色范围就置为255,不在的话就归0
    mask_yellow = cv.inRange(hsv, lower_yellow, upper_yellow)
    mask_red = cv.inRange(hsv, lower_red, upper_red)
    
    
    # 要实现多种颜色提取,则需要将多种颜色的mask进行位运算的或操作,将结果再和原图像相与
    mask = cv.bitwise_or(mask_yellow,mask_red)
    
    # 滤出对应的颜色范围图像
    res = cv.bitwise_and(frame,frame, mask= mask)
    
    
    cv.imshow('frame',frame)
    cv.imshow('mask',mask)
    cv.imshow('res',res)
    k = cv.waitKey(1) & 0xFF
    if k == 27:
        break
cv.destroyAllWindows()

二、图像几何变换

本节主要包括应用不同的几何变换图像,如平移,旋转,仿射变换等。主要用到的函数有: cv.getPerspectiveTransform()

1. 重塑图像大小

OpenCV内置了两个转换函数: cv.warpAffine 和 cv.warpPerspective,可以帮助我们进行各类转换。前者的转换矩阵为 2 * 3,而后者转换矩阵尺寸为 3 * 3

  • 图像缩放:图像缩放就是重新设置图像的大小。OpenCV提供了cv.resize函数完成此功能。图像大小能手动设置也可以传入缩放因子。根据传入缩放因子的不同,实现的功能也不同:
  1. cv.INTER_AREA:缩小
  2. cv.INTER_CUBIC(缓慢) 和 cv.INTER_LINEAR:放大

一般说来,cv.INTER_LINEAR方法适用于所有的重塑图像大小情况。

img = cv.imread('lena.png')
# 第二个参数是指定图像大小,传入None或(0,0)则表明要按比例缩放
# 后面要传入fx, fy
res = cv.resize(img, None, fx = 2, fy = 2, interpolation = cv.INTER_CUBIC )
cv_show('T',res)
# 下面功能和上面一致
height, width = img.shape[:2]
res = cv.resize(img,(2*width, 2*height), interpolation = cv.INTER_CUBIC)

2. 图像平移

平移就是图像位置的转换,若你明确了(x, y)方向的偏移,并且让他转换到(tx,ty)(t_x, t_y),那么你就可以构造出一个变换矩阵M ,M=[10tx01ty] M =\begin{bmatrix} 1 & 0 & t_x\\ 0 & 1 & t_y \end{bmatrix}

我们可以将这个矩阵使用Numpy的数组类型(其中的数据类型要保持为 np.float32)存储,便于之后的矩阵运算或传递给 cv.warpAffine ()函数

img = cv.imread('lena.png',0)
rows,cols = img.shape
M = np.float32([[1,0,100],[0,1,50]])
dst = cv.warpAffine(img,M,(cols,rows))

cv_show('result', dst)

图像平移.PNG

  • 注意:cv.warpAffine ()函数的第三个参数是输出图像的大小,它应该是 (width,height) 的形式。记住 width = 列数,height = 行数。

3. 图像旋转

旋转角度为θ\theta的图像旋转是透过变换矩阵来达到的:M=[cosθsinθsinθcosθ]M=\left[\begin{array}{cc} \cos \theta & -\sin \theta \\ \sin \theta & \cos \theta \end{array}\right],这种方法未指定旋转中心。

但是 OpenCV 提供了可调节旋转中心的缩放旋转,这样便可以以任何位置为中心旋转。修改后的变换矩阵为:[αβ(1α) center. xβ center. yβαβ center. x+(1α) center. y]\left[\begin{array}{ccc} \alpha & \beta & (1-\alpha) \cdot \text { center. } x-\beta \cdot \text { center. } y \\ -\beta & \alpha & \beta \cdot \text { center. } x+(1-\alpha) \cdot \text { center. } y \end{array}\right],

其中 center.x, center.y代表了旋转中心的坐标,而α= scale cosθβ= scale sinθ\begin{aligned} \alpha &=\text { scale } \cdot \cos \theta \\ \beta &=\text { scale } \cdot \sin \theta \end{aligned},scale是缩放比例

上面仅是数学推理过程,OpenCV提供了cv.getRotationMatrix2D 函数来帮助我们找到上面的变换矩阵。

img = cv.imread('lena.png',-1)
rows,cols = img.shape[:2]
# (cols-1)/2.0,(rows-1)/2.0)为图像中心坐标,第二个参数为旋转角度theta(逆时针),第三个参数为缩放比例
M = cv.getRotationMatrix2D(((cols-1)/2.0,(rows-1)/2.0),180,2)
dst = cv.warpAffine(img,M,(cols,rows))

cv_show('result', dst)

图像旋转.PNG

4. 仿射变换

在仿射变换中,原始图像中的所有平行线在输出图像中仍然是平行的。为了找到变换矩阵,我们需要从输入图像中找到3个点,并在输出图像中找到它们的对应位置。然后,cv.getafinetransform 将创建一个2x3矩阵,该矩阵将传递给 cv.warpAffine进行仿射变换。

img = cv.imread('lena.png',-1)

rows,cols,ch = img.shape
# 找到原图点
pts1 = np.float32([[50,50],[200,50],[50,200]])
# 规定原图变换后所在点
pts2 = np.float32([[10,100],[200,50],[100,250]])

# 解出仿射变换矩阵
M = cv.getAffineTransform(pts1,pts2)
# 变换
dst = cv.warpAffine(img,M,(cols,rows))
plt.subplot(121),plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB)),plt.title('Input')
plt.subplot(122),plt.imshow(cv.cvtColor(dst, cv.COLOR_BGR2RGB)),plt.title('Output')
plt.show()

output_50_0.png

5. 透视变换

对于透视转换,你需要一个3 × 3的变换矩阵。直线即使在变换之后也会保持直线。为了找到这个变换矩阵,你需要在输入图像和输出图像上对应的4个点。在这4点中,有3点不应该是共线的。然后可以通过函数 cv.getPerspectiveTransform 找到变换矩阵。然后应用 cv.warpPerspective 填入这个3x3变换矩阵。

rows,cols,ch = img.shape
# 找到四个透视点
pts1 = np.float32([[56,65],[368,52],[28,387],[389,390]])
pts2 = np.float32([[0,0],[300,0],[0,300],[300,300]])

# 解出透视变换矩阵
M = cv.getPerspectiveTransform(pts1,pts2)

# 应用透视变换矩阵得到结果
dst = cv.warpPerspective(img,M,(300,300))
plt.subplot(121),plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB)),plt.title('Input')
plt.subplot(122),plt.imshow(cv.cvtColor(dst, cv.COLOR_BGR2RGB)),plt.title('Output')
plt.show()

output_53_0.png