图像分割-阈值处理详解(迭代法、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;继…

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…

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

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

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…

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…

06-对图像进行腐蚀操作

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

Opencv实现利用滑动条来调整阈值

#include <opencv2/opencv.hpp> #include <iostream>using namespace cv; using namespace std; #define WINDOW_NAME "【程序窗口】" //为窗口标题定义的宏 //*--------------------------【练习】利用滑动条来调整阈值-----------------------------…

07-对图像进行膨胀操作

形态学中的膨胀操作即让照片变得更大&#xff0c;与腐蚀操作互为逆运算 cv2.dilate(erosion,kernel,iterations 1) 第一个参数&#xff1a;图像对象名称 第二个参数&#xff1a;卷积核的大小 第三个参数&#xff1a;迭代次数 此时就可与腐蚀操作进行相结合&#xff0c;腐蚀去毛…

08-开运算和闭运算

开运算和闭运算实则就是将腐蚀操作和膨胀操作结合而已&#xff0c;也就是个先后循序罢了 开运算&#xff1a;先腐蚀再膨胀 闭运算&#xff1a;先膨胀再腐蚀 cv2.morphologyEx(img_open,cv2.MORPH_OPEN,kernel) cv2.morphologyEx(img_close,cv2.MORPH_CLOSE,kernel) 第一个参数…

连通域标记——实现硬币自动计件

前言 在自动计算图像中有几枚硬币的任务中&#xff0c;分离出前景和背景后是否就可以马上实现自动计件&#xff0c;如果可以&#xff0c;如何实现&#xff1f;如果不可以&#xff0c;为什么&#xff1f; 答案是否定的。二值化之后我们的得到的只是前景总像素的多少&#xff0c…

09-梯度运算

梯度运算膨胀操作-腐蚀操作&#xff0c;这里的-操作是图像的减法&#xff0c;可不是简单的加减乘除运算 为了更加形象生动&#xff0c;先将膨胀和腐蚀操作的结果进行合并展示&#xff0c;然后再与梯度运算进行比较 cv2.morphologyEx(pie,cv2.MORPH_GRADIENT,kernel) 第一个参数…

Unity-Shader-渲染队列

Unity-Shader-渲染队列 渲染简介Unity中的几种渲染队列Background (1000)最早被渲染的物体的队列。Geometry (2000) 不透明物体的渲染队列。大多数物体都应该使用该队列进行渲染&#xff0c;也就是Unity Shader中默认的渲染队列。AlphaTest (2450) 有透明通道&#xff0c;需要进…

形态学操作——腐蚀与膨胀

预备知识 结构元&#xff08;SE&#xff09; 1、结构元的中心一般来说是放在其重心位置处&#xff0c;但原则上原点的选择是依赖于你要解决的问题的。 2、对图像操作时&#xff0c;我们要求结构元是矩形阵列。&#xff08;在结构元的基础上添加较少的背景元素实现&#xff09…

10-礼帽与黑帽操作

cv2.morphologyEx(img,cv2.MORPH_TOPHAT,kernel) 第一个参数&#xff1a;图像对象名称 第二个参数&#xff1a;运算类型TOPHAT为礼帽运算 第三个参数&#xff1a;卷积核的大小 礼帽运算&#xff1a;原始的输入-开运算&#xff08;先腐蚀再膨胀&#xff09; 原始带刺儿&#xff…

形态学操作——开闭运算、顶帽底(黑)帽变换

膨胀和腐蚀运算的问题&#xff1a; 边缘形状发生了变化&#xff0c;膨胀发生了扩张&#xff0c;腐蚀发生了收缩 目标物体变形&#xff0c;对识别时的特征提取会造成影响 解决方法&#xff1a; 开操作: B对A的开操作就是先B对A腐蚀&#xff0c;紧接着用B对结果进行膨胀 先腐…

11-图像梯度-Sobel算子

图像梯度是指图像某像素在x和y两个方向上的变化率&#xff08;与相邻像素比较&#xff09;&#xff0c;是一个二维向量&#xff0c;由2个分量组成&#xff0c;X轴的变化、Y轴的变化 。 其中X轴的变化是指当前像素右侧&#xff08;X加1&#xff09;的像素值减去当前像素左侧&…

形态学操作——击中击不中变换

操作目的 HitMiss变换是形态检测的一个工具&#xff0c;通过定义形状模板可以在图像中获取同一形状物体的位置坐标。 算法讲解 1、用击中结构去腐蚀原始图像得到击中结果X&#xff08;这个过程可以理解为在原始图像中寻找和击中结构完全匹配的模块&#xff0c;匹配上了之后&…