python的opencv操作记录11——阈值分割

news2024/9/30 13:23:27

文章目录

  • 传统图像处理分割
  • 阈值分割
    • 一个应用场景
    • opencv库中的阈值分割
    • 固定阈值
    • THRESH_OTSU 大津法阈值
    • 自适应阈值

传统图像处理分割

现在提到图像分割,很多人会直接想到当前火爆的深度学习的各种分割网络,比如实例分割,语义分割等。其实在传统的图像处理领域,也有一些分割算法,这些算法在通用的分割上来说没有深度网络的普适性好,但是在某些特殊场景是一个更轻量级的解决方案。也是图像处理学习过程中的毕竟之路。

阈值分割

阈值分割是一个基本的图像处理算法,思路很简单:
将图像中每个像素的像素值和阈值进行比较,高于阈值的是一类,低于阈值的是一类。
这样的算法一般有两种应用场景:

  • 提取前景或者背景
  • 二值化处理

一个应用场景

我在实际工作中,碰到了一个实际场景,我觉得就是可以用这个阈值分割来解决的,而不需要使用比较重的深度网络来解决。

问题是要定位一个试管中试剂的位置信息,设备中拍摄的图像如下图所示:
在这里插入图片描述

从图中可以分析,这条分界线实际上是很容易判断出来的,而且前景和背景的像素值差距非常大,所以我当时考虑的就是直接用阈值分割来实现。

opencv库中的阈值分割

在opencv库中,用于阈值分割的函数是:cv2.threshold.
该函数的接受4个参数:

  • src, 图像数据,为单通道图像。
  • thresh,阈值。
  • maxval,最大像素值
  • type,阈值分割方法。

最后的这个参数,是因为阈值分割有很多中方法,在opencv的官网上描述的非常清楚:
在这里插入图片描述

在这里插入图片描述

实际上就是根据像素值与阈值的关系,来获取最终目标图像的像素值规则。这里就不一个一个说了。我提一下我自己用到的几个方法,以及在这个试管工作中的效果。
另外,我还用到一些其他的方法,把试管的感兴趣区域确定和提取出来了,这里就说了,和这一篇文章的关系不大。

固定阈值

上面提到了,因为分界线非常明显,所以我想着直接尝试出一个固定的阈值来做分割,看能不能达到自己想要的效果。

代码如下:

if __name__ == '__main__':

    img = cv2.imread("images/tubeImg.jpeg")

    grey = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    # 如果大于阈值,赋值为255,小于阈值,赋值为0
    ret, binary = cv2.threshold(grey, 60, 255, cv2.THRESH_BINARY)

    cv2.imshow("result", binary)

    cv2.waitKey()
    cv2.destroyAllWindows()

在这里插入图片描述

效果不太理想,最上面的这个界限不是很清楚(虽然通过拟合可以基本确定),而且中间的一些杂志部分去除的不是很好(后续需要用到,所以在这一步想一次搞定)。
所以再试试其他的方法看看。

尝试了TRUNC和TOZERO的方式,如下图:
在这里插入图片描述
在这里插入图片描述

我理解实际上就是把目标图像的前景和背景的像素值拉的不是很大而已,对我想要的效果帮助不大。

而且我通过修改阈值,把60这个值改大改小效果仍然不佳,就只能谋求其他方法了。

THRESH_OTSU 大津法阈值

