Python语言OpenCV开发之使用OpenCV处理图像(上)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Eric_lmy/article/details/80962669

前言

本章内容都是和图像处理相关的,学习颜色空间的变换,几何变换,图像平滑,形态学变换,边缘检测,轮廓检测等知识。

正文

1、 颜色空间转换
OpenCV中有数百种关于不同颜色空间之间的转换的方法。当前计算机视觉中有三种常用的色彩空间:灰度、BGR以及HSV;
.灰度色彩空间是通过去除彩色信息来将其转换成灰阶,灰度色彩空间对中间处理特别有效,例如人脸检测
.BGR,即蓝-绿-红色彩空间,每一个像素点都由一个三元数组表示,分别代表蓝、绿、红三种颜色。
.HSV,H(Hue)是色调,S(Saturation)是饱和度,V(Value)表示黑暗的程度。
在诸多方法中常用的就两种:BGR和Gray之间,BRG和HSV之间。用到的函数是cv2.cvtColor(inputimg,flag).其中flag就是转换类型。对于BRG到Gray的转换,flag为cv2.COLOR_BGR2GRAY;对于BGR到HSV的转换,flag为cv2.COLOR_BGR2HSV。
注意:在OpenCV的HSV格式中,H(色彩/色度)的取值范围是[0,179],S(饱和度)的取值范围[0,255],V(亮度)的范围是[0,255],但是不同的软件使用的值可能不同,需要对比归一化;
下面看一个关于物体跟踪的例子:
将图像转换到HSV,可以利用这一点来提取带有某个特定颜色的物体。在HSV颜色空间要比在BGR空间中更容易表示一个特定的颜色。例如我们要提取一个蓝颜色的物体,有一下几个步骤:
1.从视频中获取每一帧图像
2.将图像转换到HSV空间
3.设置HSV阈值到蓝色范围
4.获取蓝色物体,比如在蓝色物体周围画个圈。

import cv2
import numpy as np 
cap = cv2.VideoCapture(0)
while(1):
    ret, frame = cap.read()# 获取每一帧
    hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)# 转换到HSV
    lower_blue = np.array([110,50,50])# 设定蓝色的阈值
    upper_blue = np.array([130,255,255])
    mask = cv2.inRange(hsv, lower_blue, upper_blue)# 根据阈值构建掩模
    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()

2、 几何变换
2.1 扩展缩放
扩展缩放只是改变图像的尺寸大小。OpenCV提供的函数cv2.resize()可以实现这个功能。图像的尺寸可以自己手动设置,也可以指定缩放因子。可以选择使用不同的插值方法。在缩放时推荐使用cv2.INTER_AREA,在扩展时推荐使用cv2.INTER_CUBIC(慢)和cv2.INTER_LINEAR。例如:

img = cv2.imread("./image/test2.jpg")
res1 = cv2.resize(img, None, fx=2, fy=2, interpolation=cv2.INTER_CUBIC)
# or 这两种方式效果一样 (fx=2, fy=2为缩放因子,所以dst为None)
height, width = img.shape[:2]
res2 = cv2.resize(img, (2*width,2*height), interpolation=cv2.INTER_CUBIC)

2.2 平移
平移就是将对象换一个位置,如果你要沿(x,y)方向移动,移动的距离是(tx,ty),你可以以下面的方式构建移动矩阵:
enter image description here
可以使用Numpy数组构建这个矩阵(数据类型是np.float32),然后传给cv2.warpAffine()。次函数的第三个参数是输出图像的大小,它的格式应该是图像的(宽,高),图像的宽对应列数,高对应行数。例如:

M = np.float32([[1,0,100],[0,1,100]])
ret = cv2.warpAffine(img, M, (img.shape[0], img.shape[1]))

