opencv第八天

发布于:2022-12-06 ⋅ 阅读:(308) ⋅ 点赞:(0)

目标

在本章中,我们将学习 - 凸性缺陷以及如何找到它们 - 查找点到多边形的最短距离 - 匹配不同的形状

理论和代码

1. 凸性缺陷

我们看到了关于轮廓的第二章的凸包。从这个凸包上的任何偏差都可以被认为是凸性缺陷。 OpenCV有一个函数来找到这个,cv.convexityDefects()。一个基本的函数调用如下:

hull = cv.convexHull(cnt,returnPoints = False)
defects = cv.convexityDefects(cnt,hull)

注意 记住,我们必须在发现凸包时,传递returnPoints= False,以找到凸性缺陷。

它返回一个数组,其中每行包含这些值—[起点、终点、最远点、到最远点的近似距离]。我们可以用图像把它形象化。我们画一条连接起点和终点的线,然后在最远处画一个圆。记住,返回的前三个值是cnt的索引。所以我们必须从cnt中获取这些值。

import cv2 as cv
import numpy as np
img = cv.imread('star.jpg')
img_gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
ret,thresh = cv.threshold(img_gray, 127, 255,0)
contours,hierarchy = cv.findContours(thresh,2,1)
cnt = contours[0]
hull = cv.convexHull(cnt,returnPoints = False)
defects = cv.convexityDefects(cnt,hull)
for i in range(defects.shape[0]):
    s,e,f,d = defects[i,0]
    start = tuple(cnt[s][0])
    end = tuple(cnt[e][0])
    far = tuple(cnt[f][0])
    cv.line(img,start,end,[0,255,0],2)
    cv.circle(img,far,5,[0,0,255],-1)
cv.imshow('img',img)
cv.waitKey(0)
cv.destroyAllWindows()

查看结果:

2. 点多边形测试

这个函数找出图像中一点到轮廓线的最短距离。它返回的距离,点在轮廓线外时为负,点在轮廓线内时为正,点在轮廓线上时为零。

例如,我们可以检查点(50,50)如下:

dist = cv.pointPolygonTest(cnt,(50,50),True)

在函数中, 参数1:轮廓。参数2:点,参数3:measureDist。如果它是真的,它会找到有符号的距离。如果为假,则查找该点是在轮廓线内部还是外部(分别返回+1、-1和0)。

注意 如果您不想找到距离,请确保第三个参数为False,因为这是一个耗时的过程。因此,将其设置为False可使速度提高2-3倍。

 

3. 形状匹配

OpenCV附带一个函数**cv.matchShapes**(),该函数使我们能够比较两个形状或两个轮廓,并返回一个显示相似性的度量。结果越低,匹配越好。它是根据矩值计算出来的。不同的测量方法在文档中有解释。

import cv2 as cv
import numpy as np
img1 = cv.imread('star.jpg',0)
img2 = cv.imread('star2.jpg',0)
ret, thresh = cv.threshold(img1, 127, 255,0)
ret, thresh2 = cv.threshold(img2, 127, 255,0)
contours,hierarchy = cv.findContours(thresh,2,1)
cnt1 = contours[0]
contours,hierarchy = cv.findContours(thresh2,2,1)
cnt2 = contours[0]
ret = cv.matchShapes(cnt1,cnt2,1,0.0)
print( ret )

我尝试过匹配下面给出的不同形状的形状:

我得到以下结果:

- 匹配的图像A与本身= 0.0

- 匹配图像A与图像B = 0.001946

- 匹配图像A与图像C = 0.326911

看,即使是图像旋转也不会对这个比较产生很大的影响

理论

在前几篇关于轮廓的文章中,我们已经讨论了与OpenCV提供的轮廓相关的几个函数。但是当我们使用**cv.findcontour**()函数在图像中找到轮廓时,我们已经传递了一个参数,轮廓检索模式。我们通常通过了**cv.RETR_LIST**或**cv.RETR_TREE**,效果很好。但这到底意味着什么呢?

另外,在输出中,我们得到了三个数组,第一个是图像,第二个是轮廓,还有一个我们命名为**hierarchy**的输出(请检查前面文章中的代码)。但我们从未在任何地方使用过这种层次结构。那么这个层级是什么?它是用来做什么的?它与前面提到的函数参数有什么关系?

