OpenCV---007(图像形态学基础操作)

本文介绍了图像形态学的基本概念,包括腐蚀、膨胀、开闭运算,以及像素距离和连通域的测量。着重讨论了如何通过二值化处理和形态学分析提取有用区域,如边界、连通域,并展示了OpenCV中用于计算距离变换和连接组件的函数。
摘要由CSDN通过智能技术生成

图像形态学操作

一些情况下 相比于物体的纹理信息 物体的形状和位置信息对我们更加重要 因此可以忽略物体内部的信息 而是以形态为基础对物体进行分析
所以对于图像形态学我们使用一定的形态的结构元素 来去度量和提取图像中的相应形状 以达到对图像进行分析和识别的目的
图像形态学操作主要包括图像的腐蚀 膨胀 开运算 闭运算

像素距离和连通域

图像形态学主要应用于从图像中提取对于表达和描述区域有意义的图像分量 以便后续的识别工作 例如边界 连通域
因为图像形态学关心图像中的区域信息 忽略纹理信息 所以 可以对图像进行二值化处理 再进行形态学分析
在图像形态学运算中 常将不与其他区域连接的独立区域 称为集合或者连通域 而集合的元素就是连通域内的全部像素点
可以将其表示为图像的坐标 像素间的距离可以表示两个连通域的关系

图像距离变换

图像中的两个元素之间的距离有多种定义方式 最常用的有 欧氏距离 街区距离 棋盘距离

欧氏距离:
    欧式距离是两个像素点间的直线距离 就是直接求取两像素点 x 和 y方向的距离 然后进行勾股定理

街区距离:
    虽然欧氏距离最短 但是我们有时候 发现两个距离间有障碍物 从一个点到另一个点需要沿着x和y走
    因此这种方式被称为街道距离   z = |x2-x1| + |y2-y1|

棋盘距离:
    和街区距离的移动方式相似 但是不表示 一个像素点到另一个像素点的距离 而是表示 到同一行或者
    同一列的最大距离

opencv提供了计算图像中不同像素距离的函数
    dst,labels = cv.distanceTransformWithLabels(src,distanceType,masksize,labelType)
    src: 输入图像
    distanceType: 计算方法 1:街区 2:欧氏 3: 棋盘
    maskSize: 距离变换掩膜矩阵大小
    labels: 与输入图像相同尺寸的二维标签数组
    labelType: 标签类型  0: 图像中的每个连接的0像素及其最接近连接区域的所有非0像素被分配相同标签
                        1: 每个0像素都有自己的标签

    该函数统计图像中所有像素距离零像素的最短距离
    街区距离下: maskSize 选择3或者5都没有影响
    欧氏距离下: 3为初略估计  5为细致估计
    棋盘距离下: 没有影响

点击查看代码
import cv2 as cv
    import sys
    import numpy
    
    
    def call_back(x):
        pass
    
    
    if __name__ == '__main__':
        img = cv.imread('../img_test.jpg')
        if img is None:
            print("图片读取失败")
            sys.exit()
    
        thresh = 50
        cv.namedWindow('my_window')
        cv.createTrackbar('thres', 'my_window', thresh, 255, call_back)
        img = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
    
        while True:
            img1 = img.copy()
            thresh = cv.getTrackbarPos('thres', 'my_window')
    
            _, img1 = cv.threshold(img1, thresh, 255, cv.THRESH_BINARY)
            _, img2 = cv.threshold(img1, thresh, 255, cv.THRESH_BINARY_INV)
            dst_rice = cv.distanceTransform(img1, 2, 3, dstType=cv.CV_32F)
            print(dst_rice.shape)
            dst_rice2 = cv.distanceTransform(img2, 2, 3, dstType=cv.CV_8U)
    
            cv.imshow('my_window', img1)
            cv.imshow('distance', dst_rice)

            cv.imshow('img2', img2)
            cv.imshow('dst', dst_rice2) 
            pirnt(img1)
            print(dst_rice)
            print(img2)
            print(dst_rice2)

    
            if cv.waitKey(1) == ord('q'):
                break
    
        cv.destroyAllWindows()

