转换颜色空间
'''目标
在本教程中,您将学习如何将图像从一种颜色空间转换为另一种颜色空间,例如 BGR ↔ Gray、BGR ↔ HSV 等。
除此之外,我们将创建一个应用程序来提取视频中的彩色对象
您将学习以下函数:cv.cvtColor()、cv.inRange() 等。
改变色彩空间
OpenCV 中有超过 150 种颜色空间转换方法可用。 但是我们只研究两个,它们是最广泛使用的:BGR ↔ Gray 和 BGR ↔ HSV。
对于颜色转换,我们使用函数 des=cv.cvtColor(input_image, flag) 其中 flag 确定转换的类型。
对于 BGR → gray转换,我们使用标志 cv.COLOR_BGR2GRAY。 同样对于 BGR → HSV,我们使用标志 cv.COLOR_BGR2HSV。 要获取其他标志,只需在 Python 终端中运行以下命令:'''
import cv2 as cv
flags = [i for i in dir(cv) if i.startswith('COLOR_')]
print( flags )#返回很多的flag值
'''对于 HSV,色调范围为 [0,179],饱和度范围为 [0,255],值范围为 [0,255]。 不同的软件使用不同的尺度。 因此,如果您将 OpenCV 值与它们进行比较,则需要对这些范围进行归一化。'''
'''现在我们知道如何将 BGR 图像转换为 HSV,我们可以使用它来提取彩色对象。 在 HSV 中,表示颜色比在 BGR 颜色空间中更容易。 在我们的应用程序中,我们将尝试提取蓝色对象。 所以这里是方法:
拍摄视频的每一帧
从 BGR 转换为 HSV 颜色空间
我们将 HSV 图像阈值设置为蓝色范围
现在单独提取蓝色对象,我们可以在该图像上做任何我们想做的事情。
下面是详细注释的代码:'''
# 相当于追踪蓝色对象
import numpy as np
cap = cv.VideoCapture(0)
while(1):
# Take each frame
_, frame = cap.read()
# Convert BGR to HSV
hsv = cv.cvtColor(frame, cv.COLOR_BGR2HSV)
# define range of blue color in HSV
lower_blue = np.array([110,50,50])
upper_blue = np.array([130,255,255])
# Threshold the HSV image to get only blue colors
# threshold n. 门槛; 门口; 阈; 界; 起始点; 开端; 起点; 入门;
mask = cv.inRange(hsv, lower_blue, upper_blue)
# Bitwise-AND mask and original image 按位与
res = cv.bitwise_and(frame,frame, mask= mask)
cv.imshow('frame',frame)
cv.imshow('mask',mask)
cv.imshow('res',res)
k = cv.waitKey(5) & 0xFF
if k == 27:
break
cv.destroyAllWindows()
'''图像中有一些噪点。 我们将在后面的章节中看到如何删除它。
这是对象跟踪中最简单的方法。 一旦你学会了轮廓的功能,你就可以做很多事情,比如找到物体的质心并用它来跟踪物体,只需在相机前移动你的手就可以绘制图表,以及其他有趣的事情。
如何找到要跟踪的 HSV 值?
这是在 stackoverflow.com 中发现的一个常见问题。 它非常简单,您可以使用相同的函数 cv.cvtColor()。
您只需传递所需的 BGR 值,而不是传递图像。 例如,要查找 Green 的 HSV 值,请在 Python 终端中尝试以下命令:'''
green = np.uint8([[[0,255,0 ]]])
hsv_green = cv.cvtColor(green,cv.COLOR_BGR2HSV)#此处传递bgr值,而不是图像
print( hsv_green )#[[[ 60 255 255]]]
'''现在您将 [H-10, 100,100] 和 [H+10, 255, 255] 分别作为下限和上限。 除了这种方法,您可以使用任何图像编辑工具
,如 GIMP 或任何在线转换器来查找这些值,但不要忘记调整 HSV 范围。'''
几何变化
1:缩放
'''学习对图像应用不同的几何变换,如平移、旋转、仿射变换等。
您将看到这些函数: cv.getPerspectiveTransform转换
OpenCV 提供了两个转换函数,cv.warpAffine 和 cv.warpPerspective,您可以使用它们执行各种转换。 cv.warpAffine 采用 2x3 变换矩阵,而 cv.warpPerspective 采用 3x3 变换矩阵作为输入。
Scaling:缩放:调整图像的大小。 cv.resize()。 图像的大小可以手动指定,也可以指定缩放因子。 使用了不同的插值方法。
首选的插值方法是用于缩小的 cv.INTER_AREA 和用于缩放的 cv.INTER_CUBIC(慢)和 cv.INTER_LINEAR。
默认情况下,插值方法 cv.INTER_LINEAR 用于所有调整大小的目的。 您可以使用以下任一方法调整输入图像的大小:'''
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('1.jpg')
cv.imshow("img",img)
cv.waitKey(0)
cv.destroyAllWindows()
# cubicadj. 立方体的; 立方的; 用立方单位度量(或表示)的; 立方形的;
# n. 三次曲线; 三次方程式; 三次多项式; 三次函数;
res = cv.resize(img,None,fx=2, fy=2, interpolation = cv.INTER_CUBIC)#使用缩放因子
cv.imshow("img",res)
cv.waitKey(0)
cv.destroyAllWindows()
#OR
height, width = img.shape[:2]#因为图像的shape=(高,宽,通道数) 【:2】表示取0和1即高宽
res = cv.resize(img,(2*width, 2*height), interpolation = cv.INTER_CUBIC)#设定指定高度
cv.imshow("img",res)
cv.waitKey(0)
cv.destroyAllWindows()
2.平移
'''平移是物体位置的移动。如果您知道在(x,y)方向上的位移,则将其设为(tx,ty),你可以创建转换矩阵
M,如下所示:
M= 1 0 tx
0 1 ty 这样一个2*3的矩阵
您可以将其放入**np.float32**类型的Numpy数组中,并将其传递给**cv.warpAffine**函数。参见下面偏移为(100, 50)的示例:'''
img = cv.imread('1.jpg',0)
'''使用函数cv2.imread(filepath,flags)读入一副图片
filepath:要读入图片的完整路径
flags:读入图片的标志
cv2.IMREAD_COLOR:默认参数,读入一副彩色图片,忽略alpha通道,可以直接写1(shape输出为三维)
cv2.IMREAD_GRAYSCALE:读入灰度图片,可以直接写0 (shape输出为2维)
cv2.IMREAD_UNCHANGED:顾名思义,读入完整图片,包括alpha通道,可以直接写-1(shape输出为三维,不过第三维多一维即多出alpha通道)
一般常用1或0,即彩色图片或灰度图片
'''
rows,cols = img.shape
M = np.float32([[1,0,100],[0,1,50]])
dst = cv.warpAffine(img,M,(cols,rows))#warpAffine包裹仿射
#cv.warpAffine函数的第三个参数是输出图像的大小,其形式应为(width,height)。记住width =列数,height =行数。
cv.imshow('img',dst)
cv.waitKey(0)
cv.destroyAllWindows()
3,旋转
img = cv.imread('1.jpg',0)
rows,cols = img.shape
'''cv2.getRotationMatrix2D()就是方便我们进行上述矩阵的获取。
其经常被使用到的参数有三个:
旋转中心
旋转角度
旋转后的缩放比例'''
# cols-1 和 rows-1 是坐标限制
M = cv.getRotationMatrix2D(((cols-1)/2.0,(rows-1)/2.0),90,1)#绕中心逆时针旋转90度,并且缩放1(即保持原来大小)的矩阵
dst = cv.warpAffine(img,M,(cols,rows))
cv.imshow('img',dst)
cv.waitKey(0)
cv.destroyAllWindows()
5.仿射变化(灵活角度变化)
'''在仿射变换中,原始图像中的所有平行线在输出图像中仍将平行。为了找到变换矩阵,
我们需要输入图像中的三个点及其在输出图像中的对应位置。然后**cv.getAffineTransform**将创建一个2x3矩阵,
该矩阵将传递给**cv.warpAffine**。
'''
img = cv.imread('1.png')
rows,cols,ch = img.shape#也可以直接row,cols=img.shape[:2]
pts1 = np.float32([[25,25],[100,25],[25,100]])#原始图像中的三个点(为什么是三个点呢?因为三个点确定一个平面)
pts2 = np.float32([[5,50],[100,25],[50,125]])#希望变化后到达的三个点
M = cv.getAffineTransform(pts1,pts2)
dst = cv.warpAffine(img,M,(cols,rows))
plt.subplot(121),plt.imshow(img),plt.title('Input')
plt.subplot(122),plt.imshow(dst),plt.title('Output')
plt.show()
官方展示:
6.透视变化
'''对于透视变换,您需要3x3变换矩阵。即使在转换后,直线也将保持直线。
要找到此变换矩阵,您需要在输入图像上有4个点,在输出图像上需要相应的点。在这四个点中,其中三个不应共线。然后可以通过函数**cv.getPerspectiveTransform**找到变换矩阵。
然后将**cv.warpPerspective**应用于此3x3转换矩阵。'''
img = cv.imread('1.png')
rows,cols,ch = img.shape
pts1 = np.float32([[28,32.5],[184,26],[14,195],[194,170]])
pts2 = np.float32([[0,0],[200,0],[0,200],[200,200]])
M = cv.getPerspectiveTransform(pts1,pts2)
dst = cv.warpPerspective(img,M,(200,200))
plt.subplot(121),plt.imshow(img),plt.title('Input')
plt.subplot(122),plt.imshow(dst),plt.title('Output')
plt.show()
官方的图片展示可能更清楚
阈值
'''threshold是“阈值”、“临界值”、“门槛”的意思。
参数:
第一个:老规律了,首先就是你搞搞事情的对象,你要搞谁,搞哪张图片。
第二个:阈值,threshold,告诉函数门槛是什么。
第三个:最大值,有可能会用到的值,取决于你后面选取的type,maxval = max value。
第四个:type,怎么搞事情,搞事情的方法。
我学到的type有一下几种,现在新版本有很多了。
'''
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread('gradient.png',0)
'''1 cv.THRESH_BINARY 首先解释一下这个参数的英文含义,这是理解的第一步。
THRESH 其实就是:threshold 也就是门槛的意思,代表cv.threshold()这个函数,然后BINARY 是binary 是二进制、二的意思,意在告诉我们结果只有两个。
用代码说的话就是:destination = (src[i,j]>threshold)? maxval : 0;
也就是:灰度图的在[i,j]位置的像素值是否大于阈值,如果大于的话,那么它的值就是maxval,如果不是的话(哪怕是等于也不行),那么就等于0。
通俗的来说就是:threshold就像是一个正直的巡逻官,他在每个灰度图的像素点去巡视(遍历),如果发现有比他小的像素,就直接把这个没用的像素给判死刑,置为0,如果发现比他还要高,还要牛逼的像素点,那么就直接把这个大哥给升到他所能升到的最高值maxval。'''
ret,thresh1 = cv.threshold(img,127,255,cv.THRESH_BINARY)
'''2 cv.THRESH_BINARY_INV 还是解释一下,前面的就不解释了,后面的INV 其实就是invert,是反的意思,也就是说和第一个方法恰恰相反。
用代码解释的话就是:destination = (src[i,j] >threshold)? 0:maxval;
就是说,灰度图图的[i,j]位置的像素点的像素值是否大于阈值,如果大于的话,那么就变成0,否则变成最大值。
'''
ret,thresh2 = cv.threshold(img,127,255,cv.THRESH_BINARY_INV)
'''3 cv.THRESH_TRUNC,“TRUNC” 可以通过百度查到,是“truncate” ,截断的意思。
用代码理解就是:destination = (src[i,j] >threshold)? maxval : src[i,j]
这个意思恰好解释了其原理的一部分。其原理就是:遍历每一个灰度图的像素,如果遍历到的像素值大于这个阈值,那么就把这个像素值变成你预先输入的maxval,
在这里maxval可以理解成一个像素值可以达到的最大值,如果小于等于,那么就保持不变。
'''
ret,thresh3 = cv.threshold(img,127,255,cv.THRESH_TRUNC)
'''4 cv.THRESH_TOZERO
“TOZERO” 可以看成 “to zero" 可以理解成:变成0的意思。
用代码理解:destination = (src[i,j] >threshold)? src[i,j] : 0;
也就是,遍历灰度图的每一个像素点,如果遍历到的像素值大于这个阈值,那么保持不变,如果小于等于这个阈值,那么不好意思,直接为0。
'''
ret,thresh4 = cv.threshold(img,127,255,cv.THRESH_TOZERO)
'''5 cv.THRESH_TOZERO_INV
”TOZERO_INV" 可以理解为:“to zero invert" 也就是,变成零的反义词,和上一个操作完全相反。
用代码理解:destination = (src[i,j] > threshold)? 0:src[i,j]
也就是:遍历灰度图的每个一像素点,如果遍历到的像素点的像素值大于这个阈值,那么直接为0,否则,如果小于等于这个阈值,那么就保持不变。'''
ret,thresh5 = cv.threshold(img,127,255,cv.THRESH_TOZERO_INV)
titles = ['Original Image','BINARY','BINARY_INV','TRUNC','TOZERO','TOZERO_INV']
images = [img, thresh1, thresh2, thresh3, thresh4, thresh5]
for i in range(6):
plt.subplot(2,3,i+1),plt.imshow(images[i],'gray')
plt.title(titles[i])
plt.xticks([]),plt.yticks([])
plt.show()
自适应阈值 在上一节中,我们使用一个全局值作为阈值。但这可能并非在所有情况下都很好,例如,如果图像在不同区域具有不同的光照条件。在这种情况下,自适应阈值阈值化可以提供帮助。在此,算法基于像素周围的小区域确定像素的阈值。因此,对于同一图像的不同区域,我们获得了不同的阈值,这为光照度变化的图像提供了更好的结果。 第一个参数表示原图像(灰度图像,8bit单通道) 第二个参数官方的解释是最大值,但是根据我的实现来看,只要不为0,不影响最后结果。 第三个参数表示阈值的计算方式: cv2.ADPTIVE_THRESH_MEAN_C:阈值取自相邻区域的平均值 cv2.ADPTIVE_THRESH_GAUSSIAN_C:阈值取值相邻区域的加权和,权重为一个高斯窗口 第四个参数表示阈值类型,参照简单阈值中的解释。 第五个参数表示邻域大小,即计算阈值的区域大小,一般是3,5,7,.... 第六个参数表示常数,计算出阈值之后,要减去这个参数,才是最后的阈值。
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread('sudoku.png',0)
img = cv.medianBlur(img,5)
ret,th1 = cv.threshold(img,127,255,cv.THRESH_BINARY)#全局阈值
'''下面的这个 \ 也可以不要 仅用于换行,不要也可以'''
th2 = cv.adaptiveThreshold(img,255,cv.ADAPTIVE_THRESH_MEAN_C,\
cv.THRESH_BINARY,11,2)#平均阈值,领域大小为11,常数为2
th3 = cv.adaptiveThreshold(img,255,cv.ADAPTIVE_THRESH_GAUSSIAN_C,\
cv.THRESH_BINARY,11,2)#高斯阈值
titles = ['Original Image', 'Global Thresholding (v = 127)',
'Adaptive Mean Thresholding', 'Adaptive Gaussian Thresholding']
images = [img, th1, th2, th3]
for i in range(4):
plt.subplot(2,2,i+1),plt.imshow(images[i],'gray')
plt.title(titles[i])
plt.xticks([]),plt.yticks([])
'''设置x或y轴对应显示的标签:xticks(ticks=None, labels=None, **kwargs)
ticks : array_like,应当放置刻度的位置列表,可以传递一个空列表来禁用xticks
labels :array_like, 放置在给定* locs *处的显式标签的列表。
**kwargs:Text属性可以用来控制标签的外观。
'''
plt.show()
Otsu的二值化
在全局阈值化中,我们使用任意选择的值作为阈值。相反,Otsu的方法避免了必须选择一个值并自动确定它的情况。
考虑仅具有两个不同图像值的图像(双峰图像),其中直方图将仅包含两个峰。一个好的阈值应该在这两个值的中间。类似地,Otsu的方法从图像直方图中确定最佳全局阈值。
为此,使用了**cv.threshold**作为附加标志传递。阈值可以任意选择。然后,算法找到最佳阈值,该阈值作为第一输出返回。
查看以下示例。输入图像为噪点图像。在第一种情况下,采用值为127的全局阈值。在第二种情况下,直接采用Otsu阈值法。在第三种情况下,首先使用5x5高斯核对图像进行滤波以去除噪声,然后应用Otsu阈值处理。了解噪声滤波如何改善结果
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread('noisy2.png',0)
# 全局阈值
ret1,th1 = cv.threshold(img,127,255,cv.THRESH_BINARY)
# Otsu阈值
ret2,th2 = cv.threshold(img,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
# 高斯滤波后再采用Otsu阈值
blur = cv.GaussianBlur(img,(5,5),0)
ret3,th3 = cv.threshold(blur,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
# 绘制所有图像及其直方图
images = [img, 0, th1,
img, 0, th2,
blur, 0, th3]
titles = ['Original Noisy Image','Histogram','Global Thresholding (v=127)',
'Original Noisy Image','Histogram',"Otsu's Thresholding",
'Gaussian filtered Image','Histogram',"Otsu's Thresholding"]
for i in range(3):
plt.subplot(3,3,i*3+1),plt.imshow(images[i*3],'gray')
plt.title(titles[i*3]), plt.xticks([]), plt.yticks([])
plt.subplot(3,3,i*3+2),plt.hist(images[i*3].ravel(),256)#256个小隔间
# plt.hist 将一个大区间划分为等间隔的小区间,并统计每个区间上样本出现的频数之和
# ravel() 将数组维度拉至一维数组
plt.title(titles[i*3+1]), plt.xticks([]), plt.yticks([])
plt.subplot(3,3,i*3+3),plt.imshow(images[i*3+2],'gray')
plt.title(titles[i*3+2]), plt.xticks([]), plt.yticks([])
plt.show()