这就是我们在本文中要讨论的内容。

层次结构是什么?

通常我们使用**cv.findcontour**()函数来检测图像中的对象,对吧?有时对象在不同的位置。但在某些情况下,某些形状在其他形状中。就像嵌套的图形一样。在这种情况下,我们把外部的称为**父类**,把内部的称为**子类**。这样,图像中的轮廓就有了一定的相互关系。我们可以指定一个轮廓是如何相互连接的,比如,它是另一个轮廓的子轮廓,还是父轮廓等等。这种关系的表示称为**Hierarchy**。

下面是一个例子: 

 

 

在这张图中,有一些形状我已经从**0-5**开始编号。*2*和*2a*表示最外层盒子的外部和内部轮廓。

这里,等高线0,1,2在**外部或最外面**。我们可以说,它们在**层级-0**中,或者简单地说,它们在**同一个层级**中。

其次是**contour-2a**。它可以被认为是**contour-2的子级**(或者反过来,contour-2是contour-2a的父级)。假设它在**层级-1**中。类似地,contour-3是contour-2的子级,它位于下一个层次结构中。最后,轮廓4,5是contour-3a的子级,他们在最后一个层级。从对方框的编号来看,我认为contour-4是contour-3a的第一个子级(它也可以是contour-5)。

我提到这些是为了理解一些术语,比如**相同层级 same hierarchy level**, , , , ,外部轮廓 external contour子轮廓 child contour父轮廓 parent contour,**第一个子轮廓 first child**等等。现在让我们进入OpenCV。

OpenCV中的分级表示

所以每个轮廓都有它自己的信息关于它是什么层次,谁是它的孩子,谁是它的父母等等。OpenCV将它表示为一个包含四个值的数组:[Next, Previous, First_Child, Parent]

“Next表示同一层次的下一个轮廓。”

例如,在我们的图片中取contour-0。谁是下一个同级别的等高线?这是contour-1。简单地令Next = 1。类似地,Contour-1也是contour-2。所以Next = 2。 contour-2呢?同一水平线上没有下一条等高线。简单地,让Next = -1。contour-4呢?它与contour-5处于同一级别。它的下一条等高线是contour-5,所以next = 5

“Previous表示同一层次上的先前轮廓。”

和上面一样。contour-1之前的等值线为同级别的contour-0。类似地,contour-2也是contour-1。对于contour-0,没有前项,所以设为-1。

“First_Child表示它的第一个子轮廓。”

没有必要作任何解释。对于contour-2, child是contour-2a。从而得到contour-2a对应的指标值。contour-3a呢?它有两个孩子。但我们只关注第一个孩子。它是contour-4。那么First_Child = 4 对contour-3a而言。

“Parent表示其父轮廓的索引。”

它与**First_Child**相反。对于轮廓线-4和轮廓线-5,父轮廓线都是轮廓线-3a。对于轮廓3a,它是轮廓-3,以此类推。

注意 如果没有子元素或父元素,则该字段被视为-1

现在我们已经了解了OpenCV中使用的层次样式,我们可以借助上面给出的相同图像来检查OpenCV中的轮廓检索模式。一些标志如 cv.RETR_LISTcv.RETR_TREE,cv.RETR_CCOMP, **cv.RETR_EXTERNAL**等等的含义。

轮廓检索模式

1. RETR_LIST

这是四个标志中最简单的一个(从解释的角度来看)。它只是检索所有的轮廓,但不创建任何亲子关系。在这个规则下,父轮廓和子轮廓是平等的,他们只是轮廓。他们都属于同一层级。

这里,第3和第4项总是-1。但是很明显,下一项和上一项都有对应的值。你自己检查一下就可以了。

下面是我得到的结果,每一行是对应轮廓的层次细节。例如,第一行对应于轮廓0。下一条轮廓是轮廓1。所以Next = 1。没有先前的轮廓,所以Previous=-1。剩下的两个,如前所述,是-1

>>> hierarchy
array([[[ 1, -1, -1, -1],
        [ 2,  0, -1, -1],
        [ 3,  1, -1, -1],
        [ 4,  2, -1, -1],
        [ 5,  3, -1, -1],
        [ 6,  4, -1, -1],
        [ 7,  5, -1, -1],
        [-1,  6, -1, -1]]])

