轮廓 可以简单地解释为 连接具有相同颜色或强度的所有连续点(沿边界)的曲线。轮廓是用于 形状分析 以及 对象检测和识别 的有用工具。
轮廓相关的注意事项:
cv2.findContours()函数
、cv2.drawContours()函数
将修改源图像。因此,如果想在找到轮廓后仍可获取源图像,需保证在调用findContours()函数
、cv2.drawContours()函数
之前已经将其存储到其他变量中;Let’s see how to find contours of a binary image:
import numpy as np
import matplotlib.pyplot as plt
import cv2
img = cv2.imread(r'/Documents/2d36d7c607b0f923a9aa3ef1a7b274cb.jpg')
# 转为灰度图
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 把原图二值化——选取一个全局阈值`thresh`,然后根据全局阈值将一幅灰度图二值化,将灰度图img中灰度值小于阈值的点置0,灰度值大于175的点置255
ret, thresh = cv2.threshold(img_gray, 127, 255, 0)
# 检测图像连通区(输入为二值化图像)
contours, heirarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# 绘制寻找到的轮廓线(在原始图像上绘制轮廓线)
img_contours = cv2.drawContours(img, contours, -1, (0,255,0), 3)
plt.imshow(img_contours)
如何绘制轮廓?
轮廓绘制轮廓,使用cv2.drawContours()
函数 。只要有边界点,它也可以用来绘制任何形状。
img = cv2.drawContours(img, contours, -1, (0,255,0), 3)
要在图像中绘制所有轮廓:
学习如何 将图像从一种颜色空间转换为另一种颜色空间 , OpenCV提供了150多种颜色空间转换方法。但是,我们将只研究两个使用最广泛的模型,即: BGR ↔ \leftrightarrow ↔Gray,BGR ↔ \leftrightarrow ↔HSV等。
对于颜色转换,我们使用cv2.cvtColor(input_image, flag)函数
来实现。其中,参数 flag
决定颜色转换类型。分别如下:
参数 flag
= cv2.COLOR_BGR2GRAY
;参数 flag
= cv2.COLOR_BGR2HSV
;要获取参数 flag
的其他取值情况,只需在Python终端中运行以下命令:
# 罗列出——所有的图片颜色转换方法,合计274种
import cv2
flags = [i for i in dir(cv2) if i.startswith('COLOR_')]
print(flags)
>>>
['COLOR_BAYER_BG2BGR', 'COLOR_BAYER_BG2BGRA', 'COLOR_BAYER_BG2BGR_EA', 'COLOR_BAYER_BG2BGR_VNG', 'COLOR_BAYER_BG2GRAY', 'COLOR_BAYER_BG2RGB', 'COLOR_BAYER_BG2RGBA', ...]
注意:
\quad
对于HSV颜色空间,其色相范围(Hue range)为[0,179],饱和度范围(Saturation range)为[0,255],值范围(Value range)为[0,255]。
\quad
不同颜色空间的色相范围、饱和度范围、值范围的取值各不相同。因此,如果比较不同颜色空间下的OpenCV值之前,则需要将这些范围标准化。
将图像的颜色空间从BGR转换为HSV之后,我们可以使用下边的方法来 提取指定颜色的对象。在HSV中,识别颜色比RGB颜色空间下更容易。
从视频图像中追踪一个蓝色对象的方法如下:
完整代码如下:
import cv2
import numpy as np
cap = cv2.VideoCapture(0)
while(1):
# Take each frame
_, frame = cap.read()
# Convert BGR to HSV
hsv = cv2.cvtColor(frame, cv2.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
mask = cv2.inRange(hsv, lower_blue, upper_blue)
# Bitwise-AND mask and original image
res = cv2.bitwise_and(frame,frame, mask= mask)
cv2.imshow('frame',frame)
cv2.imshow('mask',mask)
cv2.imshow('res',res)
k = cv2.waitKey(5) & 0xFF
if k == 27:
break
cv2.destroyAllWindows()
下图显示了对蓝色对象的跟踪:
注意:
- 图像中有一些噪点。我们将在后续探讨如何删除噪点。
- 本示例是对象跟踪最简单的方法。一旦学习了轮廓功能,您就可以做很多事情,例如:找到该对象的质心并使用它来跟踪该对象,仅通过将手移到相机前面以及其他许多有趣的东西就可以绘制图表。
这是在stackoverflow.com中发现的常见问题。这非常简单,您可以使用相同的函数cv2.cvtColor()。无需传递图像,只需传递所需的BGR值即可。例如,要查找Green的HSV值,请在Python终端中尝试以下命令:
# 寻找色彩空间BGR下的green转换为色彩空间HSV下的green的对应值
green = np.uint8([[[0,255,0]]]) # BGR模式下颜色green的值表示为[0,255,0]
hsv_green = cv2.cvtColor(green, cv2.COLOR_BGR2HSV)
print(hsv_green)
HSV( 色相/饱和度/明度)颜色空间是表示类似于RGB颜色模型的颜色空间的模型。根据色相通道(Channel)对颜色类型进行建模,因此在需要根据颜色对对象进行分割的图像处理任务中非常有用。饱和度的变化代表颜色成分的多少。明度通道描述颜色的亮度。下图显示了HSV圆柱图。
RGB颜色空间中的颜色使用三个通道对进行编码,因此基于颜色对图像中的对象进行分割更加困难。(而HSV中只有Hue一个通道表示颜色)
在OpenCV中使用cv2.cvtColor函数
将图片从一个颜色空间转换到另一个颜色空间。
图像阈值的原理:
如果像素值大于阈值,则为其分配一个值(可以为白色),否则为其分配另一个值(可以为黑色)——最终实现对图像的二值化处理。
实现图像阈值的常用方法包括:(1)使用cv2.threshold函数
进行阈值操作;(2)使用cv2.inRange函数
进行阈值操作。
cv2.threshold函数
进行阈值操作官方解释:
Python: cv2.threshold(src, thresh, maxval, type[, dst]) -> retval, dst
函数功能:
将固定级别阈值应用于每个数组元素。(Applies a fixed-level threshold to each array element.)
参数说明:
src
:第一个参数是源图像,它应该是灰度图像。thresh
:第二个参数是用于对像素值进行分类的阈值。maxval
:第三个参数是表示像素值大于(有时小于)阈值时要给定的值。type
:OpenCV提供了不同的阈值类型,它由函数的第四个参数决定。不同的
阈值类型type
的可能取值分别是:
cv2.THRESH_BINARY
cv2.THRESH_BINARY_INV
cv2.THRESH_TRUNC
cv2.THRESH_TOZERO
cv2.THRESH_TOZERO_INV
不同的阈值类型type
的显色效果如下:
返回值:
获得两个输出。
retval
:第一个是得到图像的阈值。dst
:第二个输出是经过二值化操作(阈值处理)的阈值图像。注意: 该函数输出的dst
是一幅经过二值化操作之后的图像。
实例——cv2.threshold函数:
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread(r'/Users/Documents/image01.jpg',0) # 读入灰度图
ret,thresh1 = cv2.threshold(img,127,255,cv2.THRESH_BINARY)
ret,thresh2 = cv2.threshold(img,127,255,cv2.THRESH_BINARY_INV)
ret,thresh3 = cv2.threshold(img,127,255,cv2.THRESH_TRUNC)
ret,thresh4 = cv2.threshold(img,127,255,cv2.THRESH_TOZERO)
ret,thresh5 = cv2.threshold(img,127,255,cv2.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()
cv2.inRange函数
进行阈值操作官方解释:
Python: cv2.inRange(src, lowerb, upperb[, dst]) -> dst
函数功能:
检查数组元素是否位于其他两个数组的元素之间。(Checks if array elements lie between the elements of two other arrays.)
注意:
- 这里的数组通常也就是矩阵Mat或向量。
- 该函数输出的
dst
是一幅经过二值化操作之后的图像。
使用OpenCV中的cv2.inRange函数
执行基本阈值操作,可实现二值化功能(这点类似cv2.threshold函数
),更关键的是可以同时针对多通道进行操作。
参数说明:
src
:第一个参数是原数组,可以为单通道,多通道。lowerb
:第二个参数是阈值下界,即:H、S、V的最小值,示例:Scalar(low_H, low_S, low_V)。upperb
:第三个参数是阈值上界,即:H、S、V的最大值,示例:Scalar(low_H, low_S, low_V)。返回值:
dst
:输出图像,要和输入图像有相同的尺寸且为CV_8U类关于cv2.inRange函数
的应用场景:
1、针对单通道图像:
dst(I) = lowerb(I)0 ≤ src(I)0 < upperb(I)0
即:如果一幅灰度图像的 某个像素的灰度值 在指定的高、低阈值范围之内,则在dst图像中令该像素值为255,否则令其为0,这样就生成了一幅二值化的输出图像。
示例如下:
\quad
如果是单通道,假设lower=[0],upper=[128],那么,如果每个数均在0-128之间,则在dst图像中令该像素值为255,否则令其为0;
\quad
单通道下使用cv2.inRange()函数的实例链接
2、针对三通道图像:
dst(I) = lowerb(I)0 ≤ src(I)0 < upperb(I)0 ∧ lowerb(I)1 ≤ src(I)1 < upperb(I)1 ∧lowerb(I)2 ≤ src(I)2 < upperb(I)2
即:如果一幅图像的 每个通道的像素值 都必须在规定的阈值范围内,则在dst图像中令该像素值为255,否则令其为0,这样就生成了一幅二值化的输出图像。
示例如下:
\quad
如果是多通道,假设lower=[0, 0, 0],upper=[128, 128, 128],那么,对每一行,对任意一个数,如果均在low和upper的指定通道范围内,则在各个通道执行”与“运算后,在dst图像中令该像素值为255,否则令其为0。
HSV颜色空间下不同颜色的色相阈值分布:
import numnp as np
import cv2
x = np.array([[[0, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8]],
[[ 9, 10, 11],
[12, 13, 14],
[15, 16, 17]],
[[18, 19, 20],
[1, 2, 23],
[4, 5, 26]]])
y = np.array([0,1,2])
z = np.array([18,19,20])
cv2.inRange(x,y,z)
>>>
array([[255, 255, 255],
[255, 255, 255],
[255, 0, 0]], dtype=uint8)
由于 [1, 2, 23], [4, 5, 26]有元素超出范围,所以,结果为0。
参考链接:
OpenCV中inRange()函数的使用
OpenCV学习笔记-inRange()阈值操作函数怎么用
opencv inrange函数
cv2.threshold函数
使用 全局阈值。但是,在图像在不同区域具有不同照明条件的情况下,可能效果并不理想。在这种情况下,我们采用 自适应阈值。即:算法将为图像的一小部分计算阈值。因此,对于同一图像的不同区域,我们获得了不同的阈值,对于存在光照变化的图像,采用 自适应阈值 可以为我们提供更好的结果。
官方解释:
Python: cv2.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C[, dst]) -> dst
函数功能:
将自适应阈值应用于数组。(Applies an adaptive threshold to an array.)
参数说明:
它具有三个“特殊”输入参数和一个输出参数。
adaptiveMethod
:自适应计算方法——决定如何计算阈值。cv2.ADAPTIVE_THRESH_MEAN_C:阈值是 邻近区域的平均值。
cv2.ADAPTIVE_THRESH_GAUSSIAN_C:阈值是 邻域值的加权总和 ,其中权重是高斯窗口。
blockSize
:块大小 ——决定附近区域的大小。C
:只是一个常数,它是从计算的平均值或加权平均值中减去的。实例——比较光照变化下图像的全局阈值和自适应阈值:
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('dave.jpg',0)
img = cv2.medianBlur(img,5)
ret,th1 = cv2.threshold(img,127,255,cv2.THRESH_BINARY)
th2 = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_MEAN_C,\
cv2.THRESH_BINARY,11,2)
th3 = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,\
cv2.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 xrange(4):
plt.subplot(2,2,i+1),plt.imshow(images[i],'gray')
plt.title(titles[i])
plt.xticks([]),plt.yticks([])
plt.show()
OTSU算法原理简述:
最大类间方差 是由日本学者大津(Nobuyuki Otsu)于1979年提出,是一种 自适应的阈值确定方法 。算法假设图像像素能够根据阈值,被分成背景[background]和目标[objects]两部分。然后,计算该最佳阈值来区分这两类像素,使得两类像素区分度最大。
OTSU算法的局限性在于:要求图像的灰度直方图必须具有双峰性。该方法不适合以下情形:(1)若目标与背景图像还存在较大的干扰时,该算法会分割出错误的目标;(2)当目标与背景的大小比例悬殊时,g(t)函数可能出现双峰或多峰,分割效果不好。
OTSU算法计算过程:
公式: 记 M = 256 单通道灰度分级 Sum = 像素总数
参考链接:
详细及易读懂的 大津法(OTSU)原理 和 算法实现
数字图像处理第9章——图像分割
自适应阈值的应用实例:
下图所示,为图像灰度直方图满足“双峰性”的图像原图。
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread(r'/Users/yangyang/Desktop/截屏2020-08-25上午11.31.45.png', 0)
# global thresholding
ret1, th1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
# Otsu's thresholding
ret2, th2 = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
# Otsu's thresholding after Gaussian filtering
blur = cv2.GaussianBlur(img, (5,5), 0)
ret3, th3 = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
# plot all the images and their histograms
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 filterd 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)
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()
如果提取到了干扰区域,因为干扰区域在灰度直方图上出现了一个波峰,目标区域的波峰与背景被分在一起,详见下图。图片原文链接:自动计算阈值方法-Otsu法(大津法/最大类间方差法)
1996年,Prewitt提出了直方图双峰法,即 如果灰度级直方图呈明显的双峰状,则选取两峰之间的谷底所对应的灰度级作为阈值。
注意:应用灰度直方图双峰法来分割图像,也需要一定的图像先验知识,因为同一个直方图可以对应若干个不同的图像,直方图只表明图像中各个灰度级上有多少个象素,并不描述这些象素的任何位置信息。即:仅仅考虑了直方图灰度信息而忽略了图像的空间信息。
该方法不适合直方图中双峰差别很大或双峰间的谷比较宽广而平坦的图像,以及单峰直方图的情况。
参考链接:数字图像—之双峰直方图