图像连通域分析

连通域: 指像素值相同且相连的区域 一般为了减少像素值的影响 所以会使用二值图来进行处理

图像领域: 对于图像领域有两种定义方法: 4领域和8领域
4领域: 上下左右 垂直的的方向上 相邻两坐标仅仅能有一个坐标不同 并且像素仅能差1
8领域: 允许对角相邻 但是 差值仅能有1 不同的邻域判断连通的方法也不同 所以在判断时要声明是哪种邻域

常用的判断方法有 两遍扫描法和种子填充法
两遍扫描法: 第一次扫描会给每个非0一个数字标签 当某像素上方和左方 已经有数字 去小的作为当前的标签 否则给一个新标签
最后实现 将一个连通域的不同标签合并 最后实现一个邻域内的所有像素有相同的标签
种子填充法: 将所有的非像素放到一个集合里 将集合中随机选出一个像素作为种子 根据邻域关系不断地扩充种子的连通域
在结合内删除扩充的元素 直到所有的连通域无法扩充 再选新种子

opencv给出了得到连通域图像的函数:
    ret,labels = cv.connectedComponentsWithAlgorithm(img,connectivity,ltype,ccltype)
        img: 待标记图像
        connectivity: 领域种类
        ltype: 输出图像的数据类型
        ccltype: 算法
        labels: 标记不同连通域后的输出图像

    该函数用来计算二值图像的连通域个数 不同的连通域使用不同的个label标记  标签0代表背景
    ret表示连通域个数 

阉割版:
    count,labels = cv.connectedComponents(image,labels,connectivity,ltype)
    该函数的后三个参数有默认值


点击查看代码
import cv2 as cv
    import sys
    import numpy as np
    import time
    
    def back_func(x):
        pass
    
    
    def get_color():
        return np.random.randint(0, 256, 3)
    
    
    def fill_color(img1, img2):
        # 得到整张图的长宽
        h, w = img1.shape
        # 将图变会三层
        res = np.zeros((h, w, 3), img1.dtype)
    
        # 随机颜色,有多少个连通域拿多少个颜色
        random_color = {}
        for c in range(1, count):
            random_color[c] = get_color()
    
        # 遍历整个图,将颜色在一个连通域与上
        for i in range(h):
            for j in range(w):
                item = img2[i][j]
                if item == 0:  # 0是黑的 肯定不是连通域
                    pass
                else:  # 如果不是黑的 标签相同的地方涂上一样的颜色
                    res[i, j, :] = random_color[item]
        return res


    
    if __name__ == '__main__':
        obj = cv.VideoCapture(0)
        cv.namedWindow('binimg')
        cv.createTrackbar('thr', 'binimg', 50, 255, back_func)
        while True:
    
            ret, img = obj.read()
    
            if not ret:
                print('图片读取失败')
                sys.exit()
    
            num = cv.getTrackbarPos('thr', 'binimg')
    
            img = cv.flip(img, 1)
    
            img = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
    
            img = cv.GaussianBlur(img, (3, 3), 10, 10)
    
            _, img = cv.threshold(img, num, 255, cv.THRESH_BINARY)
    
            count, res = cv.connectedComponents(img)
            img = fill_color(img, res)
    
            cv.imshow('binimg', img)
    
            if cv.waitKey(1000) == ord('q'):
                break
    
        obj.release()
        cv.destroyAllWindows()

image

找到连通域并且将每个连通域渲染为不同的颜色

连通域高级函数;
    retval,labels,stats,centroids = cv.connectedComponentsWithStats(image)
    stats: 不同连通域的统计信息
    有N个连通域输出一个 N*5的矩阵 每一行保存一个连通域的统计学特征
    [i,0]: 连通域内最左边像素的x坐标    0+1 左上角     (0+2,1+3)  右下角 
    [i,1]: 最上方像素的y坐标        
    [i,2]: 边界框的水平长度
    [i,3]: 垂直长度
    [i,4]: 面积 


    centroids: 连通域的中心坐标
    输出一个N*2的矩阵 每一行分别保存了每个连通域质心的x,y