如果您没有使用任何层次结构特性,那么这是在您的代码中使用的最佳选择。

2. RETR_EXTERNAL

如果使用此标志,它只返回极端外部标志。所有孩子的轮廓都被留下了。我们可以说,根据这项规则,每个家庭只有长子得到关注。它不关心家庭的其他成员:)

所以在我们的图像中,有多少个极端的外轮廓?在等级0级?有3个,即等值线是0 1 2,对吧?现在试着用这个标志找出等高线。这里,给每个元素的值与上面相同。并与上述结果进行了比较。以下是我得到的:

>>> hierarchy
array([[[ 1, -1, -1, -1],
        [ 2,  0, -1, -1],
        [-1,  1, -1, -1]]])

如果只想提取外部轮廓,可以使用此标志。它在某些情况下可能有用。

3. RETR_CCOMP

此标志检索所有轮廓并将其排列为2级层次结构。物体的外部轮廓(即物体的边界)放在层次结构1中。对象内部孔洞的轮廓(如果有)放在层次结构2中。如果其中有任何对象,则其轮廓仅在层次结构1中重新放置。以及它在层级2中的漏洞等等。

只需考虑在黑色背景上的“白色的零”图像。零的外圆属于第一级,零的内圆属于第二级。

我们可以用一个简单的图像来解释它。这里我用红色标注了等高线的顺序和它们所属的层次,用绿色标注(1或2),顺序与OpenCV检测等高线的顺序相同。

考虑第一个轮廓,即contour 0。这是hierarchy- 1。它有两个孔,分别是等高线1和2,属于第二级。因此,对于轮廓-0,在同一层次的下一个轮廓是轮廓-3。previous也没有。在hierarchy-2中的它的第一个子结点是contour-1。它没有父类,因为它在hierarchy-1中。所以它的层次数组是[3,-1,1,-1]

现在contour-1。它在层级-2中。相同层次结构中的下一个(在contour-1的父母关系下)是contour-2。没有previous。没有child,但是parent是contour-0。所以数组是[2,-1,-1,0]

类似的contour-2:它在hierarchy-2中。在contour-0下,同一层次结构中没有下一个轮廓。所以没有Nextprevious是contour-1。没有childparent是contour0。所以数组是[-1,1,-1,0]

contour-3:层次-1的下一个是轮廓-5。以前是contour-0。child是contour4,没有parent。所以数组是[5,0,4,-1]

contour-4:它在contour-3下的层次结构2中,它没有兄弟姐妹。没有next,没有previous,没有childparent是contour-3。所以数组是[-1,-1,-1,3]

剩下的你可以补充。这是我得到的最终答案:

>>> hierarchy
array([[[ 3, -1,  1, -1],
        [ 2, -1, -1,  0],
        [-1,  1, -1,  0],
        [ 5,  0,  4, -1],
        [-1, -1, -1,  3],
        [ 7,  3,  6, -1],
        [-1, -1, -1,  5],
        [ 8,  5, -1, -1],
        [-1,  7, -1, -1]]])

4. RETR_TREE

这是最后一个家伙,完美先生。它检索所有的轮廓并创建一个完整的家族层次结构列表。它甚至告诉,谁是爷爷,父亲,儿子,孙子,甚至更多…:)。

例如,我拿上面的图片,重写了cv的代码。RETR_TREE,根据OpenCV给出的结果重新排序等高线并进行分析。同样,红色的字母表示轮廓数,绿色的字母表示层次顺序。

contour-0:它在hierarchy-0中。同一层次结构的next轮廓是轮廓-7。没有previous的轮廓。child是contour-1,没有parent。所以数组是[7,-1,1,-1]

contour-2为例:它在hierarchy-1中。没有轮廓在同一水平。没有previouschildcontour-3。父母是contour-1。所以数组是[-1,-1,3,1]

剩下的,你自己试试。以下是完整答案:

>>> hierarchy
array([[[ 7, -1,  1, -1],
        [-1, -1,  2,  0],
        [-1, -1,  3,  1],
        [-1, -1,  4,  2],
        [-1, -1,  5,  3],
        [ 6, -1, -1,  4],
        [-1,  5, -1,  4],
        [ 8,  0, -1, -1],
        [-1,  7, -1, -1]]])

 

