基于大津法OTSU和图像分块的阈值分割算法

一、 算法目的及原理

(1) 目的:

阈值分割可以把图像中的前景目标和背景分割开,它特别适用于目标和背景占据不同灰度级范围的图像。它不仅可以极大的压缩图像的数据信息,而且也大大简化了分析和处理步骤,因此在很多情况下,是进行图像分析、特征提取与模式识别之前的必要的图像预处理过程,常用于机器视觉产品的检测。

(2) 原理:

通过统计灰度直方图,在峰与峰的灰度级之间设定阈值,把图像分割成n类。基于OTSU的阈值分割是根据计算双峰直方图的最大类间方差,从而确定把前景目标和背景区分的最佳阈值,通过阈值把原图像分割成一张二值图像,就能明显区分出前景和背景。而图像分块则是为了解决图像的光线亮度不均匀的问题,把图像分割成只含明显双峰的小块ROI图像,再做阈值分割,就能得到比较好的分割效果。
算法基本原理

二、算法设计

(1) OTSU的最佳阈值分割:

输入:一张图像
1.统计图片的归一化灰度直方图h[256];
2. x_max=0 ; index = 0 ; 遍历k=0-254的h[k] { //0-k为A类,255-k为B类
计算概率和均值:

		p1=[k]Σ[i=0] h[i];
		m1=1/p1*[k]Σ[i=0] i*pi;
		p2=1-p1;
		m2=[255]Σ[i=k+1] i*pi;

计算类间方差:

        x=p1*p2*(m1-m2)^2;//到这里就算完了一次类间方差
        //记录最大的类间方差的阈值k;
        if(x>x_max) {
    
    
				x_max=x;
				index = k;
				}

}
3.return index; //返回最佳分割阈值

(2) 图像分块中应用OTSU进行阈值分割

输入:Mat src ,int x ,int y, bool key=0;
参数解释:

原图src;
把原图分割, 横向有x块roi,纵向有y块roi;
当src.cols不能整除x时,会有余边没有纳入roi,称为边界,key可以选择是否处理边界,
默认key=0不处理边界,不处理边界时舍弃边界,边界显示为全黑

函数体:
1、把src转换成灰度图:cvtColor();
2、计算:

int cut_cols =src.cols/x; //roi的列数;
int cut_rows = src.rows/y; //roi的行数;
remainder_cols=src.cols-cut_cols*x; //右边界的列数
remainder_rows=src.rows-cut_rows*y; //下边界的行数
Mat result = Mat::zeros(src.size(), src.type());//定义结果图像

3、分割src图像成x*y块roi图像:
循环:先从src割出[0,0]位置的一块,然后循环分割到[x,y]这一块
循环体内 对某一块roi进行处理:{
调用ostu算子,求出roi的最佳阈值k;
用最佳阈值k,对roi,做阈值分割(使用阈值分割函数:threshold),成二值图像Mat temp;
把temp保存到结果图像result对应的[x,y]位置;
}

4、补充处理边界:(key=1时)
4.1 在第3步的循环体内,当处理完某一行的roi时,就可以对“右边边界”进行处理了;

      判断if(remainder_cols>0){
    
    
      取这行中最后一小块roi,大小为(remainder_cols,cut_rows)
      然后对roi进行第3步中的循环体的操作
      }

4.2 当处理完所有的[x,y]的roi时,就可以对“下边边界”进行处理了;

      判断if(remainder_rows>0){
    
    
      循环取这一行的roi,大小为(cut_cols, remainder_rows)
      然后对roi进行第3步中的循环体的操作
4.3 循环完这一行的roi之后,就可以对“下边界的右边界”进行处理了;
      取最后一块roi,大小为(remainder_cols, remainder_rows)
      然后对roi进行第3步中的循环体的操作
      }

三、 C++代码实现:

//使用OTSU大津法求解最佳阈值

 int ostu(Mat src) {
    
    
	long H[256] = {
    
     0 };  //统计直方图
	double h[256] = {
    
     0.0 };  //归一化直方图

	if (src.channels() > 1) {
    
    
		cvtColor(src, src, COLOR_BGR2GRAY);
		//imshow("gray", gray);
	}

	for (int i = 0; i < src.rows; i++) {
    
    
		uchar* ptr_gray = src.ptr<uchar>(i);
		for (int j = 0; j < src.cols; j++) {
    
    
			H[ptr_gray[j]]++;
		}
	}

	double size = src.rows*src.cols*1.0f;
	for (int i = 0; i < 256; i++) {
    
    
		h[i] = H[i] / size;
	}

	double x_max = 0;
	int index = 0;
	//求最佳阈值k
	for (int k = 0; k < 255; k++) {
    
    
		double p1 = 0.0;
		double temp1 = 0.0;
		for (int i = 0; i <= k; i++) {
    
    
			p1 += h[i];
			temp1 += i * h[i];//i=0?
		}
		double m1 = 1 / p1 * temp1;

		double p2 = 1.0 - p1;
		double temp2 = 0.0;
		for (int i = k + 1; i < 256; i++) {
    
    
			temp2 += i * h[i];
		}
		double m2 = 1 / p2 * temp2;

		double x = p1 * p2*(m1 - m2)*(m1 - m2);

		if (x > x_max) {
    
    
			x_max = x;
			index = k;
		}
	}

	return index;
}

//把图像分割成x列,y行的小ROI,并用调用上面的ostu进行阈值分割,返回一张分块阈值分割完成之后的完整图像

