例子源于OpenCV官网–基于距离变换和分水岭算法的图像分割
(https://docs.opencv.org/4.x/d2/dbd/tutorial_distance_transform.html)
使用OpenCV函数cv::filter2D来执行一些拉普拉斯滤波来进行图像锐化
使用OpenCV函数cv::distanceTransform来获得二值图像的派生(derived)表示,其中每个像素的值被其到最近的背景像素的距离所替换
使用OpenCV函数 cv::watershed将图像中的物体从背景中分离出来

代码:

#基于距离变换和分水岭算法的图像分割
from __future__ import print_function
import cv2 as cv
import numpy as np
import argparse
import random as rng
rng.seed(12345)
#加载源图像,并检查它是否加载没有任何问题,然后显示它
parser = argparse.ArgumentParser(description='Code for Image Segmentation with Distance Transform and Watershed Algorithm.\Sample code showing how to segment overlapping objects using Laplacian filtering, \in addition to Watershed and Distance Transformation')
parser.add_argument('--input', help='Path to input image.', default='cards.png')
args = parser.parse_args()src = cv.imread(cv.samples.findFile(args.input))
if src is None:print('Could not open or find the image:', args.input)exit(0)# Show source image
cv.imshow('Source Image', src)"""
然后,如果我们有一个白色背景的图像,
最好将其转换为黑色。当我们应用距离变换时,
这将帮助我们更容易地区分前景对象:
"""
src[np.all(src == 255, axis=2)] = 0
# Show output image
cv.imshow('Black Background Image', src)"""
然后,我们将锐化我们的图像,(锐化处理的主要目的是突出灰度的过度部分。)
以锐化前景对象的边缘。
我们将应用一个拉普拉斯滤波器和一个相当强的滤波器(二阶导数的近似):
"""
kernel = np.array([[1, 1, 1], [1, -8, 1], [1, 1, 1]], dtype=np.float32)
#kernel 是一个3x3的边缘特征提取器,可以提取各个方向上的边缘
# do the laplacian filtering as it is
# well, we need to convert everything in something more deeper then CV_8U
# because the kernel has some negative values,
# and we can expect in general to have a Laplacian image with negative values
# BUT a 8bits unsigned int (the one we are working with) can contain values from 0 to 255
# so the possible negative number will be truncated
imgLaplacian = cv.filter2D(src, cv.CV_32F, kernel)#执行一些拉普拉斯滤波来进行图像锐化
sharp = np.float32(src)
imgResult = sharp - imgLaplacian
"""
imgResult = sharp - imgLaplacian
由于拉普拉斯是一种微分算子,如果所使用的定义具有负的中心系数,
那么必须将原图像减去经拉普拉斯变换后的图像,从而得到锐化结果。
"""
# convert back to 8bits gray scale 转换回8位灰度
imgResult = np.clip(imgResult, 0, 255)
"""
np.clip是一个截取函数,
用于截取数组中小于或者大于某值的部分,
并使得被截取部分等于固定值。
此处规定的最小值为0,最大值为255
"""
imgResult = imgResult.astype('uint8')#作用:就是转换numpy数组的数据类型
imgLaplacian = np.clip(imgLaplacian, 0, 255)
imgLaplacian = np.uint8(imgLaplacian)
#cv.imshow('Laplace Filtered Image', imgLaplacian)
cv.imshow('New Sharped Image', imgResult)#现在我们将新的锐化源图像分别转换为灰度和二值图像:
bw = cv.cvtColor(imgResult, cv.COLOR_BGR2GRAY)
_, bw = cv.threshold(bw, 40, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)
cv.imshow('Binary Image', bw)"""
1.现在我们准备对二值图像应用距离变换。
2.此外,我们对输出图像进行了归一化,以便能够对结果进行可视化和阈值处理:
"""
dist = cv.distanceTransform(bw, cv.DIST_L2, 3)
"""
1:Opencv中distanceTransform方法用于计算图像中每一个非零点
距离离自己最近的零点的距离,
distanceTransform的第二个Mat矩阵参数dst保存了每一个点与最近的零点的距离信息,
图像上越亮的点,代表了离零点的距离越远。
"""
#2. Normalize the distance image for range = {0.0, 1.0}
# so we can visualize and threshold it
#2.对range ={0.0, 1.0}的距离图像进行归一化
#,这样我们就可以可视化并设定阈值
cv.normalize(dist, dist, 0, 1.0, cv.NORM_MINMAX)
cv.imshow('Distance Transform Image', dist)"""
我们对dist图像进行阈值,然后进行一些形态学操作(即膨胀),
以便从上述图像中提取峰值:
"""
_, dist = cv.threshold(dist, 0.4, 1.0, cv.THRESH_BINARY)#阈值函数
# Dilate a bit the dist image 膨胀
kernel1 = np.ones((3,3), dtype=np.uint8)
dist = cv.dilate(dist, kernel1)#膨胀函数
cv.imshow('Peaks', dist)"""
从每个blob中,
然后我们在cv::findContours函数(找轮廓)的帮助下
为分水岭算法创建一个种子/标记:
"""
dist_8u = dist.astype('uint8')#转换数组的数据类型
# Find total markers  发现总标记
contours, _ = cv.findContours(dist_8u, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
# Create the marker image for the watershed algorithm
#为分水岭算法创建标记图像
markers = np.zeros(dist.shape, dtype=np.int32)
# Draw the foreground markers  绘制前景标记
for i in range(len(contours)):cv.drawContours(markers, contours, i, (i+1), -1)
# Draw the background marker 背景标记
cv.circle(markers, (5,5), 3, (255,255,255), -1)
markers_8u = (markers * 10).astype('uint8')
cv.imshow('Markers', markers_8u)"""
最后,我们可以应用分水岭算法,并将结果可视化:
(分水岭算法是一种图像区域分割算法,
它把位置接近,灰度值也接近的像素点
连接起来形成一个封闭的区域。)
"""
cv.watershed(imgResult, markers)
#mark = np.zeros(markers.shape, dtype=np.uint8)
mark = markers.astype('uint8')#转换数组的数据类型
mark = cv.bitwise_not(mark)
"""
bitwise_not是对二进制数据进行“非”操作,
即对图像(灰度图像或彩色图像均可)每个像素值进行二进制“非”操作,
~1=0,~0=1
"""
# uncomment this if you want to see how the mark
# image looks like at that point
#cv.imshow('Markers_v2', mark)
# Generate random colors
colors = []
for contour in contours:colors.append((rng.randint(0,256), rng.randint(0,256), rng.randint(0,256)))
# Create the result image
dst = np.zeros((markers.shape[0], markers.shape[1], 3), dtype=np.uint8)
# Fill labeled objects with random colors
#用随机的颜色填充已标记的物体
for i in range(markers.shape[0]):for j in range(markers.shape[1]):index = markers[i,j]if index > 0 and index <= len(contours):dst[i,j,:] = colors[index-1]
# Visualize the final image 可视化最终的图像
cv.imshow('Final Result', dst)
cv.waitKey()

运行结果:

1. 原图:

#加载源图像,并检查它是否加载没有任何问题,然后显示它
parser = argparse.ArgumentParser(description='Code for Image Segmentation with Distance Transform and Watershed Algorithm.\Sample code showing how to segment overlapping objects using Laplacian filtering, \in addition to Watershed and Distance Transformation')
parser.add_argument('--input', help='Path to input image.', default='cards.png')
args = parser.parse_args()src = cv.imread(cv.samples.findFile(args.input))
if src is None:print('Could not open or find the image:', args.input)exit(0)# Show source image
cv.imshow('Source Image', src)

2.将原图转换为黑色背景:

"""
然后,如果我们有一个白色背景的图像,
最好将其转换为黑色。当我们应用距离变换时,
这将帮助我们更容易地区分前景对象:
"""
src[np.all(src == 255, axis=2)] = 0
# Show output image
cv.imshow('Black Background Image', src)

3.锐化后的图像:

"""
然后,我们将锐化我们的图像,(锐化处理的主要目的是突出灰度的过度部分。)
以锐化前景对象的边缘。
我们将应用一个拉普拉斯滤波器和一个相当强的滤波器(二阶导数的近似):
"""
kernel = np.array([[1, 1, 1], [1, -8, 1], [1, 1, 1]], dtype=np.float32)
#kernel 是一个3x3的边缘特征提取器,可以提取各个方向上的边缘
# do the laplacian filtering as it is
# well, we need to convert everything in something more deeper then CV_8U
# because the kernel has some negative values,
# and we can expect in general to have a Laplacian image with negative values
# BUT a 8bits unsigned int (the one we are working with) can contain values from 0 to 255
# so the possible negative number will be truncated
imgLaplacian = cv.filter2D(src, cv.CV_32F, kernel)#执行一些拉普拉斯滤波来进行图像锐化
sharp = np.float32(src)
imgResult = sharp - imgLaplacian
"""
imgResult = sharp - imgLaplacian
由于拉普拉斯是一种微分算子,如果所使用的定义具有负的中心系数,
那么必须将原图像减去经拉普拉斯变换后的图像,从而得到锐化结果。
"""
# convert back to 8bits gray scale 转换回8位灰度
imgResult = np.clip(imgResult, 0, 255)
"""
np.clip是一个截取函数,
用于截取数组中小于或者大于某值的部分,
并使得被截取部分等于固定值。
此处规定的最小值为0,最大值为255
"""
imgResult = imgResult.astype('uint8')#作用:就是转换numpy数组的数据类型
imgLaplacian = np.clip(imgLaplacian, 0, 255)
imgLaplacian = np.uint8(imgLaplacian)
#cv.imshow('Laplace Filtered Image', imgLaplacian)
cv.imshow('New Sharped Image', imgResult)

4.二值图像:

#现在我们将新的锐化源图像分别转换为灰度和二值图像:
bw = cv.cvtColor(imgResult, cv.COLOR_BGR2GRAY)
_, bw = cv.threshold(bw, 40, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)
cv.imshow('Binary Image', bw)

5.做距离变换 且 归一化的图像:

"""
1.现在我们准备对二值图像应用距离变换。
2.此外,我们对输出图像进行了归一化,以便能够对结果进行可视化和阈值处理:
"""
dist = cv.distanceTransform(bw, cv.DIST_L2, 3)
"""
1:Opencv中distanceTransform方法用于计算图像中每一个非零点
距离离自己最近的零点的距离,
distanceTransform的第二个Mat矩阵参数dst保存了每一个点与最近的零点的距离信息,
图像上越亮的点,代表了离零点的距离越远。
"""
#2. Normalize the distance image for range = {0.0, 1.0}
# so we can visualize and threshold it
#2.对range ={0.0, 1.0}的距离图像进行归一化
#,这样我们就可以可视化并设定阈值
cv.normalize(dist, dist, 0, 1.0, cv.NORM_MINMAX)
cv.imshow('Distance Transform Image', dist)

6.膨胀图像:

"""
我们对dist图像进行阈值,然后进行一些形态学操作(即膨胀),
以便从上述图像中提取峰值:
"""
_, dist = cv.threshold(dist, 0.4, 1.0, cv.THRESH_BINARY)#阈值函数
# Dilate a bit the dist image 膨胀
kernel1 = np.ones((3,3), dtype=np.uint8)
dist = cv.dilate(dist, kernel1)#膨胀函数
cv.imshow('Peaks', dist)

7.标记图像:

"""
从每个blob中,
然后我们在cv::findContours函数(找轮廓)的帮助下
为分水岭算法创建一个种子/标记:
"""
dist_8u = dist.astype('uint8')#转换数组的数据类型
# Find total markers  发现总标记
contours, _ = cv.findContours(dist_8u, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
# Create the marker image for the watershed algorithm
#为分水岭算法创建标记图像
markers = np.zeros(dist.shape, dtype=np.int32)
# Draw the foreground markers  绘制前景标记
for i in range(len(contours)):cv.drawContours(markers, contours, i, (i+1), -1)
# Draw the background marker 背景标记
cv.circle(markers, (5,5), 3, (255,255,255), -1)
markers_8u = (markers * 10).astype('uint8')
cv.imshow('Markers', markers_8u)

8.最终可视化图像:

"""
最后,我们可以应用分水岭算法,并将结果可视化:
(分水岭算法是一种图像区域分割算法,
它把位置接近,灰度值也接近的像素点
连接起来形成一个封闭的区域。)
"""
cv.watershed(imgResult, markers)
#mark = np.zeros(markers.shape, dtype=np.uint8)
mark = markers.astype('uint8')#转换数组的数据类型
mark = cv.bitwise_not(mark)
"""
bitwise_not是对二进制数据进行“非”操作,
即对图像(灰度图像或彩色图像均可)每个像素值进行二进制“非”操作,
~1=0,~0=1
"""
# uncomment this if you want to see how the mark
# image looks like at that point
#cv.imshow('Markers_v2', mark)
# Generate random colors
colors = []
for contour in contours:colors.append((rng.randint(0,256), rng.randint(0,256), rng.randint(0,256)))
# Create the result image
dst = np.zeros((markers.shape[0], markers.shape[1], 3), dtype=np.uint8)
# Fill labeled objects with random colors
#用随机的颜色填充已标记的物体
for i in range(markers.shape[0]):for j in range(markers.shape[1]):index = markers[i,j]if index > 0 and index <= len(contours):dst[i,j,:] = colors[index-1]
# Visualize the final image 可视化最终的图像
cv.imshow('Final Result', dst)
cv.waitKey()

【OpenCV入门学习--python】Image Segmentation with Distance Transform and Watershed Algorithm图像分割相关推荐

  1. 【OpenCV入门学习--python】Anisotropic image segmentation by a gradient structure tensor

    例子源于OpenCV官网–基于梯度结构张量的各向异性图像分割 (https://docs.opencv.org/4.x/d4/d70/tutorial_anisotropic_image_segmen ...

  2. 【OpenCV入门学习--python】索贝尔算子Sobel operator提取边缘

    例子源于OpenCV官网手册(https://docs.opencv.org/4.x/d2/d2c/tutorial_sobel_derivatives.html) 使用OpenCV函数Sobel() ...

  3. 【OpenCV入门学习--python】图像的矩Image Moments

    例子源于OpenCV官网–图像的矩 (https://docs.opencv.org/4.x/d0/d49/tutorial_moments.html) 使用OpenCV函数cv::moments: ...

  4. 【计算机视觉】opencv入门学习笔记Part.1

    [计算机视觉]opencv入门学习笔记Part.1 1 前言 1.1 opencv概述(摘取自百度百科) 1.2 图像概念引入 1.3 安装opencv库 2 图像基本操作 2.1 图像的读取 2.2 ...

  5. 零基础入门学习Python,我与python的第一次亲密接触后的感受!

    前言:Python是适合初学者入门最好的语言 Python适合初学者入门最好的语言 人工智能用Python?高考要加入Python?现在连微软官方Excel都要把Python作为官方语言!Python ...

  6. 零基础入门学python 第二版-《零基础入门学习Python》第二版和第一版的区别在哪里呢?...

    第一版 时光荏苒,一晃间,距离<零基础入门学习 Python>出版(2016年11月)已经过去两年多了,在这段时间里, Python 逐步走入了大家的视野,这门语言因其简洁的语法风格,在云 ...

  7. 0基础学python难吗-零基础入门学习Python技术难不难?

    原标题:零基础入门学习Python技术难不难? 近几年对python人才爆发式需求,导致很多人转行进入python开发行业,现如今Python这门语言的就业前景会非常好.相对于其他来说,它语法简单易读 ...

  8. 如何自学python爬虫-怎样入门学习Python爬虫?

    怎样入门学习Python爬虫? 1.掌握Python编程能基础 想要学习爬虫,首先要充分掌握Python编程技术相关的基础知识.爬虫其实就是遵循一定的规则获取数据的过程,所以在学习Python知识的过 ...

  9. 零基础python必背代码-零基础入门学习python 96集全

    零基础入门学习python 96集全 第000讲 愉快的开始(视频+课件)xa0 第001讲 我和Python第一次亲密接触(视频+课件)xa0 第002讲 用Python设第一个游戏(视频+课件+源 ...

最新文章

  1. 建议使用更加安全的ast.literal_eval去替代eval
  2. 如何跟项目经理和开发人员反馈安全测试报告的问题
  3. 关于 WPF Loading初始界面的实现方式
  4. POJ2482-Stars in Your Window【线段树,扫描线,离散化】
  5. ssl1341-最小路径覆盖【最大匹配,最小路径覆盖,图论】
  6. 战略性基础研究的由来及国际实践研究
  7. usb协议规范_USB连接标准接口简述发布
  8. 诗与远方:无题(十九)
  9. _java5条件阻塞Condition的应用
  10. python工作目录_python获取当前工作目录
  11. 李宏毅机器学习——无监督学习(一)
  12. 增值税发票开票软件卷票打印错位配置修正指南
  13. java调试查看调用堆栈_关于调试:如何阅读和理解java堆栈跟踪?
  14. 保持积极向上的人生格言
  15. 新天绿色能源与建投国融续签温室气体减排项目协议
  16. SAP S4 FI后台详细配置教程- PART5 (应收帐目和应付帐目配置篇)
  17. pdf怎么拆分成一页一页的?请看详细方法步骤
  18. 调用PC端、手机、平板摄像头拍照
  19. 数据通信基础 - 数据通信方式
  20. 中国移动清退3G进行时 1

热门文章

  1. 设置-安全-手机加密功能解说
  2. 浅谈计算机领域及职业憧憬
  3. 股票接口数据获取方式
  4. deepin-wine的安装
  5. 智能车图像处理(二)基础寻线
  6. 在每一个时光寻找,寻找适合我的孤岛。
  7. 2J9国内外相同牌号对照表
  8. Poco库使用:操作Json格式数据
  9. navicat转换word表格
  10. Threejs实现模拟管道液体流动