目标

学会 - 使用OpenCV和Numpy函数查找直方图 - 使用OpenCV和Matplotlib函数绘制直方图 - 你将看到以下函数:cv.calcHist(),np.histogram()等。

理论

那么直方图是什么?您可以将直方图视为图形或绘图,从而可以总体了解图像的强度分布。它是在X轴上具有像素值(不总是从0到255的范围),在Y轴上具有图像中相应像素数的图。

这只是理解图像的另一种方式。通过查看图像的直方图,您可以直观地了解该图像的对比度,亮度,强度分布等。当今几乎所有图像处理工具都提供直方图功能。以下是剑桥彩色网站的图片,我建议您访问该网站以获取更多详细信息。

您可以看到图像及其直方图。(请记住,此直方图是针对灰度图像而非彩色图像绘制的)。直方图的左侧区域显示图像中较暗像素的数量,而右侧区域则显示明亮像素的数量。从直方图中,您可以看到暗区域多于亮区域,而中间调的数量(中间值的像素值,例如127附近)则非常少。

寻找直方图

现在我们有了一个关于直方图的想法,我们可以研究如何找到它。OpenCV和Numpy都为此内置了功能。在使用这些功能之前,我们需要了解一些与直方图有关的术语。

BINS:上面的直方图显示每个像素值的像素数,即从0到255。即,您需要256个值来显示上面的直方图。但是考虑一下,如果您不需要分别找到所有像素值的像素数,而是找到像素值间隔中的像素数怎么办? 例如,您需要找到介于0到15之间的像素数,然后找到16到31之间,…,240到255之间的像素数。只需要16个值即可表示直方图。这就是在OpenCV教程中有关直方图的示例中显示的内容。

因此,您要做的就是将整个直方图分成16个子部分,每个子部分的值就是其中所有像素数的总和。 每个子部分都称为“ BIN”。在第一种情况下,bin的数量为256个(每个像素一个),而在第二种情况下,bin的数量仅为16个。BINS由OpenCV文档中的**histSize**术语表示。

DIMS:这是我们为其收集数据的参数的数量。在这种情况下,我们仅收集关于强度值的一件事的数据。所以这里是1。

RANGE:这是您要测量的强度值的范围。通常,它是[0,256],即所有强度值。

1. OpenCV中的直方图计算

因此,现在我们使用**cv.calcHist**()函数查找直方图。让我们熟悉一下该函数及其参数:

cv.calcHist(images,channels,mask,histSize,ranges [,hist [,accumulate]])

  1. images:它是uint8或float32类型的源图像。它应该放在方括号中,即“ [img]”。
  2. channels:也以方括号给出。它是我们计算直方图的通道的索引。例如,如果输入为灰度图像,则其值为[0]。对于彩色图像,您可以传递[0],[1]或[2]分别计算蓝色,绿色或红色通道的直方图。
  3. mask:图像掩码。为了找到完整图像的直方图,将其指定为“None”。但是,如果要查找图像特定区域的直方图,则必须为此创建一个掩码图像并将其作为掩码。(我将在后面显示一个示例。)
  4. histSize:这表示我们的BIN计数。需要放在方括号中。对于全尺寸,我们通过[256]。
  5. ranges:这是我们的RANGE。通常为[0,256]。

因此,让我们从示例图像开始。只需以灰度模式加载图像并找到其完整直方图即可。

img = cv.imread('home.jpg',0)
hist = cv.calcHist([img],[0],None,[256],[0,256])

hist是256x1的数组,每个值对应于该图像中具有相应像素值的像素数。

2. numpy的直方图计算

Numpy还为您提供了一个函数**np.histogram**()。因此,除了**calcHist**()函数外,您可以尝试下面的代码:

hist,bins = np.histogram(img.ravel(),256,[0,256])

