Threshold Segmentation Algorithm Based on Otsu Method OTSU and Image Blocking

1. Algorithm purpose and principle

(1. Purpose:

Threshold segmentation can separate the foreground object and the background in the image, and it is especially suitable for images in which the object and the background occupy different gray scale ranges. It can not only greatly compress the data information of the image, but also greatly simplify the analysis and processing steps. Therefore, in many cases, it is a necessary image preprocessing process before image analysis, feature extraction and pattern recognition. It is often used in machines. Inspection of visual products.

(2) Principle:

By counting the gray histogram, the threshold is set between the gray levels of the peaks, and the image is divided into n categories. The threshold segmentation based on OTSU is based on the calculation of the maximum inter-class variance of the bimodal histogram, so as to determine the optimal threshold for distinguishing the foreground object from the background. By dividing the original image into a binary image through the threshold, the foreground and the background can be clearly distinguished. background. The image segmentation is to solve the problem of uneven light brightness of the image. The image is divided into small ROI images containing only obvious double peaks, and then the threshold segmentation is performed to obtain a better segmentation effect.
Algorithm Fundamentals

2. Algorithm design

(1) Optimal threshold segmentation of OTSU:

Input: an image
1. The normalized gray histogram h[256] of statistical pictures;
2. x_max=0; index = 0; h[k] traverse k=0-254 { //0-k is Class A, 255-k is Class B
Calculation of probability and mean:

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

Compute the between-class variance:

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

}
3.return index; // return the best segmentation threshold

(2) Apply OTSU for threshold segmentation in image segmentation

Input: Mat src , int x , int y, bool key=0;
parameter explanation:

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

Function body:
1. Convert src to grayscale image: cvtColor();
2. Calculation:

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. Split the src image into x*y block roi images:
cycle: First cut out a block at position [0,0] from src, and then divide it into [x, y] loop
body to process a certain block of roi: { call ostu operator to find the optimal threshold k of roi; Use the best threshold k to perform threshold segmentation on roi (using the threshold segmentation function: threshold) to form a binary image Mat temp; save temp to the [x, y] position corresponding to the result image result; }



4. Supplementary processing boundary: (when key=1)
4.1 In the loop body of step 3, when the roi of a row is processed, the "right boundary" can be processed;

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

4.2 When all [x,y] roi are processed, the "lower boundary" can be processed;

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

3. C++ code implementation:

//Use the OTSU Otsu method to solve the optimal threshold

 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;
}

//Divide the image into small ROIs of x columns and y rows, and perform threshold segmentation by calling the above ostu, and return a complete image after block threshold segmentation is completed

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;
}

Processing function main function:

#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;
}

4. Processing effect

Original image:
original image
After threshold segmentation:
after treatment

5. Thinking record:

The key point of this algorithm is how to divide the blocks. After testing, it is found that it is best to let the foreground and background account for a certain proportion in each ROI after the block, so that the histogram of the segmented ROI image forms a double peak. distributed.

Guess you like

Origin blog.csdn.net/weixin_44650358/article/details/116267250