2.3 旋转
旋转和平移一样,只是旋转因子发生改变,为了获取旋转矩阵,需要使用一个函数cv2.getRotationMatrix2D(),如下:
M = cv2.getRotationMatrix2D((x,y), 45, 0.6)
参数1:旋转中心
参数2:旋转角度
参数3:旋转后的缩放因子(可以通过设置旋转中心,缩放因子以及窗口大小来防止旋转后超出边界问题)
2.4 仿射变换
在仿射变换中,原图中所有的平行线在结果图像中同样平行。为了创建这个矩阵我们需要从原图中找到三个点以及他们在输出图像中的位置(也就是原始的三个点核放射后点落的位置)。然后cv2.getArrineTransform会创建一个2x3的矩阵,最后这个矩阵会传给函数cv2.warpAffine();
2.5 视角变换
对于视角变换,我们需要一个3x3变换矩阵。在变换前后直线还是直线。要构建这个变换矩阵,我们需要在输入图像上找4个点,以及他们输出图像上对应的位置。这四点中任意三个不能共线。这个变换矩阵可以有函数cv2.getPerspectiveTransform()构建,然后把这哥矩阵传给函数cv2.warpPerspective()

3、 图像阈值
3.1 简单阈值
这个方法很简单,只要像素值高于阈值时,我们给这个像素赋予一个新的值,这个函数就是cv2.threshhold()。参数1就是原图像(原图像应该是灰度图),参数2是用来对像素进行分类的阈值。参数3就是当像素值高于(有时是小于)阈值时赢赋予的新的像素值。OpenCV提供了多种不同的阈值方法,这是有参数4决定的。参数4如下:
1. cv2.THRESH_BINARY(二值阈值化)
2. cv2.THRESH_BINARY_INV(反向二值化并翻转)
3. cv2.THRESH_TRUNC(截断阈值化)
4. cv2.THRESH_TOZERO(超过阈值被置0)
5. cv2.THRESH_TOZERO_INV(低于阈值被置为0)
此函数有两个返回值,第二个返回值即是阈值化之后的图像。第一个是retVal;
3.2 自适应阈值
上面使用的是全局阈值,整幅图像采用同一个数作为阈值。但这种做法并不使用与所有情况,尤其是当同一副图像的不同部分具有不同的亮度时,这种情况下就需要采用自适应阈值。此时的阈值时根据图像上的每一个小区域计算与其对应的阈值。因此在同一副图像上的不同区域采用的是不同的阈值,从而得到更好的结果。
函数:cv2.adaptiveThreshold()
参数1:src 原图像(灰度图)
参数2:x 指当像素值高于(有时是小于)阈值时应该被赋予的新的像素值
参数3:adaptive_method 指: CV_ADAPTIVE_THRESH_MEAN_C 或 CV_ADAPTIVE_THRESH_GAUSSIAN_C
参数4:threshold_type 指取阈值的类型,必须是下列之一cv2.THRESH_BINARY或cv2.THRESH_BINARY_INV
参数5:block_size 指用来计算阈值的像素领域大小:3,5,7,9……
参数6:param1 指与方法有关的参数,对方法cv2.ADAPTIVE_THRESH_MEAN_C 和 cv2.ADAPTIVE_THRESH_GAUSSIAN_C, 它是一个从均值或加权均值提取的常数, 尽管它可以是负数。
对方法cv2.ADAPTIVE_THRESH_MEAN_C,先求出块中的均值,再减掉param1。
对方法 cv2.ADAPTIVE_THRESH_GAUSSIAN_C ,先求出块中的加权和(gaussian), 再减掉param1;
3.3 Otsu’s 二值化
在使用全局阈值时,我们就是随便给一个数来做阈值,那我们怎么知道选取的这个数的好坏呢?就是不挺的尝试,如果是一副双峰图像(简单的来说就是直方图中存在两个峰)呢?岂不是要在两个峰之间的峰谷选择一个值作为阈值?这就是Otsu二值化要做的。就是对一副双峰图像自动根据其直方图计算出一个阈值。(非双峰的图像结果可能不太理想)。
这里用到的函数还是cv2.threshold(),但是需要多传入一个参数flag(cv2.THRESH_OTSU),这时要把阈值设为0.然后算法会找到最优阈值,这个最优阈值就是返回的retVal。如果不是Otsu二值化,返回的retVal值与设定的阈值相等。
4、 图像模糊(也叫图像平滑)
可以对2D图像实施低通滤波(LPF),高通滤波(HPF)等。LPF可以去除噪音,模糊图像。HPF可以找到图像的边缘。OpenCV提供的函数cv2.filter2D()可以对一幅图像进行卷积操作。使用低筒滤波器可以达到图像模糊的目的,这对去除噪音很有帮助,其实就是去除图像中的高频成分(eg:噪音,边界)。所有边界也会被模糊一点,也有一些模糊技术不会模糊掉边界。
4.1 平均
这是一个归一化卷积框完成的。它只是用卷积框覆盖区域所有像素的平均值来代替中心元素。可使用函数cv2.blur()和cv2.boxFilter()来完成这个任务。
注意:如果不想使用归一化卷积框,应使用cv2.boxFilter(),这时要传入参数normalize=False。例如:

