C++手敲基于梯度图和像素数量数组的OTSU阈值分割

news2024/11/18 16:41:29

 一、OTSU算法原理

➢OTSU法(最大类间方差法,有时也称之为大津算法)

➢ 使用聚类的思想,把图像的灰度数按灰度级分成2个部分, 使得两个部分之间的灰度值差异最大,每个部分之间的灰 度差异最小

➢ 通过方差的计算来寻找一个合适的灰度级别来划分。

➢ 可以在二值化的时候采用OTSU算法来自动选取阈值进行 二值化。

➢ OTSU算法被认为是图像分割中阈值选取的最佳算法,计 算简单,不受图像亮度和对比度的影响。

➢ 因此,使类间方差最大的分割意味着错分概率最小。

二、OTSU算法步骤:

全局阈值T可以按如下计算:

➢选择一个初时估计值T (一般为图像的平均灰度值)

➢使用T分割图像,产生两组像素:G1包括灰度级大于 T的像素,G2包括灰度级小于等于T的像素

➢计算G1 中像素的平均值并赋值给μ1,计算G2 中像素 的平均值并赋值给μ2

➢计算一个新的阈值:

➢重复步骤 2 ~ 4,一直到两次连续的T之间的差小于 预先给定的上界T

三、代码实现 

当我们用边缘梯度算子(如Roberts、Soberl、Prewitt等)处理原始图像拿到梯度图之后,可以创建一个1*256维的数组来存放梯度图中每个像素的个数,其具体的实现方式如下:

int pixel_num[256] = { 0 };     //数组记得初始化为0,即未统计数量之前各个像素的个数都是0
	for (int row = 0; row < img.rows; row++) {
		for (int col = 0; col < img.cols; col++) {
			pixel_num01[Sobel_City.at<uchar>(row, col)] += 1;   //遍历到的像素值作为索引,次数+1
		}
	}

以下是OTSU的实现方法:
①由传入的梯度图拿到图像的维度,创建一个空白图像用于保留运算得到的结果,并且拿到梯度图像的总像素个数。

Mat OTSU = Mat::zeros(Size(img.cols, img.rows), img.type());
int N = img.rows * img.cols;

②由像素数量数组,得到每一个像素对应的概率:

double pro[256] = { 0 };            // 定义概率数组
	for (int i = 0; i < 256; i++) {
		pro[i] = arr[i] / N;            // 得到每个像素值的概率
	}

注意创建的概率数组是浮点型,定义为整形的话会出现全部像素的概率都为零的情况。

③依次创建OTSU计算的过程量,包括两类的概率、均值,还有阈值T以及方差v等等;先计算得到两类的均值以及概率,根据公式v = w0 * w1 * pow(u1 - u0, 2);就可以得到此次遍历的像素作为阈值得到的方差。

double delta = 0, w0 = 0, w1 = 0, u0 = 0, u1 = 0, v = 0;           // 变量初始化为0
int T = 0, thresh = 0;
while (T <= 255) {
		for (int i = 0; i <= T; i++) {
			w0 += pro[i];                       // C0的概率
		}
		w1 = 1 - w0;                            // C1的概率
		for (int i = 0; i <= 255; i++) {
			if (i <= T) {
				u0 += pro[i] * i;                     // C0的平均值
			}
			else {
				u1 += pro[i] * i;                     // C1的平均值
			}
			v = w0 * w1 * pow(u1 - u0, 2);
			if (v > delta) {
				delta = v;
				thresh = T;
			}
			T += 1;
		}
	}

遍历循环的过程中要对方差进行判断,如果此次计算的方差要比之前的最大方差delta要大,那么对delta进行更新,同时也用thresh记录下历史最大方差对应的像素值,作为分割的阈值T。这样一来,遍历的结果就可以得到我们想要的,能使两类差异最大的阈值T。

④通过对与梯度图同维大小的空白图片遍历,给每一个像素进行二值化。若遍历到的像素值大于等于T,则赋值为255,否则为0。

for (int row = 0; row < img.rows; row++) {
		for (int col = 0; col < img.cols; col++) {
			if (img.at<uchar>(row, col) > thresh) {    //遍历判断
				OTSU.at<uchar>(row, col) = 255;
			}
		}
	}

⑤显示OTSU图像,得到阈值分割结果:

imshow("OTSU阈值分割", OTSU);
waitKey(0);
destroyAllWindows();


四、代码汇总

#include<opencv2/opencv.hpp>
#include<iostream>
#include<math.h>

using namespace cv;
using namespace std;

void Sobel(Mat img);
void OTSU(Mat& img, int arr[]);

int main() {
	Mat Gray = imread("C:\\Users\\Czhannb\\Desktop\\gray.png", IMREAD_GRAYSCALE);
	if (Gray.empty()) {
		cout << "读取图片错误!" << endl;
	}
	else {
		imshow("未动工之前:", Gray);
	}
	Sobel(Gray);
	return 0;
}

void OTSU(Mat& img, int arr[]) {   // 需要输入待处理的图像 和 直方图像素个数数组
	int N = img.rows * img.cols;    // 统计输入图像的像素个数N
	double pro[256] = { 0 };            // 定义概率数组
	for (int i = 0; i < 256; i++) {
		pro[i] = arr[i] / N;            // 得到每个像素值的概率
	}
	double delta = 0, w0 = 0, w1 = 0, u0 = 0, u1 = 0, v = 0;           // 变量初始化为0
	int T = 0, thresh = 0;
while (T <= 255) {
		for (int i = 0; i <= T; i++) {
			w0 += pro[i];                       // C0的概率
		}
		w1 = 1 - w0;                            // C1的概率
		for (int i = 0; i <= 255; i++) {
			if (i <= T) {
				u0 += pro[i] * i;                     // C0的平均值
			}
			else {
				u1 += pro[i] * i;                     // C1的平均值
			}
			v = w0 * w1 * pow(u1 - u0, 2);
			if (v > delta) {
				delta = v;
				thresh = T;
			}
			T += 1;
		}
	}
	Mat OTSU = Mat::zeros(Size(img.cols, img.rows), img.type());
	for (int row = 0; row < img.rows; row++) {
		for (int col = 0; col < img.cols; col++) {
			if (img.at<uchar>(row, col) > int(thresh)) {
				OTSU.at<uchar>(row, col) = 255;
			}
		}
	}
	imshow("OTSU阈值分割", OTSU);
	waitKey(0);
	destroyAllWindows();
}

void Sobel(Mat img) {                   // 基于Prewitt算子的阈值分割
	Mat Sobel_City = Mat::zeros(Size(img.cols, img.rows), img.type());  //用于计算城市距离的空白图像
	Mat Sobel_Ojld = Mat::zeros(Size(img.cols, img.rows), img.type());  //用于计算欧几里得距离的空白图像
	for (int row = 1; row < img.rows - 1; row++) {
		for (int col = 1; col < img.cols - 1; col++) {                 // 由于像素之间的加减可能会小于零,因此记得加上绝对值函数fabs()
			Sobel_City.at<uchar>(row, col) = saturate_cast<uchar>(fabs(img.at<uchar>(row - 1, col + 1) - img.at<uchar>(row - 1, col - 1) + 2*img.at<uchar>(row, col + 1) - 2*img.at<uchar>(row, col - 1) + img.at<uchar>(row + 1, col + 1) - img.at<uchar>(row + 1, col - 1)) + fabs(img.at<uchar>(row + 1, col - 1) - img.at<uchar>(row - 1, col - 1) + 2*img.at<uchar>(row + 1, col) - 2*img.at<uchar>(row - 1, col) + img.at<uchar>(row + 1, col + 1) - img.at<uchar>(row - 1, col + 1)));
		}
	}
	for (int row = 1; row < img.rows - 1; row++) {
		for (int col = 1; col < img.cols - 1; col++) {
			Sobel_Ojld.at<uchar>(row, col) = saturate_cast<uchar>(sqrt(pow(img.at<uchar>(row - 1, col + 1) - img.at<uchar>(row - 1, col - 1) + 2*img.at<uchar>(row, col + 1) - 2*img.at<uchar>(row, col - 1) + img.at<uchar>(row + 1, col + 1) - img.at<uchar>(row + 1, col - 1), 2) + pow(img.at<uchar>(row + 1, col - 1) - img.at<uchar>(row - 1, col - 1) + 2*img.at<uchar>(row + 1, col) - 2*img.at<uchar>(row - 1, col) + img.at<uchar>(row + 1, col + 1) - img.at<uchar>(row - 1, col + 1), 2)));
		}
	}
	imshow("Sobel图像(街区距离)", Sobel_City);
	imshow("Sobel图像(欧几里得距离)", Sobel_Ojld);
	int pixel_num01[256] = { 0 };            //数组记得初始化为0,即未统计数量之前各个像素的个数都是0
	for (int row = 0; row < img.rows; row++) {
		for (int col = 0; col < img.cols; col++) {
			pixel_num01[Sobel_Ojld.at<uchar>(row, col)] += 1;   //遍历到的像素值作为索引,次数+1
		}
	}
	int times = 0;         //定义遍历数组的变量,从0开始,依次输出0到255每个像素值的数目
	while (times <= 255) {
		cout << "像素值" << times << "的数目为:  " << pixel_num01[times] << endl;    // 遍历输出
		times++;                             // 不要忘了自增
	}
	//得到10作为分割阈值
	for (int row = 0; row < img.rows; row++) {
		for (int col = 0; col < img.cols; col++) {       //遍历所有像素点进行判断
			if (Sobel_Ojld.at<uchar>(row, col) < 70) {
				Sobel_Ojld.at<uchar>(row, col) = 0;     // 小于阈值赋值为0,否则赋值255
			}
			else {
				Sobel_Ojld.at<uchar>(row, col) = 255;
			}
		}
	}
	imshow("欧几里得阈值分割", Sobel_Ojld);
	int pixel_num02[256] = { 0 };            //数组记得初始化为0,即未统计数量之前各个像素的个数都是0
	for (int row = 0; row < img.rows; row++) {
		for (int col = 0; col < img.cols; col++) {
			pixel_num01[Sobel_City.at<uchar>(row, col)] += 1;   //遍历到的像素值作为索引,次数+1
		}
	}
	int time = 0;         //定义遍历数组的变量,从0开始,依次输出0到255每个像素值的数目
	while (times <= 255) {
		cout << "像素值" << time << "的数目为:  " << pixel_num02[time] << endl;    // 遍历输出
		time++;                             // 不要忘了自增
	}


	OTSU(Sobel_Ojld, pixel_num01);     // OTSU阈值分割
	OTSU(Sobel_City, pixel_num02);

}

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

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

