在计算机视觉中,轮廓检测是另一个比较重要的任务,不单是用来检测图像或者视频帧中物体的轮廓,而且还有其他操作与轮廓检测相关。这些操作中,计算多边形边界,形状逼近和计算机感 兴趣区域。这是与图像数据交互时的简单操作,因为numpy中的矩阵中的矩形区域可以使用数组切片(slice)定义。在介绍物体检测(包括人脸)和物体跟踪的概念时会大量使用这种技术。
1、cv2.threshold(src,thresh,maxval,type[,dst])函数用于图像阈值操作。
cv2.threshold(src, thresh, maxval, type[, dst]) → retval, dst
参数: |
|
---|
该功能将固定级阈值应用于单通道阵列。该函数通常用于从灰度图像中获取二值(二值)图像( compare()
也可用于此目的)或用于去除噪声,即滤除具有太小或太大值的像素。函数支持几种类型的阈值处理。它们由以下因素决定type
:
为了从一幅图像中提取我们需要的部分,应该用图像中的每一个像素点的灰度值与选取的阈值进行比较,并作出相应的判断(阈值的选取依赖于具体的问题,物体在不同的图像中可能会有不同的灰度值)。opencv提供了threshold()函数对图像的阈值进行处理,threshold()共支持五中类型的阈值化方式,分别是二进制阈值化、反二进制阈值化、截断阈值化、阈值化为0和反阈值化为0。返回阈值操作后的图像。
此外,特殊值THRESH_OTSU
可以与上述值之一组合。在这种情况下,函数使用Otsu算法确定最佳阈值,并使用它而不是指定的thresh
。该函数返回计算的阈值。目前,Otsu的方法仅适用于8位图像。
2、 cv2.findContours(image,mode,method[,contours,hierarchy[,offset]])用于寻找寻找图像轮廓。
cv2.findContours(image, mode, method[, contours[, hierarchy[, offset]]]) → contours, hierarchy
opencv中提供findContours()函数来寻找图像中物体的轮廓,并结合drawContours()函数将找到的轮廓绘制出。这个函数会修改输入图像,因此建议使用原始图像的一份拷贝(比如说img.copy()作为输入图像)。函数返回三个值:返回修改后的图像,图像的轮廓以及它们的层次。
参数: |
|
---|
模式 -
轮廓检索模式(如果使用Python,请参阅下面的注释)。
hierarchy[i][2]=hierarchy[i][3]=-1
适用于所有轮廓。contours.c
演示中构建和显示的 。轮廓近似方法(如果使用Python,请参见下面的注释)。
(x1,y1)
和(x2,y2)
轮廓将是水平,垂直或对角线邻居,即max(abs(x1-x2),abs(y2-y1))==1
。3、cv2.drawContours(image,contours,contourIdx,color[,thickness[,lineType[,hierarchy[,maxLevel[,offset]]]]])函数轮廓绘制。
cv2.drawContours(image, contours, contourIdx, color[, thickness[, lineType[, hierarchy[, maxLevel[, offset]]]]]) → None
该函数返回绘制有轮廓的图像。
为什么有了一个精确表示的轮廓却还需要得到一个近似多边形呢?这是因为一个多边形由一组直线构成,能够在一个区域里定义多边形,以便于之后进行操作与处理,这在许多计算机视觉任务中非常重要。ε值对获取有用的轮廓非常重要,所以需要理解它表示什么意思。
ε是为所得到的近似多边形周长与源轮廓周长之间的最大差值,这个值越小,近似多边形与源轮廓就越相似。
在了解了ε值是什么之后,需要得到轮廓的周长信息来作为参考值。这可以通过cv2.arcLength函数来完成:
#arcLength获取轮廓的周长
epsilon = 0.01*cv2.arcLength(cnt,True)
#计算矩形的多边形框
approx = cv2.approxPolyDP(cnt,epsilon,True)
可以通过OpenCV来有效地计算一个近似多边形。为了计算凸形状,需要利用cv2.convexHull来处理获取的轮廓信息。
#-*_ coding:utf-8 -*-
import cv2
import numpy as np
'''
created on Tues jan 08:28:51 2018
@author: ren_dong
contour detection
cv2.findContours() 寻找轮廓
cv2.drawContours() 绘制轮廓
'''
#加载图像img
img = cv2.imread('1.jpg')
cv2.imshow('origin', img)
'''
灰度化处理,注意必须调用cv2.cvtColor(),
如果直接使用cv2.imread('1.jpg',0),会提示图像深度不对,不符合cv2.CV_8U
'''
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv2.imshow('gray', gray)
#调用cv2.threshold()进行简单阈值化,由灰度图像得到二值化图像
# 输入图像必须为单通道8位或32位浮点型
ret, thresh = cv2.threshold(gray, 127, 255, 0)
cv2.imshow('thresh', thresh)
#调用cv2.findContours()寻找轮廓,返回修改后的图像,轮廓以及他们的层次
image, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cv2.imshow('image', image)
print('contours[0]:',contours[0])
print('len(contours):',len(contours))
print('hierarchy.shape:',hierarchy.shape)
print('hierarchy:',hierarchy)
#调用cv2.drawContours()在原图上绘制轮廓
img = cv2.drawContours(img, contours, -1, (0, 255, 0), 2)
cv2.imshow('contours', img)
cv2.waitKey()
cv2.destroyAllWindows()
运行后的结果如下:
找到一个正方形轮廓很简单,要找到到不规则的,歪斜的以及旋转的形状,可以用Open CV的cv2.findContours()函数,它能得到最好的结果,下面来看一副图:
现实的应用会对目标的边界框,最小矩形面积,最小闭圆特别感兴趣,将cv2.findContours()函数和少量的OpenCV的功能相结合就非常容易实现这些功能:
使用boundingRect()函数计算包围轮廓的矩形框,使用minEnclosingCircle()函数计算包围轮廓的最小圆包围。
1、先计算一个简单的边界框(水平矩形):
x,y,w,h = cv2.boundingRect(c)
然后画出这个矩形(在原图img上绘制):这个操作非常简单,它将轮廓信息转换为(x,y)坐标,并加上矩形的高度和宽度。
cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
下面来将如何找到一个旋转的矩阵和圆形轮廓。 首先加载图片,然后在源图像的灰度图像上面执行一个二值化操作。这样之后,可在这个灰度图像上执行所有计算轮廓的操作,但在源图像上可利用色彩信息来画这些轮廓。
2、计算包含出包围目标的最小矩形区域(旋转矩形):
#找到最小区域
rect = cv2.minAreaRect(c)
#计算最小矩形的坐标
box = cv2.boxPoints(rect)
#坐标转换为整数
box = np.int0(box)
这里用到一个非常有趣的机制:Open CV没有函数能直接从轮廓信息中计算出最小矩形顶点的坐标。所以需要计算最小矩形区域,然后计算这个矩形的顶点。注意计算出来的顶点左边是浮点型,但是所得像素的坐标值是整数,所以需要做一个转换。
函数 cv2.minAreaRect() 返回一个tuple:(最小外接矩形的中心(x,y),(宽度,高度),旋转角度)。
但是要绘制这个矩形,我们需要矩形的4个顶点坐标box, 通过函数 cv2.cv.BoxPoints() 获得,box:[ [x0,y0], [x1,y1], [x2,y2], [x3,y3] ]
最小外接矩形的4个顶点顺序、中心坐标、宽度、高度、旋转角度(是度数形式,不是弧度数)的对应关系如下:
注意:旋转角度θ是水平轴(x轴)逆时针旋转,与碰到的矩形的第一条边的夹角。并且这个边的边长是width,另一条边边长是height。也就是说,在这里,width与height不是按照长短来定义的。
在opencv中,坐标系原点在左上角,相对于x轴,逆时针旋转角度为负,顺时针旋转角度为正。在这里,θ∈(-90度,0]。
然后画出这个矩形(在原图img上绘制):
cv2.drawContours(img,[box],0,(255,0,0),3)
首先,该函数与所有绘图函数一样,它会修改源,其次该函数的第二个参数接收一个保存着轮廓的数组,从而可以在一次操作中绘制一系列的轮廓。因此如果只有一组点来表示多边形轮廓,可以把这组点放到一个list中,就像前面例子里处理方框(box)那样。这个函数第三个参数是绘制的轮廓数组的索引,-1表示绘制所有的轮廓,否则只绘制轮廓数组里指定的轮廓。
大多数绘图函数把绘图的颜色和线宽放在最后两个参数里。
3、最后检查的边界轮廓为最小闭圆。
#计算闭圆中心店和和半径
(x,y),radius = cv2.minEnclosingCircle(c)
#转换为整型
center = (int(x),int(y))
radius = int(radius)
#绘制闭圆(在原图img上绘制)
img = cv2.circle(img,center,radius,(0,255,0),2)
points:输入的二维点集,一般传入一个轮廓 contours[0]
cv2.minEnclosingCircle()函数会返回一个元组,第一个元素为圆心的坐标组成的元素,第二个元素为圆的半径值。把这些值转换为整数后就能很容易地绘制出圆来。
完整代码如下:
#-*- coding:utf-8 -*-
import cv2
import numpy as np
'''
created on Tues jan 09:36:30 2018
@author:ren_dong
cv2.boundingRect() 边界框即直边界矩形
cv2.minAreaRect() 最小矩形区域即旋转的边界矩形
cv2.minEnclosingCircle() 最小闭圆
'''
#载入图像img
img = cv2.pyrDown(cv2.imread('star.jpg', cv2.IMREAD_UNCHANGED))
#灰度化
gray = cv2.cvtColor(img.copy(), cv2.COLOR_BGR2GRAY)
#二值化
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
#寻找轮廓
image, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
print('hierarchy[0]:', hierarchy[0])
print('len(contours):', len(contours))
print('hierarchy.shape:', hierarchy.shape)
#遍历每一个轮廓
for C in contours:
#计算边界框坐标
x, y, w, h = cv2.boundingRect(C)
##在Img图像上绘制矩形框,颜色为green, 线宽为2
cv2.rectangle(img, (x, y), (x + w, y + h), (0,255,0), 2)
#计算包围目标的最小矩形区域
rect = cv2.minAreaRect(C)
#计算最小矩形的坐标
box = cv2.boxPoints(rect)
#坐标变为整数
box = np.int0(box)
#绘制矩形框轮廓 颜色为red 线宽为3
cv2.drawContours(img, [box], 0, (0,0,255),3)
#最小闭圆的圆心和半径
(x,y),radius = cv2.minEnclosingCircle(C)
#转换为整型
center = (int(x), int(y))
radius = int(radius)
#绘制最小闭圆
img = cv2.circle(img,center, radius, (255, 0, 0), 2)
cv2.drawContours(img,contours, -1, (0, 0, 255), 1)
cv2.imshow('contours',img)
cv2.waitKey()
cv2.destroyAllWindows()
运行后的结果:
大多数处理轮廓的时候,图的形状(包括凸形状)都是变化多样的。凸形状内部的任意两点的连线都在该形状内部。
cv2.approxPloyDP函数,它用来计算近似的多边形框。该函数有三个参数:
#从轮廓信息中计算得到凸形状
hull = cv2.convexHull(cnt)
为了理解源轮廓、近似多边形和凸包的不同之处,可以把他们放在一副图片中进行观察:
#-*- coding:utf-8 -*-
import cv2
import numpy as np
'''
created on Tues jan 10:49:30 2018
@author:ren_dong
凸轮廓和Douglas-Peucker算法
cv2.approxPloyDP()
CV2.arcLength()
cv2.convexHull()
'''
#读入图像img
img = cv2.pyrDown(cv2.imread('arc.jpg', cv2.IMREAD_COLOR))
#resize
#img = cv2.resize(img, None, fx=0.6, fy=0.6, interpolation=cv2.INTER_CUBIC)
#创建空白图像,用来绘制多边形轮廓
curve = np.zeros(img.shape,np.uint8)
#灰度变换
gray = cv2.cvtColor(img.copy(), cv2.COLOR_BGR2GRAY)
#使用定制kernel 进行中值滤波,去除一些噪声
kernel = np.ones((3,3),np.float32) / 9
#这里的-1表示目标图像和原图像具有同样的深度,比如cv2.CV_8U
gray = cv2.filter2D(gray,-1,kernel)
#阈值化 gray image --> binary image
# 输入图像必须为单通道8位或32位浮点型
# 这里使用cv2.THRESH_BINARY_INV 实现效果像素>125 设置为0(黑) 否则设置为255(白)
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY_INV)
#寻找轮廓 返回修改后的图像, 图像轮廓 以及他们的层次
image, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
##假设contours[0]为最大轮廓
cnt = contours[0]
max_area = cv2.contourArea(cnt)
##遍历contours, 和原始设置比较,获得最大轮廓区域
for i in contours:
if cv2.contourArea(i) > max_area:
cnt = i
max_area = cv2.contourArea(cnt)
print('max_area:',max_area)
##获得轮廓周长
epsilon = 0.01 * cv2.arcLength(cnt, True)
#计算得到近似的多边形框
approx = cv2.approxPolyDP(cnt, epsilon, True)
#得到凸包
hull = cv2.convexHull(cnt)
print('contours', len(contours), type(contours))
print('cnt.shape', cnt.shape, type(cnt))
print('approx.shape', approx.shape, type(approx))
print('hull.shape', hull.shape, type(hull))
#在原图像得到原始的轮廓
cv2.drawContours(img, contours, -1, (255, 0 , 0),2)
#在空白图像中得到最大轮廓, 多边形轮廓, 凸包轮廓
cv2.drawContours(curve, [cnt], -1, (0, 0, 255), 1) #
cv2.drawContours(curve, [hull], -1, (0, 255, 0), 2) ##绿色多边形轮廓 线宽2
cv2.drawContours(curve, [approx], -1, (255, 0, 0), 3) ##蓝色凸包轮廓 线宽3
cv2.imshow('contours',img)
cv2.imshow('all',curve)
cv2.waitKey()
cv2.destroyAllWindows()
运行结果如下:
如上图所示,凸包是由绿色表示,然后里面是近似多边形,使用黄色表示,在两者之间的是源图片中一个最大的轮廓,它主要由弧线构成.
检测边缘和轮廓不仅重要,还经常用到,它们也是构成其他复杂操作的基础。直线和形状检查与边缘和轮廓检测有密切的关系。
Hough变换是直线和形状检测背后的理论基础,它由Richard Duda和Peter Hart发明,他们是对Paul Hough在20世纪60年代早期所做工作的扩展。
1、直线检测
首先介绍直线检测,这可通过HoughLines和HoughLinesP函数来完成,它们仅有的差别是:第一个函数使用标准的Hough变换,第二个函数使用概率Hough变换(因此名称里有一个P)。
HoughLinesP函数之所以称为概率版本的Hough变换是因为它只通过分析点的子集并估计这些点都属于一条直线的概率,这是标准Hogh变换的优化版本。该函数的计算代价会少一些,执行会变得更快。
完整代码如下:
#-*- coding:utf-8 -*-
import cv2
import numpy as np
'''
created on Tues jan 13:57:30 2018
@author:ren_dong
Hough变换检测直线
cv2.HoughLine()
cv2.HoughLines()
'''
#载入图片img
img = cv2.imread('line.jpg')
#灰度化 --> gray
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
##中值滤波去除噪声 图像平滑
gray = cv2.medianBlur(gray, ksize=3)
#Canny边缘检测
edges = cv2.Canny(gray, 50, 120)
#最小直线长度, 更短的直线会被消除
minLineLength = 10
#最大线段间隙,一条线段的间隙长度大于这个值会被认为是两条分开的线段
maxLineGap = 5
##Hough 变换检测直线
lines = cv2.HoughLinesP(edges, 1, np.pi/180, 80, minLineLength, maxLineGap)
for i in range(len(lines)):
for x1, y1, x2, y2 in lines[i]:
#给定两点 在原始图片绘制线段
cv2.line(img, (x1, y1), (x2, y2), (0, 0, 255), 2)
cv2.imshow('edges', edges)
cv2.imshow('lines', img)
cv2.waitKey()
cv2.destroyAllWindows()
实现结果如下:
除了HoughLinesP函数调用是这段代码的关键点以外,设置最小直线长度(更短的直线会被消除)和最大线段间隙也很重要,一条线段长度大于这个值会被视为两条分开的线段。
注意:HoughLinesP函数会接收一个由Candy边缘检测滤波器处理过的单通道二值图像。不一定需要Candy滤波器,但是一个经过去噪并且只有边缘的图像当中Hough变换的输入会很不错,因此使用Candy滤波器是一个普遍的惯例。
HoughLinesP函数参数如下:
该函数返回一个numpy.array类型,形状为[num,1,4],每一行对应一条直线,每条直线形状为(1,4),这4个数值表示起始点和终止点坐标。
2、圆检测
OpenCV的HoughCircles函数可用来检测圆,其主要是利用霍尔变换在图像中寻找圆。我们知道,一个圆形的表达式为(x-x_center)2+(y-y_center)2=r2,一个圆环的确定需要三个参数,那么霍尔变换的累加器必须是三维的,但是这样的计算效率很低,而opencv采用了霍夫梯度的方法,这里利用了边界的梯度信息。
首先对图像进行Candy边缘检测,对边缘中的每一个非0点,通过sobel算子进行计算局部梯度。那么计算得到的梯度方向,实际上就是圆切线的法线。三条法线即可确定一个圆心,同理在累加器中对圆心通过的法线进行累加,就得到可圆环的判定。
cv2.HoughCircles(img,method,dp,minDist,circles,param1,param2,minRadius,maxRadius)函数的参数如下:
下面是一个例子:
#-*- coding:utf-8 -*-
import cv2
import numpy as np
'''
created on Tues jan 14:37:10 2018
@author:ren_dong
Hough变换检测圆
cv2.HoughCricles()
'''
#载入图片img
img = cv2.imread('plant.jpg')
#灰度化处理 --> gray image
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
##中值滤波 图像平滑
gray = cv2.medianBlur(gray, 7)
##Hough变换检测圆
circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1, 200,
param1=200, param2=20, minRadius= 0, maxRadius=0 )
circles = np.uint16(np.around(circles))
##绘制圆
for i in circles[0,:]:
cv2.circle(img, (i[0], i[1]), i[2], (0, 255, 0), 2)
cv2.circle(img, (i[0], i[1]), 2, (0, 0, 255), 2)
cv2.imshow('HoughCircles',img)
cv2.waitKey()
cv2.destroyAllWindows()
实现效果如下:
3、检测其他形状
Hough变换能检测的形状仅限于圆,但是前面曾提到过检测任何形状的方法,特别是用approxPloyDP函数来检测。该函数提供多边形的近似,所以如果你的图像有多边形,再结合cv2.findContous函数和cv2.approxPloyDP函数,就可以相当准确的检测出来。
参考文章: https://www.cnblogs.com/zyly/p/9327425.html
个人github主页:https://github.com/RenDong3/OpenCV_Notes
opencv 官方文档:https://docs.opencv.org/2.4/index.html