图像分割-阈值处理详解(迭代法、Otsu法、平滑改善法、边缘改进法、分块处理法、局部特性法、移动平均法)

博主联系方式:
QQ:1540984562
QQ交流群:892023501
群里会有往届的smarters和电赛选手,群里也会不时分享一些有用的资料,有问题可以在群里多问问。

阈值处理详解

    • 基础:
    • 基于全局的阈值处理
      • 1迭代算法(最小概率误判)
      • 2基于Otsu最佳全局阈值方法(非常有效)
      • 3用图像平滑改善全局阈值处理
      • 4利用边缘改进全局阈值处理
    • 基于局部的阈值处理
      • 1图像分块可变阈值处理
      • 2基于局部图像特性的可变阈值处理
      • 3基于移动平均法的可变阈值

基础:

首先将灰度图转化成灰度直方图,横坐标是灰度值(0-255),纵坐标是像素个数。(归一化之后表征的是像素出现的概率)
如下图所示:
灰度直方图性质:
灰度直方图性质
两幅灰度直方图两幅灰度直方图
如图,从图A可以看出,直方图有两个明显的波峰和一个明显的波谷,表明灰度普遍分为两个密集区域。此时将门限设置在两者之间的波谷,则可以很好地分割出背景和物体。
同理,观察图B,有三个明显的波峰和两个明显的波谷,此时可以设置双门限,将图像分割为三类,如下图冰山就是很好的例子,分割为暗背景、冰山的明亮区域和阴影区域。
冰山
然而并不是所有图像的直方图都是有明显的多个波峰和波谷的。
单峰型:
单峰型

无明显波谷型
无明显波谷型

灰度趋于一致型(被噪声污染过)
灰度趋于一致型

灰度阈值取决于波谷的宽度和深度,影响波谷特性的关键因素有:
1、波峰的间隔(波峰离得越远,分离这些模式机会越好)
2、图像中的噪声内容(模式随噪声的增加而展宽)
3、物体和背景的相对尺寸
4、光源的均匀性
5、图像反射的均匀性

接下来的所有的阈值处理方法,其目的都是:将灰度直方图变得好处理 并 找到分割背景和物体的门限灰度值

基于全局的阈值处理

1迭代算法(最小概率误判)

公式推导:
1
2
3
4
算法步骤:
分割前提基于全局的迭代算法
代码实现:

void Iteration(Mat* srcImage, Mat* dstImage, float delta_T)
{//【1】求最大灰度和最小灰度byte max_his = 0;byte min_his = 255;int height = (*srcImage).rows;int width = (*srcImage).cols;for (int j = 0;j < height;j++){for (int i = 0;i < width;i++){if ((*srcImage).at<uchar>(j, i) > max_his){max_his = (*srcImage).at<uchar>(j, i);}if ((*srcImage).at<uchar>(j, i) < min_his){min_his = (*srcImage).at<uchar>(j, i);}}}float T = 0.5 * (max_his+ min_his);float m1 = 255;		//当m1 m2都取0时,会有错误float m2 = 0;float old_T = T;float new_T = 0.5 * (m1 + m2);int times = 10;//while (times--)while (abs(new_T - old_T) > delta_T){int G1 = 0;int G2 = 0;int timer_G1 = 0;int timer_G2 = 0;for (int j = 0;j < height;j++){for (int i = 0;i < width;i++){if ((*srcImage).at<uchar>(j, i) > old_T){G1 += (*srcImage).at<uchar>(j, i);timer_G1++;}else{G2 += (*srcImage).at<uchar>(j, i);timer_G2++;}}}m1 = G1 * 1.0f / timer_G1;m2 = G2 * 1.0f / timer_G2;old_T = new_T;new_T = 0.5 * (m1 + m2);}cout << "迭代方法阈值为:" << new_T << endl;	//根据得出的阈值二值化图像for (int j = 0;j < height;j++){for (int i = 0;i < width;i++){if ((*srcImage).at<uchar>(j, i) > new_T){(*dstImage).at<uchar>(j, i) = 255;}else{(*dstImage).at<uchar>(j, i) = 0;}}}
}
int main()
{Mat srcImage = imread("D:\\opencv_picture_test\\新垣结衣\\test2.jpg", 0);	//读入的时候转化为灰度图namedWindow("原始图", WINDOW_NORMAL);//WINDOW_NORMAL允许用户自由伸缩窗口imshow("原始图", srcImage);Mat dstImage;dstImage.create(srcImage.rows, srcImage.cols, CV_8UC1);double time0 = static_cast<double>(getTickCount());	//记录起始时间//阈值处理+二值化//My_P_tile(&srcImage,&dstImage,20);			//设P为20Iteration(&srcImage, &dstImage,0.02);//一系列处理之后time0 = ((double)getTickCount() - time0) / getTickFrequency();//cout << "此方法运行时间为:" << time0 << "秒" << endl;	//输出运行时间namedWindow("效果图", WINDOW_NORMAL);//WINDOW_NORMAL允许用户自由伸缩窗口imshow("效果图", dstImage);dstImage = My_Rraw_histogram(&srcImage);namedWindow("一维直方图", WINDOW_NORMAL);//WINDOW_NORMAL允许用户自由伸缩窗口imshow("一维直方图", dstImage);waitKey(0);return 0;
}

