计算机视觉——OpenCV Otsu阈值法原理及实现

算法简介

Otsu阈值法,也被称为大津算法,是一种在图像处理中广泛使用的自动阈值分割技术。这种方法由日本学者大津展之于1979年提出,旨在根据图像的灰度直方图来自动选择最佳全局阈值。Otsu阈值法的核心思想是最小化类内方差或最大化类间方差。

以下是Otsu阈值法的一般步骤:

  1. 预处理:对输入图像进行预处理,以减少噪声和增强图像特征。常见的预处理方法包括高斯平滑滤波,这有助于平滑图像,减少随机噪声。

  2. 灰度直方图:计算图像的灰度直方图,即统计图像中每个灰度级出现的频率。直方图可以提供图像的灰度分布信息。

  3. 阈值计算:这是Otsu算法的关键步骤。算法通过遍历所有可能的阈值,计算每个阈值对应的类间方差与类内方差之比。Otsu的目标是找到一个阈值,使得这个比值最大化。类内方差最小化意味着阈值两边的像素点尽可能相似,而类间方差最大化意味着阈值两边的像素点差异尽可能大。

  4. 二值化:使用计算得到的阈值T对原图像进行二值化处理。所有小于或等于阈值T的像素点被设置为背景像素(例如0),而所有大于阈值T的像素点被设置为前景像素(例如255)。

Otsu阈值法的优点在于它的简单性和有效性,特别是在对比度较高的图像中。然而,对于具有复杂背景或光照不均的图像,这种方法可能不够准确。在这些情况下,可能需要更高级的阈值技术或结合其他图像处理技术来获得更好的分割效果。

由于您提供的链接无法解析,如果您需要关于Otsu阈值法的更多信息或有其他相关问题,请告知,我会尽力帮助您。

算法的逻辑

双峰图像(bimodal images)的像素直方图具有两个明显的峰值,这通常意味着图像中存在两种明显不同的像素强度区域,这些区域分别对应于图像中的前景和背景。在这类图像中,前景和背景在灰度或颜色上有明显的区分,因此使用Otsu阈值法可以有效地将它们分开。

双峰直方图的特点是:

  1. 两个峰值:直方图有两个明显的峰值,分别代表图像中的前景和背景的像素强度分布。

  2. 低谷:在两个峰值之间存在一个低谷,这个低谷的位置可以作为潜在的阈值,用于区分前景和背景。

  3. 对比度:两个峰值之间的对比度越高,使用Otsu阈值法的效果通常越好,因为这意味着前景和背景之间的区分度更高。

Otsu算法的核心思想正是利用这种双峰分布的特性,通过最大化类间方差来确定最佳阈值。类间方差衡量的是前景和背景两个类别之间的差异性,而类内方差衡量的是类别内部的一致性。Otsu算法寻找的阈值能够最大化类间方差与类内方差的比值,从而实现最佳的前景和背景分离。

如下就是一个双峰图像的示例:


假设一副灰度图,像素值灰度级为,如我们常见的灰度图像,灰度级是256。

像素值为第个灰度级的像素点有个,则这幅图像总的像素点个数为 N = n 1 + n 2 + . . . n L N=n_1 + n_2 + ...n_L N=n1+n2+...nL

基于上述假设,某个像素点为灰度级的概率可表示为:
p i = n i N p_{i}\,=\,\frac{n_{i}}{N} pi=Nni

满足以下条件:
p i > 0 , ∑ i = 1 L p i = 1 p_{i}\gt 0,\sum_{i=1}^{L}p_{i}=1 pi>0,i=1Lpi=1

取灰度级为阈值将这幅图像的像素点分成 C 1 C_1 C1C_2和两簇,

  • C 1 C_1 C1包含像素级为[1,2,…,t]的像素
  • C 2 C_2 C2包含像素级为[t+1,…,L]的像素

对于图像中某个像素属于 C 1 / C 2 C_1/C_2 C1/C2类的概率可表示为:
ω 1 ( t ) = ∑ i = 1 t p i \omega_{1}(t)=\sum_{i=1}^{t}p_{i} ω1(t)=i=1tpi
ω 2 ( t ) = ∑ i = t + 1 L p i \omega_{2}(t)=\sum_{i=t+1}^{L}p_{i} ω2(t)=i=t+1Lpi