blur2=cv2.boxFilter(img,-1,(5,5)) 
blur = cv2.blur(img,(5,5))  
plt.subplot(121),plt.imshow(img),plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(blur),plt.title('Blurred')
plt.xticks([]), plt.yticks([])
plt.show()

enter image description here

4.2 高斯模糊
把卷积核换成高斯核(简单来说,方框不变,将原来每个方框的值是相等的,现在里面的值是符合高斯分布的,方框中心的值最大,其余方框根据距离中心元素的距离递减,构成一个高斯小山包。原来的求平均数现在变成求加权平均数,全就是方框里的值)。实现的函数是 cv2.GaussianBlur()。我们需要指定高斯核的宽和高(必须是奇数)以及高斯函数沿X,Y方向的标准差,如果只指定X方向的标准差,Y方向也会去相同值。如果都是0,那么函数会根据窗口的大小自己计算。高斯滤波可以有效的从图像去除高斯噪音。也可以使用cv2.getGaussianKernel()自己构建一个高斯核。

# 0 表示根据窗口大小(5,5)来计算高斯函数标准差
Blur = cv2.GaussianBlur(img, (5,5), 0)

4.3 中值模糊
用与卷积框对应像素的中值代替中心像素的值。这个滤波器经常用来去除椒盐噪音。前面的滤波器都是用计算得到的一个新值来取代中心像素的值,而中值滤波是用中心像素周围(也可以使他本身)的值来取代他。他能有效的去除噪声。卷积核的大小也应该是一个奇数。使用的函数是:cv2.medianBlur(img, 5)

Median = cv2.medianBlur(img, 5)

enter image description here
4.4 双边滤波
函数cv2.bilateralFilter()能在保持边界清晰的情况下有效的去除噪音。但是这种操作比较慢。高斯滤波器只考虑像素之间的空间关系,而不会考虑像素值之间的关系(像素的相似度),所以高斯滤波不会考虑一个像素是否位于边界,因此边界也会模糊掉。
双边滤波在同时使用空间高斯权重和灰度值相似性高斯权重,空间高斯函数确保只有近邻区域的像素对中心点有影响,灰度值相似性高斯函数确保只有中心像素灰度值相近的才会被用来模糊运算。所以此方法会确保边界不会被模糊掉,因为边界处的灰度值变化比较大

# 9 邻域半径,两个75分别表示空间高斯函数标准差,灰度值相似性高斯函数标准差
Blur = cv2.bilateralFilter(img, 9, 75, 75)

enter image description here
(图像的纹理被模糊掉,但边界还在)

扫描二维码关注公众号,回复: 5830627 查看本文章

5、 形态学转换
形态学操作是根据形态进行的简单操作。一般情况下对二值化图像进行的操作,需要输入两个参数,一个是原始图像,第二个被称为结构化元素或核,它是用来决定操作的性质的。两个基本的形态学操作是腐蚀和膨胀。他们的变体构成了开运算,闭运算,梯度等。例如原始图片如下:
enter image description here
5.1 腐蚀
这个操作会把前景物体的边界腐蚀掉(前景是白色)。卷积核沿着图像滑动,如果与卷积核对应的原图的所有像素都是1,那么中心元素就保持原来的像素值,否则就变为0。根据卷积核的大小靠近前景的所有像素都会被腐蚀掉(变0),所以前景物体会变小,整幅图像的白色区域会减少。这对于去除白色噪音很有用,也可以用来断开两个连在一起的物体等。例如:

img = cv2.imread(“j.png”,  0)
kernel = np.ones((5,5),  np.uint8)
erosion = cv2.erode(img, kernel, iterations=1)

enter image description here
5.2 膨胀
与腐蚀相反,与卷积核对应的原图像的像素值中只要有一个是1,中心元素的像素值就是1。所以操作会增加图像的白色区域(前景)。一般在去噪声时先用腐蚀再用膨胀。因为腐蚀在去掉白噪声的同时,也会使前景对象变小。所以我们再对他进行膨胀。这时噪声已经被去除了,不会再回来了,但是前景还在并会增加。膨胀也可以用来连接两个分开的物体.

dilation = cv2.dilate(img, kernel, iterations=1)

enter image description here
5.3 开运算
先进行腐蚀再进行膨胀就叫做开运算,用来去除噪声。

opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)

enter image description here
5.4 闭运算
先膨胀再腐蚀。它经常用来填充物体中的小洞,或者前景物体上的小黑点。

closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)

enter image description here
5.5 形态学梯度
其实就是一幅图像膨胀与腐蚀的差别,看上去就像前景物体的轮廓

gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernei)

enter image description here
5.6 礼帽
原始图像与进行开运算之后得到的图像的差,(9x9的核);

tophat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)

enter image description here
5.7 黑帽
进行闭运算之后得到的图像与原始图像的差。

blackhat = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernei)

enter image description here
5.8 形态学操作之间的关系
opening: dst = open(src,element) = dilate(erode(src, element), element)
closing: dst = close(src, element) = erode(dilate(src,element), element)
mor……:dst = morph_grad(src, element) = dilate(src,element)-erode(src,element)
top hat: dst = tophat(src,element) = src-open(src,element)
blacjhat: dst = blackbat(src, element) = close(src,elemengt) – src

6、图像梯度
梯度简单来说就是求导。OpenCV提供了三种不同的梯度滤波器(高通滤波器):Sobel,Scharr和Laplacian。Sobel,Scharr其实就是求一阶或二阶导数。Scharr是对Sobel(求解梯度角度时使用小的卷积核求解)的优化。Laplacian是求二阶导数。
6.1 Sobel算子和Scharr算子
Sobel算子是高斯平滑与微分操作的结合体,所以它的抗噪声能力很好。可以设定求导的方向(xorder或yorder)。还可以设定使用的卷积核的大小(ksize)。如果ksize=-1,会使用3x3的Scharr滤波器,它的效果比3x3的sobel滤波器好(而且速度相同,所以在使用3x3滤波器时应该尽量使用Scharr滤波器)。3x3的Scharr滤波器卷积核如下(这些表格不太好制作,所以直接截图了):
enter image description here
6.2 Laplacian算子
拉普拉斯算子可以使用二阶导数的形式定义,可假设其离散实现类似于二阶Sobel导数,事实上,OpenCV在计算拉普拉斯算子时直接调用Sobel算子。公式如下:
enter image description here
实例如下:

import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread("./image/sudoku.png", 0)
#cv2.CV_64F 输出图像的深度(数据类型),可以使用-1, 与原图像保持一致 np.uint8
laplacian = cv2.Laplacian(img,cv2.CV_64F)
# 参数 1,0 为只在 x 方向求一阶导数,最大可以求 2 阶导数。
sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=5)
# 参数 0,1 为只在 y 方向求一阶导数,最大可以求 2 阶导数。
sobely = cv2.Sobel(img,cv2.CV_64F,0,1,ksize=5)
plt.subplot(2,2,1),plt.imshow(img,cmap = 'gray')
plt.title('Original'), plt.xticks([]), plt.yticks([])
plt.subplot(2,2,2),plt.imshow(laplacian,cmap = 'gray')
plt.title('Laplacian'), plt.xticks([]), plt.yticks([])
plt.subplot(2,2,3),plt.imshow(sobelx,cmap = 'gray')
plt.title('Sobel X'), plt.xticks([]), plt.yticks([])
plt.subplot(2,2,4),plt.imshow(sobely,cmap = 'gray')
plt.title('Sobel Y'), plt.xticks([]), plt.yticks([])
plt.show()

