一、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,即未统计数量之前各个像素的个数都是0for (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;    // 统计输入图像的像素个数Ndouble 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;           // 变量初始化为0int 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,即未统计数量之前各个像素的个数都是0for (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,即未统计数量之前各个像素的个数都是0for (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);}

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

  1. 基于MATLAB改进Otsu阈值分割的车道线检测

    基于MATLAB改进Otsu阈值分割的车道线检测 摘要:在判断车道偏离以防止车辆碰撞等危害时,车道标线检测需要通过图像处理来进行,检测方法是否适用于各种背景环境条件以及检测的及时性至关重要传统的Ots ...

  2. 【MATLAB教程案例23】基于MATLAB图像分割算法仿真——阈值分割法、Otsu阈值分割法、K均值聚类分割法等

    FPGA教程目录 MATLAB教程目录 目录 1.软件版本 2.通过二值图实现图像分割 3.通过Otsu阈值分割实现图像分割

  3. 【图像分割】基于收缩系数的粒子群混合引力搜索算法多级图像阈值分割算法研究附matlab代码

  4. 支持动图、一键生成,基于 React 的开源像素绘画应用 —— Pixel Art to CSS

    目录 ​编辑 前言简介--何为React? 基于React的 Pixel Art to CSS 绘画编辑器 示例 效果演示 生成代码 具体操作 其他功能 下载地址 前言简介--何为React? 基于R ...

  5. 学习—吴恩达《机器学习》—手敲代码_准备工作之基于Ubuntu系统的 Anaconda(python环境)搭建

    题记--初听不识曲中意,再听已是曲中人. 序曲 一直以来想找个机会与时间去了解一下机器学习.与此同时,吴恩达博士的名字一直在耳边回响,却不知为何如此响彻.后来,在couresa上看到了吴恩达博士的&l ...

  6. 【CV】CVPR2020丨SPSR:基于梯度指导的结构保留超分辨率方法

    作者 | Alan 授权转载自 | https://zhuanlan.zhihu.com/p/121721537 CVPR2020:Structure-Preserving Super Resolut ...

  7. 显著改善分割预测,ETH开源基于情景图储存网络的视频目标分割|ECCV2020

    原文链接:https://bbs.cvmart.net/articles/3119 专注计算机视觉前沿资讯和技术干货 微信公众号:极市平台 官网:https://www.cvmart.net/ 本文主 ...

  8. 合流超几何函数_【CV】CVPR2020丨SPSR:基于梯度指导的结构保留超分辨率方法

    作者 | Alan 授权转载自 | https://zhuanlan.zhihu.com/p/121721537 CVPR2020:Structure-Preserving Super Resolut ...

  9. C++手敲Roberts_Prewitt_Sobel实现阈值分割

    使用OPENCV,编写代码,学习图像二值化算法,以及边缘检测算法,进行图像的分割. 下面主要介绍Robert算子的实现过程: ①任务分析 调入并显示图像:使用Roberts 算子对图像进行边缘检测处理 ...

最新文章

  1. vs code vue模板创建
  2. 运用BeautifulSoup抓取网页的链接
  3. 网站开发建设过程中所涉及到的技术问题应当如何面对?
  4. Spring boot拦截器登录检查
  5. __str__的用法
  6. 【Java基础总结】多线程
  7. chart.js绘制精美的数据化图形--入门示例
  8. c++ object model
  9. 下载腾讯视频里的视频_手机腾讯视频如何升级新版本
  10. 【Elasticsearch】使用自适应副本选择改进弹性搜索的响应延迟
  11. ArcGIS中,一个点集里的点两两连线,比如有4个点,就连6条线
  12. python 字符串_Python中常用的8种字符串操作方法
  13. 强化学习与环境不确定_不确定性意识强化学习
  14. 极速PDF编辑器提示缺少字体如何解决
  15. QTreeWidget支持双击编辑Item节点的内容
  16. Java进阶之路对标阿里P6(8)——分布式理论及框架设计Netty
  17. 闭区间连续函数的性质
  18. Matlab根据特征值排序特征向量
  19. minus 如何实现不去重效果
  20. charles https 抓包

热门文章

  1. AutoCAD怎么自定义线型
  2. 计算机如何配置交换机,Win7电脑怎么配置交换机|Win7电脑配置交换机的详细步骤...
  3. Minecraft TrMenu 菜单插件 研究心得
  4. 「Python」学习Day 3. dict记录学生名字和成绩
  5. 最有效的期货趋势策略:期货反向跟单
  6. ESP32开发之旅——AS608指纹识别模块
  7. Python爬虫之煎蛋网妹子图爬虫,解密图片链接加密方式
  8. 【转载】swf 加密:采用byteArray 方式,增加字符串加密,可加密大文件swf
  9. 南京睿督解读:ISO12944涂料防腐蚀等级分类及防腐蚀年限规定
  10. McAfee VirusScan v8.7官方版