[Python从零到壹] 六十二.图像识别及经典案例篇之基于均值漂移算法和分水岭算法的图像分割

Posted Eastmount

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Python从零到壹] 六十二.图像识别及经典案例篇之基于均值漂移算法和分水岭算法的图像分割相关的知识,希望对你有一定的参考价值。

祝大家新年快乐,阖家幸福,健康快乐!

欢迎大家来到“Python从零到壹”,在这里我将分享约200篇Python系列文章,带大家一起去学习和玩耍,看看Python这个有趣的世界。所有文章都将结合案例、代码和作者的经验讲解,真心想把自己近十年的编程经验分享给大家,希望对您有所帮助,文章中不足之处也请海涵。Python系列整体框架包括基础语法10篇、网络爬虫30篇、可视化分析10篇、机器学习20篇、大数据分析20篇、图像识别30篇、人工智能40篇、Python安全20篇、其他技巧10篇。您的关注、点赞和转发就是对秀璋最大的支持,知识无价人有情,希望我们都能在人生路上开心快乐、共同成长。

该系列文章主要讲解Python OpenCV图像处理和图像识别知识,前期主要讲解图像处理基础知识、OpenCV基础用法、常用图像绘制方法、图像几何变换等,中期讲解图像处理的各种运算,包括图像点运算、形态学处理、图像锐化、图像增强、图像平滑等,后期研究图像识别、图像分割、图像分类、图像特效处理以及图像处理相关应用。

第一部分作者介绍了图像处理基础知识,第二部分介绍了图像运算和图像增强,接下来第三部分我们将详细讲解图像识别及图像处理经典案例,该部分属于高阶图像处理知识,能进一步加深我们的理解和实践能力。图像分割是将图像分成若干具有独特性质的区域并提取感兴趣目标的技术和过程,它是图像处理和图像分析的关键步骤。主要分为基于阈值的分割方法、基于区域的分割方法、基于边缘的分割方法和基于特定理论的分割方法。上一篇文章介绍基于纹理背景的图像分割和基于聚类算法的图像分割,这篇文章将详细讲解基于均值漂移算法和分水岭算法的图像分割。希望文章对您有所帮助,如果有不足之处,还请海涵。

文章目录

下载地址:记得点赞喔 O(∩_∩)O

前文赏析:(尽管该部分占大量篇幅,但我舍不得删除,哈哈!)

第一部分 基础语法

第二部分 网络爬虫

第三部分 数据分析和机器学习

第四部分 Python图像处理基础

第五部分 Python图像运算和图像增强

第六部分 Python图像识别和图像高阶案例

第七部分 NLP与文本挖掘

第八部分 人工智能入门知识

第九部分 网络攻防与AI安全

第十部分 知识图谱构建实战

扩展部分 人工智能高级案例

作者新开的“娜璋AI安全之家”将专注于Python和安全技术,主要分享Web渗透、系统安全、人工智能、大数据分析、图像识别、恶意代码检测、CVE复现、威胁情报分析等文章。虽然作者是一名技术小白,但会保证每一篇文章都会很用心地撰写,希望这些基础性文章对你有所帮助,在Python和安全路上与大家一起进步。


一.基于均值漂移算法的图像分割

均值漂移(Mean Shfit)算法是一种通用的聚类算法,最早是1975年Fukunaga等人在一篇关于概率密度梯度函数的估计论文中提出[1]。它是一种无参估计算法,沿着概率梯度的上升方向寻找分布的峰值。Mean Shift算法先算出当前点的偏移均值,移动该点到其偏移均值,然后以此为新的起始点,继续移动,直到满足一定的条件结束。

图像分割中可以利用均值漂移算法的特性,实现彩色图像分割。在OpenCV中提供的函数为pyrMeanShiftFiltering(),该函数严格来说并不是图像分割,而是图像在色彩层面的平滑滤波,它可以中和色彩分布相近的颜色,平滑色彩细节,侵蚀掉面积较小的颜色区域,所以在OpenCV中它的后缀是滤波“Filter”,而不是分割“segment”。该函数原型如下所示:

  • dst = pyrMeanShiftFiltering(src, sp, sr[, dst[, maxLevel[, termcrit]]])
    – src表示输入图像,8位三通道的彩色图像
    – dst表示输出图像,需同输入图像具有相同的大小和类型
    – sp表示定义漂移物理空间半径的大小
    – sr表示定义漂移色彩空间半径的大小
    – maxLevel表示定义金字塔的最大层数
    – termcrit表示定义的漂移迭代终止条件,可以设置为迭代次数满足终止,迭代目标与中心点偏差满足终止,或者两者的结合

均值漂移pyrMeanShiftFiltering()函数的执行过程是如下:

  • 构建迭代空间。以输入图像上任一点P0为圆心,建立以sp为物理空间半径,sr为色彩空间半径的球形空间,物理空间上坐标为x和y,色彩空间上坐标为RGB或HSV,构成一个空间球体。其中x和y表示图像的长和宽,色彩空间R、G、B在0至255之间。
  • 求迭代空间的向量并移动迭代空间球体重新计算向量,直至收敛。在上一步构建的球形空间中,求出所有点相对于中心点的色彩向量之和,移动迭代空间的中心点到该向量的终点,并再次计算该球形空间中所有点的向量之和,如此迭代,直到在最后一个空间球体中所求得向量和的终点就是该空间球体的中心点Pn,迭代结束。
  • 更新输出图像dst上对应的初始原点P0的色彩值为本轮迭代的终点Pn的色彩值,完成一个点的色彩均值漂移。
  • 对输入图像src上其他点,依次执行上述三个步骤,直至遍历完所有点后,整个均值偏移色彩滤波完成。

下面的代码是图像均值漂移的实现过程:

# -*- coding: utf-8 -*-
# By: Eastmount
import cv2
import numpy as np
import matplotlib.pyplot as plt

#读取原始图像灰度颜色
img = cv2.imread('scenery.png') 

spatialRad = 50 #空间窗口大小
colorRad = 50 #色彩窗口大小
maxPyrLevel = 2 #金字塔层数

#图像均值漂移分割
dst = cv2.pyrMeanShiftFiltering( img, spatialRad, colorRad, maxPyrLevel)

#显示图像
cv2.imshow('src', img)
cv2.imshow('dst', dst)
cv2.waitKey()
cv2.destroyAllWindows()

当漂移物理空间半径设置为50,漂移色彩空间半径设置为50,金字塔层数 为2,输出的效果图如图1所示。

当漂移物理空间半径设置为20,漂移色彩空间半径设置为20,金字塔层数 为2,输出的效果图如图2所示。对比可以发现,半径为20时,图像色彩细节大部分存在,半径为50时,森林和水面的色彩细节基本都已经丢失。

写到这里,均值偏移算法对彩色图像的分割平滑操作就完成了,为了达到更好地分割目的,借助漫水填充函数进行下一步处理,在下一篇文章将详细介绍,这里只是引入该函数。完整代码如下所示:

# -*- coding: utf-8 -*-
# By: Eastmount
import cv2
import numpy as np
import matplotlib.pyplot as plt

#读取原始图像灰度颜色
img = cv2.imread('scenery.png') 

#获取图像行和列
rows, cols = img.shape[:2]

#mask必须行和列都加2且必须为uint8单通道阵列
mask = np.zeros([rows+2, cols+2], np.uint8) 

spatialRad = 100 #空间窗口大小
colorRad = 100   #色彩窗口大小
maxPyrLevel = 2  #金字塔层数

#图像均值漂移分割
dst = cv2.pyrMeanShiftFiltering( img, spatialRad, colorRad, maxPyrLevel)

#图像漫水填充处理
cv2.floodFill(dst, mask, (30, 30), (0, 255, 255),
              (100, 100, 100), (50, 50, 50),
              cv2.FLOODFILL_FIXED_RANGE)

#显示图像
cv2.imshow('src', img)
cv2.imshow('dst', dst)
cv2.waitKey()
cv2.destroyAllWindows()

输出的效果图如图3所示,它将天空染成黄色。


二.基于分水岭算法的图像分割

图像分水岭算法(Watershed Algorithm)是将图像的边缘轮廓转换为“山脉”,将均匀区域转换为“山谷”,从而提升分割效果的算法[3]。分水岭算法是基于拓扑理论的数学形态学的分割方法,灰度图像根据灰度值把像素之间的关系看成山峰和山谷的关系,高亮度(灰度值高)的地方是山峰,低亮度(灰度值低)的地方是山谷。接着给每个孤立的山谷(局部最小值)不同颜色的水(Label),当水涨起来,根据周围的山峰(梯度),不同的山谷也就是不同颜色的像素点开始合并,为了避免这个现象,可以在水要合并的地方建立障碍,直到所有山峰都被淹没。所创建的障碍就是分割结果,这个就是分水岭的原理[3]。

分水岭算法的计算过程是一个迭代标注过程,主要包括排序和淹没两个步骤。由于图像会存在噪声或缺失等问题,该方法会造成分割过度。OpenCV提供了watershed()函数实现图像分水岭算法,并且能够指定需要合并的点,其函数原型如下所示:

  • markers = watershed(image, markers)
    – image表示输入图像,需为8位三通道的彩色图像
    – markers表示用于存储函数调用之后的运算结果,输入/输出32位单通道图像的标记结构,输出结果需和输入图像的尺寸和类型一致。

下面是分水岭算法实现图像分割的过程。假设存在一幅彩色硬币图像,如图4所示,硬币相互之间挨着。