相关文章

数学建模-2022年亚太赛C题(含思路过程和代码)

目录 一、题目以及大概的思路 二、数据预处理 三、预测模型 四、全球变暖的相关性分析 五、赛后总结 一、题目以及大概的思路 先对数据进行无量纲化处理&#xff0c;根据所给不确定度与数据&#xff0c;计算出相对不确定度&#xff0c;并将其异常点剔除&#xff0c;通常情况…

[附源码]计算机毕业设计病房管理系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

Cisco ASA防火墙——远程控制与多安全区域

作者简介&#xff1a;一名在校云计算网络运维学生、每天分享网络运维的学习经验、和学习笔记。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 目录 前言 一.远程管理ASA 1.配置Telnet接入 2.配置SSH接入 3.配置ASDM接…

Java数据结构与Java算法学习Day02---算法排序

目录 一、简单排序 1.1Comparable接口介绍 11 1.2冒泡排序 12、13、14 1.3选择排序 15、16、17 1.4插入排序 18、19、20 二、高级排序 2.1希尔排序 21、22、23 2.2归并排序 24 2.2.1递归 24 2.2.2归并排序 25 2.3快速排序 32 2.3.1快速排序的原理 32 2.3.2快速排序…

这可能是我见过最可爱的乒乓女孩了!

3D角色艺术家Carlos Sanz曾在U-tab学习动画&#xff0c;在CICE学习角色创作&#xff0c;现在正致力于创作她的作品集并成为3D动画行业的一员&#xff0c;本文是作者在ZBrush和Maya等软件中设计乒乓女孩角色造型的教程&#xff1a; 首先给大家做个自我介绍。我叫Carlos Sanz&am…

[附源码]计算机毕业设计springboot网上电影购票系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

(阅读笔记)急性卒中CT灌注分析在临床中的实际问题

&#xff08;阅读笔记&#xff09;急性卒中CT灌注分析在临床中的实际问题IntroductionUnderstanding the basics of CTP acquisition and processingCTP thresholds and quantificationPitfalls of perfusion imagingTechnical pitfallsPatient motionContrast bolusRadiationC…

CMMI和SPCA是一样的吗?有什么区别

CMMI资质相信有很多企业都了解了&#xff0c;对于SPCA可能有些企业是比较陌生的&#xff0c;不太了解什么是SPCA&#xff0c;简单来说可以理解为CMMI是国外的资质&#xff0c;而SPCA可以理解为国内的&#xff0c;那现在就跟随同邦信息科技的小编一起来看看具体的区别是哪些吧 C…