点击查看代码
import cv2 as cv
    import sys
    import numpy as np
    import time
    
    
    def back_func(x):
        pass
    
    
    def get_color():
        return np.random.randint(0, 256, 3)
    
    
    def fill_color(img1, n, img2):
        # 得到整张图的长宽
        h, w = img1.shape
        # 将图变会三层
        res = np.zeros((h, w, 3), img1.dtype)
    
        # 随机颜色,有多少个连通域拿多少个颜色
        random_color = {}
        for c in range(1, n):
            random_color[c] = get_color()
    
        # 遍历整个图,将颜色在一个连通域与上
        for i in range(h):
            for j in range(w):
                item = img2[i][j]
                if item == 0:  # 0是黑的 肯定不是连通域
                    pass
                else:  # 如果不是黑的 标签相同的地方涂上一样的颜色
                    res[i, j, :] = random_color[item]
        return res
    
    
    def mark(img, n, stat, cent):
        for i in range(1, n):
            cv.circle(img, (int(cent[i, 0]), int(cent[i, 1])), 2, (0, 255, 0), -1)
            color = list(map(lambda x: int(x), get_color()))
            cv.rectangle(img, (stat[i, 0], stat[i, 1]), (stat[i, 0] + stat[i, 2], stat[i, 1] + stat[i, 3]), color)
    
            font = cv.FONT_HERSHEY_SIMPLEX
            cv.putText(img, str(i), (int(cent[i, 0] + 5), int(cent[i, 1] + 5)), font, 0.5, (0, 0, 255), 1)
    
    
    if __name__ == '__main__':
        obj = cv.VideoCapture(0)
        cv.namedWindow('binimg')
        cv.createTrackbar('thr', 'binimg', 50, 255, back_func)
        while True:
    
            ret, img = obj.read()
    
            if not ret:
                print('图片读取失败')
                sys.exit()
    
            num = cv.getTrackbarPos('thr', 'binimg')
    
            img = cv.flip(img, 1)
    
            img = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
    
            img = cv.GaussianBlur(img, (3, 3), 10, 10)
    
            _, img = cv.threshold(img, num, 255, cv.THRESH_BINARY)
    
            count, res, stats, cents = cv.connectedComponentsWithStats(img)
    
            img = fill_color(img, count, res)
            mark(img, count, stats, cents)
    
            cv.imshow('binimg', img)
    
            if cv.waitKey(100) == ord('q'):
                break
    
            for i in range(1, count):
                print('第{}个连通域面积为:{}'.format(i, stats[i, 4]))
    
        obj.release()
        cv.destroyAllWindows()
-------------------------------------------

image

找点连通域并且圈起来

腐蚀和膨胀

腐蚀和膨胀是形态学的基本运算 可以去掉图像中的噪音 分割出独立的区域或者将两个连通域连接在一起。

图像腐蚀:
    图像腐蚀的过程和卷积有点像 都需要模板矩阵来控制运算的结果 在图像的腐蚀和膨胀过程 这个模板被称为
    结构元素 将结构元素中心依次放在非0处 当所覆盖的元素均不为0就保留 否则删除中心的像素

    图像腐蚀所要的结构元素可以自己生成 但是为了方便
    opencv提供了 
        retval = cv.getStructuringElement(shape,ksize)
        shape: 结构元素种类
        0: 矩形
        1: 十字
        2: 椭圆

        ksize: 结构元素大小
        

    opencv提供了腐蚀函数:
        dst = cv.erode(src,kernel,iterations)
        src: 图片
        kernel: 结构元素
        iterations: 腐蚀次数


图像膨胀:
    与图像腐蚀是相反的操作 
    opencv提供了图像膨胀的操作
    dst = cv.dilate(src,kernel,iterations)

形态学应用

图像形态学的腐蚀可以将噪声区域去除 但是会将图像主要区域的面积缩小 造成形状发生改变
而膨胀可以扩张每一个区域的面积 填充较小的空洞 但同样会增加 噪声的面积
因此两者的运算需要进行相互结合

