Python+Opencv中的轮廓の(01)cv2.findContours检索图像的轮廓

Python+Opencv中的轮廓の(01)cv2.findContours检索图像的轮廓

  • 01、了解轮廓是什么?
  • 02、学习寻找轮廓,绘制轮廓等
  • 03、改变色彩空间
  • 04、对象追踪——基于HSV颜色空间中的像素值范围来检测对象
    • 如何找到要追踪对象的HSV值?
  • 05、HSV和RGB色彩空间的构成
    • HSV色彩空间
  • 06、图像阈值——实现图像二值化处理
    • 方法一:使用`cv2.threshold函数`进行阈值操作
    • 方法二、使用`cv2.inRange函数`进行阈值操作
  • 07、自适应阈值
  • 08、自动计算阈值方法-Otsu法(大津法/最大类间方差法)的数学原理
  • 10、双峰直方图

01、了解轮廓是什么?

轮廓 可以简单地解释为 连接具有相同颜色或强度的所有连续点(沿边界)的曲线。轮廓是用于 形状分析 以及 对象检测和识别 的有用工具。

轮廓相关的注意事项:

  • 为了获得更高的准确性,请使用二进制图像(binary images)。因此,在找到轮廓之前,请应用阈值(threshold )或 Canny边缘检测(canny edge detection);
  • 调用cv2.findContours()函数cv2.drawContours()函数将修改源图像。因此,如果想在找到轮廓后仍可获取源图像,需保证在调用findContours()函数cv2.drawContours()函数之前已经将其存储到其他变量中;
  • 在OpenCV中,寻找图像轮廓类似于从黑色背景中找到白色物体。因此,请记住,要找到的对象应该是白色,背景应该是黑色。

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)

Python+Opencv中的轮廓の(01)cv2.findContours检索图像的轮廓_第1张图片

02、学习寻找轮廓,绘制轮廓等

如何绘制轮廓?

轮廓绘制轮廓,使用cv2.drawContours()函数 。只要有边界点,它也可以用来绘制任何形状。

img = cv2.drawContours(img, contours, -1, (0,255,0), 3)
  • img:第一个参数是 源图像
  • contours:第二个参数是 作为Python列表传递的轮廓
  • 第三个参数:是 轮廓的索引 (在绘制单个轮廓时有用。要绘制所有轮廓,请传递-1);
  • 其余参数:是 颜色,厚度 等等

要在图像中绘制所有轮廓:

03、改变色彩空间

学习如何 将图像从一种颜色空间转换为另一种颜色空间 , OpenCV提供了150多种颜色空间转换方法。但是,我们将只研究两个使用最广泛的模型,即: BGR ↔ \leftrightarrow Gray,BGR ↔ \leftrightarrow HSV等。

对于颜色转换,我们使用cv2.cvtColor(input_image, flag)函数来实现。其中,参数 flag 决定颜色转换类型。分别如下:

  • 图像颜色转换: BGR → \rightarrow Gray,设置参数 flag = cv2.COLOR_BGR2GRAY
  • 图像颜色转换: BGR → \rightarrow HSV,设置参数 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值之前,则需要将这些范围标准化。

04、对象追踪——基于HSV颜色空间中的像素值范围来检测对象

将图像的颜色空间从BGR转换为HSV之后,我们可以使用下边的方法来 提取指定颜色的对象。在HSV中,识别颜色比RGB颜色空间下更容易。

从视频图像中追踪一个蓝色对象的方法如下:

  • 拍摄视频的每一帧
  • 将图像的颜色空间从BGR转换为HSV;
  • 我们将HSV模式图片的阈值范围(threshold)设为蓝色;
  • 现在,仅提取蓝色对象,我们就可以在所需图像上执行任何操作。

完整代码如下:

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

下图显示了对蓝色对象的跟踪:

Python+Opencv中的轮廓の(01)cv2.findContours检索图像的轮廓_第2张图片

注意:

  • 图像中有一些噪点。我们将在后续探讨如何删除噪点。
  • 本示例是对象跟踪最简单的方法。一旦学习了轮廓功能,您就可以做很多事情,例如:找到该对象的质心并使用它来跟踪该对象,仅通过将手移到相机前面以及其他许多有趣的东西就可以绘制图表。

如何找到要追踪对象的HSV值?

这是在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)

05、HSV和RGB色彩空间的构成

HSV色彩空间

HSV色相/饱和度/明度)颜色空间是表示类似于RGB颜色模型的颜色空间的模型。根据色相通道(Channel)对颜色类型进行建模,因此在需要根据颜色对对象进行分割的图像处理任务中非常有用。饱和度的变化代表颜色成分的多少。明度通道描述颜色的亮度。下图显示了HSV圆柱图。
Python+Opencv中的轮廓の(01)cv2.findContours检索图像的轮廓_第3张图片

RGB颜色空间中的颜色使用三个通道对进行编码,因此基于颜色对图像中的对象进行分割更加困难。(而HSV中只有Hue一个通道表示颜色)

Python+Opencv中的轮廓の(01)cv2.findContours检索图像的轮廓_第4张图片
在OpenCV中使用cv2.cvtColor函数将图片从一个颜色空间转换到另一个颜色空间。

06、图像阈值——实现图像二值化处理

图像阈值的原理:

如果像素值大于阈值,则为其分配一个值(可以为白色),否则为其分配另一个值(可以为黑色)——最终实现对图像的二值化处理。

