程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)

第五节、轮廓检测、直线和圆、多边形检测

一、轮廓检测

在计算机视觉中,轮廓检测是另一个比较重要的任务,不单是用来检测图像或者视频帧中物体的轮廓,而且还有其他操作与轮廓检测相关。这些操作中,计算多边形边界,形状逼近和计算机感 兴趣区域。

这是与图像数据交互时的简单操作,因为numpy中的矩阵中的矩形区域可以使用数组切片(slice)定义。在介绍物体检测(包括人脸)和物体跟踪的概念时会大量使用这种技术。

1.1 图像阈值操作

为了从一幅图像中提取我们需要的部分,应该用图像中的每一个像素点的灰度值与选取的阈值进行比较,并作出相应的判断(阈值的选取依赖于具体的问题,物体在不同的图像中可能会有不同的灰度值)。

opencv提供了threshold()函数对图像的阈值进行处理,图像阈值操作函数原型:

cv2.threshold(src,thresh,maxval,type[,dst])

threshold()共支持五中类型的阈值化方式,分别是:

  • 二进制阈值化;
  • 反二进制阈值化;
  • 截断阈值化;
  • 阈值化为0和反阈值化为0。

函数返回阈值操作后的图像。函数参数如下:

  • src: 输入图像,图像必须为单通道8位或32位浮点型图像;
  • thresh: 设定的阈值;
  • maxval::使用cv2.THRESH_BINARYcv2.THRESH_BINARY_INV类型的最大值;
  • type: 阈值化类型,可以通过ThresholdTypes查看,下面给出opencv中五种阈值化类型及其对应公式:
  • dst: 输出图像,与输入图像尺寸和类型相同;

1.2 寻找图像轮廓

opencv中提供findContours()函数来寻找图像中物体的轮廓,并结合drawContours()函数将找到的轮廓绘制出。寻找图像轮廓函数原型:

cv2.findContours(image,mode,method[,contours,hierarchy[,offset]])

这个函数会修改输入图像,因此建议使用原始图像的一份拷贝(比如说img.copy()作为输入图像)。函数返回三个值:返回修改后的图像,图像的轮廓以及它们的层次。

函数参数:

(1) image:输入图像,函数接受的参数是二值图,即黑白的(不是灰度图),我们同样可以使用cv2.compare,cv2.inRange,cv2.threshold,cv2.adaptiveThreshold,cv2.Canny等函数来创建二值图像,如果第二个参数为cv2.RETR_CCOMPcv2.RETR_FLOODFILL,输入图像可以是32-bit整型图像(cv2.CV_32SC1);

(2) mode:轮廓检索模式,如下:

其中:

  • RETR_EXTERNAL:表示只检测最外层轮廓,这对消除包含在其他轮廓中的轮廓很有用(比如在大多数情况下,不需要检测一个目标包含在另一个与之相同的目标里面),对所有轮廓设置hierarchy[i][2]=hierarchy[i][3]=-1
  • RETR_LIST:提取所有轮廓,检测的轮廓不建立等级关系;
  • RETR_CCOMP:提取所有轮廓,并将轮廓组织成双层结构(two-level hierarchy),顶层为连通域的外围边界,次层位内层边界;
  • RETR_TREE:提取所有轮廓并重新建立网状轮廓结构;
  • RETR_FLOODFILL:官网没有介绍,应该是洪水填充法;

(3) method:轮廓近似方法,如下:

其中:

  • CHAIN_APPROX_NONE:获取每个轮廓的每个像素,相邻的两个点的像素位置差不超过1;
  • CHAIN_APPROX_SIMPLE:压缩水平方向,垂直方向,对角线方向的元素,值保留该方向的重点坐标,如果一个矩形轮廓只需4个点来保存轮廓信息;
  • CHAIN_APPROX_TC89_L1CHAIN_APPROX_TC89_KCOS使用Teh-Chinl链逼近算法中的一种;
  • contours:检测到的轮廓(list),每个轮廓都是一个ndarray,每个ndarray是一个轮廓上点的集合。一个轮廓并不是存储轮廓上所有的点,而是只存储可以用直线描述轮廓的点,比如一个正方形,只需要四个顶点就能描述轮廓了;
  • hierarchy:函数返回一个可选的hierarchy结果,这是一个ndarray,形状为[1,轮廓个数,4],其中hierarchy[0]元素的个数和轮廓个数相同。每个轮廓contours[0][i]对应4个hierarchy元素hierarchy[0][i][0]~hierarchy[0][i][3],分别表示后一个轮廓,前一个轮廓,父轮廓,内嵌轮廓的索引,如果没有对应项,则相应的hierarchy[0][i]设置为负数;

(4) offset:轮廓点可选偏移量,有默认值。

