文章目录
前言
逼近多边形是轮廓的高度近似,但是有时候,我们希望使用一个多边形的凸包来简化它。凸包跟逼近多边形很像,只不过它是物体最外层的“凸”多边形。凸包指的是完全包含原有轮廓,并且仅由轮廓上的点所构成的多边形。凸包的每一处都是凸的,即在凸包内连接任意两点的直线都在凸包的内部。在凸包内,任意连续三个点的内角小于180°。
例如,在图1中,最外层的多边形为机械手的凸包,在机械手边缘与凸包之间的部分被称为凸缺陷(Convexity Defect),凸缺陷能够用来处理手势识别等问题。
获取凸包
OpenCV提供函数cv2.convexHull()用于获取轮廓的凸包。该函数的语法格式为:
hull = cv2.convexHull( points[, clockwise[, returnPoints]] )
式中的返回值hull为凸包角点。
式中的参数如下:
- points:轮廓。
- clockwise:布尔型值。该值为True时,凸包角点将按顺时针方向排列;该值为False时,则以逆时针方向排列凸包角点。
- returnPoints:布尔型值。默认值是True,函数返回凸包角点的x/y轴坐标;当为False时,函数返回轮廓中凸包角点的索引。
【例1】观察函数cv2.convexHull()内参数returnPoints的使用情况。
import cv2
o = cv2.imread('C:\\Users\\Administrator\\Desktop\\caonima.png')
gray = cv2.cvtColor(o, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(binary,
cv2.RETR_TREE,
cv2.CHAIN_APPROX_SIMPLE)
hull = cv2.convexHull(contours[0]) # 返回坐标值
print("returnPoints为默认值True时返回值hull的值:\n", hull)
hull2 = cv2.convexHull(contours[0], returnPoints=False) # 返回索引值
print("returnPoints为False时返回值hull的值:\n", hull2)
【例2】使用函数cv2.convexHull()获取轮廓的凸包。
import cv2
# --------------读取并绘制原始图像------------------
o = cv2.imread('hand.bmp')
cv2.imshow("original", o)
# --------------提取轮廓------------------
gray = cv2.cvtColor(o, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray,127,255, cv2.THRESH_BINARY)
image, contours, hierarchy = cv2.findContours(binary,
cv2.RETR_LIST,
cv2.CHAIN_APPROX_SIMPLE)
# --------------寻找凸包,得到凸包的角点------------------
hull = cv2.convexHull(contours[0])
# --------------绘制凸包------------------
cv2.polylines(o, [hull], True, (0, 255, 0), 2)
# --------------显示凸包------------------
cv2.imshow("result", o)
cv2.waitKey()
cv2.destroyAllWindows()
运行上述程序,会显示如图2所示的图像。
- 左图是图像o。
- 右图是包含获取的凸包的图像。
凸缺陷
凸包与轮廓之间的部分,称为凸缺陷。在OpenCV中使用函数cv2.convexityDefects()获取凸缺陷。其语法格式如下:
convexityDefects = cv2.convexityDefects( contour, convexhull )
式中的返回值convexityDefects为凸缺陷点集。它是一个数组,每一行包含的值是[起点,终点,轮廓上距离凸包最远的点,最远点到凸包的近似距离]。
需要注意的是,返回结果中[起点,终点,轮廓上距离凸包最远的点,最远点到凸包的近似距离]的前三个值是轮廓点的索引,所以需要到轮廓点中去找它们。
式中的参数如下:
- contour是轮廓。
- convexhull是凸包。
需要注意的是,用cv2.convexityDefects()计算凸缺陷时,要使用凸包作为参数。在查找该凸包时,所使用函数cv2.convexHull()的参数returnPoints的值必须是False。为了让大家更直观地观察凸缺陷点集,我们尝试将凸缺陷点集在一幅图内显示出来。实现方式为,将起点和终点用一条线连接,在最远点画一个圆圈。下面我们通过一个例子来展示上述操作。
【例3】使用函数cv2.convexityDefects()计算凸缺陷。
import cv2
#----------------原图--------------------------
img = cv2.imread('hand.bmp')
cv2.imshow('original', img)
#----------------构造轮廓--------------------------
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray, 127, 255,0)
image, contours, hierarchy = cv2.findContours(binary,
cv2.RETR_TREE,
cv2.CHAIN_APPROX_SIMPLE)
#----------------凸包--------------------------
cnt = contours[0]
hull = cv2.convexHull(cnt, returnPoints = False)
defects = cv2.convexityDefects(cnt, hull)
print("defects=\n", defects)
#----------------构造凸缺陷--------------------------
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])
cv2.line(img, start, end, [0,0,255],2)
cv2.circle(img, far,5, [255,0,0], -1)
#----------------显示结果,释放图像--------------------------
cv2.imshow('result', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
图中用点标出了凸缺陷。
几何学测试
本节介绍几种与凸包有关的几何学测试。
测试轮廓是否是凸形的
在OpenCV中,可以用函数cv2.isContourConvex()来判断轮廓是否是凸形的,其语法格式为:
retval = cv2.isContourConvex( contour )
式中:
- 返回值retval是布尔型值。该值为True时,表示轮廓为凸形的;否则,不是凸形的。
- 参数contour为要判断的轮廓。
【例4】使用函数cv2.isContourConvex()来判断轮廓是否是凸形的。
根据题目的要求,编写代码如下:
import cv2
o = cv2.imread('C:\\Users\\Administrator\\Desktop\\caonima.png')
cv2.imshow("original", o)
gray = cv2.cvtColor(o, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray,127,255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(binary,
cv2.RETR_LIST,
cv2.CHAIN_APPROX_SIMPLE)
#--------------凸包----------------------
image1=o.copy()
hull = cv2.convexHull(contours[0])
cv2.polylines(image1, [hull], True, (0, 255, 0), 2)
print("使用函数cv2.convexHull()构造的多边形是否是凸形的:",
cv2.isContourConvex(hull))
cv2.imshow("result1", image1)
#------------逼近多边形--------------------
image2=o.copy()
epsilon = 0.01*cv2.arcLength(contours[0], True)
approx = cv2.approxPolyDP(contours[0], epsilon, True)
image2=cv2.drawContours(image2, [approx],0, (0,0,255),2)
print("使用函数cv2.approxPolyDP()构造的多边形是否是凸形的:",
cv2.isContourConvex(approx))
cv2.imshow("result2", image2)
#------------释放窗口--------------------
cv2.waitKey()
cv2.destroyAllWindows()
- 左上1图是图像o。
- 左上2图显示了在图像o上使用函数cv2.convexHull()构造的凸包。
- 右下图2显示了在图像o上使用函数cv2.approxPolyDP()构造的逼近多边形。
从以上运行结果可以看出:
- 使用函数cv2.convexHull()构造凸包后,对绘制的凸包使用函数cv2.isContourConvex()判断,返回值为True,说明该轮廓是凸形的。
- 使用函数cv2.approxPolyDP()构造逼近多边形后,对绘制的逼近多边形使用函数cv2.isContourConvex()判断,返回值为False,说明该轮廓(多边形)不是凸形的。
点到轮廓的距离
在OpenCV中,函数cv2.pointPolygonTest()被用来计算点到多边形(轮廓)的最短距离(也就是垂线距离),这个计算过程又称点和多边形的关系测试。该函数的语法格式为:
retval = cv2.pointPolygonTest( contour, pt, measureDist )
式中的返回值为retval,与参数measureDist的值有关。
式中的参数如下:
- contour为轮廓。
- pt为待判定的点。
- measureDist为布尔型值,表示距离的判定方式。
- 当值为True时,表示计算点到轮廓的距离。如果点在轮廓的外部,返回值为负数;如果点在轮廓上,返回值为0;如果点在轮廓内部,返回值为正数。
- 当值为False时,不计算距离,只返回“-1”、“0”和“1”中的一个值,表示点相对于轮廓的位置关系。如果点在轮廓的外部,返回值为“-1”;如果点在轮廓上,返回值为“0”;如果点在轮廓内部,返回值为“1”。
【例5】使用函数cv2.pointPolygonTest()计算点到轮廓的最短距离。使用函数cv2.pointPolygonTest()计算点到轮廓的最短距离,需要将参数measureDist的值设置为True。
import cv2
# ----------------原始图像-------------------------
o = cv2.imread('C:\\Users\\Administrator\\Desktop\\caonima.png')
cv2.imshow("original", o)
# ----------------获取凸包------------------------
gray = cv2.cvtColor(o, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# 使用新版 findContours 函数,只返回 contours 和 hierarchy
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
# 获取第一个轮廓的凸包
hull = cv2.convexHull(contours[0])
# 将灰度图像转换为 BGR 格式以便显示彩色凸包线条
image = cv2.cvtColor(binary, cv2.COLOR_GRAY2BGR)
cv2.polylines(image, [hull], True, (0, 255, 0), 2)
# ----------------内部点A到轮廓的距离-------------------------
distA = cv2.pointPolygonTest(hull, (300, 150), True)
font = cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(image, 'A', (300, 150), font, 1, (0, 255, 0), 3)
print("distA=", distA)
# ----------------外部点B到轮廓的距离-------------------------
distB = cv2.pointPolygonTest(hull, (300, 250), True)
cv2.putText(image, 'B', (300, 250), font, 1, (0, 255, 0), 3)
print("distB=", distB)
# ------------正好处于轮廓上的点C到轮廓的距离-----------------
distC = cv2.pointPolygonTest(hull, (423, 112), True)
cv2.putText(image, 'C', (423, 112), font, 1, (0, 255, 0), 3)
print("distC=", distC)
# ----------------显示-------------------------
cv2.imshow("result1", image)
cv2.waitKey()
cv2.destroyAllWindows()
从以上结果可以看出,
-
A点算出来的距离为
20.314188617040656
,是一个正数,说明A点在轮廓内部。 -
B点算出来的距离为
-34.0
,是一个负数,说明B点在轮廓外部。 -
C点算出来的距离为
7.0
,说明C点在轮廓上。
在实际使用中,如果想获取位于轮廓上的点,可以通过打印轮廓点集的方式获取。例如,本例中可以通过语句“print(hull)”获取轮廓上的点。在获取轮廓上的点以后,可以将其用作函数cv2.pointPolygonTest()的参数,以测试函数返回值是否为零。
【例6】使用函数cv2.pointPolygonTest()判断点与轮廓的关系。
使用函数cv2.pointPolygonTest()判断点与轮廓的关系时,需要将参数measureDist的值设置为False。
import cv2
#----------------原始图像-------------------------
o = cv2.imread('cs.bmp')
cv2.imshow("original", o)
#----------------获取凸包------------------------
gray = cv2.cvtColor(o, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray,127,255, cv2.THRESH_BINARY)
image, contours, hierarchy = cv2.findContours(binary,
cv2.RETR_LIST,
cv2.CHAIN_APPROX_SIMPLE)
hull = cv2.convexHull(contours[0])
image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
cv2.polylines(image, [hull], True, (0, 255, 0), 2)
#----------------内部点A与轮廓的关系-------------------------
distA = cv2.pointPolygonTest(hull, (300, 150), False)
font=cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(image, 'A', (300,150), font, 1, (0,255,0),3)
print("distA=", distA)
#----------------外部点B与轮廓的关系-------------------------
distB = cv2.pointPolygonTest(hull, (300, 250), False)
font=cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(image, 'B', (300,250), font, 1, (0,255,0),3)
print("distB=", distB)
#----------------边缘线上的点C与轮廓的关系----------------------
distC = cv2.pointPolygonTest(hull, (423, 112), False)
font=cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(image, 'C', (423,112), font, 1, (0,255,0),3)
print("distC=", distC)
#print(hull) #测试边缘到底在哪里,然后再使用确定位置的点绘制文字
#----------------显示-------------------------
cv2.imshow("result", image)
cv2.waitKey()
cv2.destroyAllWindows()
从以上结果可以看出,
- A点算出来的关系值为
1
,说明该点在轮廓的内部。 - B点算出来的关系值为
1
,说明该点在轮廓的外部。 - C点算出来的关系值为
1
,说明该点在轮廓的内部。
形状场景算法比较轮廓
不介绍
轮廓的特征值
轮廓自身的一些属性特征及轮廓所包围对象的特征对于描述图像具有重要意义。本节介绍几个轮廓自身的属性特征及轮廓所包围对象的特征。
宽高比
可以使用宽高比(AspectRation)来描述轮廓,例如矩形轮廓的宽高比为:宽高比=宽度(Width)/高度(Height)
【例7】计算矩形轮廓的宽高比。
import cv2
o = cv2.imread('C:\\Users\\Administrator\\Desktop\\caonima.png')
cv2.imshow("original", o)
gray = cv2.cvtColor(o, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(binary,
cv2.RETR_LIST,
cv2.CHAIN_APPROX_SIMPLE)
x, y, w, h = cv2.boundingRect(contours[0])
cv2.rectangle(o, (x, y), (x + w, y + h), (255, 255, 255), 3)
aspectRatio = float(w) / h
print(aspectRatio)
cv2.imshow("result", o)
cv2.waitKey()
cv2.destroyAllWindows()
########################result###############################
D:\Code\py_demo\.venv\Scripts\python.exe D:\Code\py_demo\operator_cv\凸包.py
2.1785714285714284
可以看出,轮廓的宽高比约为2。
Extent
可以使用轮廓面积与矩形边界(矩形包围框、矩形轮廓)面积之比Extend来描述图像及其轮廓特征。计算方法为:
【例8】计算图像的轮廓面积与其矩形边界面积之比。
import cv2
o = cv2.imread('C:\\Users\\Administrator\\Desktop\\caonima.png')
cv2.imshow("original", o)
gray = cv2.cvtColor(o, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray,127,255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(binary,
cv2.RETR_LIST,
cv2.CHAIN_APPROX_SIMPLE)
x, y, w, h = cv2.boundingRect(contours[0])
cv2.drawContours(o, contours[0], -1, (0,0,255),3)
cv2.rectangle(o, (x, y), (x+w, y+h), (255,0,0),3)
rectArea=w*h
cntArea=cv2.contourArea(contours[0])
extend=float(cntArea)/rectArea
print(extend)
cv2.imshow("result", o)
cv2.waitKey()
cv2.destroyAllWindows()
################################result#################################
D:\Code\py_demo\.venv\Scripts\python.exe D:\Code\py_demo\operator_cv\凸包.py
0.680144906323185
可以看出,本例中图像的轮廓面积与矩形边界面积的比值大约为0.7。
Solidity
可以使用轮廓面积与凸包面积之比Solidity来衡量图像、轮廓及凸包的特征。其计算方法为:
【例9】计算图像轮廓面积与凸包面积之比。
import cv2
o = cv2.imread('C:\\Users\\Administrator\\Desktop\\caonima.png')
cv2.imshow("original", o)
gray = cv2.cvtColor(o, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(o, contours[0], -1, (0, 0, 255), 3)
cntArea = cv2.contourArea(contours[0])
hull = cv2.convexHull(contours[0])
hullArea = cv2.contourArea(hull)
cv2.polylines(o, [hull], True, (0, 255, 0), 2)
solidity = float(cntArea) / hullArea
print(solidity)
cv2.imshow("result", o)
cv2.waitKey()
cv2.destroyAllWindows()
###############################result############################
D:\Code\py_demo\.venv\Scripts\python.exe D:\Code\py_demo\operator_cv\凸包.py
0.9110158068864109
可以看出,本例中图像的轮廓面积与凸包面积的比值约为0.9。
等效直径(Equivalent Diameter)
可以用等效直径来衡量轮廓的特征值,该值是与轮廓面积相等的圆形的直径。其计算公式为:
【例10】计算与轮廓面积相等的圆形的直径,并绘制与该轮廓等面积的圆。
import cv2
import numpy as np
o = cv2.imread('C:\\Users\\Administrator\\Desktop\\caonima.png')
cv2.imshow("original", o)
gray = cv2.cvtColor(o, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(binary,
cv2.RETR_LIST,
cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(o, contours[0], -1, (0, 0, 255), 3)
cntArea = cv2.contourArea(contours[0])
equiDiameter = np.sqrt(4 * cntArea / np.pi)
print(equiDiameter)
cv2.circle(o, (100, 100), int(equiDiameter / 2), (0, 0, 255), 3) # 展示等直径大小的圆
cv2.imshow("result", o)
cv2.waitKey()
cv2.destroyAllWindows()
- ● 左图是图像o。
- ● 右图显示了图像o的轮廓及与该轮廓等面积的圆(显示为灰色)。
同时,程序还会显示如下的运行结果:
可以看出,与本例中与轮廓面积相等的圆形的直径约为99。
方向
在OpenCV中,函数cv2.fitEllipse()可以用来构造最优拟合椭圆,还可以在返回值内分别返回椭圆的中心点、轴长、旋转角度等信息。使用这种形式,能够更直观地获取椭圆的方向等信息。
函数cv2.fitEllipse()返回各个属性值的语法格式为:
(x, y), (MA, ma), angle = cv2.fitEllipse(cnt)
式中几个返回值的意义如下:
- (x, y):椭圆的中心点。
- (MA, ma):椭圆水平方向轴和垂直方向轴的长度。
- angle:椭圆的旋转角度。
【例11】观察函数cv2.fitEllipse()的不同返回值。
import cv2
o = cv2.imread('C:\\Users\\Administrator\\Desktop\\caonima.png')
cv2.imshow("original", o)
gray = cv2.cvtColor(o, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray,127,255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(binary,
cv2.RETR_LIST,
cv2.CHAIN_APPROX_SIMPLE)
ellipse = cv2.fitEllipse(contours[0])
retval=cv2.fitEllipse(contours[0])
print("单个返回值形式:")
print("retval=\n", retval)
(x, y), (MA, ma), angle = cv2.fitEllipse(contours[0])
print("三个返回值形式:")
print("(x, y)=(", x, y, ")")
print("(MA, ma)=(", MA, ma, ")")
print("angle=", angle)
cv2.ellipse(o, ellipse, (0,0,255),2)
cv2.imshow("result", o)
cv2.waitKey()
cv2.destroyAllWindows()
###############################result###########################
D:\Code\py_demo\.venv\Scripts\python.exe D:\Code\py_demo\operator_cv\凸包.py
单个返回值形式:
retval=
((388.0275573730469, 155.02651977539062), (97.1426010131836, 259.5243225097656), 82.80935668945312)
三个返回值形式:
(x, y)=( 388.0275573730469 155.02651977539062 )
(MA, ma)=( 97.1426010131836 259.5243225097656 )
angle= 82.80935668945312
从以上运行结果可以看出,函数cv2.fitEllipse()以不同形式返回的值是相同的。
掩模和像素点
有时,我们希望获取某对象的掩模图像及其对应的点。12.1.3节介绍了将函数cv2.drawContours()的轮廓宽度参数thickness设置为“-1”,即可获取特定对象的实心轮廓,即特定对象的掩模。
另外,我们可能还希望获取轮廓像素点的具体位置信息。本节介绍如何获取轮廓(实心、空心)的像素点位置信息。
一般情况下,轮廓是图像内非零的像素点,可以通过两种方式获取轮廓像素点的位置信息。一种是使用Numpy函数,另外一种是使用OpenCV函数。
使用Numpy函数获取轮廓像素点
numpy.nonzero()函数能够找出数组内非零元素的位置,但是其返回值是将行、列分别显示的。
【例12】使用Numpy函数获取一个数组内的非零值元素的位置信息。
import numpy as np
#------------生成一个元素都是零值的数组a-------------------
a=np.zeros((5,5), dtype=np.uint8)
#-------随机将其中10个位置上的数值设置为1------------
#---times控制次数
#---i, j是随机生成的行、列位置
#---a[i, j]=1,将随机挑选出来的位置上的值设置为1
for times in range(10):
i=np.random.randint(0,5)
j=np.random.randint(0,5)
a[i, j]=1
#-------打印数组a,观察数组a内值的情况-----------
print("a=\n", a)
#------查找数组a内非零值的位置信息------------
loc=np.transpose(np.nonzero(a))
#-----输出数组a内非零值的位置信息------------
print("a内非零值的位置:\n", loc)
【例13】使用Numpy函数获取一个图像内的轮廓点位置。
import cv2
import numpy as np
#-----------------读取原始图像----------------------
o = cv2.imread('cc.bmp')
cv2.imshow("original", o)
#-----------------获取轮廓------------------------
gray = cv2.cvtColor(o, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray,127,255, cv2.THRESH_BINARY)
image, contours, hierarchy = cv2.findContours(binary,
cv2.RETR_LIST,
cv2.CHAIN_APPROX_SIMPLE)
cnt=contours[0]
#-----------------绘制空心轮廓------------------------
mask1 = np.zeros(gray.shape, np.uint8)
cv2.drawContours(mask1, [cnt],0,255,2)
pixelpoints1 = np.transpose(np.nonzero(mask1))
print("pixelpoints1.shape=", pixelpoints1.shape)
print("pixelpoints1=\n", pixelpoints1)
cv2.imshow("mask1", mask1)
#-----------------绘制实心轮廓---------------------
mask2 = np.zeros(gray.shape, np.uint8)
cv2.drawContours(mask2, [cnt],0,255, -1)
pixelpoints2 = np.transpose(np.nonzero(mask2))
print("pixelpoints2.shape=", pixelpoints2.shape)
print("pixelpoints2=\n", pixelpoints2)
cv2.imshow("mask2", mask2)
#-----------------释放窗口------------------------
cv2.waitKey()
cv2.destroyAllWindows()
运行结果图像。其中:
- 左图是图像o。
- 中间的是空心轮廓图像mask1。
- 右图是实心轮廓图像mask2。
使用OpenCV函数获取轮廓点
OpenCV提供了函数cv2.findNonZero()用于查找非零元素的索引。该函数的语法格式为:
idx = cv2.findNonZero( src )
式中:
- idx为返回值,表示非零元素的索引位置。需要注意的是,在返回的索引中,每个元素对应的是(列号,行号)的格式。
- src为参数,表示要查找非零元素的图像
【例14】使用OpenCV函数cv2.findNonZero()获取一个数组内的非零值。
import cv2
import numpy as np
# ------------生成一个元素都是零值的数组a-------------------
a = np.zeros((5, 5), dtype=np.uint8)
# -------随机将其中10个位置上的值设置为1------------
# ---times控制次数
# ---i, j是随机生成的行、列位置
# ---a[i, j]=1,将随机挑选出来的位置上的值设置为1
for times in range(10):
i = np.random.randint(0, 5)
j = np.random.randint(0, 5)
a[i, j] = 1
# -------打印数组a,观察数组a内值的情况-----------
print("a=\n", a)
# ------查找数组a内非零值的位置信息------------
loc = cv2.findNonZero(a)
# -----输出数组a内非零值的位置信息------------
print("a内非零值的位置:\n", loc)
运行上述程序,会显示如下的运行结果:
【例15】使用OpenCV函数cv2.findNonZero()获取一个图像内的轮廓点的位置。
import cv2
import numpy as np
# -----------------读取原始图像----------------------
o = cv2.imread('C:\\Users\\Administrator\\Desktop\\caonima.png')
cv2.imshow("original", o)
# -----------------获取轮廓------------------------
gray = cv2.cvtColor(o, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(binary,
cv2.RETR_LIST,
cv2.CHAIN_APPROX_SIMPLE)
cnt = contours[0]
# -----------------绘制空心轮廓------------------------
mask1 = np.zeros(gray.shape, np.uint8)
cv2.drawContours(mask1, [cnt], 0, 255, 2)
pixelpoints1 = cv2.findNonZero(mask1)
print("pixelpoints1.shape=", pixelpoints1.shape)
print("pixelpoints1=\n", pixelpoints1)
cv2.imshow("mask1", mask1)
# -----------------绘制实心轮廓---------------------
mask2 = np.zeros(gray.shape, np.uint8)
cv2.drawContours(mask2, [cnt], 0, 255, -1)
pixelpoints2 = cv2.findNonZero(mask2)
print("pixelpoints2.shape=", pixelpoints2.shape)
print("pixelpoints2=\n", pixelpoints2)
cv2.imshow("mask2", mask2)
# -----------------释放窗口------------------------
cv2.waitKey()
cv2.destroyAllWindows()
最大值和最小值及它们的位置
OpenCV提供了函数cv2.minMaxLoc(),用于在指定的对象内查找像素最大值、最小值及其位置。该函数的语法格式是:
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(imgray, mask = mask)
式中的返回值为:
- min_val:最小值。
- max_val:最大值。
- min_loc:最小值的位置。
- max_loc:最大值的位置。
式中的参数如下:
- imgray:单通道图像。
- mask:掩模。通过使用掩模图像,可以得到掩模指定区域内的最值信息。
平均颜色及平均灰度
OpenCV提供了函数cv2.mean(),用于计算一个对象的平均颜色或平均灰度。该函数的语法格式为:
mean_val = cv2.mean(im, mask = mask)
式中的返回值为mean_val,表示返回的平均值。
式中的参数如下:
- im:原图像。
- mask:掩模。
import cv2
import numpy as np
# --------读取并显示原始图像-----------------
o = cv2.imread('C:\\Users\\Administrator\\Desktop\\pitu.png')
cv2.imshow("original", o)
# --------获取轮廓-----------------
gray = cv2.cvtColor(o, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
# 检查是否检测到足够的轮廓
if len(contours) > 2:
cnt = contours[2] # 选择第3个轮廓
else:
print(f"检测到的轮廓数不足: {len(contours)}")
cnt = contours[-1] # 选择最后一个轮廓,或根据需要选择其他索引
# --------使用掩模获取感兴趣区域的均值-----------------
mask = np.zeros(gray.shape, np.uint8) # 构造mean所使用的掩模(必须是单通道的)
cv2.drawContours(mask, [cnt], 0, (255, 255, 255), -1)
meanVal = cv2.mean(o, mask=mask) # mask是一个区域,所以必须是单通道的
print("meanVal=\n", meanVal)
# --------使用掩模获取感兴趣区域并显示-----------------
masko = np.zeros(o.shape, np.uint8)
cv2.drawContours(masko, [cnt], -1, (255, 255, 255), -1)
loc = cv2.bitwise_and(o, masko)
cv2.imshow("mask", loc)
# --------释放窗口-----------------
cv2.waitKey()
cv2.destroyAllWindows()
- 左图是图像o。
- 右图是获取的感兴趣区域。
从上述结果可以看出,函数cv2.mean()能够计算各个通道的均值。上述4个值分别是RGB和A通道(alpha通道)的均值。本例中,RGB三个通道的值相同,所以计算出的均值也是一样的。
极点
有时,我们希望获取某个对象内的极值点,例如最左端、最右端、最上端、最下端的四个点。OpenCV提供了相应的函数来找出这些点,通常的语法格式是:
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])
【例16】计算一幅图像内的极值点。
import cv2
import numpy as np
o = cv2.imread('C:\\Users\\Administrator\\Desktop\\caonima.png')
# --------获取并绘制轮廓-----------------
gray = cv2.cvtColor(o, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
contours, hierarchy =cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
mask = np.zeros(gray.shape, np.uint8)
cnt = contours[0]
cv2.drawContours(mask, [cnt], 0, 255, -1)
# --------计算极值-----------------
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])
# --------打印极值-----------------
print("leftmost=", leftmost)
print("rightmost=", rightmost)
print("topmost=", topmost)
print("bottommost=", bottommost)
# --------绘制说明文字-----------------
font = cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(o, 'A', leftmost, font, 1, (0, 0, 255), 2)
cv2.putText(o, 'B', rightmost, font, 1, (0, 0, 255), 2)
cv2.putText(o, 'C', topmost, font, 1, (0, 0, 255), 2)
cv2.putText(o, 'D', bottommost, font, 1, (0, 0, 255), 2)
# --------绘制图像-----------------
cv2.imshow("result", o)
# --------释放窗口-----------------
cv2.waitKey()
cv2.destroyAllWindows()
运行结果图像。
同时,程序还会显示如下的运行结果: