Detailed description of threshold processing

    • Basis:
    • Global-based threshold processing
      • 1 Iterative algorithm (Minimum probability misjudgment)
      • 2 Optimal global threshold method based on Otsu (very effective)
      • 3. Improve global threshold processing by image smoothing
      • Use edge to improve global threshold processing
    • Local threshold based processing
      • 1 image segmentation variable threshold processing
      • 2. Variable threshold processing based on local image characteristics
      • Variable threshold based on moving average method

Basis:

First, the grayscale image is transformed into a grayscale histogram. The abscissa is the grayscale value (0-255) and the ordinate is the number of pixels. (Normalization represents the probability of pixel appearance)

As shown below:

Properties of gray histogram:



Two gray histograms

As shown in Figure A, there are two obvious wave peaks and one obvious trough in the histogram, indicating that gray level is generally divided into two dense regions. If the threshold is set at the trough between the two, the background and object can be well separated.

Similarly, when observing Figure B, there are three obvious peaks and two obvious troughs. In this case, double thresholds can be set to divide the image into three categories. A good example is the iceberg in the following figure, which is divided into dark background, bright area of iceberg and Shadowed area.



However, not all histogram images are marked with multiple peaks and troughs.

Unimodal type:

No obvious trough type

Gray scale tends to be consistent (contaminated by noise)

Gray-level threshold depends on the width and depth of the trough, the key factors influencing the trough characteristics are: 1, the interval of wave crest (wave the farther away, these patterns to the better separation) 2, the noise of the image content (model along with the increase of noise bandwidth) 3, the relative size of the object and background 4 5, the uniformity of the light source, the uniformity of image reflection

The purpose of all the following threshold processing methods is to make the gray histogram easy to handle and find the threshold gray value of segmentation background and object.

Global-based threshold processing

1 Iterative algorithm (Minimum probability misjudgment)

Formula derivation:









Algorithm steps:



Code implementation:

void Iteration(Mat* srcImage, Mat* dstImage, float delta_T)
{
	// [1] find the maximum gray scale and the minimum gray scale
	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;		// an error occurs when both m1 and m2 are set to 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.0 f / timer_G1;
		m2 = G2 * 1.0 f / timer_G2;
		old_T = new_T;
		new_T = 0.5 * (m1 + m2);
	}
	cout << "The iterative method threshold is:" << new_T << endl;	
	// Binarize the image according to the resulting threshold
	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(a)
{
		Mat srcImage = imread("D:\\opencv_picture_test\\ Niigaki Noyi \\test2.jpg".0);	// Convert to grayscale when read
		namedWindow("Original drawing", WINDOW_NORMAL);//WINDOW_NORMAL allows users to freely scale Windows
		imshow("Original drawing", srcImage);
	
		Mat dstImage;
		dstImage.create(srcImage.rows, srcImage.cols, CV_8UC1);
		double time0 = static_cast<double>(getTickCount());	// Record the start time
		// Threshold processing + binarization
		//My_P_tile(&srcImage,&dstImage,20); // set P to 20
		Iteration(&srcImage, &dstImage,0.02);
		// After a series of processing
		time0 = ((double)getTickCount() - time0) / getTickFrequency();
		//cout << "< time0 <<" seconds "<< endl; // Outputs the running time
		namedWindow("Effect drawing", WINDOW_NORMAL);//WINDOW_NORMAL allows users to freely scale Windows
		imshow("Effect drawing", dstImage);
		dstImage = My_Rraw_histogram(&srcImage);
		namedWindow("One-dimensional histogram", WINDOW_NORMAL);//WINDOW_NORMAL allows users to freely scale Windows
		imshow("One-dimensional histogram", dstImage);
		waitKey(0);
		return 0;
}
Copy the code

This is good when there are obvious troughs in the histogram. Delta T controls the number of iterations. Here is the code implementation







2 Optimal global threshold method based on Otsu (very effective)

OTSU method is also called maximum variance method and maximum threshold method (OTSU).

The basic idea is to use a threshold to divide the data in an image into two categories,

The gray values of the pixels in one class are all less than this threshold, and the gray values of the pixels in the other class are all greater than or equal to this threshold. // In general, use the traversal method to find

If the variance of pixel gray in these two classes is larger, it indicates that the threshold obtained is the best threshold

(Variance is a measure of the uniformity of gray distribution. The greater the inter-class variance between background and foreground, the greater the difference between the two parts of the image. When part of foreground is wrongly divided into background or part of background is wrongly divided into foreground, the difference between the two parts will become smaller. Therefore, segmentation that maximizes the variance between classes means the lowest probability of misclassification.) .

The threshold can be used to divide the image into foreground and background.

And what we’re interested in is usually the foreground.

For images with two peaks in the histogram of gray distribution, T obtained by Otsu method is approximately equal to the trough between the two peaks.