hist与我们之前计算的相同。但是bin将具有257个元素,因为Numpy计算出bin的范围为0-0.991-1.992-2.99等。因此最终范围为255-255.99。为了表示这一点,他们还在最后添加了256。但是我们不需要256。最多255就足够了。

  • 另外 Numpy还有另一个函数**np.bincount**(),它比np.histogram()快10倍左右。因此,对于一维直方图,您可以更好地尝试一下。不要忘记在np.bincount中设置minlength = 256。例如,hist = np.bincount(img.ravel(),minlength = 256)

注意 OpenCV函数比np.histogram()快大约40倍。因此,尽可能使用OpenCV函数。

现在我们应该绘制直方图,但是怎么绘制?

绘制直方图

有两种方法, 1. 简短的方法:使用Matplotlib绘图功能 2. 稍长的方法:使用OpenCV绘图功能

1. 使用Matplotlib

Matplotlib带有直方图绘图功能:matplotlib.pyplot.hist() 它直接找到直方图并将其绘制。您无需使用**calcHist**()或np.histogram()函数来查找直方图。请参见下面的代码:

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('home.jpg',0)
plt.hist(img.ravel(),256,[0,256]); plt.show()

你将得到如下的结果: 

或者,您可以使用matplotlib的法线图,这对于BGR图是很好的。为此,您需要首先找到直方图数据。试试下面的代码:

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('home.jpg')
color = ('b','g','r')
for i,col in enumerate(color):
    histr = cv.calcHist([img],[i],None,[256],[0,256])
    plt.plot(histr,color = col)
    plt.xlim([0,256])
plt.show()


'''plt.xlim()   (即limit) 和 plt.xticks()
plt.xlim() 显示的是x轴的作图范围,同时plt.ylim() 显示的是y轴的作图范围,而 plt.xticks() 表达的是x轴的刻度内容的范围

 plt.xlim()有两个参数输入:

1 plt.xlim(num1, num2)
2 plt.xlim(xmin=num1,xmax=num2)
用于设置x轴的范围

但是如果图片已经画好了,plt.xlim在图片完成之后,则会直接输出x轴的范围值

 plt.xticks()

在matplotlib中ticks表示的是刻度,而刻度有两层意思,一个是刻标(locs),一个是刻度标签(tick labels)。在作图时,x轴y轴都是连续的,所以刻标可以随意指定,就是在连续变量上找寻位置,而刻度标签则可以对应替换

xticks(rotation:旋转度数):更改绘制x轴标签方向(与水平方向的逆时针夹角度数)

复制代码
#plt.xticks()返回了两个对象,一个是刻标(locs),另一个是刻度标签
locs, labels = plt.xticks()

# 显示x轴的刻标
plt.xticks( arange(6) )

# 显示x轴的刻标以及对应的标签
pltxticks( arange(5), ('Tom', 'Dick', 'Harry', 'Sally', 'Sue') )'''

结果: 

您可以从上图中得出,蓝色在图像中具有一些高值域(显然这应该是由于天空)

2. 使用 OpenCV

好吧,在这里您可以调整直方图的值及其bin值,使其看起来像x,y坐标,以便您可以使用**cv.line**()或cv.polyline()函数绘制它以生成与上述相同的图像。OpenCV-Python2官方示例已经提供了此功能。检查示例/python/hist.py中的代码。

掩码的应用

我们使用了cv.calcHist()来查找整个图像的直方图。如果你想找到图像某些区域的直方图呢?只需创建一个掩码图像,在你要找到直方图为白色,否则黑色。然后把这个作为掩码传递。

img = cv.imread('home.jpg',0)
# create a mask
mask = np.zeros(img.shape[:2], np.uint8)
mask[100:300, 100:400] = 255
masked_img = cv.bitwise_and(img,img,mask = mask)
# 计算掩码区域和非掩码区域的直方图
# 检查作为掩码的第三个参数
hist_full = cv.calcHist([img],[0],None,[256],[0,256])
hist_mask = cv.calcHist([img],[0],mask,[256],[0,256])
plt.subplot(221), plt.imshow(img, 'gray')
plt.subplot(222), plt.imshow(mask,'gray')
plt.subplot(223), plt.imshow(masked_img, 'gray')
plt.subplot(224), plt.plot(hist_full), plt.plot(hist_mask)
plt.xlim([0,256])
plt.show()

查看结果。在直方图中,蓝线表示完整图像的直方图,绿线表示掩码区域的直方图。

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到