OpenCV(26)图像分割 -- 距离变换与分水岭算法(硬币检测、扑克牌检测、车道检测)

目录

一、基础理论

1、思想

2、原理

二、分水岭实战:硬币

步骤归纳

 1、把原图像转二值图

2、开运算去噪

3、确定背景区域(膨胀)(得到背景/最大连通域)

4、确定前景区域(距离变换) (分离)(得到种子/前景)

5、找到未知区域(未知区域=背景-前景)

6、根据种子标记最大连通域

7、使用分水岭算法:合并种子和不确定区域、标记边界为-1

8、涂色并显示

总代码及效果(硬币)

三、分水岭实战:扑克牌

步骤归纳

1、锐化

2、开运算去噪

3、获取背景(全连通域)

3、确定前景区域(距离变换)(种子)

4、找未知区域(未知区域 = 确定的背景-确定的前景)

5、根据种子标记最大连通域

6、使用分水岭算法,合并不确定区域和种子,边界修改为-1

7、涂色并显示

总代码及效果(扑克牌)

四、分水岭实战:路面检测

1、 锐化

2、锐化之后再二值化

3、膨胀获取背景

4、距离变换获取前景

 5、获取不确定区域(背景-前景)

 6、显示标记情况

7、成功检测出路面的分水岭

总代码

参考资料


一、基础理论

1、思想

        防止同化。 

        任何灰度图像都可以看作是一个地形表面,其中高强度表示山峰,低强度表示山谷。你开始用不同颜色的水(标签)填充每个孤立的山谷(局部最小值)。随着水位的上升,根据附近的山峰(坡度),来自不同山谷的水明显会开始合并颜色也不同为了避免这种情况,你要在水融合的地方建造屏障。你继续填满水,建造障碍,直到所有的山峰都在水下。然后你创建的屏障将返回你的分割结果。这就是Watershed背后的“思想”。你可以访问Watershed的CMM网页,了解它与一些动画的帮助。

        但是这种方法会由于图像中的噪声或其他不规则性而产生过度分割的结果。因此OpenCV实现了一个基于标记的分水岭算法,你可以指定哪些是要合并的山谷点,哪些不是。这是一个交互式的图像分割

2、原理

        我们所做的是:给我们知道的对象赋予不同的标签用一种颜色(或强度)标记我们确定为前景或对象的区域用另一种颜色标记我们确定为背景或非对象的区域最后用0标记我们不确定的区域这是我们的标记。然后应用分水岭算法。然后我们的标记将使用我们给出的标签进行更新对象的边界值将为-1

靠近对象中心的区域是前景,而离对象中心很远的区域是背景,剩下的区域是不确定区域。

二、分水岭实战:硬币

首先看看原图:

步骤归纳

1、图像二值化 

2、开运算去噪

3、确定背景区域(膨胀)(得到背景/最大连通域)

4、确定前景区域(距离变换) (分离)(得到种子/前景)

5、找到未知区域(未知区域=背景-前景)

6、根据种子标记最大连通域

7、使用分水岭算法:合并种子和不确定区域、标记边界为-1

8、涂色并显示

 1、把原图像转二值图

# 转二值图像
def ToBinary():
    global gray, binary
    # 灰度化
    gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
    # 二值化
    ret, binary = cv.threshold(gray, 0, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU)
    cv.imshow('binary', binary)

(为了方便后面的调用,这里把灰度图和二值图都设置为了global全局变量)

 

2、开运算去噪

开运算,去除噪声

# 1、开运算去噪
    opening = cv.morphologyEx(binary, cv.MORPH_OPEN, (3,3), iterations=2)
    cv.imshow('opening', opening)

3、确定背景区域(膨胀)(得到背景/最大连通域)

膨胀之后,对象扩大,背景减小,此时对象>原对象,此时背景 < 原背景,那么此时的背景自然可以确定为原背景的一部分。(离对象中心很远的是背景)

