目标

在本章中,

  • 我们将学习使用分水岭算法实现基于标记的图像分割
  • 我们将看到:cv.watershed()

理论

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

但是这种方法会由于图像中的噪声或其他不规则性而产生过度分割的结果。因此OpenCV实现了一个基于标记的分水岭算法,你可以指定哪些是要合并的山谷点,哪些不是。这是一个交互式的图像分割。我们所做的是给我们知道的对象赋予不同的标签。用一种颜色(或强度)标记我们确定为前景或对象的区域,用另一种颜色标记我们确定为背景或非对象的区域,最后用0标记我们不确定的区域。这是我们的标记。然后应用分水岭算法。然后我们的标记将使用我们给出的标签进行更新,对象的边界值将为-1

代码

下面我们将看到一个有关如何使用距离变换和分水岭来分割相互接触的对象的示例。

考虑下面的硬币图像,硬币彼此接触。即使你设置阈值,它也会彼此接触。

我们先从寻找硬币的近似估计开始。因此,我们可以使用Otsu的二值化。

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('coins.png')
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(gray,0,255,cv.THRESH_BINARY_INV cv.THRESH_OTSU)

现在我们需要去除图像中的任何白点噪声。为此,我们可以使用形态学扩张。要去除对象中的任何小孔,我们可以使用形态学侵蚀。因此,现在我们可以确定,靠近对象中心的区域是前景,而离对象中心很远的区域是背景。我们不确定的唯一区域是硬币的边界区域。

因此,我们需要提取我们可确定为硬币的区域。侵蚀会去除边界像素。因此,无论剩余多少,我们都可以肯定它是硬币。如果物体彼此不接触,那将起作用。但是,由于它们彼此接触,因此另一个好选择是找到距离变换并应用适当的阈值。接下来,我们需要找到我们确定它们不是硬币的区域。为此,我们扩张了结果。膨胀将对象边界增加到背景。这样,由于边界区域已删除,因此我们可以确保结果中背景中的任何区域实际上都是背景。参见下图。

剩下的区域是我们不知道的区域,无论是硬币还是背景。分水岭算法应该找到它。这些区域通常位于前景和背景相遇(甚至两个不同的硬币相遇)的硬币边界附近。我们称之为边界。可以通过从sure_bg区域中减去sure_fg区域来获得。

# 噪声去除
kernel = np.ones((3,3),np.uint8)
opening = cv.morphologyEx(thresh,cv.MORPH_OPEN,kernel, iterations = 2)
# 确定背景区域
sure_bg = cv.dilate(opening,kernel,iterations=3)
# 寻找前景区域
dist_transform = cv.distanceTransform(opening,cv.DIST_L2,5)
ret, sure_fg = cv.threshold(dist_transform,0.7*dist_transform.max(),255,0)
# 找到未知区域
sure_fg = np.uint8(sure_fg)
unknown = cv.subtract(sure_bg,sure_fg)

查看结果。在阈值图像中,我们得到了一些硬币区域,我们确定它们是硬币,并且现在已分离它们。(在某些情况下,你可能只对前景分割感兴趣,而不对分离相互接触的对象感兴趣。在那种情况下,你无需使用距离变换,只需侵蚀就足够了。侵蚀只是提取确定前景区域的另一种方法。)

现在我们可以确定哪些是硬币的区域,哪些是背景。因此,我们创建了标记(它的大小与原始图像的大小相同,但具有int32数据类型),并标记其中的区域。我们肯定知道的区域(无论是前景还是背景)都标有任何正整数,但是带有不同的整数,而我们不确定的区域则保留为零。为此,我们使用cv.connectedComponents()。它用0标记图像的背景,然后其他对象用从1开始的整数标记。

但是我们知道,如果背景标记为0,则分水岭会将其视为未知区域。所以我们想用不同的整数来标记它。相反,我们将未知定义的未知区域标记为0。

# 类别标记
ret, markers = cv.connectedComponents(sure_fg)
# 为所有的标记加1,保证背景是0而不是1
markers = markers 1
# 现在让所有的未知区域为0
markers[unknown==255] = 0

参见JET colormap中显示的结果。深蓝色区域显示未知区域。当然,硬币的颜色不同。剩下,肯定为背景的区域显示在较浅的蓝色,跟未知区域相比。

现在我们的标记已准备就绪。现在是最后一步的时候了,使用分水岭算法。然后标记图像将被修改。边界区域将标记为-1。

markers = cv.watershed(img,markers)
img[markers == -1] = [255,0,0]

请参阅下面的结果。对某些硬币,它们接触的区域被正确地分割,而对于某些硬币,却不是。

附加资源

  1. CMM page on Watershed Transformation

练习

  1. OpenCV samples has an interactive sample on watershed segmentation, watershed.py. Run it, Enjoy it, then learn it.

欢迎关注磐创博客资源汇总站:http://docs.panchuang.net/

欢迎关注PyTorch官方中文教程站:http://pytorch.panchuang.net/

OpenCV中文官方文档:http://woshicver.com/