当直方图存在比较明显的波谷时,这种方法是比较好的。δT控制迭代次数,下面是代码实现效果
原图
二值图
一维直方图
阈值

2基于Otsu最佳全局阈值方法(非常有效)

大津法又叫最大类间方差法、最大类间阈值法(OTSU)。
它的基本思想是,用一个阈值将图像中的数据分为两类,
一类中图像的像素点的灰度均小于这个阈值,另一类中的图像的像素点的灰度均大于或者等于该阈值。 //一般来说使用遍历的方法来求
如果这两个类中像素点的灰度的方差越大,说明获取到的阈值就是最佳的阈值
(方差是灰度分布均匀性的一种度量,背景和前景之间的类间方差越大,说明构成图像的两部分的差别越大,当部分前景错分为背景或部分背景错分为前景都会导致两部分差别变小。因此,使类间方差最大的分割意味着错分概率最小。)。
则利用该阈值可以将图像分为前景和背景两个部分。
而我们所感兴趣的部分一般为前景。
对于灰度分布直方图有两个峰值的图像,大津法求得的T近似等于两个峰值之间的低谷。
(这段阐述转自这里https://www.jianshu.com/p/56b140f9535a)
公式推导
1
2
3
从一篇博客截来的图,罗列了我们要计算的变量。https://blog.csdn.net/u012198575/article/details/81128799
需要计算的变量

代码实现

void My_Ostu(Mat* srcImage, Mat* dstImage)
{int height = (*srcImage).rows;int width = (*srcImage).cols;int Ostu_Threshold = 0; //大津阈值int size = height * width;float variance;   //类间方差float maxVariance = 0, w1 = 0, w2 = 0, avgValue = 0;float u0 = 0, u1 = 0, u2 = 0;//生成灰度直方图int pixels[256];float histgram[256];for (int i = 0; i < 256; i++){pixels[i] = 0;}for (int j = 0; j < height; j++){for (int i = 0; i < width; i++) {pixels[(*srcImage).at<uchar>(j, i)]++;}}for (int i = 0; i < 256; i++){histgram[i] = pixels[i] * 1.0f / size;}//遍历找出类间方差最大(maxVariance)的阈值(Ostu_Threshold)for (int i = 0;i <= 255;i++){w1 = 0;w2 = 0;u1 = 0;u2 = 0;//计算背景像素占比,平均灰度for (int j = 0;j <= i;j++){w1 += histgram[j];u1 += histgram[j] * j;}u1 = u1 / w1;//计算前景像素占比,平均灰度w2 = 1 - w1;if (i == 255){u2 = 0;}else{for (int j = i + 1;j <= 255;j++){u2 += histgram[j] * j;}}u2 = u2 / w2;//计算类间方差variance = w1 * w2 * (u1 - u2) * (u1 - u2);if (variance > maxVariance){ //找到使灰度差最大的值maxVariance = variance;Ostu_Threshold = i;            //那个值就是阈值}}cout << "大津法阈值为:" << Ostu_Threshold << endl;//【3】二值化for (int j = 0; j < height; j++){for (int i = 0; i < width; i++){if ((*srcImage).at<uchar>(j, i) >= Ostu_Threshold){(*dstImage).at<uchar>(j, i) = 255;}else{(*dstImage).at<uchar>(j, i) = 0;}}}
}
int main()
{Mat srcImage = imread("D:\\opencv_picture_test\\新垣结衣\\test2.jpg", 0);	//读入的时候转化为灰度图namedWindow("原始图", WINDOW_NORMAL);//WINDOW_NORMAL允许用户自由伸缩窗口imshow("原始图", srcImage);Mat dstImage;dstImage.create(srcImage.rows, srcImage.cols, CV_8UC1);double time0 = static_cast<double>(getTickCount());	//记录起始时间//阈值处理+二值化//My_P_tile(&srcImage,&dstImage,20);			//设P为20//My_Iteration(&srcImage, &dstImage,0.02);My_Ostu(&srcImage, &dstImage);//一系列处理之后time0 = ((double)getTickCount() - time0) / getTickFrequency();cout << "此方法运行时间为:" << time0 << "秒" << endl;	//输出运行时间namedWindow("效果图", WINDOW_NORMAL);//WINDOW_NORMAL允许用户自由伸缩窗口imshow("效果图", dstImage);dstImage = My_Rraw_histogram(&srcImage);namedWindow("一维直方图", WINDOW_NORMAL);//WINDOW_NORMAL允许用户自由伸缩窗口imshow("一维直方图", dstImage);waitKey(0);return 0;
}

效果:
1
2

3用图像平滑改善全局阈值处理

总的来说就是在二值化之前先用33或者55之类的均值模板将整个图像处理一下。
不过这样的坏处是使物体与背景的边界变得有些模糊。侵蚀越多,边界误差越大。
在某些极端情况下,这种方法效果并不好。

4利用边缘改进全局阈值处理

这种方法将关注聚焦于物体与背景的边缘像素,在边缘的灰度跳动非常明显,由此得到的灰度直方图将会得到很大的改善。
在这里我们求得边缘的方法主要是梯度算子和拉普拉斯算子。
算法步骤:
算法步骤
一般来说我们确定阈值T是根据,梯度最大值或者拉普拉斯最大值的某百分比来确定的。当有不同需求时,采用不同的占比。

基于局部的阈值处理

这种阈值处理的目的是为了解决光照和反射带来的问题。

1图像分块可变阈值处理

其实就是把一个图片分割为多块,分别使用大津阈值。分块处理
分块处理后的子图像直方图
子图像直方图
上面的是书上的样例,我把原图截下来,试了试自己写的代码,效果并不是很好。
代码实现:

void My_local_adaptive(Mat* srcImage, Mat* dstImage, int areas_of_H, int areas_of_W)		//局部自适应法   基于大津阈值areas_of_H:竖直方向分割的个数  areas_of_W:横坐标方向分割的个数
{int height = (*srcImage).rows/ areas_of_H;			//每一小块的heightint width = (*srcImage).cols/ areas_of_W;			//每一小块的widthint Ostu_Threshold = 0; //大津阈值int size = height * width/ areas_of_H/ areas_of_W;		//每一小块的size//一行一行地来for (int y = 0; y < areas_of_H; y++)	{for (int x = 0; x < areas_of_W; x++){float variance = 0;   //类间方差float maxVariance = 0, w1 = 0, w2 = 0, avgValue = 0;float u0 = 0, u1 = 0, u2 = 0;//生成areas_of_W*areas_of_H个局部灰度直方图int pixels[256];float histgram[256];for (int i = 0; i < 256; i++){pixels[i] = 0;}//【处理每个小区域并且二值化】//【计算直方图】for (int j = y* height; j < ((y + 1 == areas_of_H) ? (*srcImage).rows : (y + 1) * height); j++) //? : 是一个三目运算符,也是唯一的一个三目运算符。?前面表逻辑条件,:前面也就是?后面表示条件成立时的值,:后面表条件不成立时的值。例如,当a > b时,x = 1否则x = 0,可以写成x = a > b ? 1 : 0。{for (int i = x * width; i < ((x + 1 == areas_of_W) ? (*srcImage).cols : (x + 1) * width); i++){pixels[(*srcImage).at<uchar>(j, i)]++;}}//【直方图归一化】for (int i = 0; i < 256; i++){histgram[i] = pixels[i] * 1.0f / size;}//遍历找出类间方差最大(maxVariance)的阈值(Ostu_Threshold)for (int i = 0;i <= 255;i++){w1 = 0;w2 = 0;u1 = 0;u2 = 0;//计算背景像素占比,平均灰度for (int j = 0;j <= i;j++){w1 += histgram[j];u1 += histgram[j] * j;}u1=u1/w1;//计算前景像素占比,平均灰度w2 = 1 - w1;if (i == 255){u2 = 0;}else{for (int j = i + 1;j <= 255;j++){u2 += histgram[j] * j;}}u2=u2/w2;//计算类间方差variance = w1 * w2 * (u1 - u2) * (u1 - u2);if (variance > maxVariance){ //找到使灰度差最大的值maxVariance = variance;Ostu_Threshold = i;            //那个值就是阈值}}cout << "大津法阈值为:" << Ostu_Threshold << endl;//【3】二值化for (int j = y * height; j < ((y + 1 == areas_of_H) ? (*srcImage).rows : (y + 1) * height); j++) //? : 是一个三目运算符,也是唯一的一个三目运算符。?前面表逻辑条件,:前面也就是?后面表示条件成立时的值,:后面表条件不成立时的值。例如,当a > b时,x = 1否则x = 0,可以写成x = a > b ? 1 : 0。{for (int i = x * width; i < ((x + 1 == areas_of_W) ? (*srcImage).cols : (x + 1) * width); i++){if ((*srcImage).at<uchar>(j, i) >= Ostu_Threshold){(*dstImage).at<uchar>(j, i) = 255;}else{(*dstImage).at<uchar>(j, i) = 0;}}}}}
}
int main()
{//Mat srcImage = imread("D:\\opencv_picture_test\\新垣结衣\\test2.jpg", 0);	//读入的时候转化为灰度图//Mat srcImage = imread("D:\\opencv_picture_test\\miku\\miku2.jpg", 0);	//读入的时候转化为灰度图Mat srcImage = imread("D:\\opencv_picture_test\\阈值处理\\带噪声阴影的图.png", 0);	//读入的时候转化为灰度图namedWindow("原始图", WINDOW_NORMAL);//WINDOW_NORMAL允许用户自由伸缩窗口imshow("原始图", srcImage);Mat dstImage;dstImage.create(srcImage.rows, srcImage.cols, CV_8UC1);double time0 = static_cast<double>(getTickCount());	//记录起始时间//阈值处理+二值化//My_P_tile(&srcImage,&dstImage,20);			//设P为20//My_Iteration(&srcImage, &dstImage,0.01);//My_Ostu(&srcImage, &dstImage);My_local_adaptive(&srcImage, &dstImage, 1, 2);//一系列处理之后time0 = ((double)getTickCount() - time0) / getTickFrequency();cout << "此方法运行时间为:" << time0 << "秒" << endl;	//输出运行时间namedWindow("效果图", WINDOW_NORMAL);//WINDOW_NORMAL允许用户自由伸缩窗口imshow("效果图", dstImage);dstImage = My_Rraw_histogram(&srcImage);namedWindow("一维直方图", WINDOW_NORMAL);//WINDOW_NORMAL允许用户自由伸缩窗口imshow("一维直方图", dstImage);waitKey(0);return 0;
}

全局大津阈值效果
all black
2
局部阈值法:1*2分割
2
![2](https://img-blog.csdnimg.cn/202003
迭代阈值法:
1
2
看来仍然需要改进

2基于局部图像特性的可变阈值处理

算法步骤:
1、计算以某一像素为中心的邻域的灰度标准差和均值
计算所需变量
2、设定可变阈值算法步骤
3、观察是否满足阈值条件算法步骤
4、二值化
阈值处理

其中a和b都是需要人工整定。
效果图:
效果图

3基于移动平均法的可变阈值

算法描述
有关的链接:(这个算法我还没有理解,等我理解了再来补充)
https://blog.csdn.net/qq_34510308/article/details/93162142

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

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

相关文章

java 用户控件_C#自定义控件VS用户控件

C#中自定义控件VS用户控件大比拼1 自定义控件与用户控件区别WinForm中&#xff0c;用户控件(User Control)&#xff1a;继承自 UserControl&#xff0c;主要用于开发 Container 控件&#xff0c;Container控件可以添加其他Controls控件自定义控件(Custom Control)&#xff1a;继…

Java FileDescriptor sync()方法与示例

FileDescriptor类sync()方法 (FileDescriptor Class sync() method) sync() method is available in java.io package. sync()方法在java.io包中可用。 sync() method is used to synchronize all the system buffer with the underlying device. sync()方法用于将所有系统缓冲…

windows 7资源管理器崩溃解决方法

最近被频繁的资源管理器explorer崩溃搞得几乎生活不能处理&#xff0c;一打开带有视频的文件夹&#xff0c;就explorer崩溃&#xff0c;要疯了。 日志中含有mpc_mtcontrol.dll 查来查去&#xff0c;没有一个确定的答案&#xff0c;baidu&#xff0c;google都没用&#xff0c;最…

03-对图像进行数值计算和加权融合

数值计算 import cv2img_1 cv2.imread(E:\Jupyter_workspace\study\data/beyond.png) img_2 cv2.imread(E:\Jupyter_workspace\study\data/water2.png)img_1_1 img_1 10 img_1[:2,:,0]#输出前两行就行 img_1_1[:2,:,0]#[h,w,c] img_2[:2,:,0]#[h,w,c](img_1 img_2)[:2,:,…

SQL Server存储过程(procedure)应用

用户反映&#xff0c;系统操作日志会使用数据库快速增大&#xff0c;情况可参考下图&#xff0c; 问题分析&#xff0c;整个系统每个页面&#xff0c;都有写记录用户操作代码&#xff0c;修改或禁用这个代码&#xff0c;看来是不可能的。 在原有系统参数表添加一个选项&#xf…

Opencv一维直方图的绘制

下面是我参考《opencv3编程入门》写的绘制一维直方图的代码 using namespace cv; using namespace std; #define byte uchar #define TYEPE_GRAY 0 #define TYEPE_RGB 1 /*--------------------------绘制RGB三色一维直方图-------------------------------------*/ Mat My_R…

Java类class forName()方法及示例

类类forName()方法 (Class class forName() method) forName() method is available in java.lang package. forName()方法在java.lang包中可用。 forName() method is used to return the class object for the Class with the given class_name. forName()方法用于返回具有给…

04-图像的阈值操作

对图像的阈值操作 import cv2 import matplotlib.pyplot as pltimg cv2.imread(E:/Jupyter_workspace/study/data/cat.png,1) ret, thresh1 cv2.threshold(img,127,255,cv2.THRESH_BINARY) ret, thresh2 cv2.threshold(img,127,255,cv2.THRESH_BINARY_INV) ret, thresh3 c…

java虚拟机内存监控_深入理解JVM虚拟机9:JVM监控工具与诊断实践

本文转自&#xff1a;https://juejin.im/post/59e6c1f26fb9a0451c397a8c本系列文章将整理到我在GitHub上的《Java面试指南》仓库&#xff0c;更多精彩内容请到我的仓库里查看https://github.com/h3pl/Java-Tutorial喜欢的话麻烦点下Star哈文章将同步到我的个人博客&#xff1a;…

详解:数据库名、实例名、ORACLE_SID、数据库域名、全局数据库名、服务名

数据库名、实例名、数据库域名、全局数据库名、服务名&#xff0c;这是几个令很多初学者容易混淆的概念。相信很多初学者都与我一样被标题上这些个概念搞得一头雾水。我们现在就来把它们弄个明白。 一、数据库名什么是数据库名&#xff1f;数据库名就是一个数据库的标识&#…

颜色缩减 -利用指针、迭代器、动态地址实现访问像素

为什么要使用颜色缩减 在对单通道图像进行处理时&#xff0c;像素的可能值为256个&#xff0c;但处理多通道时&#xff0c;像素的处理就会相当麻烦&#xff0c;其实用这些颜色中具有代表性的一小部分就可以达到同样的效果&#xff0c;所以颜色空间缩减就可以派上用场了。一个信…

setlenient_Java日历setLenient()方法与示例

setlenient日历类setLenient()方法 (Calendar Class setLenient() method) setLenient() method is available in java.util package. setLenient()方法在java.util包中可用。 setLenient() method is used to set or unset lenient status of date or time interpretations. s…

PowerShell_9_零基础自学课程_9_高级主题:静态类和类的操作

哈哈&#xff0c;昨天弄了个ubuntu 11.10在虚拟机上运行&#xff0c;发现11.10界面非常绚丽&#xff0c;但是其需要的系统资源非常多&#xff0c;我虚拟机设定内存为512M&#xff0c;1个CPU4个核心&#xff0c; 进入以后发现根本动不了&#xff0c;因此今天我就下载了一个Fedor…

05-图像的平滑处理(不同的滤波操作)

对图像进行平滑处理实则就是对图像进行滤波操作罢了 每张图片都有若干个像素点所构成&#xff0c;滤波操作上就是将照片上的某些部分像素点进行修改从而达到平滑的效果 先展示一下原图 import cv2 img cv2.imread(E:\Jupyter_workspace\study\data/test1.png)cv2.imshow(te…

js删除mysql记录_(DELETEUPDATE)修改、删除数据记录_MySQL

有时&#xff0c;希望除去某些记录或更改它们的内容。DELETE 和 UPDATE 语句令我们能做到这一点。用update修改记录UPDATE tbl_name SET 要更改的列WHERE 要更新的记录这里的 WHERE 子句是可选的&#xff0c;因此如果不指定的话&#xff0c;表中的每个记录都被更新。例如&#…

C++设计模式之Abstract Factory模式

一、功能   提供一个创建一系列相关或相互依赖对象的接口&#xff0c;而无需指定它们具体的类。 二、结构图类厂最基本的结构示意图如下&#xff1a; 在实际应用中&#xff0c;类厂模式可以扩充到很复杂的情况&#xff0c;如下图所示&#xff1a; 三、优缺点 优点&#xff1…

数字图像处理小练习存档1

小练习的题目&#xff1a; 1、读取一张图&#xff0c;分解RGB三个通道 /************练习1**********************/ int main() {Mat img1 imread("D:\\opencv_picture_test\\miku2.jpg",2|4); //灰度图if (img1.empty()){printf("Could not find the imag…

UIImage 压缩

1.改变图片大小 -(UIImage*)imageWithImage:(UIImage*)image scaledToSize:(CGSize)newSize {// Create a graphics image contextUIGraphicsBeginImageContext(newSize);// Tell the old image to draw in this new context, with the desired// new size[image drawInRect:CG…

06-对图像进行腐蚀操作

形态学中的腐蚀操作一般处理的图像数据为二值的 cv2.erode(img,kernel,iterations 1) kernel表示拿多大的卷积核去腐蚀 iterations表示迭代次数 可以将一些带有毛毛的图像去毛毛化 原图 import cv2 import numpy as npdef show_photo(name,picture):cv2.imshow(name,picture)…

Java BufferedReader skip()方法与示例

BufferedReader类skip()方法 (BufferedReader Class skip() method) skip() method is available in java.io package. skip()方法在java.io包中可用。 skip() method is used to skip the given number of bytes of characters (n_bytes_of_char) from this BufferedReader. s…