w 1 ( t ) , w 2 ( t ) w_1(t),w_2(t) w1(t),w2(t),满足关系 w 1 ( t ) = 1 − w 2 ( t ) w_1(t) = 1-w_2(t) w1(t)=1w2(t)

求每个簇对应的像素均值:
在这里插入图片描述
同样可推导:
μ 2 ( t ) = ∑ i = t + 1 L i p i ω 2 ( t ) \mu_{2}(t)=\sum_{i=t+1}^{L}\frac{i p_{i}}{\omega_{2}(t)} μ2(t)=i=t+1Lω2(t)ipi

整幅图像的像素均值记为:
μ T = ∑ i L i ∗ p i = ω 1 ( t ) μ 1 ( t ) + ω 2 ( t ) μ 2 ( t ) \mu_{T}=\sum_{i}^{L}i*p_{i}=\omega_{1}(t)\mu_{1}(t)+\omega_{2}(t)\mu_{2}(t) μT=iLipi=ω1(t)μ1(t)+ω2(t)μ2(t)

C 1 / C 2 C_1/C_2 C1/C2每个簇对应的像素值方差:
σ 1 2 ( t ) = ∑ i = 1 t [ i − μ 1 ( t ) ] 2 ∗ n i ∑ i = 1 t n i = ∑ i = 1 t [ i − μ 1 ( t ) ] 2 ∗ n i N ∑ i = 1 t n i = ∑ i = 1 t [ i − μ 1 ( t ) ] 2 p i ω 1 ( t ) \sigma_{1}^{2}(t)=\frac{\sum_{i=1}^{t}[i-\mu_{1}(t)]^{2}*n_{i}}{\sum_{i=1}^{t}n_{i}}=\frac{\sum_{i=1}^{t}\frac{[i-\mu_{1}(t)]^{2}*n_{i}}{N}}{\sum_{i=1}^{t}n_{i}}=\frac{\sum_{i=1}^{t}[i-\mu_{1}(t)]^{2}p_{i}}{\omega_{1}(t)} σ12(t)=i=1tnii=1t[iμ1(t)]2ni=i=1tnii=1tN[iμ1(t)]2ni=ω1(t)i=1t[iμ1(t)]2pi

同样可推导:
σ 2 2 ( t ) = ∑ i = t + 1 L [ i − μ 2 ( t ) ] 2 p i ω 2 ( t ) \sigma_{2}^{2}(t)=\frac{\sum_{i=t+1}^{L}[i-\mu_{2}(t)]^{2}p_{i}}{\omega_{2}(t)} σ22(t)=ω2(t)i=t+1L[iμ2(t)]2pi

为了衡量所取阈值的二值化效果,作者定义了三种方差,分别是:

类内方差:
σ W 2 = ω 1 σ 1 2 + ω 2 σ 2 2 \sigma_{W}^{2}=\omega_{1}\sigma_{1}^{2}+\omega_{2}\sigma_{2}^{2} σW2=ω1σ12+ω2σ22
类间方差:
σ B 2 = ω 1 ( μ 1 − μ T ) 2 + ω 2 ( μ 2 − μ T ) 2 = ω 1 ω 2 ( μ 1 − μ 2 ) 2 \sigma_{B}^{2}=\omega_{1}(\mu_{1}-\mu_{T})^{2}+\omega_{2}(\mu_{2}-\mu_{T})^{2}=\omega_{1}\omega_{2}(\mu_{1}-\mu_{2})^{2} σB2=ω1(μ1μT)2+ω2(μ2μT)2=ω1ω2(μ1μ2)2
图像总的像素值方差:
σ T 2 = ∑ i = 1 L ( i − μ T ) 2 p i \sigma_{T}^{2}=\sum_{i=1}^{L}(i-\mu_{T})^{2}p_{i} σT2=i=1L(iμT)2pi
可以推导三者之间有如下关系:
σ W 2 + σ B 2 = σ T 2 \sigma_{W}^{2}+\sigma_{B}^{2}=\sigma_{T}^{2} σW2+σB2=σT2
从上面的定义可以发现 σ W 2 / σ B 2 \sigma_{W}^{2}/\sigma_{B}^{2} σW2/σB2,于阈值t有关,而 σ T 2 \sigma_{T}^{2} σT2与阈值无关。
上面是的二阶函数,是的一阶函数,更易优化。最后,求阈值可以变成最大化类间方差
σ B 2 ( t ∗ ) = max ⁡ 1 ≤ t ≤ L σ B 2 ( t ) \sigma_{B}^{2}(t^{*})=\operatorname*{max}_{1\le t\le L}\sigma_{B}^{2}(t) σB2(t)=1tLmaxσB2(t)

