简单物体追踪和几何转换
一、更改颜色空间
有时候,我们需要对图像的颜色空间进行转换,比如说在图像进行阈值处理、边缘检测时,三个通道的图像难以进行梯度检测,所以我们进行色域转换:BGR <-> gray, BGR <-> HSV
另外,接下来我们将会尝试将一个视频中的有色物体提取出来。
下面我们将会学习两个函数:cv.cvtColor()转换颜色空间,cv.inRange()等
1. 改变图像色域
OpenCV里面包含了150多种色域转换的方式,但是我们只需关注两种:BGR <-> gray, BGR <-> HSV,色域转换函数如下:
- cv.cvtColor(input_image, flag) ,其中flag决定了转换类型,有如下取值:
- cv.COLOR_BGR2GRAY,实现BGR -> gray 转换
- 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图像中,我们更易于表示一种颜色。接下来的应用中,我们将使用以下步骤来提取有色物体:
- 获取到视频的每一帧
- 将帧图像的色域从BGR转换到HSV
- 将HSV图像阈值处理为蓝色范围
- 单独提取出蓝色,便于我们之后处理
- cv.inRange的用法: cv.inRange( src, lowerb, upperb[, dst] ) -> dst,得到一副二值图像
- OpenCV中HSV的BGR角度范围:
- 红色:0
- 绿色:60
- 蓝色:120
- 黄色:30
- 青色:90
- 紫色: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()
- 注意: 可以看见图像中存在一些噪声,稍后我们会讨论解决办法,这是图像追踪的最最简单的一种方式,若是学写了轮廓函数,我们就能做很多事情,比如找到物体的质心,然后用它来跟踪物体,仅仅通过在摄像机前移动你的手绘制图表,还有其他有趣的事情。
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)
# 进行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函数完成此功能。图像大小能手动设置也可以传入缩放因子。根据传入缩放因子的不同,实现的功能也不同:
- cv.INTER_AREA:缩小
- 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)方向的偏移,并且让他转换到,那么你就可以构造出一个变换矩阵M ,。
我们可以将这个矩阵使用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)
- 注意:cv.warpAffine ()函数的第三个参数是输出图像的大小,它应该是 (width,height) 的形式。记住 width = 列数,height = 行数。
3. 图像旋转
旋转角度为的图像旋转是透过变换矩阵来达到的:,这种方法未指定旋转中心。
但是 OpenCV 提供了可调节旋转中心的缩放旋转,这样便可以以任何位置为中心旋转。修改后的变换矩阵为:,
其中 center.x, center.y代表了旋转中心的坐标,而,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)
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()
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()