# 2、膨胀确定背景区域
    sure_bg = cv.dilate(opening, (3,3), iterations=3)
    cv.imshow('sure_bg', sure_bg)

4、确定前景区域(距离变换) (分离)(得到种子/前景)

原理:距离变换,在二值图中把对象缩小,得到的就是原图的一部分,可以确定为前景。类似于分离。(不分离的话,可以不用距离变换,只用腐蚀就够了)

为什么不能直接缩小?

直接缩小会让图片也跟着变化,我们要的只是把对象缩小,图片不变的。

distanceTransform函数计算非零像素到最近零像素点的最短距离。一般用于求解图像的骨骼(二值图)

def distanceTransform(src: Any,
                      distanceType: Any,
                      maskSize: Any,
                      dst: Any = None,
                      dstType: Any = None) -> None

distanceType :所用的求解距离的类型。

mask_size :距离变换掩模的大小,可以是 3 或 5。 对 CV_DIST_L1 或 CV_DIST_C 的情况,参数值被强制设定为 3, 因为 3×3 mask 给出 5×5 mask 一样的结果,而且速度还更快。 

# 3、确定前景区域(距离变换)
    # 3-1、求对象最大宽度/长度(直径)
    dist_transform = cv.distanceTransform(opening, cv.DIST_L2, 5)
    # 3-2、最长直径缩小一定程度,确定前景
    ret, sure_fg = cv.threshold(dist_transform, 0.7 * dist_transform.max(), 255, cv.THRESH_BINARY)
    #   前景          阈值函数                    阈值                        最大值 二值化方式
    sure_fg = np.uint8(sure_fg)
    cv.imshow('sure_fg', sure_fg)