C++ 源码实现

// Include Libraries
#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc.hpp>using namespace std;
using namespace cv;int main() {// Read the image in grayscale formatMat testImage = imread("boat.jpg", IMREAD_GRAYSCALE);int bins_num = 256;// Get the histogramlong double histogram[256];// Initialize all intensity values to 0for (int i = 0; i < bins_num; i++) {histogram[i] = 0;}// Calculate the number of pixels for each intensity valuefor (int y = 0; y < testImage.rows; y++) {for (int x = 0; x < testImage.cols; x++) {histogram[(int)testImage.at<uchar>(y, x)]++;}}// Calculate bin edges and bin midslong double bin_edges[256];bin_edges[0] = 0.0;long double increment = 0.99609375;for (int i = 1; i < bins_num; i++) {bin_edges[i] = bin_edges[i - 1] + increment;}long double bin_mids[256];for (int i = 0; i < bins_num; i++) {bin_mids[i] = (bin_edges[i] + bin_edges[i + 1]) / 2;}// Calculate weights for each classlong double weight1[256];weight1[0] = histogram[0];for (int i = 1; i < bins_num; i++) {weight1[i] = histogram[i] + weight1[i - 1];}int total_sum = 0;for (int i = 0; i < bins_num; i++) {total_sum += histogram[i];}long double weight2[256];weight2[0] = total_sum;for (int i = 1; i < bins_num; i++) {weight2[i] = weight2[i - 1] - histogram[i - 1];}// Calculate class meanslong double histogram_bin_mids[256];for (int i = 0; i < bins_num; i++) {histogram_bin_mids[i] = histogram[i] * bin_mids[i];}long double cumsum_mean1[256];cumsum_mean1[0] = histogram_bin_mids[0];for (int i = 1; i < bins_num; i++) {cumsum_mean1[i] = cumsum_mean1[i - 1] + histogram_bin_mids[i];}long double cumsum_mean2[256];cumsum_mean2[0] = histogram_bin_mids[255];for (int i = 1, j = bins_num - 1; i < bins_num; i++, j--) {cumsum_mean2[i] = cumsum_mean2[i - 1] + histogram_bin_mids[j];}long double mean1[256];for (int i = 0; i < bins_num; i++) {mean1[i] = cumsum_mean1[i] / weight1[i];}long double mean2[256];for (int i = 0, j = bins_num - 1; i < bins_num; i++, j--) {mean2[j] = cumsum_mean2[i] / weight2[j];}// Calculate Inter_class_variancelong double Inter_class_variance[255];long double dnum = 10000000000.0; // Scaling factor to avoid overflowfor (int i = 0; i < 255; i++) {Inter_class_variance[i] = ((weight1[i] * weight2[i] * (mean1[i] - mean2[i + 1])) / dnum) * (mean1[i] - mean2[i + 1]);}// Maximize interclass variance to find the thresholdlong double maxi = 0;int getmax = 0;for (int i = 0; i < 255; i++) {if (maxi < Inter_class_variance[i]) {maxi = Inter_class_variance[i];getmax = i;}}cout << "Otsu's algorithm implementation thresholding result: " << bin_mids[getmax] << endl;return 0;
}

Python 代码实现

import cv2
import numpy as npdef otsu_thresholding(image_path):# 读取图像image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)if image is None:print("Error: 图像未找到.")return# 获取直方图hist = cv2.calcHist([image], [0], None, [256], [0, 256])# 计算总像素total_pixels = image.size# 初始化类间方差inter_class_variance = 0# 初始化权重和w0 = w1 = 0# 初始化类内总和sum0 = np.sum(image[image < inter_class_variance])sum1 = np.sum(image[image > inter_class_variance])# 初始化类内平方和var0 = np.sum((image[image < inter_class_variance] - sum0 / w0) ** 2)var1 = np.sum((image[image > inter_class_variance] - sum1 / w1) ** 2)# 寻找最佳阈值max_variance = 0threshold = 0for threshold in range(1, 256):w0 += hist[threshold - 1]w1 = total_pixels - w0sum0 += threshold * hist[threshold - 1]sum1 -= threshold * hist[threshold - 1]var0 = w0 / (w0 + w1) * np.sum((image[image <= threshold] - (sum0 / w0)) ** 2)var1 = w1 / (w0 + w1) * np.sum((image[image > threshold] - (sum1 / w1)) ** 2)inter_class_variance = var0 + var1if inter_class_variance > max_variance:max_variance = inter_class_variancethreshold = threshold# 使用最佳阈值二值化图像_, binary_image = cv2.threshold(image, threshold, 255, cv2.THRESH_BINARY)return binary_image, threshold# 使用函数
image_path = 'boat.jpg'  # 请确保路径正确
binary_image, threshold = otsu_thresholding(image_path)# 显示结果
cv2.imshow('Original Image', cv2.imread(image_path, cv2.IMREAD_GRAYSCALE))
cv2.imshow('Binary Image', binary_image)
cv2.waitKey(0)
cv2.destroyAllWindows()

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

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