开运算:
    可以去除图像中的噪声,消除较小的连通域,保留较大的连通域。同时开运算 能够在两个物体纤细的
    连接处将他们分离 并且在不明显改变较大连通域的面积情况下 平滑连通域的边界
    原理: 先对图像进行腐蚀 消除噪音 在膨胀弥补损失 

    opencv没有提供图像开运算函数 而是提供了图像腐蚀和膨胀不同组合形式的函数
    cv.morphologyEx(src,op,kernel,interations)
    该函数 根据结构元素 对输入图像进行多种形态学操作 并且将操作后的结果进行返回 
    若输入图像为多通道 则对每个通道单独处理
    第二个参数 是集合类型:
    op: 0 图像腐蚀
        1 图像膨胀
        2 开运算
        3 闭运算 
        4.形态学梯度运算
        5.顶帽运算
        6.黑帽运算
        7.击中击不中运算

开运算效果:

image


闭运算: 
    可以去除连通域内的小型空洞 平滑物体轮廓 连接两个近邻连通域 先对图像进行膨胀
    在进行 腐蚀

    cv.MORPH_CLOSE   3

效果如下:

image


形态学梯度运算:
    形态学梯度运算能够描述目标的边界 根据图像腐蚀 和 膨胀 与 原图间的距离关系计算得到
    形态学梯度可以分为基本梯度 内部梯度 外部梯度
    基本梯度: 是原图像膨胀后和腐蚀后 图像间的差值图 
    内部梯度: 原图像和腐蚀后图像的差值图
    外部梯度: 是膨胀后和原图像的差值图
    本函数仅提供了基本梯度 

    得到显示的是一个个圈  因为是 膨胀和腐蚀的差

    cv.MORPH_GRADIENT  4

    效果如下:

image


顶帽运算:
    原图像和开运算间的差 往往用来分离 比邻近点亮一点的斑块 
    由于开运算 放大了裂缝 或是 亮度较低区域 所有原图像减去开运算后
    会得到 被视为噪音的区域
    cv.MORPH_TOPHAT  5
    效果如下:

image


黑帽运算:
    与顶帽运算相对的 是与闭运算间的差值
    可以得到较暗的斑块 
    闭运算 先膨胀再腐蚀 原图像减去后 得到连接区域
cv.MORRH_BLACKHAT  6

效果如下:

image


击中击不中变换:
    比图像腐蚀更加苛刻的一种形态学操作
    击中击不中变换要求 原图像中存在 与结构元素一摸一样的结构
    及结构的非0元素也要考虑进去

    cv.MORPH_HITMISS  7

    效果如下:

image


图像细化

将图像中的线条 从多像素宽度 减少到单位像素宽度 的过程 又称为骨架化
图像细化 是 模式识别领域的重要处理之一 常用在文字识别。
文字细化可以有效地增加文字的可识别度 并且降低数据量和存储难度
图像细化要求 保证图像骨架的连通性 原图像的细节特征要较好保留
线条端点要保留 图像细化 适合用在线条形状组成的物体

非迭代细化算法

不以像素为基础 该方法通过一次遍历产生线条的某一中值或中心线 而不是所有的单个元素
该算法 可以一次遍历出结果 主要有: 基于距离变化的算法 游程长度编码算法
该方法很快 但是 容易产生噪音

迭代算法

通过 重复的删除图像边缘中满足一定条件的像素点来达到细化目的 。
不同的迭代算法又分为 串行算法和并行算法
在串行中 每次迭代都固定次序检测 取绝与前方n-1次全部的结果
并行中 第n次迭代时像素的删除仅取决n-1的结果 每次都是独立的
并行算法中 Zhang方法被广泛应用

细化函数

在Opencv中提供了 图像二值化细化的函数
    dst = cv.ximgproc.thinning(src,thinningType)
    thinningType: cv.THINNING_ZHANGSUEN  0
                  cv.THINNING_GUOHALL  1

效果如下:

image

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值