这个分割方法和上述的分割方法不一样,上述的分割方法的阈值是固定的,而这个方法的阈值时需要通过一些计算才能得到的,这种计算方式就是大津法。
大津法的原理和思路如下(单通道):

  • 大津法的思路就是要找到一个阈值,根据这个阈值划分成两类(A, B)之后,这A, B两类像素值的方差最大。那么问题就变成两个:
    • 如何计算A,B两类像素的方差。
    • 使用最快速的方法找到让方差最大的那个阈值。
  • 一张M * N大小的图像,假设阈值为k。
  • 那么大于k的像素点(如果是BINARY的方法则表示前景,否则就是反过来,下同)的个数 N 0 N_0 N0,小于k的像素点的个数为 N 0 N_0 N0。那么前景分类的像素点在整张图像中的占比就为 ω 0 = N 0 M ∗ N \omega_0=\frac{N_0}{M * N} ω0=MNN0,背景的就是 ω 1 = N 1 M ∗ N \omega_1=\frac{N_1}{M * N} ω1=MNN1.
  • 大于k的像素点的平均灰度值可计为 u 0 u_0 u0,小于的平均灰度值可计为 u 1 u_1 u1
  • 整张图像的平均灰度为 u u u
  • 那么现在可以回答上面的第一个问题,两类像素的方差定位为
    g = ω 0 ( u 0 − u ) 2 + ω 1 ( u 1 − u ) 2 g = \omega_0(u_0-u)^2 + \omega_1(u_1-u)^2 g=ω0(u0u)2+ω1(u1u)2
  • 大津法就是要找到这个最大的g值。那么第二个问题来了,怎么去算这个g值。最简单的办法就是遍历,让k从0到255,依次计算这个g值,哪个最大,阈值就取那个k。opencv是不是这么实现的不知道,有兴趣的可以去翻翻源码。

通过大津法计算得到阈值之后,还要确定是使用哪种分类方法,所以代码可以写成:

if __name__ == '__main__':

    img = cv2.imread("images/tubeImg.jpeg")

    grey = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    # 先使用大津法计算阈值,然后再分类
    # 如果大于阈值,赋值为255,小于阈值,赋值为0
    ret, binary = cv2.threshold(grey, 60, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)

    cv2.imshow("result", binary)

    cv2.waitKey()
    cv2.destroyAllWindows()

图是下图:
在这里插入图片描述

我感觉上面的那条分界线更加分明和分布平均一点。

自适应阈值

上面的几种方法里的思路里基本上都是一个阈值在应用与整个图像的分割,不管是自己手动设的,还是通过某种方法计算出来的。但是一旦确定后,这个阈值就会应用到整张图像中去。
在opencv中还提供了一种阈值分割方法,这个阈值在整张图像的分割操作中是变化的,是 根据该像素周边的邻居像素(neighborhood pix)值来确定该像素的比较阈值,然后再根据之前的阈值比较逻辑来确定是前景类还是后台类

opencv中的这个函数是adaptiveThreshold,这个函数有下面几个参数:

  • src,源图像,为灰度图。
  • maxValue, 最大值
  • adaptiveMethod,根据邻居像素如何计算这个阈值,也就是说确定这个邻域像素的计算方法。
    • ADAPTIVE_THRESH_MEAN_C,邻域的像素计算平均值作为阈值(减去一个偏移量)
    • ADAPTIVE_THRESH_GAUSSIAN_C,邻域的像素通过一个高斯核作为掩码来计算权重值作为阈值(减去一个偏移量)。根据不同的blockSize,opencv会调用getGaussianKernel这个来获取这个高斯核,有兴趣可以具体了解下高斯核的生成原理和逻辑。
  • thresholdType,确定阈值之后的分类方式,就支持两种:THRESH_BINARY(大于阈值取255,小于取0),THRESH_BINARY_INV(大于阈值取0,小于取255)
  • blockSize, 这个邻域的边长,一般是3,5,7.和图像中的卷积核类似。
  • C,上面计算阈值过程中提到的偏移量。

我在我的工程里试了一下:

if __name__ == '__main__':

    img = cv2.imread("/Users/zoulei/files/personal/blog/images/tubeImg.jpeg")

    grey = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    # 如果大于阈值,赋值为255,小于阈值,赋值为0
    # ret, binary = cv2.threshold(grey, 60, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)

    # 自适应阈值分割
    binary = cv2.adaptiveThreshold(grey, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 15, 2)

    cv2.imshow("result", binary)

    cv2.waitKey()
    cv2.destroyAllWindows()

效果如下:
在这里插入图片描述

试过蛮多的blockSize,基本上都是这个效果。和我想要的效果完全不符合,所以这个方法对我来说没什么用。
这个方法应该是用于做对比度较大的图像杂质分割会比较有用,我这个工程里暂时没有用到,有兴趣的朋友可以去试一下。

