opencv-python 小白笔记(8)

今天的全是与轮廓有关的,许多图像处理都需要用到轮廓,因为用轮廓可以解决很多问题。

什么是轮廓:轮廓可以简单认为成连续的点(连着边界)连在一起的曲线,具有相同的颜色或者灰度。轮廓在形状分析和物体的检测和识别中很有用

第八节:轮廓的提取与轮廓的应用

  • (一)获取轮廓(cv2.findContours, cv2.drawContours)
  • (二)快速获得最大的轮廓(cv2.contourArea,sorted)
  • (三)cv2.approxPolyDP函数的妙用(cv2.approxPolyDP)
  • (四)它俩cv2.minAreaRect,cv2.boundingRect得到的矩形有啥区别
  • (五)最小外接圆,最小外接椭圆(cv2.minEnclosingCircle,cv2.ellipse)
  • (六)识别图中的所有图形并标注
  • (七)轮廓的一大堆性质
  • (八)备注(cv2.approxPolyDP函数再讲解)
  • (九)结语

(一)获取轮廓(cv2.findContours, cv2.drawContours)

这里我们需要用到cv2.findContour函数,cv2.findContours()有三个参数,第一个是输入图像,第二个是轮廓检索模式,第三个是轮廓近似方法。

cv2.findContours(img,mode,method)

mode:轮廓检索模式:
RETR_EXTERNAL :只检索最外面的轮廓;
RETR_LIST:检索所有的轮廓,并将其保存到一条链表当中;
RETR_CCOMP:检索所有的轮廓,并将他们组织为两层:顶层是各部分的外部边界,第二层是空洞的边界;
RETR_TREE:检索所有的轮廓,并重构嵌套轮廓的整个层次;

method:轮廓逼近方法:
CHAIN_APPROX_NONE:以Freeman链码的方式输出轮廓,所有其他方法输出多边形(顶点的序列)。
CHAIN_APPROX_SIMPLE:压缩水平的、垂直的和斜的部分,也就是,函数只保留他们的终点部分。

对于这个函数的返回值,需特别注意,因为不同版本的OPCV它有不同的返回值,我给的建议是新入手的opcv的小伙伴,我的建议是使用3.4.2的版本(anaconda上都能安装到),以为这个版本的opcv的大多数算法多有,并且网上的一些实例都是基于老版本的,所以使用这个版本可以再让你运行一些别人的工程时少一些报错,你一个原因时最新的一些算法被申请专利(所以你就用不了),而我刚刚推荐的版本基本上所以的算法都能用,还有我觉的新版本的坑比较多。,我估计小伙伴安装的都是最新版的(爱尝新),为了配合你们,我这里所有的实例程序都是基于opcv 4.0版的(4.0往上,具体多少忘记了,影响不大)。好,回到我们的正题上,我推荐的版本返回值是3个(老版),这里我使用使用的版本返回值是2个,轮廓(的返回值)是一个Python列表,其中储存这图像中所有轮廓。每一个轮廓都是一个Numpy数组,包含对象边界点(x,y)的坐标。

为了更形象的展示我所取得的轮廓,我们这里有cv2.drawContours画出轮廓,函数cv2.drawContours()可以被用来绘制轮廓。它可以根据你提供的边界点绘制任何形状。它的第一个参数是原始图像,第二个参数是轮廓,一个python列表,第三个参数是轮廓的索引(在绘制独立轮廓是很有用,当设置为-1时绘制所有轮廓)。接下来的参数是轮廓的颜色和厚度(如果字体厚度设置为-1,就会将整个轮廓填充)。
上代码:

import cv2
import numpy as np

img = cv2.imread('1.png')

imgGray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)#转为灰度图
imgBlur = cv2.GaussianBlur(imgGray,(7,7),1)#高斯降噪
imgCanny = cv2.Canny(imgBlur,50,50)#canny边缘检测
contours,hierarchy = cv2.findContours(imgCanny,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)#提取图像外轮廓,并返回至contours