第一步,通过图像灰度化和阈值化处理提取图像灰度轮廓,采用OTSU二值化处理获取硬币的轮廓。

# -*- coding: utf-8 -*-
# By: Eastmount
import numpy as np
import cv2
from matplotlib import pyplot as plt

#读取原始图像
img = cv2.imread('coin.jpg')

#图像灰度化处理
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

#图像阈值化处理
ret, thresh = cv2.threshold(gray, 0, 255, 
cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)

#显示图像
cv2.imshow('src', img)
cv2.imshow('res', thresh)
cv2.waitKey()
cv2.destroyAllWindows()

输出结果如图5所示。

第二步,通过形态学开运算过滤掉小的白色噪声。同时,由于图像中的硬币是紧挨着的,所以不能采用图像腐蚀去掉边缘的像素,而是选择距离转换,配合一个适当的阈值进行物体提取。这里引入一个图像膨胀操作,将目标边缘扩展到背景,以确定结果的背景区域。

# -*- coding: utf-8 -*-
# By: Eastmount
import numpy as np
import cv2
from matplotlib import pyplot as plt

#读取原始图像
img = cv2.imread('coin.jpg')

#图像灰度化处理
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

#图像阈值化处理
ret, thresh = cv2.threshold(gray, 0, 255,
                            cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)

#图像开运算消除噪声
kernel = np.ones((3,3),np.uint8)
opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel, iterations = 2)

#图像膨胀操作确定背景区域
sure_bg = cv2.dilate(opening,kernel,iterations=3)

#距离运算确定前景区域
dist_transform = cv2.distanceTransform(opening,cv2.DIST_L2,5)
ret, sure_fg = cv2.threshold(dist_transform, 0.7*dist_transform.max(), 255, 0)

#寻找未知区域
sure_fg = np.uint8(sure_fg)
unknown = cv2.subtract(sure_bg, sure_fg)

#用来正常显示中文标签
plt.rcParams['font.sans-serif']=['SimHei']

#显示图像
titles = ['原始图像', '阈值化', '开运算',
          '背景区域', '前景区域', '未知区域']  
images = [img, thresh, opening, sure_bg, sure_fg, unknown]  
for i in range(6):  
   plt.subplot(2,3,i+1), plt.imshow(images[i], 'gray')  
   plt.title(titles[i])  
   plt.xticks([]),plt.yticks([])  
plt.show()

输出结果如图6所示,包括原始图像、阈值化处理、开运算、背景区域、前景区域、未知区域等。由图可知,在使用阈值过滤的图像里,确认了图像的硬币区域,而在有些情况,可能对前景分割更感兴趣,而不关心目标是否需要分开或挨着,那时可以采用腐蚀操作来求解前景区域。

第三步,当前处理结果中,已经能够区分出前景硬币区域和背景区域。接着我们创建标记变量,在该变量中标记区域,已确认的区域(前景或背景)用不同的正整数标记出来,不确认的区域保持0,使用cv2.connectedComponents()函数来将图像背景标记成0,其他目标用从1开始的整数标记。注意,如果背景被标记成0,分水岭算法会认为它是未知区域,所以要用不同的整数来标记。

最后,调用watershed()函数实现分水岭图像分割,标记图像会被修改,边界区域会被标记成0,完整代码如下所示。

# -*- coding: utf-8 -*-
# By: Eastmount
import numpy as np
import cv2
from matplotlib import pyplot as plt

#读取原始图像
img = cv2.imread('coin.jpg')

#图像灰度化处理
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

#图像阈值化处理
ret, thresh = cv2.threshold(gray, 0, 255,
                            cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)

#图像开运算消除噪声
kernel = np.ones((3,3),np.uint8)
opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel, iterations = 2)

#图像膨胀操作确定背景区域
sure_bg = cv2.dilate(opening,kernel,iterations=3)

#距离运算确定前景区域
dist_transform = cv2.distanceTransform(opening,cv2.DIST_L2,5)
ret, sure_fg = cv2.threshold(dist_transform, 0.7*dist_transform.max(), 255, 0)

#寻找未知区域
sure_fg = np.uint8(sure_fg)
unknown = cv2.subtract(sure_bg, sure_fg)

#标记变量
ret, markers = cv2.connectedComponents(sure_fg)

#所有标签加一,以确保背景不是0而是1
markers = markers+1

#用0标记未知区域
markers[unknown==255]=0

#分水岭算法实现图像分割
markers = cv2.watershed(img, markers)
img[markers == -1] = [255,0,0]

#用来正常显示中文标签
plt.rcParams['font.sans-serif']=['SimHei']