相关文章

数据结构 第七章 图(一)

&#x1f680; 【考纲要求】图的基本概念 一、图的基本概念 1.1 图的定义 图由顶点和边组成&#xff0c;所以我们在表示一个图的时候&#xff0c;使用 G ( V , E ) G(V,E) G(V,E)&#xff0c;来表示一个G图&#xff0c;其中的V表示G图中的顶点&#xff0c;E表示G图中的边&am…

【SAP ME 35】SAP ME DEBUG模式开启

1、Debug基础参数配置 2、NWDS Debug模式开启 3、Debug模式下删除锁&#xff08;如果以上尝试无效&#xff0c;就执行删除锁&#xff09; 找到对应的锁任务进行删除&#xff01; -------------------------------------------------------------- SAP ME涉及问题较多&#…

(MATLAB)安装指南

参考链接&#xff1a;MATLAB2019a安装教程&#xff08;避坑版&#xff09;

MySQL 高级 - 第二章 | 数据库目录结构与文件系统

目录 前言一、数据库主要目录结构1.1 数据目录路径1.2 相关命令目录1.3 配置文件路径 二、数据库和文件系统的关系2.1 默认数据库2.2 数据库在文件系统中的表示2.3 数据表在文件系统中的表示2.3.1 InnoDB 存储引擎模式2.3.2 MyISAM 存储引擎模式 2.4 视图在文件系统中的表示2.5…

基于FPGA的多路彩灯控制器VHDL代码Quartus仿真

名称&#xff1a;基于FPGA的多路彩灯控制器VHDL代码Quartus仿真&#xff08;文末获取&#xff09; 软件&#xff1a;Quartus 语言&#xff1a;VHDL 代码功能&#xff1a; 多路彩灯控制器 综合训练内容要求 设计一台基于FPGA的多路彩灯控制器的设计。要求如下 1.彩灯从左…

怎样扫描二维码后看图片?图片二维码的制作方式

二维码是一种可以用来存储大量内容&#xff0c;通过扫描二维码的方式来向其他人提供内容&#xff0c;比较常见的展示内容有视频、图片、文件、文本、音频等。那么图片生成二维码的方法是什么样的呢&#xff1f;通过扫码查看图片&#xff0c;可以不下载的图片的同时快速预览内容…

工控人机交互界面编辑软件附描述(电脑软件分享)

HMI 概述&#xff1a;本文为分享型文档 本文摘要 昆仑通泰触摸屏软件分享。   给触摸屏下载程序时使用。   本人用过案例西门子s7-1200/200smart ST30与触摸屏型号“TPC1061Ti”通讯。 文章目录 本文摘要1.MCGS组态环境嵌入式版&#xff0c;大部分人用过此款&#xff0c;容…

JavaScript余数运算符

console.log(5 % 2); //5 2 * 2 1 console.log(8 % 3); //8 2 * 3 2 console.log(6 % 2); //6 2 * 3 0 console.log(7 % 2); //7 2 * 3 1● 我们可以利用这个特性来判断一个数是奇数还是偶数 const isEven n >n % 2 0 ? console.log(${n}是偶数) : console.…

麦肯锡精英高效阅读法笔记

系列文章目录 如何有效阅读一本书笔记 读懂一本书笔记 麦肯锡精英高效阅读法笔记 文章目录 系列文章目录序章 无法读书的5个理由无法读书的理由① 忙于工作&#xff0c;没时间读书无法读书的理由② 不知应该读什么无法读书的理由③ 没读完的书不断增多无法读书的理由④ 工作繁…