sure_fg是种子 。(后面的标记需要根据种子获取标记

5、找到未知区域(未知区域=背景-前景)

未知区域 = 确定的背景 - 确定的前景 

# 4、找未知区域(未知区域 = 确定的背景-确定的前景)
    unknown = cv.subtract(sure_bg, sure_fg)
    cv.imshow('unknown', unknown)

6、根据种子标记最大连通域

根据种子标记最大连通域(大于1为内部区域,标记1为背景区域,0为未知区域)
# 5、根据种子标记最大连通域(大于1为内部区域,标记1为背景区域,0为未知区域)
    ret, markers = cv.connectedComponents(sure_fg)  #标记最大连通域
    markers = markers+1                             #最大连通域标记为1(背景)
    markers[unknown == 255] = 0                     #未知区域标记为0

 显示:连通域(背景)、不确定区域、种子(前景)

#显示各区域(连通域/背景、不确定区域、种子/前景)
def Show_Markers():
    mark = img.copy()
    mark[markers == 1] = (255, 0, 0)    #连通域/背景(蓝)
    mark[markers == 0] = (0, 255, 0)    #不确定区域(绿)
    mark[markers > 1] = (0, 0, 255)     #前景/种子(红)

    mark[markers == -1] = (0, 255, 0)   #边界(绿)
    cv.imshow('Markers', mark)

 

7、使用分水岭算法:合并种子和不确定区域、标记边界为-1

# 6、使用分水岭算法,边界修改为-1,边界涂红(-1)(分界:连通域背景 -- 未知区域+种子)
    markers = cv.watershed(img, markers)  # 分水岭算法(修改边界为-1)

8、涂色并显示

# 7、涂色并显示(边界(markers==-1)涂色)
    dst = img.copy()
    dst[markers == -1] = [0, 0, 255]                #边界(-1)涂色
    cv.imshow('dst', dst)

总代码及效果(硬币)

# 距离变换与分水岭算法
import numpy as np
import cv2 as cv


# 转二值图像
def ToBinary():
    global gray, binary
    # 灰度化
    gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
    # 二值化
    ret, binary = cv.threshold(gray, 0, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU)
    cv.imshow('binary', binary)


#显示各区域(连通域/背景、不确定区域、种子/前景)
def Show_Markers():
    mark = img.copy()
    mark[markers == 1] = (255, 0, 0)    #连通域/背景(蓝)
    mark[markers == 0] = (0, 255, 0)    #不确定区域(绿)
    mark[markers > 1] = (0, 0, 255)     #前景/种子(红)

    mark[markers == -1] = (0, 255, 0)   #边界(绿)
    cv.imshow('Markers', mark)


# 分水岭找边界
def Watershed():
    global markers
    # 1、开运算去噪
    opening = cv.morphologyEx(binary, cv.MORPH_OPEN, (3,3), iterations=2)
    cv.imshow('opening', opening)

    # 2、确定背景区域(膨胀)
    sure_bg = cv.dilate(opening, (3,3), iterations=3)
    cv.imshow('sure_bg', sure_bg)

    # 3、确定前景区域(距离变换)(种子)
    # 3-1、求对象最大宽度/长度(直径)
    dist_transform = cv.distanceTransform(opening, cv.DIST_L2, 5)
    # 3-2、最长直径按比例缩小,确定前景
    ret, sure_fg = cv.threshold(dist_transform, 0.7 * dist_transform.max(), 255, cv.THRESH_BINARY)
    #   前景          阈值函数                    阈值                        最大值 二值化方式
    sure_fg = np.uint8(sure_fg)
    cv.imshow('sure_fg', sure_fg)

    # 4、找未知区域(未知区域 = 确定的背景-确定的前景)
    unknown = cv.subtract(sure_bg, sure_fg)
    cv.imshow('unknown', unknown)

    # 5、根据种子标记最大连通域(大于1为内部区域,标记1为背景区域,0为未知区域)
    ret, markers = cv.connectedComponents(sure_fg)  #标记最大连通域
    markers = markers+1                             #背景标记为1(此为最大连通域)
    markers[unknown == 255] = 0                     #未知区域标记为0

    Show_Markers()          #显示各区域(连通域/背景、不确定区域、种子/前景)

    # 6、使用分水岭算法,合并不确定区域和种子,边界修改为-1,(分界:连通域背景 -- 未知区域+种子)
    markers = cv.watershed(img, markers)  # 分水岭算法(修改边界为-1)

    Show_Markers()          #显示各区域(连通域/背景、不确定区域、种子/前景)

    # 7、涂色并显示(边界(markers==-1)涂色)
    dst = img.copy()
    dst[markers == -1] = [0, 0, 255]                #边界(-1)涂色
    cv.imshow('dst', dst)


if __name__ == '__main__':
    img = cv.imread('Resource/test19.jpg')
    cv.imshow('img', img)

    ToBinary()          #转二值图
    Watershed()         #分水岭找边界

    cv.waitKey(0)

三、分水岭实战:扑克牌

(这个是自实现,不那么规范)

原图:

步骤归纳

1、锐化

2、开运算去噪

3、获取背景(全连通域)

3、确定前景区域(距离变换)(种子)

4、找未知区域(未知区域 = 确定的背景-确定的前景)

5、根据种子标记最大连通域

6、使用分水岭算法,合并不确定区域和种子,边界修改为-1

7、涂色并显示

1、锐化

如果只是用硬币的代码,会出现这样的效果(只能分出外部的区域):

 这里我们有必要在二值化前进行锐化

一开始使用的是卷积核:

kernel = np.array([
                    [1, 1, 1],
                    [1, -8, 1],
                    [1, 1, 1]
                    ])

发现边缘还是不够明显,继续升级卷积核:

kernel = np.array([
                    [2, 2, 2],
                    [2, -16, 2],
                    [2, 2, 2]
                    ])

 现在可以看到,二值图像边缘已经比较清晰锐利了。

2、开运算去噪

# 1、开运算去噪
    opening = cv.morphologyEx(binary, cv.MORPH_OPEN, (3,3), iterations=2)
    cv.imshow('opening', opening)

3、获取背景(全连通域)

这里的扑克牌只是空架子,就不膨胀处理了,

# 2、确定背景区域(这里只是空架子,就不膨胀了)
    sure_bg = opening.copy()
    cv.imshow('sure_bg', sure_bg)

3、确定前景区域(距离变换)(种子)

# 3、确定前景区域(距离变换)(种子)
    # 3-1、求对象最大宽度/长度(直径)
    dist_transform = cv.distanceTransform(opening, cv.DIST_L2, 3, 5)
    # 3-2、最长直径按比例缩小,确定前景
    ret, sure_fg = cv.threshold(dist_transform, 0.01 * dist_transform.max(), 255, cv.THRESH_BINARY)
    #   前景          阈值函数                    阈值                        最大值 二值化方式
    sure_fg = np.uint8(sure_fg)
    cv.imshow('sure_fg', sure_fg)

4、找未知区域(未知区域 = 确定的背景-确定的前景)

# 4、找未知区域(未知区域 = 确定的背景-确定的前景)
    unknown = cv.subtract(sure_bg, sure_fg)
    cv.imshow('unknown', unknown)

5、根据种子标记最大连通域

# 5、根据种子标记最大连通域(大于1为内部区域,标记1为背景区域,0为未知区域)
    ret, markers = cv.connectedComponents(sure_fg)  #标记最大连通域
    markers = markers+1                             #背景标记为1(此为最大连通域)
    markers[unknown == 255] = 0                     #未知区域标记为0

    Show_Markers()          #显示各区域(连通域/背景、不确定区域、种子/前景)

6、使用分水岭算法,合并不确定区域和种子,边界修改为-1

# 6、使用分水岭算法,合并不确定区域和种子,边界修改为-1(分界:连通域背景 -- 未知区域+种子)
    markers = cv.watershed(img, markers)  # 分水岭算法(修改边界为-1)

    Show_Markers()          #显示各区域(连通域/背景、不确定区域、种子/前景)

7、涂色并显示

# 7、涂色并显示(边界(markers==-1)涂色)
    dst = img.copy()
    dst[markers == -1] = [0, 255, 0]                #边界(-1)涂色
    cv.imshow('dst', dst)

总代码及效果(扑克牌)

# 距离变换与分水岭算法
import numpy as np
import cv2 as cv


# 转二值图像
def ToBinary():
    global gray, binary
    # 1、锐化
    kernel = np.array([
                    [2, 2, 2],
                    [2, -16, 2],
                    [2, 2, 2]
                    ])
    sharp = cv.filter2D(img, -1, kernel)
    cv.imshow('sharp', sharp)
    # 灰度化
    gray = cv.cvtColor(sharp, cv.COLOR_BGR2GRAY)
    cv.imshow('gray', gray)
    # 二值化
    ret, binary = cv.threshold(gray, 0, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU)
    cv.imshow('binary', binary)


#显示各区域(连通域/背景、不确定区域、种子/前景)
def Show_Markers():
    mark = img.copy()
    mark[markers == 1] = (255, 0, 0)    #连通域/背景(蓝)
    mark[markers == 0] = (0, 255, 0)    #不确定区域(绿)
    mark[markers > 1] = (0, 0, 255)     #前景/种子(红)

    mark[markers == -1] = (0, 255, 0)   #边界(绿)
    cv.imshow('Markers', mark)


# 分水岭找边界
def Watershed():
    global markers
    # 1、开运算去噪
    opening = cv.morphologyEx(binary, cv.MORPH_OPEN, (3,3), iterations=2)
    cv.imshow('opening', opening)

    # 2、确定背景区域(这里只是空架子,就不膨胀了)
    sure_bg = opening.copy()
    cv.imshow('sure_bg', sure_bg)

    # 3、确定前景区域(距离变换)(种子)
    # 3-1、求对象最大宽度/长度(直径)
    dist_transform = cv.distanceTransform(opening, cv.DIST_L2, 3, 5)
    # 3-2、最长直径按比例缩小,确定前景
    ret, sure_fg = cv.threshold(dist_transform, 0.01 * dist_transform.max(), 255, cv.THRESH_BINARY)
    #   前景          阈值函数                    阈值                        最大值 二值化方式
    sure_fg = np.uint8(sure_fg)
    cv.imshow('sure_fg', sure_fg)

    # 4、找未知区域(未知区域 = 确定的背景-确定的前景)
    unknown = cv.subtract(sure_bg, sure_fg)
    cv.imshow('unknown', unknown)

    # 5、根据种子标记最大连通域(大于1为内部区域,标记1为背景区域,0为未知区域)
    ret, markers = cv.connectedComponents(sure_fg)  #标记最大连通域
    markers = markers+1                             #背景标记为1(此为最大连通域)
    markers[unknown == 255] = 0                     #未知区域标记为0

    Show_Markers()          #显示各区域(连通域/背景、不确定区域、种子/前景)

    # 6、使用分水岭算法,合并不确定区域和种子,边界修改为-1(分界:连通域背景 -- 未知区域+种子)
    markers = cv.watershed(img, markers)  # 分水岭算法(修改边界为-1)

    Show_Markers()          #显示各区域(连通域/背景、不确定区域、种子/前景)

    # 7、涂色并显示(边界(markers==-1)涂色)
    dst = img.copy()
    dst[markers == -1] = [0, 255, 0]                #边界(-1)涂色
    cv.imshow('dst', dst)


if __name__ == '__main__':
    img = cv.imread('Resource/test20.jpg')
    cv.imshow('img', img)

    ToBinary()          #转二值图
    Watershed()         #分水岭找边界

    cv.waitKey(0)

四、分水岭实战:路面检测

先锐化再进行处理,会适用于很多种情况。

这里和扑克牌的类似,都是边界不够明显,需要锐化处理的前面的扑克牌只有一个框架,没法膨胀,路面可以膨胀处理获取背景

原图:

1、 锐化

2、锐化之后再二值化

3、膨胀获取背景

和上面扑克牌不同的就是这里,这里的路面不是空架子,可以进行膨胀。

4、距离变换获取前景

 5、获取不确定区域(背景-前景)

 

 6、显示标记情况

7、成功检测出路面的分水岭

总代码

# 距离变换与分水岭算法(路面检测)
import numpy as np
import cv2 as cv


# 转二值图像
def ToBinary():
    global gray, binary
    # 1、锐化
    kernel = np.array([
                    [2, 2, 2],
                    [2, -16, 2],
                    [2, 2, 2]
                    ])
    sharp = cv.filter2D(img, -1, kernel)
    cv.imshow('sharp', sharp)
    # 灰度化
    gray = cv.cvtColor(sharp, cv.COLOR_BGR2GRAY)
    cv.imshow('gray', gray)
    # 二值化
    ret, binary = cv.threshold(gray, 0, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU)
    cv.imshow('binary', binary)


#显示各区域(连通域/背景、不确定区域、种子/前景)
def Show_Markers():
    mark = img.copy()
    mark[markers == 1] = (255, 0, 0)    #连通域/背景(蓝)
    mark[markers == 0] = (0, 255, 0)    #不确定区域(绿)
    mark[markers > 1] = (0, 0, 255)     #前景/种子(红)

    mark[markers == -1] = (0, 255, 0)   #边界(绿)
    cv.imshow('Markers', mark)


# 分水岭找边界
def Watershed():
    global markers
    # 1、开运算去噪
    opening = cv.morphologyEx(binary, cv.MORPH_OPEN, (3,3), iterations=3)
    cv.imshow('opening', opening)

    # 2、确定背景区域(膨胀)
    sure_bg = cv.dilate(opening, (3,3), iterations=2)
    cv.imshow('sure_bg', sure_bg)

    # 3、确定前景区域(距离变换)(种子)
    # 3-1、求对象最大宽度/长度(直径)
    dist_transform = cv.distanceTransform(opening, cv.DIST_L2, 3, 5)
    # 3-2、最长直径按比例缩小,确定前景
    ret, sure_fg = cv.threshold(dist_transform, 0.01 * dist_transform.max(), 255, cv.THRESH_BINARY)
    #   前景          阈值函数                    阈值                        最大值 二值化方式
    sure_fg = np.uint8(sure_fg)
    cv.imshow('sure_fg', sure_fg)

    # 4、找未知区域(未知区域 = 确定的背景-确定的前景)
    unknown = cv.subtract(sure_bg, sure_fg)
    cv.imshow('unknown', unknown)

    # 5、根据种子标记最大连通域(大于1为内部区域,标记1为背景区域,0为未知区域)
    ret, markers = cv.connectedComponents(sure_fg)  #标记最大连通域
    markers = markers+1                             #背景标记为1(此为最大连通域)
    markers[unknown == 255] = 0                     #未知区域标记为0

    Show_Markers()          #显示各区域(连通域/背景、不确定区域、种子/前景)

    # 6、使用分水岭算法,合并不确定区域和种子,边界修改为-1(分界:连通域背景 -- 未知区域+种子)
    markers = cv.watershed(img, markers)  # 分水岭算法(修改边界为-1)

    Show_Markers()          #显示各区域(连通域/背景、不确定区域、种子/前景)

    # 7、涂色并显示(边界(markers==-1)涂色)
    dst = img.copy()
    dst[markers == -1] = [0, 255, 0]                #边界(-1)涂色
    cv.imshow('dst', dst)


if __name__ == '__main__':
    img = cv.imread('Resource/road.jpg')
    cv.imshow('img', img)

    ToBinary()          #转二值图
    Watershed()         #分水岭找边界

    cv.waitKey(0)

参考资料

 http://woshicver.com/FifthSection/4_15_%E5%9B%BE%E5%83%8F%E5%88%86%E5%89%B2%E4%B8%8E%E5%88%86%E6%B0%B4%E5%B2%AD%E7%AE%97%E6%B3%95/

  • 27
    点赞
  • 107
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
要下载OpenCV学习中的运动目标(前景)检测源码,可以按照以下步骤进行。 首先,访问OpenCV的官方网站(https://opencv.org/)或GitHub的OpenCV仓库(https://github.com/opencv/opencv),找到源代码的下载选项。 在官方网站上,可以选择下载最新版本的OpenCV,或者根据特定版本的需求进行选择。在GitHub上,可以浏览仓库的不同分支和版本标签,并选择下载相应的源代码。 一旦选择了合适的源码下载选项,点击下载按钮进行下载。下载完成后,将源代码文件解压缩至本地目录。 接下来,在下载的源代码文件夹中,找到与运动目标检测相关的示例代码或项目。这些示例代码通常位于“samples”或“examples”文件夹中,可以根据名称或说明找到与运动目标检测相关的示例。 打开示例代码文件,使用合适的集成开发环境(IDE)或文本编辑器加载源代码。确保已正确配置编译环境和OpenCV库文件。 阅读示例代码的注释和文档,理解实现运动目标检测的算法和方法。 对于初学者,建议阅读和运行示例代码,以更好地理解和学习运动目标检测的概念和实践。根据需要,可以根据示例代码进行修改和调整,以满足特定的需求。 总之,要下载OpenCV学习中的运动目标(前景)检测源码,首先选择合适的源代码下载选项,然后解压缩源代码文件夹,找到与运动目标检测相关的示例代码或项目,最后阅读和运行示例代码以学习和实践运动目标检测

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_(*^▽^*)_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值