实现图像阈值的常用方法包括:(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的显色效果如下:
Python+Opencv中的轮廓の(01)cv2.findContours检索图像的轮廓_第5张图片

返回值:

获得两个输出。

  • 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()

Python+Opencv中的轮廓の(01)cv2.findContours检索图像的轮廓_第6张图片

方法二、使用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颜色空间下不同颜色的色相阈值分布:

Python+Opencv中的轮廓の(01)cv2.findContours检索图像的轮廓_第7张图片
图像阈值实例—— cv2.inRange函数の三通道图像:

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

07、自适应阈值

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

Python+Opencv中的轮廓の(01)cv2.findContours检索图像的轮廓_第8张图片
OpenCV——Python版官方教程

08、自动计算阈值方法-Otsu法(大津法/最大类间方差法)的数学原理

Python+Opencv中的轮廓の(01)cv2.findContours检索图像的轮廓_第9张图片

Python+Opencv中的轮廓の(01)cv2.findContours检索图像的轮廓_第10张图片

Python+Opencv中的轮廓の(01)cv2.findContours检索图像的轮廓_第11张图片

Python+Opencv中的轮廓の(01)cv2.findContours检索图像的轮廓_第12张图片

OTSU算法原理简述:

最大类间方差 是由日本学者大津(Nobuyuki Otsu)于1979年提出,是一种 自适应的阈值确定方法 。算法假设图像像素能够根据阈值,被分成背景[background]和目标[objects]两部分。然后,计算该最佳阈值来区分这两类像素,使得两类像素区分度最大。

OTSU算法的局限性在于要求图像的灰度直方图必须具有双峰性。该方法不适合以下情形:(1)若目标与背景图像还存在较大的干扰时,该算法会分割出错误的目标;(2)当目标与背景的大小比例悬殊时,g(t)函数可能出现双峰或多峰,分割效果不好。

OTSU算法计算过程:

公式: 记 M = 256 单通道灰度分级 Sum = 像素总数

  1. 背景像素占比: ω 1 = N 1 S u m \omega1 = \frac{N1}{Sum} ω1=SumN1
  2. 前景像素占比: ω 2 = 1 − ω 1 = N 2 S u m = 1 − N 1 S u m \omega2 = 1- \omega1 = \frac{N2}{Sum} =1- \frac{N1}{Sum} ω2=1ω1=SumN2=1SumN1
  3. 背景的平均灰度值: μ 1 = ∑ i = 0 t i ∗ P r ( i ∣ C 0 ) = ∑ i = 0 t i ∗ P i / ∑ i = 0 t P i = μ ( t ) ) ω 1 \mu 1 = \sum_{i = 0}^{t} i *Pr(i | C_{0}) = \sum_{i = 0}^{t} i *Pi / \sum_{i = 0}^{t} Pi = \frac{\mu(t))}{\omega_{1}} μ1=i=0tiPr(iC0)=i=0tiPi/i=0tPi=ω1μ(t))
  4. 前景的平均灰度值: μ 2 = ∑ i = t + 1 M − 1 i ∗ P r ( i ∣ C 1 ) = ∑ i = t + 1 M − 1 i ∗ P i / ∑ i = t + 1 M − 1 P i = μ − μ ( t ) ) ω 2 \mu 2 = \sum_{i = t+1}^{M - 1} i *Pr(i | C_{1}) = \sum_{i = t+1}^{M - 1} i *Pi / \sum_{i = t+1}^{M - 1} Pi = \frac{\mu - \mu(t))}{\omega _{2}} μ2=i=t+1M1iPr(iC1)=i=t+1M1iPi/i=t+1M1Pi=ω2μμ(t))
  5. 0~M灰度区间的灰度累计值: μ = μ 1 ∗ ω 1 + μ 2 ∗ ω 2 \mu = \mu1*\omega 1 + \mu2*\omega 2 μ=μ1ω1+μ2ω2
  6. 类间方差: g = ω 1 ∗ ( μ − μ 1 ) 2 + ω 2 ∗ ( μ − μ 2 ) 2 g = \omega 1 * (\mu - \mu1)^{2} + \omega 2 * (\mu - \mu2)^{2} g=ω1(μμ1)2+ω2(μμ2)2
  7. 将公式3.4.5带入公式6 可得最终简化公式: g = ω 1 ∗ ω 2 ∗ ( μ 1 − μ 2 ) 2 g = \omega 1 * \omega2 * (\mu1 - \mu2)^{2} g=ω1ω2(μ1μ2)2

参考链接:
详细及易读懂的 大津法(OTSU)原理 和 算法实现
数字图像处理第9章——图像分割

自适应阈值的应用实例:

下图所示,为图像灰度直方图满足“双峰性”的图像原图。

Python+Opencv中的轮廓の(01)cv2.findContours检索图像的轮廓_第13张图片
对“双峰图像”进行二值化操作的完整代码如下:

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

Python+Opencv中的轮廓の(01)cv2.findContours检索图像的轮廓_第14张图片
如果提取到了干扰区域,因为干扰区域在灰度直方图上出现了一个波峰,目标区域的波峰与背景被分在一起,详见下图。Python+Opencv中的轮廓の(01)cv2.findContours检索图像的轮廓_第15张图片图片原文链接:自动计算阈值方法-Otsu法(大津法/最大类间方差法)

10、双峰直方图

1996年,Prewitt提出了直方图双峰法,即 如果灰度级直方图呈明显的双峰状,则选取两峰之间的谷底所对应的灰度级作为阈值

Python+Opencv中的轮廓の(01)cv2.findContours检索图像的轮廓_第16张图片
注意:应用灰度直方图双峰法来分割图像,也需要一定的图像先验知识,因为同一个直方图可以对应若干个不同的图像,直方图只表明图像中各个灰度级上有多少个象素,并不描述这些象素的任何位置信息。即:仅仅考虑了直方图灰度信息而忽略了图像的空间信息

该方法不适合直方图中双峰差别很大或双峰间的谷比较宽广而平坦的图像,以及单峰直方图的情况。

参考链接:数字图像—之双峰直方图

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