img_output=cv2.drawContours(img.copy(), contours, -1, (0, 255, 0), 3)#这里使用原谅色
#这里有一个细节,那就是我们并没有直接在原图上画出轮廓,因为在原图上,会直接破会原图信息

cv2.imshow("img_output", img_output)

cv2.waitKey(0)

opencv-python 小白笔记(8)_第1张图片

(二)快速获得最大的轮廓(cv2.contourArea,sorted)

有的时候你只需要面积最大的轮廓(或者说你想要的目标它的轮廓最大),这是你就需cv2.contourArea函数,它能计算每个轮廓的面积,再配合python3的内置函数sorted的使用,你就可以快速得到面积最大的轮廓。

sorted(iterable, key=None, reverse=False)
参数说明:
iterable – 可迭代对象。
key – 主要是用来进行比较的元素,只有一个参数,具体的函数的参数就是取自于可迭代对象中,指定可迭代对象中的一个元素来进行排序。
reverse – 排序规则,reverse = True 降序 , reverse = False 升序(默认)。
这里说一下,sorted函数就是一个排序函数,对迭代对象的什么方面排序由key参数决定,小伙伴如果还不明白,就百度一下吧
这是廖雪峰对这一函数的理解

import cv2
import numpy as np

img = cv2.imread('shapes.png')

imgGray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)#转为灰度图
imgBlur = cv2.GaussianBlur(imgGray,(5,5),1)#高斯降噪(别问为什么要高斯滤波,问就是固定搭配)
imgCanny = cv2.Canny(imgBlur,50,50)#canny边缘检测
contours,hierarchy = cv2.findContours(imgCanny,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)#提取图像外轮廓,并返回至contours
cnts = sorted(contours, key = cv2.contourArea, reverse = True)[0]
#上面将所有的轮廓进行排序(降序),然后我们取出最大的,也就是第0个

img_output=cv2.drawContours(img.copy(), cnts, -1, (0, 255, 0), 3)#这里使用原谅色
#这里有一个细节,那就是我们并没有直接在原图上画出轮廓,因为在原图上,会直接破坏原图信息

cv2.imshow("img_output", img_output)

cv2.waitKey(0)

opencv-python 小白笔记(8)_第2张图片
这里显示六边形的面积最大

(三)cv2.approxPolyDP函数的妙用(cv2.approxPolyDP)

cv2.approxPolyDP函数,(这函数它有3个参数,第一个是需要传入的轮廓,第二个是轮廓近似参数,第三个是指定对象的形状是闭合的(True),还是非闭合的(false))这个函数对于后面提取不同图像的轮廓有巨大的帮助。换言之,这个函数很有用。有的时候我们虽然得到了某个图像的轮廓信息,但我们可能并不是想要它的轮廓信息(怎么说呢,语文不好,表达不出想要表达的意思),算了,上代码:

注:一般cv2.approxPolyDP函数使用需cv2.arcLength函数(计算轮廓周长的函数)的配合,该函数有两个参数,一个是图像的轮廓,第二参数可以用来指定对象的形状是闭合的(True),还是非闭合的(false)。肯定会有小伙伴会问,如果指定对象的形状是闭合的,我强行改为flase,会发什么,自己试试呗(这里我试了,如果强行赋予False,得到的周长会比用True短一些)

import cv2
import numpy as np

img = cv2.imread('2.png')

imgGray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)#转为灰度图
imgBlur = cv2.GaussianBlur(imgGray,(5,5),1)#高斯降噪
imgCanny = cv2.Canny(imgBlur,50,50)#canny边缘检测
contours,hierarchy = cv2.findContours(imgCanny,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)#提取图像外轮廓,并返回至contours

cnt=contours[0]#这里为选取第一个轮廓

peri=cv2.arcLength(cnt,True)
approx = cv2.approxPolyDP(cnt,0.11*peri,True)
img_output=cv2.drawContours(img.copy(), [approx], -1, (0, 255, 0), 3)#还是原谅色