enter image description here
注意:关于matplotlib如何使用显示图像的这里没有介绍而是直接使用了,想了解matplotlib的朋友可以自行学习,官网上有很多简单的教程;

7、 Canny边缘检测
边缘在人类的视觉和计算机视觉中均起着重要的作用。人类能够仅凭着一张背景剪影或一个草图就识别出物体的类型和姿态。
OpenCV提供了一个非常方便的Canny函数做边缘检测,该算法非常流行,步进效果好,而且在OpenCV程序中实现也非常简单,其实Canny边缘检测的算法非常复杂,OpenCV里使用一个函数就完成了,下面介绍下Canny的算法步骤:
1.使用高斯滤波器对图像进行去噪。
2.计算梯度。
3.在边缘上使用非最大抑制(NMS).
4.滞后阈值。
5.最后还会分子所有边缘及其之间的链接,以保留真正的边缘并消除不明显的边缘。
而cv2.Canny()函数就可以完成以上几步:
参数1:输入的图像
参数2,3:分别是minVal和maxVal
参数4:L2gradient。可以用来设定求梯度大小的方程。默认是False,默认的方程是:enter image description here;True的话是另外一个方程:
enter image description here
看一个简单的例子:

import cv2 
import numpy as np 
from matplotlib import pyplot as plt 
img = cv2.imread("./image/test3.jpg", 0)
edges = cv2.Canny(img, 100, 200)
plt.subplot(121),plt.imshow(img, cmap='gray')
plt.title("Original")
plt.subplot(122),plt.imshow(edges, cmap='gray')
plt.title("Edge")
plt.show()

enter image description here

8、图像金字塔
在不知道目标在图像中的尺寸大小的情况下,需要创建一组图像,这些图像是具有不同分辨的原始图像。So叫做图像金字塔。有两类图像金字塔:高斯金字塔和拉普拉斯金字塔。
高斯金字塔的顶部是通过将底部图像中的连续的行和列去除得到的。顶部图像中的每个像素值等于下一层图像中5个像素的高斯加权平均值。这样操作一次一个 MxN 的图像就变成了一个 M/2xN/2 的图像。所以这幅图像的面积就变为原来图像面积的四分之一。这被称为 Octave。连续进行这样的操作我们就会得到一个分辨率不断下降的图像金字塔。我们可以使用函数cv2.pyrDown() 和 cv2.pyrUp() 构建图像金字塔
函数cv2.pyrDown()从一个高分辨率大尺寸的图像向上构建一个金字塔(尺寸变小,分辨率降低)。
函数 cv2.pyrUp() 从一个低分辨率小尺寸的图像向下构建一个金子塔(尺寸变大,但分辨率不会增加)。
一旦使用cv2.pyrDown(),图像的分辨率就会下降,信息就会被丢失。
拉普拉斯金字塔可以有高斯金字塔计算得来,公式如下:
Li = Gi – PyrUp(Gi+1)
拉普拉斯金字塔的图像看起来就像边界图,其中很多像素都是0。经常被用来在图像压缩中。
下面讲一个图像金字塔的应用,即使用金字塔进行图像的融合,就拿苹果和橘子的融合来说,步骤如下:
1. 读入两幅图像,苹果和橘子
2. 构建苹果和橘子的高斯金字塔(6层)
3. 根据高斯金字塔计算拉普拉斯金字塔
4. 在拉普拉斯的每一层进行图像融合(一边一半)
5. 根据融合后的图像金字塔重新构建原图像

import cv2
import numpy as np
A = cv2.imread('./image/apple.jpg')
B = cv2.imread('./image/orange.jpg')
# generate Gaussian pyramid for A
G = A.copy()
gpA = [G]
for i in range(6):
    G = cv2.pyrDown(G)
    gpA.append(G)
# generate Gaussian pyramid for B
G = B.copy()
gpB = [G]
for i in range(6):
    G = cv2.pyrDown(G)
    gpB.append(G)