在2-3-4树上实现连接与分裂操作的算法与实现

在2-3-4树上实现连接与分裂操作的算法与实现 引言1. 维护2-3-4树结点的高度属性伪代码示例 2. 实现连接操作伪代码示例 3. 证明简单路径p的划分性质4. 实现分裂操作伪代码示例 C代码示例结论 引言 2-3-4树是一种平衡搜索树&#xff0c;它保证了树的高度被有效控制&#xff0c;…

GhostNetV2 Enhance Cheap Operation with Long-Range Attention 论文学习

论文地址&#xff1a;https://arxiv.org/abs/2211.12905 代码地址&#xff1a;https://github.com/huawei-noah/Efficient-AI-Backbones/tree/master/ghostnetv2_pytorch 解决了什么问题&#xff1f; 在计算机视觉领域&#xff0c;深度神经网络在诸多任务上扮演着重要角色。为…

机器学习实践:超市商品购买关联规则分析

第2关&#xff1a;动手实现Apriori算法 任务描述 本关任务&#xff1a;编写 Python 代码实现 Apriori 算法。 相关知识 为了完成本关任务&#xff0c;你需要掌握 Apriori 算法流程。 Apriori 算法流程 Apriori 算法的两个输人参数分别是最小支持度和数据集。该算法首先会生成所…

【最大公约数 并集查找 调和级数】1998. 数组的最大公因数排序

本文涉及知识点 最大公约数 并集查找 调和级数 LeetCode1998. 数组的最大公因数排序 给你一个整数数组 nums &#xff0c;你可以在 nums 上执行下述操作 任意次 &#xff1a; 如果 gcd(nums[i], nums[j]) > 1 &#xff0c;交换 nums[i] 和 nums[j] 的位置。其中 gcd(nums…

面试经验分享 | 蓝队面试经验

关于蓝队面试经验 1.自我介绍能力 重要性 为什么将自我介绍能力放在第一位&#xff0c;实际上自我介绍才是面试中最重要的一点&#xff0c;因为护网面试并没有确定的题目&#xff0c;让面试官去提问 更多是的和面试官的一种 “交谈” &#xff0c;面试的难易程度也自然就取决…

三维点云处理-模型拟合

以直线拟合为例&#xff0c;模型拟合常用的方法有Least Square&#xff08;最小二乘&#xff09;、Hough Transform&#xff08;霍夫变换&#xff09;、Random Sample Consensus&#xff08;RANSAC&#xff09;等。那么该如何区分和使用这几种方法呢&#xff1f; 情况1&#x…

基于springboot实现夕阳红公寓管理系统项目【项目源码+论文说明】

基于springboot实现夕阳红公寓管理系统演示 摘要 如今社会上各行各业&#xff0c;都在用属于自己专用的软件来进行工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。互联网的发展&#xff0c;离不开一些新的技术&#xff0c;而新技术的产生往往是…

深入理解Java虚拟机(JVM)

引言&#xff1a; Java虚拟机&#xff08;JVM&#xff09;是Java平台的核心组件&#xff0c;它负责将Java字节码转换成平台特定的机器指令&#xff0c;并在相应的硬件和操作系统上执行。JVM的引入使得Java语言具有“一次编写&#xff0c;到处运行”的跨平台特性。本文将深入探…

W801学习笔记二十一:英语背单词学习应用——上

英语背单词是比较常见的学习APP&#xff0c;参考唐诗宋词应用&#xff0c;本章做一个类似的应用。 一、单词数据清洗及格式转换 诗词数据的获取渠道很多&#xff0c;一般可以按照年级来分文件。如一到九年级&#xff0c;四六级&#xff0c;雅思等等。 1、先从网上某某地方下载…

【计算机科学速成课】笔记一

文章目录 写在前面1.计算机的早期历史2.电子计算机3.布尔运算和逻辑门4.二进制5.算术逻辑单元-ALU6.寄存器和内存 写在前面 所有的一切源于这样一个网站——CS自学指南。 这是新手小白入门计算机科学必要了解的知识——【计算机科学速成课】[40集全/精校] - Crash Course Comp…

HTML_CSS学习:尚硅谷——尚品汇

一、index.html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>荣耀</title> <!-- 引入页签图标--><link rel"shortcut icon" href"./HONOR%20.ico" type&qu…