cv2.imshow("img",img)
cv2.imshow("img_output", img_output)
cv2.waitKey(0)

opencv-python 小白笔记(8)_第3张图片
这里原图是一个多边形,我们可能有事可能并不想画出它的所有轮廓,我们就可以调整cv2.approxPolyDP函数的轮廓近似参数(0.11*peri),这个值越小,它与原图中的轮廓就越近似。

这里需注意的是,这里如果将下面的第一行改为第二行,会有不同的效果

img_output=cv2.drawContours(img.copy(), [approx], -1, (0, 255, 0), 3)#还是原谅色
img_output=cv2.drawContours(img.copy(), approx, -1, (0, 255, 0), 3)#还是原谅色

opencv-python 小白笔记(8)_第4张图片
修改了那一行后,图像中的4个点就不会连接起来(俺也不知道为啥,能力不够)。到现在为止,你可能并不觉得cv2.approxPolyDP函数有啥用(主要是我能力不够,不能很好的表达它的作用),不过后面还会有这个函数的使用。最后在总结一下就是:用这个函数,你可以自己设置轮廓拟合点的个数

(四)它俩cv2.minAreaRect,cv2.boundingRect得到的矩形有啥区别

cv2.minAreaRect(),它需要传入一个的参数(指定对象的轮廓),返回的是一个Box2D结构,其中包含矩形最上角角点坐标(x,y)矩形的宽(w,h)以及旋转角度。但是要绘制这个矩形需要矩形的4个角点,可以通过函数cv2.boxPoints()获得
上代码,上图(看了你就懂)

import cv2
import numpy as np

img = cv2.imread('timg.jpg')

imgGray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)#转为灰度图
imgBlur = cv2.GaussianBlur(imgGray,(5,5),1)#高斯降噪
imgCanny = cv2.Canny(imgBlur,50,50)#canny边缘检测

contours,hierarchy = cv2.findContours(imgCanny,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)#提取图像外轮廓,并返回至contours
cnts = sorted(contours, key = cv2.contourArea, reverse = True)[0]#所有的排序,然后取得最大的轮廓
rect = cv2.minAreaRect(cnts)#用最小的矩形框,框住指定的轮廓
box = np.int0(cv2.boxPoints(rect))#通过函数cv2.boxPoints获得绘制这个矩形需要矩形的4个角点
img_output=cv2.drawContours(img.copy(),  [box], -1, (0, 255, 0), 3)

cv2.imshow("img",img)
cv2.imshow("img_output", img_output)
cv2.waitKey(0)

opencv-python 小白笔记(8)_第5张图片
cv2.boundingRect函数它框出的矩形是一个水平的矩形,它需要传入的参数只有一个(指定对象的轮廓),它返回的参数有四个x,y,w,h

import cv2
import numpy as np

img = cv2.imread('timg.jpg')

imgGray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)#转为灰度图
imgBlur = cv2.GaussianBlur(imgGray,(5,5),1)#高斯降噪
imgCanny = cv2.Canny(imgBlur,50,50)#canny边缘检测

contours,hierarchy = cv2.findContours(imgCanny,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)#提取图像外轮廓,并返回至contours
cnts = sorted(contours, key = cv2.contourArea, reverse = True)[0]#所有的排序,然后取得最大的轮廓
x,y,w,h=cv2.boundingRect(cnts)
img_output=cv2.rectangle(img.copy(),(x,y),(x+w,y+h),(0,255,0),3)#在之前的章节中,已经讲过

cv2.imshow("img",img)
cv2.imshow("img_output", img_output)
cv2.waitKey(0)

opencv-python 小白笔记(8)_第6张图片
对比这两张图片,你就会发现cv2.minAreaRect函数框选的矩形是面积最小的矩形(为了框选,它甚至会旋转),而cv2.boundingRect函数只是水平的框选一个矩形

(五)最小外接圆,最小外接椭圆(cv2.minEnclosingCircle,cv2.ellipse)