#显示图像
titles = ['标记区域', '图像分割']  
images = [markers, img]  
for i in range(2):  
   plt.subplot(1,2,i+1), plt.imshow(images[i], 'gray')  
   plt.title(titles[i])  
   plt.xticks([]),plt.yticks([])  
plt.show()

最终分水岭算法的图像分割如图7所示,它将硬币的轮廓成功提取。

图8是采用分水岭算法提取图像Windows中心轮廓的效果图。

分水岭算法对微弱边缘具有良好的响应,图像中的噪声、物体表面细微的灰度变化,都会产生过度分割的现象。但同时应当看出,分水岭算法对微弱边缘具有良好的响应,是得到封闭连续边缘的保证。另外,分水岭算法所得到的封闭的集水盆,为分析图像的区域特征提供了可能。


三.总结

本文主要讲解了图像分割方法,包括基于均值漂移算法的图像分割方法、基于分水岭算法的图像分割方法,通过这些处理能有效分割图像的背景和前景,识别某些图像的区域。

感谢在求学路上的同行者,不负遇见,勿忘初心。图像处理系列主要包括三部分,分别是:

2022年即将离去,感谢大家的鼓励和陪伴。还是写一篇年终总结吧,也是自己写下的第10篇年终总结。曾记否,2013年感叹《一万年太久,只争朝夕》;2014年本科毕业写下《忆大学四年得与失》;2015年选择回贵州工作,《再见北理工》依依不舍;2016年成为一名菜鸟教师,《教书路开启,爱情路初尝》;2017年又借调到省发改委学习忙碌一年,留下《人生百味,有你真好》;2018年数不清加班,尝不尽的酸甜,《向死而生,为爱而活,忆编程戎马岁月》;2019年奔波考博重返校园,两人通过书信寄托着情感,《把能努力的都努力好,最终等待命运垂青》;2020年小珞珞降生,《敏儿多思,宁静致远》,希望他永远记住妈妈的辛苦;2021年,已为人父不再年轻,写下《缘起性空,归来不少年》。那今年,我又写什么呢?

就​简单写几句和回顾下吧,叫《为梦想擦去蒙尘,愿大家平安健康快乐》。这一年仿佛什么都没做,又好像做了些啥,忙忙碌碌,浑浑噩噩的过了。好在,脚踏实地;好在,亲人支持。是啊,平平淡淡的生活,才是最真最美的生活,家人和亲情才是最重要的。看着小珞珞纯真的笑容,真好!新的一年里,希望所有朋友和博友都健健康康,平平安安,开开心心。祝好!



(By:Eastmount 2023-01-10 夜于贵阳 http://blog.csdn.net/eastmount/ )


参考文献:

  • [1]冈萨雷斯著. 数字图像处理(第3版)[M]. 北京:电子工业出版社,2013.
  • [2]阮秋琦. 数字图像处理学(第3版)[M]. 北京:电子工业出版社,2008.
  • [3]毛星云,冷雪飞. OpenCV3编程入门[M]. 北京:电子工业出版社,2015.
  • [4]张铮,王艳平,薛桂香等. 数字图像处理与机器视觉——Visual C++与Matlab实现[M]. 北京:人民邮电出版社,2014.
  • [5]Jumping boy. python+OpenCV图像处理(十一)图像轮廓检测[EB/OL]. (2018-08-30). https://blog.csdn.net/qq_40962368/article/details/82078732.
  • [6]Eastmount. [Python图像处理] 四十.全网首发Python图像分割万字详解(阈值分割、边缘分割、纹理分割、分水岭算法、K-Means分割、漫水填充分割、区域定位)[EB/OL]. (2021-05-18). https://blog.csdn.net/Eastmount/article/details/116952580.
  • [7]杨秀璋, 颜娜. Python网络数据爬取及分析从入门到精通(分析篇)[M]. 北京:北京航天航空大学出版社, 2018.
  • [8]Fukunaga K. and Hostetler L.D. The Estimation of the Gradient of a Density Function, with Applications in Pattern Recognition[J]. IEEE Transactions on Information Theory, 1975, 21, 32-10.

以上是关于[Python从零到壹] 六十二.图像识别及经典案例篇之基于均值漂移算法和分水岭算法的图像分割的主要内容,如果未能解决你的问题,请参考以下文章

[Python从零到壹] 六十五.图像识别及经典案例篇之图像霍夫变换详解

[Python从零到壹] 六十.图像识别及经典案例篇之基于阈值及边缘检测的图像分割

[Python从零到壹] 六十三.图像识别及经典案例篇之图像漫水填充分割应用

[Python从零到壹] 六十三.图像识别及经典案例篇之图像漫水填充分割应用

[Python从零到壹] 六十一.图像识别及经典案例篇之基于纹理背景和聚类算法的图像分割

[Python从零到壹] 六十一.图像识别及经典案例篇之基于纹理背景和聚类算法的图像分割