# generate Laplacian Pyramid for A
lpA = [gpA[5]]
for i in range(5, 0, -1):
    GE = cv2.pyrUp(gpA[i])
    L = cv2.subtract(gpA[i-1], GE)
    lpA.append(L)
# generate Laplacian Pyramid for B
lpB = [gpB[5]]
for i in range(5, 0, -1):
    GE = cv2.pyrUp(gpB[i])
    L = cv2.subtract(gpB[i-1], GE)
    lpB.append(L)
LS = []
for la, lb in zip(lpA, lpB):
    rows, cols, dpt = la.shape
    ls = np.hstack((la[:, 0:cols/2], lb[:, cols/2:]))
    LS.append(ls)
# now reconstruct
ls_ = LS[0]
for i in range(1, 6):
    ls_ = cv2.pyrUp(ls_)
    ls_ = cv2.add(ls_, LS[i])
# image with direct connecting each half
real = np.hstack((A[:,:cols/2],B[:,cols/2:]))
cv2.imwrite('Pyramid_blending2.jpg', ls_)
cv2.imwrite('Direct_blending.jpg', real)

对比效果如下:
enter image description here

9、 OpenCV中的轮廓
可能会有些人搞不清楚轮廓和边缘的区别,轮廓可以简单认为成将连续的点(连着边界)连在一起的曲线,具有相同的颜色或者灰度。边缘可以理解成物体的形态,看来边缘就能识别出是什么物体。
查找轮廓的函数:cv2.findContours()
参数1:输入图像
参数2:轮廓检索模式
参数3:轮廓近似方法
返回值1:输出图像
返回值2:轮廓(是一个python列表,其中存储着图像的所有轮廓,每个轮廓都是一个Numpy数组,包含对象边界点(x,y)的坐标)。
返回值3:轮廓的层析结构
注意:查找轮廓的函数会修改原始图像(若不想可使用img.copy()函数)。在OpenCV中,查找轮廓就像在黑色背景中找白色物体。So要找的物体应该是白色,而背景应该是黑色。
如何绘制轮廓呢?使用函数cv2.drawContours()。该函数可以根据提供的边界点绘制任何形状;第一个参数是原始图像,第二个参数是轮廓(一个Python列表),第三个参数是轮廓的索引(在绘制独立轮廓时很有用,设置为-1时绘制所有的轮廓),接下来的参数就是轮廓的颜色和厚度等。
9.1 轮廓的特征
9.1.1 矩
图像的矩可以帮助我们计算图像的质心,面积等。
函数cv2.moments()会将计算得到的矩以一个字典的形式返回。例如:

M = cv2.moments(contours[1])
print(M)

可以计算出对象的重心:Cx = int(M[‘m10’] / M[‘m00’])
Cy = int(M[‘m01’] / M[‘m00’])
9.1.2 轮廓面积
轮廓的面积可以使用函数:cv2.contourArea()计算得到,也可以使用矩(0阶矩)M[‘m00’]。

area = cv2.contourArea(contours[1])
print(area)

9.1.3 轮廓周长
也称为弧长。可以使用函数cv2.arcLength()计算得到。这个函数的第二个参数可以用来指定对象的形状是闭合的(True),还是打开的(一个曲线)

perimeter = cv2.arcLength(contours[1], True)
print(perimeter)

9.1.4 轮廓近似
将轮廓形状近似到另一种由更少点组成的轮廓形状,新轮廓的点多额数目有我们设定的准确度来决定。

epsilon = cv2.arcLength(contours[0], True)
approx = cv2.approxPolyDP(contours[0], epsilon, True)

9.1.5 凸包
凸包与轮廓近似相似,但不同,虽然有些情况下他们给出的结果是一样的。函数cv2.convexHull()可以用来检测一个曲线是否具有凸性缺陷,并能纠正缺陷。