1.3 轮廓绘制

轮廓绘制函数原型:

cv2.drawContours(image,contours,contourIdx,color[,thickness[,lineType[,hierarchy[,maxLevel[,offset]]]]])

该函数返回绘制有轮廓的图像。

  • image:输入/输出图像,指明在哪个图像上绘制轮廓。并且该函数会修改源图像image
  • contours:使用findContours检测到的轮廓数据,传入一个list
  • contourIdx:绘制轮廓的索引变量(表示绘制第几个轮廓),如果为负值则绘制所有输入轮廓;
  • color:轮廓颜色;
  • thickness:绘制轮廓所用线条粗细度,如果值为负值,则在轮廓内部绘制;
  • lineTpye:线条类型,有默认值LINE_8,有如下可选类型;
  • hierarchy:可选层次结构信息;
  • maxLevel:用于绘制轮廓的最大等级;
  • offset:可选轮廓便宜参数,用制定偏移量offset=(dx, dy)给出绘制轮廓的偏移量;

1.4 示例程序

轮廓检测示例:

'''
轮廓检测
'''

#加载图像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])  
print('len(contours)',len(contours))  
print('hierarchy,shape',hierarchy.shape)  
print('hierarchy[0]:',hierarchy[0])

#在原图img上绘制轮廓contours
img = cv2.drawContours(img,contours,-1,(0,255,0),2)
cv2.imshow('contours',img)

        
cv2.waitKey()
cv2.destroyAllWindows()

二、查找最小闭圆轮廓案例

找到一个正方形轮廓很简单,要找到到不规则的,歪斜的以及旋转的形状,可以用OpenCVcv2.findContours()函数,它能得到最好的结果,下面来看一副图:

现实的应用会对目标的边界框,最小矩形面积,最小闭圆特别感兴趣,将cv2.findContours()函数和少量的OpenCV的功能相结合就非常容易实现这些功能:

使用boundingRect()函数计算包围轮廓的矩形框,使用minEnclosingCircle()函数计算包围轮廓的最小圆包围。

2.1 计算矩形边界框

cv2.boundingRect(img)函数 计算并返回点集最外面的矩形边界,参数一般传入一个轮廓,contours[0]

x,y,w,h = cv2.boundingRect(c)

函数返回四个值,分别是x,y,w,hx,y是矩阵左上点的左边,w,h是矩阵的宽和高。

然后画出这个矩形(在原图img上绘制):这个操作非常简单,它将轮廓信息转换为(x,y)坐标,并加上矩形的高度和宽度。

cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)

下面来将如何找到一个旋转的矩阵和圆形轮廓。 首先加载图片,然后在源图像的灰度图像上面执行一个二值化操作。这样之后,可在这个灰度图像上执行所有计算轮廓的操作,但在源图像上可利用色彩信息来画这些轮廓。

2.2 计算最小矩形边界框

计算包含出包围目标的最小矩形区域(旋转矩形):

#找到最小区域
rect = cv2.minAreaRect(c)

#计算最小矩形的坐标
box = cv2.boxPoints(rect)

#坐标转换为整数
box = np.int0(box)

这里用到一个非常有趣的机制:OpenCV没有函数能直接从轮廓信息中计算出最小矩形顶点的坐标。所以需要计算最小矩形区域,然后计算这个矩形的顶点。注意计算出来的顶点左边是浮点型,但是所得像素的坐标值是整数,所以需要做一个转换。

函数cv2.minAreaRect()返回一个tuple:(最小外接矩形的中心(x,y),(宽度,高度),旋转角度)。

但是要绘制这个矩形,我们需要矩形的4个顶点坐标box, 通过函数cv2.cv.BoxPoints() 获得,box[ [x0,y0], [x1,y1], [x2,y2], [x3,y3] ]

最小外接矩形的4个顶点顺序、中心坐标、宽度、高度、旋转角度(是度数形式,不是弧度数)的对应关系如下:

注意:旋转角度θ是水平轴(x轴)逆时针旋转,与碰到的矩形的第一条边的夹角。并且这个边的边长是width,另一条边边长是height。也就是说,在这里,widthheight不是按照长短来定义的。

opencv中,坐标系原点在左上角,相对于x轴,逆时针旋转角度为负,顺时针旋转角度为正。在这里,θ∈(-90度,0]

然后画出这个矩形(在原图img上绘制):

  cv2.drawContours(img,[box],0,(255,0,0),3)

首先,该函数与所有绘图函数一样,它会修改源,其次该函数的第二个参数接收一个保存着轮廓的数组,从而可以在一次操作中绘制一系列的轮廓。因此如果只有一组点来表示多边形轮廓,可以把这组点放到一个list中,就像前面例子里处理方框(box)那样。这个函数第三个参数是绘制的轮廓数组的索引,-1表示绘制所有的轮廓,否则只绘制轮廓数组里指定的轮廓。