This statement is taken from herewww.jianshu.com/p/56b140f95…)

The formulas







A graph from a blog listing the variables we need to calculate.Blog.csdn.net/u012198575/…

Code implementation

void My_Ostu(Mat* srcImage, Mat* dstImage)
{
	int height = (*srcImage).rows;
	int width = (*srcImage).cols;
	int Ostu_Threshold = 0; // Otsu threshold
	int size = height * width;
	float variance;   // Interclass variance
	float maxVariance = 0, w1 = 0, w2 = 0, avgValue = 0;
	float u0 = 0, u1 = 0, u2 = 0;
	// Generate gray histogram
	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.0 f / size;
	}
	// Iterate to find the Ostu_Threshold for maxVariance between classes
	for (int i = 0; i <=255; i++) { w1 =0;
		w2 = 0;
		u1 = 0;
		u2 = 0;
		// Calculate the proportion of background pixels and the average gray level
		for (int j = 0; j <= i; j++) { w1 += histgram[j]; u1 += histgram[j] * j; } u1 = u1 / w1;// Calculate the ratio of foreground pixels to average gray level
		w2 = 1 - w1;
		if (i == 255)
		{
			u2 = 0;
		}
		else
		{
			for (int j = i + 1; j <=255; j++) { u2 += histgram[j] * j; } } u2 = u2 / w2;// Calculate the variance between classes
		variance = w1 * w2 * (u1 - u2) * (u1 - u2);
		if (variance > maxVariance)
		{ // Find the value that maximizes the grayscale difference
			maxVariance = variance;
			Ostu_Threshold = i;            // That value is the threshold}}cout << "Otsu method threshold is:" << Ostu_Threshold << endl;
	// [3] binarization
	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(a)
{
		Mat srcImage = imread("D:\\opencv_picture_test\\ Niigaki Noyi \\test2.jpg".0);	// Convert to grayscale when read
		namedWindow("Original drawing", WINDOW_NORMAL);//WINDOW_NORMAL allows users to freely scale Windows
		imshow("Original drawing", srcImage);
	
		Mat dstImage;
		dstImage.create(srcImage.rows, srcImage.cols, CV_8UC1);
		double time0 = static_cast<double>(getTickCount());	// Record the start time
		// Threshold processing + binarization
		//My_P_tile(&srcImage,&dstImage,20); // set P to 20
		/ / My_Iteration (& srcImage, & dstImage, 0.02);
		My_Ostu(&srcImage, &dstImage);
		// After a series of processing
		time0 = ((double)getTickCount() - time0) / getTickFrequency();
		cout << "This method runs for:" << time0 << "Seconds" << endl;	// Outputs the running time
		namedWindow("Effect drawing", WINDOW_NORMAL);//WINDOW_NORMAL allows users to freely scale Windows
		imshow("Effect drawing", dstImage);
		dstImage = My_Rraw_histogram(&srcImage);
		namedWindow("One-dimensional histogram", WINDOW_NORMAL);//WINDOW_NORMAL allows users to freely scale Windows
		imshow("One-dimensional histogram", dstImage);
		waitKey(0);
		return 0;
}
Copy the code

Effect:



3. Improve global threshold processing by image smoothing

Basically, the whole image is processed using a mean value template like 33 or 55 before binarization. The downside is that the boundary between the object and the background becomes a little blurred. The more erosion, the greater the boundary error. In some extreme cases, this approach does not work well.

Use edge to improve global threshold processing

This method focuses on the edge pixels of the object and the background, and the gray pulse at the edge is very obvious, so the gray histogram obtained will be greatly improved.

The way we find the edges here is mainly the gradient operator and the Laplacian operator.

Algorithm steps:



In general, we determine the threshold T by some percentage of the maximum gradient or the maximum Laplace. When there are different requirements, use different proportions.

Local threshold based processing

The purpose of this threshold processing is to solve the problems caused by lighting and reflection.

1 image segmentation variable threshold processing

Basically, you divide an image into chunks using otsu thresholds.

Histogram of subimage after block processing



Above is an example from the book. I cut down the original image and tried my own code, but it didn’t work very well.

Code implementation:

void My_local_adaptive(Mat* srcImage, Mat* dstImage, int areas_of_H, int areas_of_W)		Areas_of_H: indicates the number of vertical segmentation areas_of_W: indicates the number of horizontal segmentation
{
	int height = (*srcImage).rows/ areas_of_H;			// The height of each piece
	int width = (*srcImage).cols/ areas_of_W;			// Width of each piece
	int Ostu_Threshold = 0; // Otsu threshold
	int size = height * width/ areas_of_H/ areas_of_W;		// The size of each piece
	// Line by line
	for (int y = 0; y < areas_of_H; y++)	
	{
		for (int x = 0; x < areas_of_W; x++)
		{
			float variance = 0;   // Interclass variance
			float maxVariance = 0, w1 = 0, w2 = 0, avgValue = 0;
			float u0 = 0, u1 = 0, u2 = 0;
			// Generate areas_of_W*areas_of_H local gray histogram
			int pixels[256];
			float histgram[256];
			for (int i = 0; i < 256; i++)
			{
				pixels[i] = 0;
			}
			// [Handle each small area and binarize]
			//
			for (int j = y* height; j < ((y + 1 == areas_of_H) ? (*srcImage).rows : (y + 1) * height); j++) / /? : is a ternary operator, and the only ternary operator. ? Table logical condition, : The following represents the value when the condition is true, : the following table indicates the value when the condition is not true. For example, when a is greater than b, x is equal to 1 and otherwise x is equal to 0, you can write x is equal to a > b, right? 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)]++; }}// [histogram normalization]
			for (int i = 0; i < 256; i++)
			{
				histgram[i] = pixels[i] * 1.0 f / size;
			}
			// Iterate to find the Ostu_Threshold for maxVariance between classes
			for (int i = 0; i <=255; i++) { w1 =0;
				w2 = 0;
				u1 = 0;
				u2 = 0;
				// Calculate the proportion of background pixels and the average gray level
				for (int j = 0; j <= i; j++) { w1 += histgram[j]; u1 += histgram[j] * j; } u1=u1/w1;// Calculate the ratio of foreground pixels to average gray level
				w2 = 1 - w1;
				if (i == 255)
				{
					u2 = 0;
				}
				else
				{
					for (int j = i + 1; j <=255; j++) { u2 += histgram[j] * j; } } u2=u2/w2;// Calculate the variance between classes
				variance = w1 * w2 * (u1 - u2) * (u1 - u2);
				if (variance > maxVariance)
				{ // Find the value that maximizes the grayscale difference
					maxVariance = variance;
					Ostu_Threshold = i;            // That value is the threshold
				}
			}
			cout << "Otsu method threshold is:" << Ostu_Threshold << endl;
			// [3] binarization
			for (int j = y * height; j < ((y + 1 == areas_of_H) ? (*srcImage).rows : (y + 1) * height); j++) / /? : is a ternary operator, and the only ternary operator. ? Table logical condition, : The following represents the value when the condition is true, : the following table indicates the value when the condition is not true. For example, when a is greater than b, x is equal to 1 and otherwise x is equal to 0, you can write x is equal to a > b, right? 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(a)
{
		//Mat srcImage = imread("D:\ opencv_picture_test\\ test2.jpg", 0); // Convert to grayscale when read
		//Mat srcImage = imread("D:\\opencv_picture_test\\miku\\miku2.jpg", 0); // Convert to grayscale when read
		Mat srcImage = imread("D:\\opencv_picture_test\\ Threshold processing \\ image with noise.png".0);	// Convert to grayscale when read
		namedWindow("Original drawing", WINDOW_NORMAL);//WINDOW_NORMAL allows users to freely scale Windows
		imshow("Original drawing", srcImage);
	
		Mat dstImage;
		dstImage.create(srcImage.rows, srcImage.cols, CV_8UC1);
		double time0 = static_cast<double> (getTickCount());	// Record the start time
		// Threshold processing + binarization
		//My_P_tile(&srcImage,&dstImage,20); // set P to 20
		/ / My_Iteration (& srcImage, & dstImage, 0.01);
		//My_Ostu(&srcImage, &dstImage);
		My_local_adaptive(&srcImage, &dstImage, 1.2);
		// After a series of processing
		time0 = ((double)getTickCount() - time0) / getTickFrequency(a); cout <<"This method runs for:" << time0 << "Seconds" << endl;	// Outputs the running time
		namedWindow("Effect drawing", WINDOW_NORMAL);//WINDOW_NORMAL allows users to freely scale Windows
		imshow("Effect drawing", dstImage);
		dstImage = My_Rraw_histogram(&srcImage);
		namedWindow("One-dimensional histogram", WINDOW_NORMAL);//WINDOW_NORMAL allows users to freely scale Windows
		imshow("One-dimensional histogram", dstImage);
		waitKey(0);
		return 0;
}
Copy the code

Global Otsu threshold effect





Local threshold method: 1*2 segmentation





Iterative threshold method:





There still seems to be a need for improvement

2. Variable threshold processing based on local image characteristics

Algorithm steps:

1. Calculate the gray standard deviation and mean value of the neighborhood centered on a certain pixel



2. Set variable thresholds

3. Check whether the threshold conditions are met

4. Binarization

Both A and B need to be fixed.

Effect:

Variable threshold based on moving average method



(I don’t understand this algorithm yet, I will add it when I do)

Blog.csdn.net/qq_34510308…