hull = cv2.convexHull(points[, hull[, clockwise[, returnPoints]]

参数: points传入的轮廓
Hull输出,通常不需要
Clockwise方向标志,如果设置为True,输出的凸包是顺时针方向否则是逆时针
returnPoints默认值为True。她会返回凸包上点的坐标,如果设置为False,就会返回与凸包点对应得轮廓上的点。

   hull = cv2.convexHull(contours[0])
   print(hull)# [[[388 272]] [[ 21 272]] [[ 21  24]] [[388  24]]] ###1
   hull = cv2.convexHull(contours[0], returnPoints = False)
   print(hull)# [[147] [ 73] [  0] [164]]  ###2
   print(contours[0][147]) # [[388 272]] ###3
    ###2就是###1轮廓点的索引。

9.1.6 凸性检测
函数:cv2.isContourConvex()可以用来检测一个曲线是不是凸的,返回True是凸的,False不是:

K = cv2.isContourConvex(contours[0])

9.1.7 边界矩形
直边界矩形(没有旋转的矩形),使用函数cv2.boundingRect(),返回4个值,分别是矩形左上角坐标(x,y),矩形的宽和高(w,h)

x,y,w,h = cv2.boundingRect(contours[1])
img = cv2.rectangle(img, (x,y), (x+w,y+h), (255,0,0), 2)

旋转的边界矩形,这个边界矩形是面积最小的。使用函数cv2.minAreaRect()。返回的是一个Box2D结构,其中包含矩形左上角角点的坐标(x,y),矩形的宽和高(w,h),以及旋转角度。但是绘制这个矩形需要矩形的4个角点,可以通过函数cv2.boxPoints()获得。

rect = cv2.minAreaRect(contours[3])
box = cv2.boxPoints(rect)
box = np.int0(box)
img = cv2.drawContours(img, [box], 0, (0,0,255), 2)

9.1.8 最小外接圆
函数cv2.minEnclosingCircle()可以找到一个对象的外切圆。

(x, y), radius = cv2.minEnclosingCircle(contours[3])
cennt = (int(x), int(y))
radius = int(radius)
img = cv2.circle(img, cennt, radius, (9,211,55), 2)

9.1.9 椭圆拟合
使用的函数为cv2.ellipse(),返回值其实就是旋转边界矩形的内切圆。

ellipse = cv2.fitEllipse(contours[3])
img = cv2.ellipse(img, ellipse, (0,157,157), 2)

enter image description here
9.2 轮廓的性质
9.2.1 长宽比
边界矩形的宽高比;width/height。
9.2.2 Extent
轮廓面积与边界矩形面积的比;
9.2.3 Solidity
轮廓面积与凸包面积的比;
9.2.4 Equivalent Diameter
与轮廓面积相等的圆形的直径

Area = cv2.contourArea(cnt)
Diameter = np.sqrt(4*area/np.pi)

9.2.5 方向
对象的方向,下面的方法还会返回长轴和短轴的长度

(x,y),(MA,ma),angle = cv2.fitEllipse(cnt)

9.2.6 掩模和像素点
构建对象的所有像素点,如下做法:

Mask = np.zeros(imgray.shape, np.uint8)
Cv2.drawContours(mask, [cnt], 0, 255, -1)# 这里一定要使用参数-1,绘制填充的轮廓
Pixelpoints = np.transpose(np.nonzero(mask))
# or 
Pixelpoints = cv2.findNonZero(mask)

9.2.7 最大值和最小值及他们的位置
可以使用掩模图像得到这些参数。

Min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(imgray, mask=mask)

9.2.8 平均颜色以及平均灰度
也可以使用同样的掩模求一个对象的平均颜色或平均灰度

Mean_val = cv2.mean(img, mask=mask)

9.2.9 极点
一个对象最上面,最下面,最左边,最右边的点

leftmost = tuple(cnt[cnt[:,:,0].argmin()][0])
rightmost = tuple(cnt[cnt[:,:,0].argmax()][0])
topmost = tuple(cnt[cnt[:,:,1].argmin()][0])
bottommost = tuple(cnt[cnt[:,:,1].argmax()][0])

结语:本章节有点多,但都是很必要的知识点,下一章节会讲图像的直方图,变换,模板匹配以及Hough变换和图像分割。谢谢

猜你喜欢

转载自blog.csdn.net/Eric_lmy/article/details/80962669