函数cv2.minEnclosingCircle函数可以帮我们找到指定对象的最小外接圆。它需要传入的参数只有一个(指定对象的轮廓),它返回的参数有两个(x,y),radius(即,圆点的中心,和圆的半径)

import cv2
import numpy as np

img = cv2.imread('1.png')

imgGray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)#转为灰度图
imgBlur = cv2.GaussianBlur(imgGray,(5,5),1)#高斯降噪
imgCanny = cv2.Canny(imgBlur,50,50)#canny边缘检测

contours,hierarchy = cv2.findContours(imgCanny,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)#提取图像外轮廓,并返回至contours
cnts = sorted(contours, key = cv2.contourArea, reverse = True)[0]#所有的排序,然后取得最大的轮廓
(x,y),radius = cv2.minEnclosingCircle(cnts)
center = (int(x),int(y))
radius = int(radius)
img_output = cv2.circle(img.copy(),center,radius,(0,255,0),3)

cv2.imshow("img",img)
cv2.imshow("img_output", img_output)
cv2.waitKey(0)

opencv-python 小白笔记(8)_第7张图片
这里框选的是六边形的最小圆

cv2.ellipse(img, center, axes, angle, startAngle, endAngle, color, thickness, lineType, shift) 这是画椭圆函数,它的参数包括:
img:需要绘图的图像
center:椭圆中心点坐标
axes:椭圆尺寸(即长短轴)
angle:旋转角度(顺时针方向)
startAngle:绘制的起始角度(顺时针方向)
endAngle:绘制的终止角度(例如,绘制整个椭圆是0,360,绘制下半椭圆就是0,180)
color:线条颜色(BGR)
thickness:线条粗细(默认值=1)
lineType:线条类型(默认值=8)
shift:圆心坐标点和数轴的精度(默认值=0)

cv2.fitEllipse函数它的传入参数有只有一个(指定对象的轮廓),返回参数即为cv2.ellipse返回值

import cv2
import numpy as np

img = cv2.imread('timg.jpg')

imgGray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)#转为灰度图
imgBlur = cv2.GaussianBlur(imgGray,(5,5),1)#高斯降噪
imgCanny = cv2.Canny(imgBlur,50,50)#canny边缘检测

contours,hierarchy = cv2.findContours(imgCanny,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)#提取图像外轮廓,并返回至contours
cnts = sorted(contours, key = cv2.contourArea, reverse = True)[0]#所有的排序,然后取得最大的轮廓

ellipse = cv2.fitEllipse(cnts)
img_output = cv2.ellipse(img.copy(),ellipse,(0,255,0),3)#原谅色

cv2.imshow("img",img)

cv2.imshow("img_output", img_output)
cv2.waitKey(0)

opencv-python 小白笔记(8)_第8张图片

(六)识别图中的所有图形并标注

话不都说,上代码,你都懂

import cv2
import numpy as np

img = cv2.imread('shapes.png')
img1=img.copy()
imgGray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)#转为灰度图
imgBlur = cv2.GaussianBlur(imgGray,(5,5),1)#高斯降噪
imgCanny = cv2.Canny(imgBlur,50,50)#canny边缘检测
contours, hierarchy = cv2.findContours(imgCanny, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)  # 提取图像外轮廓,并返回至contours