当然,最后我自己的解决方案是选择用OTSU大津法的阈值分割方法,再加上对分界线的一些拟合方法和一些逻辑判断来解决我自己的问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/338637.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java爬虫Selenium+Java+ChromeDriver

一、爬虫工具 selenium 是一个模拟浏览器操作的工具,背后有google 维护源代码,支持全部主流浏览器,支持主流的编程语言,包括:java,Python,C#,PHP,Ruby,等,在本项目上使用的Java语言。 官网:https://www.sel…

【Vue】参数传递:如何同时传递 DTO 和 file 文件

日常开发时,如果遇到较为复杂的 DTO 对象的参数传递时,通常前端使用的请求头为:application/json(JSON 格式的数据);而后端可以使用 RequestBody 接收一个 DTO 对象。但当需要在一个界面上同时传递 DTO 和附…

Java集合中的Map

MapMap接口键 值 对存储键不能重复&#xff0c;值可以重复Map三个实现类的存储结构HashMap&#xff1a;Hash表链表红黑树结构 线程不安全TreeMap&#xff1a; 底层红黑树实现HashTable&#xff1a;hash表链表红黑树 线程安全HashMapHashMap常用方法HashMap<String,String>…

[测开篇]设计测试用例的方法如何正确描述Bug

​ 文章目录为什么测试人员要写测试用例&#xff1f;怎样设计测试用例&#xff1f;&#xff08;总的方面&#xff09;1.基于需求设计测试用例&#xff08;总的方面&#xff09; 2.页面&#xff08;总的方面&#xff09; 3.非功能性测试&#xff08;具体方面&#xff09; 4.1 等…

「Python|环境安装|Windows」如何在Windows上安装Python环境?

本文主要介绍如何在Windows上安装Python&#xff0c;帮助初学者或者非程序员伙伴快速搭建可以运行python代码的环境。 文章目录安装python做一点小配置验证python如何安装指定版本的python编程语言的环境搭建一直是学习编程的第一道门槛。 对于如何在Linux系统上安装指定版本的…

谷歌蜘蛛池怎么搭建?Google蜘蛛池可以帮助谷歌排名吗?

本文主要分享关于谷歌蜘蛛池的搭建疑问&#xff0c;以及Google对谷歌排名的影响到底有多大。 本文由光算创作&#xff0c;有可能会被剽窃和修改&#xff0c;我们佛系对待这种行为吧。 谷歌蜘蛛池怎么搭建&#xff1f; 答案是&#xff1a;需要一个内链外链体系复杂的站群系统…

154、【动态规划】leetcode ——494. 目标和:回溯法+动态规划(C++版本)

题目描述 原题链接&#xff1a;494. 目标和 解题思路 &#xff08;1&#xff09;回溯法 本题的特点是nums中每个元素只能使用一次&#xff0c;分别试探加上nums[index]和减去nums[index]&#xff0c;然后递归的遍历下一个元素index 1。 class Solution { public:int res …

java中flatMap用法

java中map是把集合每个元素重新映射&#xff0c;元素个数不变&#xff0c;但是元素值发生了变化。而flatMap从字面上来说是压平这个映射&#xff0c;实际作用就是将每个元素进行一个一对多的拆分&#xff0c;细分成更小的单元&#xff0c;返回一个新的Stream流&#xff0c;新的…

1629_MIT_6.828_xv6_chapter1操作系统的组织

全部学习汇总&#xff1a;GreyZhang/g_unix: some basic learning about unix operating system. (github.com) 这一次整理一下操作系统组织相关的知识&#xff0c;主要还是xv6教学操作系统相关的知识。当然&#xff0c;很多知识在这类技术领域是通用的。 1. 操作系统的主要功能…

SAP ABAP Odata

GetEntity和GetEntitys GetEntitys 创建Odata Project 导入结构 选择需要的字段 设定Key 勾选字段的creatable、updatable、sortable、nullable、filterable属性值。 再依上述步骤创建ZPOITEM结构和实体集 3. 创建ZPOHEADER和ZPOITEM的Association 两个实体集的关联字段&…

