• 图像轮廓检测


    图像金字塔

    金字塔是一种由下到上依次变小的结构,在图像领域中,我们也可以见到这样的结构,对图像做一些处理的时候经常会把它的矩阵维度放大或者缩小,应对不同的需求。
    在这里插入图片描述

    常见的图像金字塔有高斯金字塔和拉普拉斯金字塔。

    高斯金字塔

    高斯金字塔有以下采样模式:

    • 向下采样法:
      在这里插入图片描述
      该方法会将图像缩小(例如由level 0到level 1)。其做法为将图像矩阵与高斯内核卷积,然后将得到的矩阵所有偶数行列去除。
    • 向上采样法
      在这里插入图片描述
      该方法会先将原像素矩阵进行扩充(方法如图),然后使用上面介绍的高斯卷积核与扩充后的矩阵进行卷积,获得近似值。下面我们看一下结果:
    import cv2
    import numpy as np
    import matplotlib.pyplot as plt
    img=cv2.imread("AM.png")
    print(img.shape)
    # 向上采样
    up=cv2.pyrUp(img)
    print (up.shape)
    # 向下采样
    down=cv2.pyrDown(img)
    print (down.shape)
    titles=['img','up','down']
    img_show=[img,up,down]
    fig, ax = plt.subplots(1,3)
    for i in range(3):
        ax[i].set_title(titles[i])
        ax[i].imshow(cv2.cvtColor(img_show[i],cv2.COLOR_BGR2RGB))
    plt.show()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在这里插入图片描述
    从输出结果和图像坐标轴可以看出这三张图片的大小关系。这里的显示图片比较小,大家可能觉得高斯金字塔对图像变换之后可以完整保留原有图像的特点,但是,如果我们进行多次向上—向下采样,就能看到生成的图像和原始图像的区别了:

    import cv2
    import numpy as np
    img=cv2.imread("AM.png")
    
    down=img.copy() # 将img图片复制,以免变换之中影响原始图像结果
    # 四次向上—向下采样
    for i in range(4):
        # 向上采样
        up=cv2.pyrUp(down)
        # 向下采样
        down=cv2.pyrDown(up)
    # 原图与采样结果对比
    cv2.imshow('up_down',np.hstack((img,down)))
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在这里插入图片描述
    虽然图像基本的主要形状和色彩还在,但是清晰度损失了很多。所以,高斯金字塔并不是对图像的无损变换

    拉普拉斯金字塔

    拉普拉斯金字塔是依据高斯金字塔变换而来的。但是,拉普拉斯金字塔并不会改变图像的大小,其运算结构如下:
    在这里插入图片描述

    步骤为:

    • 1.低通滤波
    • 2.使用高斯金字塔进行向下(缩小)—向上(放大)采样
    • 3.图像相减

    拉普拉斯变换可以重复运算很多次,我们可以对比下效果:

    import cv2
    import numpy as np
    import matplotlib.pyplot as plt
    img=cv2.imread("AM.png")
    # 一次拉普拉斯
    down=cv2.pyrDown(img)
    down_up=cv2.pyrUp(down)
    l_1=img-down_up
    up = l_1.copy()
    up_= l_1.copy()
    # 四次拉普拉斯
    for i in range(3):
        down = cv2.pyrDown(up_)
        up_ = cv2.pyrUp(down)
        up =up-up_
        up_ = up.copy()
    cv2.imshow('l_1---l_4',np.hstack((l_1,up_))) # 对比
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    在这里插入图片描述
    可以看出,这样的变换也需要搭配使用,并不适合单独使用。

    图像轮廓

    之前我们介绍过图像边缘,在这里我们就介绍一下图像轮廓。这两者之间有一定的相似之处,我们可以这样理解:它们都可以得到图像的大体形状信息,提取图像边缘会获得更多的细节信息,而提取图像轮廓可以将每一部分轮廓形状分别存储,以便于搭配更多的操作,但提取轮廓不会得到太多的细节。提取图像轮廓用到的是findContours()函数:

    cv2.findContours(img,mode,method)
    
    • 1

    其中,第二个参数mode代表轮廓的检索模式,通常有以下的备选内容:

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

    通常情况下我们使用RETR_TREE作为参数,检索所有的轮廓。
    第三个参数method代表轮廓逼近方法,通常有以下的备选内容:

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

    为了更高的准确率,通常会先将图片转换成二值图像,然后再使用findContours()函数处理。如果安装的opencv是3版本,该函数会返回三个值,但是4版本只会返回两个值。我们需要用到的返回值有contours(4版本的第一个返回值,3版本的第二个返回值)和hierarchy(4版本的第二个返回值3版本的第三个返回值)。3版本的第一个返回值是原二值图像,我们并不会用到。

    轮廓绘制

    轮廓信息会保存在返回值contours之中,我们提取好轮廓之后,就可以在原图像中将轮廓信息绘制出来:

    import cv2
    img = cv2.imread('contours.png')
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    # 二值图像
    ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
    
    contours,hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
    draw_img = img.copy()
    # 传入要绘制轮廓的图像,轮廓,轮廓索引(-1为绘制全部轮廓),颜色模式,线条厚度
    res = cv2.drawContours(draw_img, contours, 1, (0, 0, 255), 2)
    cv2.imshow('res',res)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述

    当然,我们可以换一幅更复杂的图片:
    在这里插入图片描述大家可以尝试绘制某个边缘,这里就不再展示了。

    轮廓特征

    对于提取到的轮廓,我们可以计算其数学特征,如面积,周长等。opencv同样给了我们求取这些特征的函数,比如我们可以求出红框圈出三角的面积和周长:

    import cv2
    img = cv2.imread('contours.png')
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    # 二值图像
    ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
    # 获得轮廓信息contours
    contours,hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
    # 上文绘制的三角形边缘存储在contours[1]中
    cnt = contours[1]
    # 求轮廓面积
    print(cv2.contourArea(cnt))
    # 求轮廓周长,True表示轮廓是闭合的
    print(cv2.arcLength(cnt,True))
    # 输出为:6767.5
    #        395.8061298131943
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    周长和面积是最常用到的特征,但opencv并不是只能得到这两种特质,以后用到的时候,我们再做介绍。

    轮廓近似

    大多数情况下,轮廓都是曲线。但曲线其实是由很多条很短的直线段组成的。为了节约算力,我们可以用更少的直线来表示曲线,方法如下:
    在这里插入图片描述
    设弧AB是图像轮廓的一部分,C是弧AB上距离直线AB最远的点,C到AB的距离d如果小于设定值,那么我们就可以认为弧AB可以用直线AB近似。获取轮廓近似值的函数为

    cv2.approxPolyDP(cnt,epsilon,True)
    
    • 1

    其中,cnt代表要近似的轮廓信息,epsilon是设置的认为可以近似的最大距离,第三个参数经常选择True,意思是当d等于最小设定值时,按照允许近似处理。代码如下:

    import cv2
    import numpy as np
    img = cv2.imread('contours2.png')
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 颜色空间转换
    ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 转换成二值图像
    contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
    cnt = contours[0]
    
    draw_img1 = img.copy()
    draw_img0=img.copy()
    epsilon = 0.1*cv2.arcLength(cnt,True) # epsilon要作为approx函数的参数,该值一般按照周长的百分比进行设置
    # 获取并存储轮廓近似值
    approx = cv2.approxPolyDP(cnt,epsilon,True)
    # 绘制轮廓近似值而非轮廓值
    res0 = cv2.drawContours(draw_img0, contours, 0, (0, 0, 255), 2)
    res1 = cv2.drawContours(draw_img1, [approx], -1, (0, 0, 255), 2)
    res=np.hstack((res0,res1)) # 轮廓对比
    cv2.imshow('res',res)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    在这里插入图片描述

    边界矩形

    边界矩形是根据图像轮廓做出的外接矩形,绘制外接矩形需要两个函数,分别为cv2.boundingRect()和cv2.rectangle()。其中:

    x,y,w,h=cv2.boundingRect(cnt)
    
    • 1

    该函数会产生四个返回值,x,y是外接矩形的最左上角的像素点坐标,w为外接矩形的宽,h为外接矩形的高。绘制外接矩形的函数是cv2.rectangle():

    # (0,255,0)指定绘制外接矩形使用的颜色为绿色,2为外接矩形线宽
    cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
    
    • 1
    • 2

    下面我们绘制一个外接矩形,并计算轮廓面积与边界矩形面积比:

    import cv2
    img = cv2.imread('contours.png')
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
    contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
    cnt = contours[3]
    
    x,y,w,h = cv2.boundingRect(cnt)
    img = cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
    cv2.imshow('img',img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    # 面积比
    area = cv2.contourArea(cnt)
    rect_area = w * h
    print ('轮廓面积与边界矩形比',float(area) / rect_area)
    # 控制台输出为:轮廓面积与边界矩形比 0.7354456063680349
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在这里插入图片描述

    外接圆

    除了边界矩形,还有很多外接图形,比如外接圆。绘制这些外接图形,只需要调用不同的函数即可,比如绘制外接圆:

    import cv2
    img = cv2.imread('contours.png')
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
    contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
    cnt = contours[3]
    (x,y),radius = cv2.minEnclosingCircle(cnt)
    center = (int(x),int(y))
    radius = int(radius)
    img = cv2.circle(img,center,radius,(0,255,0),2)
    cv2.imshow('img',img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述
    这届的内容就介绍到这里了,下一节我们会继续介绍直方图和模板匹配,不见不散~

  • 相关阅读:
    什么是原生IP与广播IP?如何区分判定?
    有没有办法让pycharm直接在debug时显示或print出诸如\x3这样的特殊字符?
    web学习---Vue---笔记(一)
    关于redis客户端lettuce连接被服务器端关闭而造成连接重置导致操作失败的问题
    六、K8S之StatefulSet
    Django--models.py
    Hadoop的基本框架
    高精度NTP时钟服务器(时间同步服务器)技术方案探讨
    数据库(入门)
    MySQL基础进阶
  • 原文地址:https://blog.csdn.net/weixin_54929649/article/details/126422005