进程以及线程

目录 &#x1f43c;今日良言:希望是生命的源泉&#xff0c;失去它生命就会枯萎。 &#x1f42f;一、进程 &#x1f415;1.概念 &#x1f415;2.PCB &#x1f415;3.进程调度 &#x1f42d;二、线程 &#x1f411;1.概念 &#x1f407;三、进程和线程的联系和区别 &…

Qt实现抽奖程序

一、简介 该程序命名为Lucky&#xff0c;实现的功能如下&#xff1a; 1. 加载抽奖人员名单&#xff0c;并保存加载路径&#xff1b; 2. 单击左键或者点击ctrls开始抽奖&#xff0c;并滚动显示人员名单&#xff0c;显示的人员名单格式为 部门-姓名。 3. 单击左键或者点击ctrls…

了解并应用数字隔离器的安全限值

介绍 电流隔离在工业和汽车系统中很常见&#xff0c;作为防止高电压或抵消接地电位差的一种手段。设计人员传统上使用光耦合器进行隔离&#xff0c;但在过去几年中&#xff0c;使用电容和磁隔离的数字隔离器变得越来越流行。对于任何此类隔离器&#xff0c;了解其安全限值的重…

关于如何找环形链表的入环点

目录一、判断一个链表是否有环二、找到链表入环的第一个节点一、判断一个链表是否有环 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的…

菇多糖-聚乙二醇-大环配体NOTA,大环配体NOTA-PEG-香菇多糖

菇多糖-聚乙二醇-大环配体NOTA&#xff0c;大环配体NOTA-PEG-香菇多糖 中文名称&#xff1a;香菇多糖-大环配体NOTA 英文名称&#xff1a;Lentinan-NOTA 别称&#xff1a;NOTA修饰香菇多糖&#xff0c;NOTA-香菇多糖 PEG接枝修饰香菇多糖 Lentinan-PEG-NOTA 香菇多糖-聚乙…

设置Excel表格“只读模式”的两种方法

Excel表格的“只读模式”可以帮助我们防止意外更改表格&#xff0c;根据不同需求&#xff0c;表格可以设置“有密码”和“无密码”的两种“只读模式”&#xff0c;下面来说说具体设置方法。 一、无密码“只读模式” 如果主要是想防止自己意外修改了表格&#xff0c;可以设置没…

Jenkins拉分支代码 + tortoiseGit删除分支

日常部署测试代码都使用Jenkins代码手工上传代码&#xff0c;主要减减减减工作量&#xff0c;提高工作效率&#xff1b; 一、安装Git、git-parameter插件及配置方法&#xff0c;安装方法忽略一万字&#xff0c;解决不了绕道度娘问问 二、创建项目&#xff0c;设置参数 This pr…

[操作系统笔记]基本分页存储管理

内容系听课复习所做笔记&#xff0c;图例多来自课程截图 基本分页存储管理 两次访存&#xff0c;第一次查页表&#xff0c;第二次访问目标内存单元 将内存空间分为一个个大小相等的分区&#xff08;比如每个分区4KB&#xff09;&#xff0c;每个分区就是一个“页框”&#xff0…

[附源码]计算机毕业设计springboot物业管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

澜沧古茶在港交所上市申请失效:收入不及八马茶业,股东提前套现

12月1日&#xff0c;贝多财经从港交所披露易了解到&#xff0c;普洱澜沧古茶股份有限公司&#xff08;下称“澜沧古茶”&#xff09;的上市申请材料失效&#xff0c;目前已无法正常查看或下载。据贝多财经了解&#xff0c;“失效”并不意味着上市失败。 事实上&#xff0c;招股…

selnium操作输入框无法输入内容

问题描述 分析问题 1、开始以为等待时间问题没有找到元素&#xff08;没解决&#xff09; 2、使用js操作元素&#xff08;没解决&#xff09; 3、定位到光标元素 4、种cookie直接走接口调用 问题描述 selenium.common.exceptions.ElementNotInteractableException: Mess…

企业数据图表- FineReport函数计算组成和语法概述

1. 概述 1.1 版本 1.2 功能简介 在设计模板时用户需要频繁的使用公式函数&#xff0c;例如&#xff1a;求和、求个数、做判断等等。 本文介绍函数的计算组成和语法。 2. 计算语法 2.1 概览 组成部分 语法 示例 函数 SUM(合同金额)、SUM(A1) 数据列 可输入有数据列的…