大多数绘图函数把绘图的颜色和线宽放在最后两个参数里。

2.3 计算最小闭圆

cv2.minEnclosingCircle( points )函数 利用迭代算法,对给定的二维点集寻找计算可包围点集的最小圆形;

其中参数有:

  • points: 输入的二维点集,一般传入一个轮廓contours[0]

函数会返回一个元组,第一个元素为圆心的坐标组成的元素,第二个元素为圆的半径值。把这些值转换为整数后就能很容易地绘制出圆来。

代码如下:

#计算闭圆中心店和和半径
(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)

2.4 完整代码

完整代码如下:

'''
边框 最小矩形区域和最小闭圆的轮廓
'''
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算法

大多数处理轮廓的时候,图的形状(包括凸形状)都是变化多样的。凸形状内部的任意两点的连线都在该形状内部。

3.1 cv2.approxPloyDP

cv2.approxPloyDP函数,它用来计算近似的多边形框。该函数有三个参数:

  • 第一个参数为轮廓;
  • 第二个参数为ε值,它表示源轮廓与近似多边形的最大差值(这个值越小,近似多边形与源轮廓越接近);
  • 第三个参数为布尔标记,它表示这个多边形是否闭合;

ε值对获取有用的轮廓非常重要,所以需要理解它表示什么意思。ε是为所得到的近似多边形周长与源轮廓周长之间的最大差值,这个值越小,近似多边形与源轮廓就越相似。

为什么有了一个精确表示的轮廓却还需要得到一个近似多边形呢?这是因为一个多边形由一组直线构成,能够在一个区域里定义多边形,以便于之后进行操作与处理,这在许多计算机视觉任务中非常重要。

3.2 cv2.arcLength

在了解了ε值是什么之后,需要得到轮廓的周长信息来作为参考值。这可以通过cv2.arcLength函数来完成:

#arcLength获取轮廓的周长
epsilon = 0.01*cv2.arcLength(cnt,True)
#计算矩形的多边形框
approx = cv2.approxPolyDP(cnt,epsilon,True)

3.3 cv2.convexHull

可以通过OpenCV来有效地计算一个近似多边形。为了计算凸形状,需要利用cv2.convexHull来处理获取的轮廓信息。

#从轮廓信息中计算得到凸形状
hull = cv2.convexHull(cnt)

3.4 测试

为了理解源轮廓、近似多边形和凸包的不同之处,可以把他们放在一副图片中进行观察:

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 DudaPeter Hart发明,他们是对Paul Hough在20世纪60年代早期所做工作的扩展。

Hough变换的原理实际上是将直线坐标问题转换为参数平面问题,具体实现原理可以参考:《史上最详细的Hough直线检测》。

4.1 直线检测

亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。

日期姓名金额
2023-09-06*源19
2023-09-11*朝科88
2023-09-21*号5
2023-09-16*真60
2023-10-26*通9.9
2023-11-04*慎0.66
2023-11-24*恩0.01
2023-12-30I*B1
2024-01-28*兴20
2024-02-01QYing20
2024-02-11*督6
2024-02-18一*x1
2024-02-20c*l18.88
2024-01-01*I5
2024-04-08*程150
2024-04-18*超20
2024-04-26.*V30
2024-05-08D*W5
2024-05-29*辉20
2024-05-30*雄10
2024-06-08*:10
2024-06-23小狮子666
2024-06-28*s6.66
2024-06-29*炼1
2024-06-30*!1
2024-07-08*方20
2024-07-18A*16.66
2024-07-31*北12
2024-08-13*基1
2024-08-23n*s2
2024-09-02*源50
2024-09-04*J2
2024-09-06*强8.8
2024-09-09*波1
2024-09-10*口1
2024-09-10*波1
2024-09-12*波10
2024-09-18*明1.68
2024-09-26B*h10
2024-09-3010
2024-10-02M*i1
2024-10-14*朋10
2024-10-22*海10
2024-10-23*南10
2024-10-26*节6.66
2024-10-27*o5
2024-10-28W*F6.66
2024-10-29R*n6.66
2024-11-02*球6
2024-11-021*鑫6.66
2024-11-25*沙5
2024-11-29C*n2.88
posted @   大奥特曼打小怪兽  阅读(25840)  评论(1编辑  收藏  举报
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
如果有任何技术小问题,欢迎大家交流沟通,共同进步

公告 & 打赏

>>

欢迎打赏支持我 ^_^

最新公告

程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)。

了解更多

点击右上角即可分享
微信分享提示