一 轮廓检测在计算机视觉中,轮廓检测是另一个比较重要的任务,不单是用来检测图像或者视频帧中物体的轮廓,而且还有其他操作与轮廓检测相关。这些操作中,计算多边形边界,形状逼近和计算机感 兴趣区域。这是与图像数据交互时的简单操作,因为numpy中的矩阵中的矩形区域可以使用数组切片(slice)定义。在介绍物体检测(包括人脸)和物体跟踪的概念时会大量使用这种技术。 1、cv2.threshold(src,thresh,maxval,type[,dst])函数用于图像阈值操作。 为了从一幅图像中提取我们需要的部分,应该用图像中的每一个像素点的灰度值与选取的阈值进行比较,并作出相应的判断(阈值的选取依赖于具体的问题,物体在不同的图像中可能会有不同的灰度值)。opencv提供了threshold()函数对图像的阈值进行处理,threshold()共支持五中类型的阈值化方式,分别是二进制阈值化、反二进制阈值化、截断阈值化、阈值化为0和反阈值化为0。返回阈值操作后的图像。
2、 cv2.findContours(image,mode,method[,contours,hierarchy[,offset]])用于寻找寻找图像轮廓。 opencv中提供findContours()函数来寻找图像中物体的轮廓,并结合drawContours()函数将找到的轮廓绘制出。这个函数会修改输入图像,因此建议使用原始图像的一份拷贝(比如说img.copy()作为输入图像)。函数返回三个值:返回修改后的图像,图像的轮廓以及它们的层次。
其中
该函数返回绘制有轮廓的图像。
''' 轮廓检测 ''' #加载图像img img = cv2.imread('./image/img6.jpg',cv2.IMREAD_COLOR) cv2.imshow('img',img) #转换为灰色gray_img gray_img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) cv2.imshow('gray_img',gray_img) #对图像二值化处理 输入图像必须为单通道8位或32位浮点型 ret,thresh = cv2.threshold(gray_img,127,255,0) cv2.imshow('thresh',thresh) #寻找图像轮廓 返回修改后的图像 图像的轮廓 以及它们的层次 image,contours,hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE) cv2.imshow('image',image) print('contours[0]:',contours[0]) 二 边界框、最小矩形区域和最小闭圆的轮廓找到一个正方形轮廓很简单,要找到到不规则的,歪斜的以及旋转的形状,可以用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、计算包含出包围目标的最小矩形区域(旋转矩形): 这里用到一个非常有趣的机制: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()函数会返回一个元组,第一个元素为圆心的坐标组成的元素,第二个元素为圆的半径值。把这些值转换为整数后就能很容易地绘制出圆来。 完整代码如下: ''' 边框 最小矩形区域和最小闭圆的轮廓 ''' img = cv2.pyrDown(cv2.imread('./image/img16.jpg',cv2.IMREAD_UNCHANGED)) #转换为灰色gray_img gray_img = cv2.cvtColor(img.copy(),cv2.COLOR_BGR2GRAY) #对图像二值化处理 输入图像必须为单通道8位或32位浮点型 ret,thresh = cv2.threshold(gray_img,127,255,cv2.THRESH_BINARY) #寻找最外面的图像轮廓 返回修改后的图像 图像的轮廓 以及它们的层次 image,contours,hierarchy = cv2.findContours(thresh,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE) print(type(contours)) print(type(contours[0])) print(len(contours)) #遍历每一个轮廓 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) #绘制轮廓 最小矩形 blue cv2.drawContours(img,[box],0,(255,0,0),3) #计算闭圆中心店和和半径 (x,y),radius = cv2.minEnclosingCircle(c) #转换为整型 center = (int(x),int(y)) radius = int(radius) #绘制闭圆 img = cv2.circle(img,center,radius,(0,255,0),2) cv2.drawContours(img,contours,-1,(0,0,255),2) cv2.imshow('contours',img) 运行后的结果: 三 凸轮廓与Douglas-Peucker算法大多数处理轮廓的时候,图的形状(包括凸形状)都是变化多样的。凸形状内部的任意两点的连线都在该形状内部。 cv2.approxPloyDP函数,它用来计算近似的多边形框。该函数有三个参数:
ε值对获取有用的轮廓非常重要,所以需要理解它表示什么意思。ε是为所得到的近似多边形周长与源轮廓周长之间的最大差值,这个值越小,近似多边形与源轮廓就越相似。 为什么有了一个精确表示的轮廓却还需要得到一个近似多边形呢?这是因为一个多边形由一组直线构成,能够在一个区域里定义多边形,以便于之后进行操作与处理,这在许多计算机视觉任务中非常重要。 在了解了ε值是什么之后,需要得到轮廓的周长信息来作为参考值。这可以通过cv2.arcLength函数来完成: #arcLength获取轮廓的周长 epsilon = 0.01*cv2.arcLength(cnt,True) #计算矩形的多边形框 approx = cv2.approxPolyDP(cnt,epsilon,True) 可以通过OpenCV来有效地计算一个近似多边形。为了计算凸形状,需要利用cv2.convexHull来处理获取的轮廓信息。 #从轮廓信息中计算得到凸形状 hull = cv2.convexHull(cnt) 为了理解源轮廓、近似多边形和凸包的不同之处,可以把他们放在一副图片中进行观察: img = cv2.imread('./image/img18.jpg',cv2.IMREAD_COLOR) img = cv2.resize(img,None,fx=0.6,fy=0.6,interpolation=cv2.INTER_CUBIC) #创建一个空白图像,用来绘制轮廓 canvas = np.zeros(img.shape,np.uint8) #转换为灰色gray_img gray_img = cv2.cvtColor(img.copy(),cv2.COLOR_BGR2GRAY) #进行均值滤波,去除一些噪声 kernel = np.ones((3,3),np.float32)/9 gray_img = cv2.filter2D(gray_img,-1,kernel) #cv2.imshow('gray_img',gray_img) #对图像二值化处理 输入图像必须为单通道8位或32位浮点型 像素>125 设置为0(黑) 否则设置为255(白) ret,thresh = cv2.threshold(gray_img,125,255,cv2.THRESH_BINARY_INV) #cv2.imshow('thresh',thresh) #寻找图像轮廓 返回修改后的图像 图像的轮廓 以及它们的层次 image,contours,hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE) #获取最大的一个轮廓 cnt = contours[0] max_area = cv2.contourArea(cnt) #对每一个轮廓进行遍历 for cont in contours: if cv2.contourArea(cont) > max_area: cnt = cont max_area = cv2.contourArea(cont) print('max_area',max_area) '''计算最大轮廓的多边形框''' #arcLength获取轮廓的周长 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)) #在源图像中绘制所有轮廓 传入的死一个list cv2.drawContours(img,contours,-1,(0,255,0),2) #GREEN 绘制所有的轮廓 cv2.drawContours(canvas,[cnt],-1,(0,255,0),2) #GREEN 绘制最大的轮廓 cv2.drawContours(canvas,[approx],-1,(0,0,255),2) #RED 绘制最大轮廓对应的多边形框 cv2.drawContours(canvas,[hull],-1,(255,0,0),2) #BLUE 绘制最大轮廓对应的凸包 cv2.imshow('img',img) cv2.imshow('ALL',canvas) cv2.waitKey() cv2.destroyAllWindows() 如上图所示,凸包是由蓝色表示,然后里面是近似多边形,使用红色表示,在两者之间的是源图片中一个最大的轮廓,它主要由弧线构成。 四 直线和圆检测检测边缘和轮廓不仅重要,还经常用到,它们也是构成其他复杂操作的基础。直线和形状检查与边缘和轮廓检测有密切的关系。 Hough变换是直线和形状检测背后的理论基础,它由Richard Duda和Peter Hart发明,他们是对Paul Hough在20世纪60年代早期所做工作的扩展。 1、直线检测首先介绍直线检测,这可通过HoughLines和HoughLinesP函数来完成,它们仅有的差别是:第一个函数使用标准的Hough变换,第二个函数使用概率Hough变换(因此名称里有一个P)。 HoughLinesP函数之所以称为概率版本的Hough变换是因为它只通过分析点的子集并估计这些点都属于一条直线的概率,这是标准Hogh变换的优化版本。该函数的计算代价会少一些,执行会变得更快。 img = cv2.imread('./image/img19.jpg') #转换为灰度图片 gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) #中值滤波 gray = cv2.medianBlur(gray,ksize=3) #边缘检测 edges = cv2.Canny(gray,50,100) minLineLength = 200 maxLineGap = 5 #直线检测 lines = cv2.HoughLinesP(edges,1,np.pi/180,100, minLineLength,maxLineGap) print('len(lines)',len(lines),type(lines)) print('lines[0].shape',lines[0].shape) for i in range(len(lines)): for x1,y1,x2,y2 in lines[i]: cv2.line(img, (x1,y1), (x2,y2),(i*20,100+i*20,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)函数的参数如下:
下面是一个例子: img = cv2.imread('./image/img20.jpg') #缩小 img = cv2.resize(img,None,fx=0.5,fy=0.5,interpolation=cv2.INTER_CUBIC) #转换为灰度图片 gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) #中值滤波 gray = cv2.medianBlur(gray,ksize=3) #圆检测 circles = cv2.HoughCircles(gray,cv2.HOUGH_GRADIENT,1,120,param1=100,param2=30,minRadius=0,maxRadius=0) print('circles',type(circles),circles.shape) #circles <class 'numpy.ndarray'> (1, 3, 3) circles = np.uint16(np.around(circles)) for i in circles[0,:]: #绘制圆 (i[0],i[1])为圆心,i[2]为半径 cv2.circle(img,(i[0],i[1]),i[2],(0,255,0),2) #绘制圆心 cv2.circle(img,(i[0],i[1]),2,(255,0,0),3) cv2.imshow('circles',img) cv2.waitKey() cv2.destroyAllWindows() 3、检测其他形状Hough变换能检测的形状仅限于圆,但是前面曾提到过检测任何形状的方法,特别是用approxPloyDP函数来检测。该函数提供多边形的近似,所以如果你的图像有多边形,再结合cv2.findContous函数和cv2.approxPloyDP函数,就可以相当准确的检测出来。 参考文章: [1]python-opencv2利用cv2.findContours()函数来查找检测物体的轮廓 [2]Python下opencv使用笔记(十一)(详解hough变换检测直线与圆) |
|