• 函数:cv2.findContours(),cv2.drawContours()
轮廓查找:
• 为了更加准确,要使用二值化图像。在寻找轮廓之前,要进行阈值化处理或者Canny 边界检测。
• 查找轮廓的函数会修改原始图像。如果你在找到轮廓之后还想使用原始图像的话,你应该将原始图像存储到其他变量中。
• 在OpenCV 中,查找轮廓就像在黑色背景中找白色物体。 你应该记住,要找的物体应该是白色而背景应该是黑色。
cv2.findContours(image, mode, method[, contours[, hierarchy[, offset ]]])
函数cv2.findContours() 有三个参数,第一个是输入图像,第二个是轮廓检索模式,第三个是轮廓近似方法。返回值有三个,第一个是图像,第二个是轮廓,第三个是(轮廓的)层析结构。轮廓(第二个返回值)是一个Python列表,其中存储这图像中的所有轮廓。每一个轮廓都是一个Numpy 数组,包含对象边界点(x,y)的坐标。
import cv2
img = cv2.imread('3.jpg',1)
imgray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(imgray,127,255,0)
contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(img,contours,-1,(255,255,0),3)
cv2.imshow("img",img)
print(hierarchy)
cv2.waitKey()
函数返回值:
contours:hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
返回值contours:定义为“vector
hierarchy:定义为“vector hierarchy”,先来看一下Vec4i的定义:
typedef Vec
Vec4i是Vec
实例2(二值化图像):
import cv2
import numpy as np
def contour(img):
dst = cv2.GaussianBlur(img,(3,3),0)
gray = cv2.cvtColor(dst,cv2.COLOR_BGR2GRAY)
ret,binary = cv2.threshold(gray,0,255,cv2.THRESH_BINARY|cv2.THRESH_OTSU)
cv2.imshow("binary",binary)
#contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
contours, hierarchy = cv2.findContours(binary,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
for i,contour in enumerate(contours):
cv2.drawContours(img,contours,-1,(0,0,255),2)
print(i)
cv2.imshow('img', img)
img = cv2.imread('4.jpg',1)
cv2.namedWindow('img', 0)
cv2.namedWindow('imgsrc', 0)
cv2.namedWindow('binary', 0)
cv2.resizeWindow('img', 400, 480)
cv2.resizeWindow('imgsrc', 400, 480)
cv2.resizeWindow('binary', 400, 480)
cv2.imshow('imgsrc', img)
contour(img)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.drawContours(img,contours,-1,(0,0,255),-1) #最后一位为-1是为闭区间
实例3(Canny 边界检测后图像):
import cv2
import numpy as np
def edge_demo(image):
blurred = cv2.GaussianBlur(image, (3, 3), 0)
gray = cv2.cvtColor(blurred, cv2.COLOR_RGB2GRAY)
# xgrad = cv.Sobel(gray, cv.CV_16SC1, 1, 0) #x方向梯度
# ygrad = cv.Sobel(gray, cv.CV_16SC1, 0, 1) #y方向梯度
# edge_output = cv.Canny(xgrad, ygrad, 50, 150)
edge_output = cv2.Canny(gray, 220, 250)
cv2.imshow("Canny Edge", edge_output)
return edge_output
def contour(img):
binary = edge_demo(img)
#contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
contours, hierarchy = cv2.findContours(binary,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
for i,contour in enumerate(contours):
# cv2.drawContours(img,contours,-1,(0,0,255),2)
cv2.drawContours(img, contours, -1, (0, 0, 255),2)
print(i)
cv2.imshow('img', img)
img = cv2.imread('7.jpg',1)
cv2.namedWindow('img', 0)
cv2.namedWindow('imgsrc', 0)
cv2.namedWindow('Canny Edge', 0)
cv2.resizeWindow('img', 400, 480)
cv2.resizeWindow('imgsrc', 400, 480)
cv2.resizeWindow('Canny Edge', 400, 480)
cv2.imshow('imgsrc', img)
#edge_demo(img)
contour(img)
cv2.waitKey(0)
cv2.destroyAllWindows()
重心求取:
cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])
零阶矩、一阶矩、二阶矩、三阶矩代表的含义:
数学上,“矩”是一组点组成的模型的特定的数量测度。
在力学和统计学中都有用到“矩”。
如果这些点代表“质量”,那么:
零阶矩表示所有点的 质量;
一阶矩表示 质心;
二阶矩表示 转动惯量。
如果这些点代表“概率密度”,那么:
零阶矩表示这些点的 总概率(也就是1);
一阶矩表示 期望;
二阶(中心)矩表示 方差;
三阶(中心)矩表示 偏斜度;
四阶(中心)矩表示 峰度;
求面积:
area = cv2.contourArea(cnt)
求轮廓周长:
这个函数的第二参数可以用来指定对象的形状是闭合的(True),还是打开的(一条曲线)。
perimeter = cv2.arcLength(cnt,True)
求轮廓近似:
将轮廓形状近似到另外一种由更少点组成的轮廓形状,新轮廓的点的数目由我们设定的准确度来决定。为了帮助理解,假设我们要在一幅图像中查找一个矩形,但是由于图像的种种原因,我们不能到一个完美的矩形,而是一个“坏形状”(如下图所示)。现在你就可以使用这个函数来近似这个形状()了。这个函数的第二个参数叫epsilon,它是从原始轮廓到近似轮廓的最大距离。它是一个准确度参数。选择一个好的epsilon 对于得到满意结果非常重要。
epsilon = 0.1*cv2.arcLength(cnt,True)
approx = cv2.approxPolyDP(cnt,epsilon,True)
下边,第二幅图中的绿线是当epsilon = 10% 时得到的近似轮廓,第三幅图是当epsilon = 1% 时得到的近似轮廓。第三个参数设定弧线是否闭合。
求凸包:
//利用图像做差 来查找凸包缺陷
hull = cv2.convexHull(points[, hull[, clockwise[, returnPoints])
上面一段是参考的,与自己使用结果不一致,留下记录。
hull = cv2.convexHull(cnt, True, False) #
cv2.drawContours(img,hull,-1,(255,255,0),3)
k = cv2.isContourConvex(cnt)
print("是否凸性:")
print(k)
边界矩形:
直边界矩形一个直矩形(就是没有旋转的矩形)。它不会考虑对象是否旋转。所以边界矩形的面积不是最小的。可以使用函数cv2.boundingRect() 查找得到。(x,y)为矩形左上角的坐标,(w,h)是矩形的宽和高。
x,y,w,h = cv2.boundingRect(cnt)
img = cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
旋转的边界矩形这个边界矩形是面积最小的,因为它考虑了对象的旋转。用到的函数为cv2.minAreaRect()。返回的是一个Box2D 结构,其中包含矩形左上角角点的坐标(x,y),矩形的宽和高(w,h),以及旋转角度。但是要绘制这个矩形需要矩形的4 个角点,可以通过函数cv2.boxPoints() 获得。
x,y,w,h = cv2.boundingRect(cnt)
img = cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
x,y,w,h = cv2.boundingRect(cnt)
img = cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
x,y,w = cv2.minAreaRect(cnt)
print(x)
print(y)
print(w)
img = cv2.rectangle(img,(int(x[0]),int(x[1])),(int(x[0]+y[1]),int(x[1]+y[0])),(255,255,0),2)
**椭圆拟合:**
ellipse = cv2.fitEllipse(cnt)
im = cv2.ellipse(im,ellipse,(0,255,0),2)
直线拟合:
我们可以根据一组点拟合出一条直线,同样我们也可以为图像中的白色点拟合出一条直线。
[vx,vy,x,y] = cv2.fitLine(cnt, cv2.DIST_L2,0,0.01,0.01)
print([vx,vy,x,y])
lefty = int((-x*vy/vx) + y)
righty = int(((cols-x)*vy/vx)+y)
img = cv2.line(img,(cols-1,righty),(0,lefty),(0,255,0),2)
x,y,w,h = cv2.boundingRect(cnt)
aspect_ratio = float(w)/h
轮廓面积与边界矩形面积的比(Extent):
area = cv2.contourArea(cnt)
x,y,w,h = cv2.boundingRect(cnt)
rect_area = w*h
extent = float(area)/rect_area
轮廓面积与凸包面积的比(Solidity):
area = cv2.contourArea(cnt)
hull = cv2.convexHull(cnt)
hull_area = cv2.contourArea(hull)
solidity = float(area)/hull_area
与轮廓面积相等的圆形的直径(Equivalent Diameter):
area = cv2.contourArea(cnt)
equi_diameter = np.sqrt(4*area/np.pi)
方向:
对象的方向,下面的方法还会返回长轴和短轴的长度
(x,y),(MA,ma),angle = cv2.fitEllipse(cnt)
掩模和像素点:
有时我们需要构成对象的所有像素点,我们可以这样做:
mask = np.zeros(imgray.shape,np.uint8)
# 这里一定要使用参数-1, 绘制填充的的轮廓
cv2.drawContours(mask,[cnt],0,255,-1)
pixelpoints = np.transpose(np.nonzero(mask))
最大值和最小值及它们的位置:
我们可以使用掩模图像得到这些参数。
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(imgray,mask = mask)
平均颜色及平均灰度:
我们也可以使用相同的掩模求一个对象的平均颜色或平均灰度
mean_val = cv2.mean(im,mask = mask)
极点:
一个对象最上面,最下面,最左边,最右边的点。
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])
#1.先找到轮廓
img = cv2.imread('convex.jpg', 0)
_, thresh = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
image, contours, hierarchy = cv2.findContours(thresh, 3, 2)
cnt = contours[0]
#2.寻找凸包,得到凸包的角点
hull = cv2.convexHull(cnt)
#3.绘制凸包
image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
cv2.polylines(image, [hull], True, (0, 255, 0), 2)
其中函数cv2.convexHull()有个可选参数returnPoints,默认是True,代表返回角点的x/y坐标;如果为False的话,表示返回轮廓中是凸包角点的索引,比如说:
print(hull[0]) # [[362 184]](坐标)
hull2 = cv2.convexHull(cnt, returnPoints=False)
print(hull2[0]) # [510](cnt中的索引)
print(cnt[510]) # [[362 184]]
点到轮廓距离:
其中参数3为True时表示计算距离值:点在轮廓外面值为负,点在轮廓上值为0,点在轮廓里面值为正;参数3为False时,只返回-1/0/1表示点相对轮廓的位置,不计算距离。以点(100,100)为例:
dist = cv2.pointPolygonTest(cnt, (100, 100), True) # -3.53
形状匹配:
函数cv2.matchShape() 可以帮我们比较两个形状或轮廓的相似度。如果返回值越小,匹配越好。它是根据Hu 矩来计算的。发生了旋转对匹配的结果影响也不是非常大。
import cv2
import numpy as np
img1 = cv2.imread('star.jpg',0)
img2 = cv2.imread('star2.jpg',0)
ret, thresh = cv2.threshold(img1, 127, 255,0)
ret, thresh2 = cv2.threshold(img2, 127, 255,0)
contours,hierarchy = cv2.findContours(thresh,2,1)
cnt1 = contours[0]
contours,hierarchy = cv2.findContours(thresh2,2,1)
cnt2 = contours[0]
ret = cv2.matchShapes(cnt1,cnt2,1,0.0)
print(ret)
我得到的结果是:
. A 与自己匹配0.0
. A 与B 匹配0.001946
. A 与C 匹配0.326911
通常我们使用函数cv2.findContours 在图片中查找一个对象。有时对象可能位于不同的位置。还有些情况,**一个形状在另外一个形状的内部。这种情况下我们称外部的形状为父,内部的形状为子。**按照这种方式分类,一幅图像中的所有轮廓之间就建立父子关系。这样我们就可以确定一个轮廓与其他轮廓是怎样连接的,比如它是不是某个轮廓的子轮廓,或者是父轮廓。这种关系就成为组织结构。
在这幅图像中,我给这几个形状编号为0-5。2 和2a 分别代表最外边矩形的外轮廓和内轮廓。在这里边轮廓0,1,2 在外部或最外边。我们可以称他们为(组织结构)0 级,简单来说就是他们属于同一级。接下来轮廓2a。我们把它当成轮廓2 的子轮廓。它就成为(组织结构)第1 级。同样轮廓3 是轮廓2 的子轮廓,成为(组织结构)第3 级。最后轮廓4,5 是轮廓3a 的子轮廓,成为(组织结构)4 级(最后一级)。按照这种方式给这些形状编号,我们可以说轮廓4 是轮廓3a 的子轮廓(当然轮廓5 也是)。
不管层次结构是什么样的,每一个轮廓都包含自己的信息:谁是父,谁是子等。OpenCV 使用一个含有四个元素的数组表示。**[Next,Previous,First_Child,Parent]。**Next 表示同一级组织结构中的下一个轮廓。以上图中的轮廓0 为例,轮廓1 就是他的Next。同样,轮廓1 的Next是2,Next=2。那轮廓2 呢?在同一级没有Next。这时Next=-1。而轮廓4 的Next为5,所以它的Next=5。Previous 表示同一级结构中的前一个轮廓。与前面一样,轮廓1 的Previous 为轮廓0,轮廓2 的Previous 为轮廓1。轮廓0 没有Previous,所以Previous=-1。First_Child 表示它的第一个子轮廓。没有必要再解释了,轮廓2 的子轮廓2a。所以它的First_Child 为2a。那轮廓3a 呢?它有两个子轮廓。但是我们只要第一个子轮廓,所以是轮廓4(按照从上往下,从左往右的顺序排序)。Parent 表示它的父轮廓。与First_Child 刚好相反。轮廓4 和5 的父轮廓是轮廓3a。而轮廓3a的父轮廓是3。
轮廓检索模式:
RETR_LIST 从解释的角度来看,这中应是最简单。它只是提取所有的轮
廓,而不去创建任何父子关系。换句话说就是“人人平等”,它们属于同一级组织轮廓。
RETR_EXTERNAL 如果你选择这种模式的话,只会返回最外边的的轮廓,
所有的子轮廓都会被忽略掉。
RETR_TREE 终于到最后一个了,也是最完美的一个。这种模式下会返回
所有轮廓,并且创建一个完整的组织结构列表。
RETR_CCOMP 在这种模式下会返回所有的轮廓并将轮廓分为两级组织结
构。
现在我们考虑轮廓0,它的组织结构为第1 级。其中有两个空洞1 和2,它们属于第2 级组织结构。所以对于轮廓0 来说跟他属于同一级组织结构的下一个(Next)是轮廓3,并且没有Previous。它的Fist_Child 为轮廓1,组织结构为2。由于它是第1 级,所以没有父轮廓。因此它的组织结构数组为[3,-1,1,-1]。现在是轮廓1,它是第2 级。处于同一级的下一个轮廓为2。没有Previous,也没有Child,(因为是第2 级所以有父轮廓)父轮廓是0。所以数组是[2,-1,-1,0]。轮廓2:它是第2 级。在同一级的组织结构中没有Next。Previous 为轮廓1。没有子,父轮廓为0,所以数组是[-1,1,-1,0]轮廓3:它是第1 级。在同一级的组织结构中Next 为5。Previous 为轮廓0。子为4,没有父轮廓,所以数组是[5,0,4,-1]轮廓4:它是第2 级。在同一级的组织结构中没有Next。没有Previous,没有子,父轮廓为3,所以数组是[-1,-1,-1,3]