OpenCV-Python 图像分割与Watershed算法 | 三十四的更多相关文章

  1. Python学习之旅(三十四)

    Python基础知识(33):网络编程(Ⅱ) UDP编程 相对TCP,UDP则是面向无连接的协议 使用UDP协议时,不需要建立连接,只需要知道对方的IP地址和端口号,就可以直接发数据包 虽然用UDP传 ...

  2. 孤荷凌寒自学python第三十四天python的文件操作对file类的对象学习

     孤荷凌寒自学python第三十四天python的文件操作对file类的对象学习 (完整学习过程屏幕记录视频地址在文末,手写笔记在文末) 一.close() 当一个file对象执行此方法时,将关闭当前 ...

  3. 剑指Offer(三十四):第一个只出现一次的字符

    剑指Offer(三十四):第一个只出现一次的字符 搜索微信公众号:'AI-ming3526'或者'计算机视觉这件小事' 获取更多算法.机器学习干货 csdn:https://blog.csdn.net ...

  4. 第三十四个知识点:描述攻击离散对数问题的baby-step/Giant-step方法

    第三十四个知识点:描述攻击离散对数问题的baby-step/Giant-step方法 Baby-step/Giant-step是Dnaiel Shanks为解决DLP问题开发的算法.DLP问题已经是许 ...

  5. COJ966 WZJ的数据结构(负三十四)

    WZJ的数据结构(负三十四) 难度级别:C: 运行时间限制:20000ms: 运行空间限制:262144KB: 代码长度限制:2000000B 试题描述 给一棵n个节点的树,请对于形如"u  ...

  6. NeHe OpenGL教程 第三十四课:地形

    转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...

  7. JAVA之旅(三十四)——自定义服务端,URLConnection,正则表达式特点,匹配,切割,替换,获取,网页爬虫

    JAVA之旅(三十四)--自定义服务端,URLConnection,正则表达式特点,匹配,切割,替换,获取,网页爬虫 我们接着来说网络编程,TCP 一.自定义服务端 我们直接写一个服务端,让本机去连接 ...

  8. Java进阶(三十四)Integer与int的种种比较你知道多少?

    Java进阶(三十四)Integer与int的种种比较你知道多少? 前言 如果面试官问Integer与int的区别:估计大多数人只会说到两点:Ingeter是int的包装类,注意是一个类:int的初值 ...

  9. Gradle 1.12用户指南翻译——第三十四章. JaCoCo 插件

    本文由CSDN博客万一博主翻译,其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Githu ...

随机推荐

  1. 文件传送协议FTP

    文件传送协议FTP 1.1.概述 文件传送协议 FTP (File Transfer Protocol) 是因特网上使用得最广泛的文件传送协议. FTP 提供交互式的访问,允许客户指明文件的类型与格式 ...

  2. RTL8812AU双频无线网卡在ubuntu19和20上的驱动安装

    旧爱已去 疫情在家,突然邻居敲门说,我这网上不了,帮下忙呗兄弟:兄弟都叫了,哥就冒回险,口罩扎起,一顿xxxx,原来是路由器没插到wlan口,看他拉网线可怜,就把我台式机上无线网卡送给他了,这就是又送 ...

  3. Spring Cloud Feign 组成和配置

    Feign的组成 接口 作用 默认值 Feign.Builder Feign的入口 Feign.Builder Client Feign底层用什么去请求 和Ribbon配合时:LoadBalancer ...

  4. Ubuntu18.04LTS安装docker报错:Command 'lsb_release' not found

    Ubuntu18.04LTS安装docker在执行sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/ ...

  5. Grafana使用总结

    最近工作需求学习了下grafana,根据创建的几个dashboard简要记录下创建过程. 本次使用了grafana做可视化展示,data source使用的rds是postgresql和时序数据库in ...

  6. Think 框架漏洞利用

    下午有点闲,又没有女朋友陪,该干嘛呢??? 对了,做安全的是不需要女朋友的,哈哈哈哈哈 废话不多说,本机搭建环境: 首先nmap扫描一下,哦哈,有点东西. 开的端口有点多,这个时候有点窃喜,开的端口太 ...

  7. SSRF漏洞的挖掘思路与技巧

    什么是SSRF? SSRF(Server-Side Request Forgery:服务器端请求伪造) 是一种由攻击者构造形成由服务端发起请求的一个安全漏洞.一般情况下,SSRF攻击的目标是从外网无法 ...

  8. Ansible-基本概述

    为什么要自动化运维 纯手动软件安装部署方式 我们以 10 台机器部署 Nginx 为例.部署步骤如下: 1.通过 ssh 登录一台机器: 2.yum install -y nginx 或者 获取安装包 ...

  9. 动态构造任意复杂的 Linq Where 表达式

    前言 Linq 是 C# 中一个非常好用的集合处理库,用好了能帮我们简化大量又臭又长的嵌套循环,使处理逻辑清晰可见.EF 查询主要也是依赖 Linq.但是 Linq 相对 sql 也存在一些缺点,最主 ...

  10. ES6/JavaScript一些‘巧用’

    前言 第一次发表文章,如有不好的地方请见谅/ 在编写JavaScript代码的时候存在的一些方法和技巧,虽然有时候条条大路都通向罗马,但是也许总会有那么一条最短的路径可走.本文将一些都知道却不怎么用的 ...