Mat cutImage(Mat src, int x, int y,bool key) {
    
    

	if (src.channels() > 1) {
    
    
		cvtColor(src, src, COLOR_BGR2GRAY);
	}

	int cutCols = (int)(src.cols / x);//分块后,每一小块的长、列数、坐标的x
	int cutRows = (int)(src.rows / y);//分块后,每一小块的宽、行数、坐标的y

	int remainder_cols = src.cols - cutCols * x;
	int remainder_rows = src.rows - cutRows * y;

	Mat newImage = Mat::zeros(src.size(), src.type());//装阈值分割后的图像

	int k = 0;//最佳阈值
	Mat temp;//图像小块
	Mat ostuImage;//小块阈值分割图像

	int i, j;

	for (i = 0; i < y; i++) {
    
    
		for (j = 0; j < x; j++) {
    
    
			Point2f p(j*cutCols, i*cutRows);
			//cout << p << endl;
			temp = src(Rect(p, Size(cutCols, cutRows)));
			k = ostu(temp);
			//cout<<"k: "<<k<<endl;
			threshold(temp, ostuImage, k, 255, THRESH_BINARY);

			//图像保存
			for (int a = i * cutRows, n = 0; n < cutRows; a++, n++) {
    
    //a < (i + 1) * cutRows
				uchar* ptr_newImage = newImage.ptr<uchar>(a);
				uchar* ptr_ostuImage = ostuImage.ptr<uchar>(n);
				for (int b = j * cutCols, m = 0; m < cutCols; b++, m++) {
    
    // b < (j + 1)*cutCols
					ptr_newImage[b] = ptr_ostuImage[m];
				}
			}
		}

		//右余边处理
		if (remainder_cols > 0 && key == 1) {
    
    
			Point2f p(j*cutCols, i*cutRows);
			temp = src(Rect(p, Size(remainder_cols, cutRows)));
			k = ostu(temp);
			threshold(temp, ostuImage, k, 255, THRESH_BINARY);

			for (int a = i * cutRows, n = 0; n < cutRows; a++, n++) {
    
    
				uchar* ptr_newImage = newImage.ptr<uchar>(a);
				uchar* ptr_ostuImage = ostuImage.ptr<uchar>(n);
				for (int b = cutCols * x, m = 0; m < remainder_cols; b++, m++) {
    
    
					ptr_newImage[b] = ptr_ostuImage[m];
				}
			}
		}

	}

	//下余边处理
	if (remainder_rows > 0 && key == 1) {
    
    //分块的最后的一行
		for (j = 0; j < x; j++) {
    
    //分块的列
			Point2f p(j*cutCols, cutRows*y);
			temp = src(Rect(p, Size(cutCols, remainder_rows)));
			k = ostu(temp);
			threshold(temp, ostuImage, k, 255, THRESH_BINARY);

			//图像保存
			for (int a = cutRows * y, n = 0; n < remainder_rows; a++, n++) {
    
    //a < (i + 1) * cutRows
				uchar* ptr_newImage = newImage.ptr<uchar>(a);
				uchar* ptr_ostuImage = ostuImage.ptr<uchar>(n);
				for (int b = j * cutCols, m = 0; m < cutCols; b++, m++) {
    
    // b < (j + 1)*cutCols
					ptr_newImage[b] = ptr_ostuImage[m];
				}
			}

		}
		//下余边的右余边处理
		if (remainder_cols > 0) {
    
    
			Point2f p(j*cutCols, y*cutRows);
			temp = src(Rect(p, Size(remainder_cols, remainder_rows)));
			k = ostu(temp);
			threshold(temp, ostuImage, k, 255, THRESH_BINARY);

			for (int a = y * cutRows, n = 0; n < remainder_rows; a++, n++) {
    
    
				uchar* ptr_newImage = newImage.ptr<uchar>(a);
				uchar* ptr_ostuImage = ostuImage.ptr<uchar>(n);
				for (int b = cutCols * x, m = 0; m < remainder_cols; b++, m++) {
    
    
					ptr_newImage[b] = ptr_ostuImage[m];
				}
			}
		}
	}
	return newImage;
}

处理的函数main函数:

#include <opencv.hpp>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>
#include <ctime>

using namespace cv;
using namespace std;

int ostu(Mat src);
Mat cutImage(Mat src, int x, int y);

int main()
{
    
    
	Mat src = imread("C:/Users/***/Desktop/blobs.tif");//读取本地图像
	if (src.empty()) {
    
    
		cout << "error!" << endl;
		return 0;
	}
	imshow("src", src);

	if (src.channels() > 1) {
    
    
		cvtColor(src, src, COLOR_RGB2GRAY);
	}

	int k;//阈值
	k = ostu(src);

	Mat ostuImage;//分割图像
	threshold(src, ostuImage, k, 255, THRESH_BINARY);
	imshow("ostuImage", ostuImage);  //背景为白色 
	
	Mat cut;
	cut = cutImage(src, 4, 2);//最大余边15, 14,最好效果4,2   
	
	imshow("cut", cut);

	waitKey(0);
	return 0;
}

四、处理效果

原图:
原图
阈值分割后:
处理后

五、 思考记录:

这种算法的关键点在于分块怎么分,经过测试发现,最好就是让分块后每一块ROI里都让前景和背景都占一定的比例,让分割后的ROI图像的直方图形成双峰分布。

猜你喜欢

转载自blog.csdn.net/weixin_44650358/article/details/116267250