for cnt in contours:#遍历所有的轮廓
    peri = cv2.arcLength(cnt, True)
    approx = cv2.approxPolyDP(cnt, 0.02 * peri, True)
    x, y, w, h = cv2.boundingRect(approx)  # 获得每个轮廓的外接矩形

    if len(approx) == 3:#三个点的认为是三角形
        Type = "Triangle"
    elif len(approx) == 4:#四个点可能为矩形or正方形,所以这里引入了轮廓的外接矩形的长宽比来判断
        Ratio = w / float(h)
        if Ratio > 0.97 and Ratio < 1.03:
            Type = "Square"
        else:
            Type = "Rectangle"
    elif len(approx) > 4:#大于四个点的,在该图中认为是圆形
        Type = "Circles"
    else:
        Type = "None"

    cv2.putText(img1, Type, (x + (w // 2) - 10, y + (h // 2) - 10), cv2.FONT_HERSHEY_COMPLEX, 0.7, (0, 0, 0),1)  # 这里选择黑色,显示更清楚(原谅色看不清)



cv2.imshow("img",img)
cv2.imshow("img_output", img1)
cv2.waitKey(0)

opencv-python 小白笔记(8)_第9张图片

(七)轮廓的一大堆性质

1长宽比
边界矩形的宽高比
x,y,w,h=cv2.boundingRect(cnt)
aspect_ratio = float(w)/h

2.Extent
轮廓面积与边界矩形面积的比
area=cv2.contourArea(cnt)
x,y,w,h=cv2.boundingRect(cnt)
rect_area=w*h
extent=float(area)/rect_area

3.Solidity
轮廓面积与凸包面积的比
area=cv2.contourArea(cnt)
hull=cv2.convexHull(cnt)
hull_area=cv2.contourArea(hull)
solidity=float(area)/hull_area

4.与轮廓面积相等的圆形的直径
area=cv2.contourArea(cnt)
equi_diameter=np.sqrt(4*area/np.pi)
5.方向
对象的方向,下面的方法还会返回长轴和短轴的长度
(x,y),(MA,ma),angle=cv2.fitEllipse(cnt)

6.掩模和像素点
有时我们需要构成对象的所有像素点
mask=np.zeros(imgray.shate,np.uint8)
#这里一定要使用参数-1,绘制填充的轮廓
cv2.drawContours(mask,[cnt],0,255,-1)
pixelpoints=np.transpose(np.nonzero(mask))
7.最大值和最小值及它们的位置
可以使用掩模图像得到这些参数
min_val,max_val,min_loc,max_loc=cv2.minMaxLoc(imgray,mask=mask)

8.平均颜色及平均灰度
同样使用相同的掩模来求得
mean_val=cv2.mean(im,mask=mask)

9.极点
一个对象最上,最下,最左,和最右的点
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])

注:这里基本上是copy的,这些性质都很重要,在你需要对一些特殊的轮廓的提取,这些性质会起到意想不到的作用

(八)备注(cv2.approxPolyDP函数再讲解)

opencv-python 小白笔记(8)_第10张图片
小伙伴们可以看见上图中有一条黑色的弧线和两条黑的虚线(h是虚线到弧线上的最远距离),当问需要用直线近似图中的曲线时,我们就需要用到cv2.approxPolyDP函数,当cv2.approxPolyDP函数第二个轮廓近似参数(不记得的小伙伴可以回到上面在看看)大于h时,就可以表示为图中的弧线可以由图中的虚线近似代替,当cv2.approxPolyDP函数第二个轮廓近似参数小于h时,则可能需要两条甚至更多的虚线来近似代替图中的弧线。总而言之,cv2.approxPolyDP函数第二个轮廓近似参数设置的越小,需要的虚线越多,近似出的虚线就越接近弧线的轮廓
opencv-python 小白笔记(8)_第11张图片
如上图所示,轮廓近似参数设置的越小,需要的近似曲线就越多(因为近似的虚线越多,每条虚线的h也就越小)。不知道我有没有讲明白,语言表达能力有限。这个函数真的巨有用

(九)结语

学习opencv有很多的方法,我的建议是你可以加一些群,可以充分利用B站,CSDN,和百度。

在我的博客中,我不会讲解opencv的算法实现(当然我也不太会),我只会讲解一些函数的调用,不理解就多改一些参数,多尝试尝试,慢慢你就理解来。相信你总有一天可以说opencv不过“Ctrl+C,Crtl+V”

PS:如果有什么错误的地方,还请大家批评指正,不过错了也没啥关系,反正也没什么人看
最后,希望小伙伴们都能有所收获。码字不易,喜欢的话,关注一波在走吧

你可能感兴趣的:(opencv-python笔记,opencv,python,计算机视觉)