RocketMQ-消息消费模式 顺序消费

RocketMQ-消息消费模式 顺序消费RocketMQ-消息消费模式集群模式集群模式的演示(本身就默认)Rocketmq存储队列广播模式顺序消费如何改实现顺序消费RocketMQ-消息消费模式 集群模式 在消费模式为集群的情况下,如果机器是集群的,消息只会给集群中的其中一台机器消费到 集群模…

【数据结构】双向链表的模拟实现(无头)

目录 前言&#xff1a; 1、认识双向链表中的结点 2、认识并创建无头双向链表 3、实现双向链表当中的一些方法 3.1、遍历输出方法&#xff08;display&#xff09; 3.2、得到链表的长度&#xff08;size&#xff09; 3.3、查找关键字key是否包含在双链表中(contains) 3.…

基于I2S通讯MAX98357模块的JetsonNano声音外放

前言有很多方法可以为 Jetson 设备添加音频功能。USB 扬声器和USB 麦克风是一种简单的解决方案&#xff0c;但它们确实占用了宝贵的 USB 插槽&#xff0c;这些插槽可能更适合用于键盘、蓝牙功能、Internet Keys 和其他配件。在 Jetson 设备上&#xff0c;NVIDIA 通过 40 针 GPI…

微软发布会精华回顾:“台式电脑”抢了风头

Lightbot北京时间2016年10月26日晚10点&#xff0c;微软在纽约发布了名为 Surface Studio 的一体机、名为 Surface Dial 的配件以及外观未变的顶配版 Surface Book。同时&#xff0c;微软宣布了 Windows 10 下一个重要版本——“Creators Update”的数项新功能&#xff0c;包括…

【Linux】冯诺依曼体系结构和操作系统概念

文章目录&#x1f3aa; 冯诺依曼体系结构&#x1f680;1.体系概述&#x1f680;2.CPU和内存的数据交换&#x1f680;3.体系结构中数据的流动&#x1f3aa; 操作系统概念理解&#x1f680;1.简述&#x1f680;2.设计目的&#x1f680;3.定位&#x1f680;4.理解&#x1f680;5.管…

AOP面向切面编程思想。

目录 一、AOP工作流程 1、基本概念 2、AOP工作流程 二、AOP核心配置 1、AOP切入点表达式 2、AOP通知类型 三、AOP通知获取数据 1、获取参数 2、获取返回值 3、获取异常 四、AOP事务管理 1、Spring事务简介 2、Spring事务角色 3、事务属性 一、AOP工作流程 1、…

Linux内核启动(理论,0.11版本)分段与分页

为什么要虚拟内存 我们知道&#xff0c;在之前上微机原理时&#xff0c;我们的程序是可以直接访问内存的&#xff0c;而且访问的是直接的物理内存&#xff0c;在实模式下&#xff0c;寄存器是16位的&#xff0c;数组总线&#xff08;data bus&#xff09;是16位的&#xff0c;…

设计模式-值类型与引用类型、深拷贝与浅拷贝、原型模式详解

一. 值类型和引用类型 1. 前言 (1). 分类 值类型包括&#xff1a;布尔类型、浮点类型(float、double、decimal、byte)、字符类型(char)、整型&#xff08;int、long、short等&#xff09;、枚举(entum)、结构体(struct)。 引用类型&#xff1a;数组、字符串(string)、类、接口…

DamiCMS SQL注入分析

2023年将会持续于B站、CSDN等各大平台更新&#xff0c;可加入粉丝群与博主交流:838681355&#xff0c;为了老板大G共同努力。 一、入口文件(单入口文件模式) 看一下Index.php文件代码&#xff1a;引入了php_safe.php文件 查看一下php_safe.php防御文件&#xff1a; 对变量e…

2019_41 考研408

2019年(单链表)41.(13分)设线性表采用带头结点的单链表保存&#xff0c;链表中的结点定义如下:typedef struct node {int data;struct node* next;}NODE;请设计一个空间复杂度为O(1)且时间上尽可能高效的算法&#xff0c;重新排列L中的各结点&#xff0c;得到线性表L(q,a,,a,an…