Segmentación del umbral de OTSU de golpes manuales de C++ basada en el mapa de degradado y la matriz de números de píxeles

 1. Principio del algoritmo OTSU

➢Método OTSU (método de varianza máxima entre clases, a veces llamado algoritmo Otsu)

➢ Utilizando la idea de agrupamiento, divida el número de escala de grises de la imagen en dos partes según el nivel de escala de grises, de modo que la diferencia de valor de escala de grises entre las dos partes sea la mayor y la diferencia de escala de grises entre cada parte sea la menor

➢ Encuentre un nivel de gris adecuado para dividir calculando la varianza.

➢ El algoritmo OTSU se puede utilizar para seleccionar automáticamente el umbral de binarización durante la binarización.

➢ El algoritmo OTSU se considera el mejor algoritmo para la selección de umbrales en la segmentación de imágenes, es simple de calcular y no se ve afectado por el brillo y el contraste de la imagen.

➢ Por lo tanto, la división que maximiza la varianza entre clases significa la menor probabilidad de clasificación errónea.

2. Pasos del algoritmo OTSU:

El umbral global T se puede calcular de la siguiente manera:

➢Elija un valor estimado inicial T (generalmente el valor promedio de gris de la imagen)

➢Utilice T para segmentar la imagen para generar dos grupos de píxeles: G 1 incluye píxeles cuyo nivel de gris es mayor que T , G 2 incluye píxeles cuyo nivel de gris es menor o igual que T

➢Calcular el valor promedio de píxeles en G 1 y asignarlo a μ 1, calcular el valor promedio de píxeles en G 2 y asignarlo a μ 2

➢ Calcular un nuevo umbral:

➢Repita los pasos 2 a 4 hasta que la diferencia entre dos T consecutivos sea menor que el límite superior preestablecido T

3. Implementación del código 

Cuando usamos operadores de degradado de borde (como Roberts, Soberl, Prewitt, etc.) para procesar la imagen original y obtener el mapa de degradado, podemos crear una matriz de 1*256 dimensiones para almacenar el número de cada píxel en el mapa de degradado. El camino es el siguiente:

int pixel_num[256] = { 0 };     //数组记得初始化为0,即未统计数量之前各个像素的个数都是0
	for (int row = 0; row < img.rows; row++) {
		for (int col = 0; col < img.cols; col++) {
			pixel_num01[Sobel_City.at<uchar>(row, col)] += 1;   //遍历到的像素值作为索引,次数+1
		}
	}

El siguiente es el método de implementación de OTSU:
① Obtenga la dimensión de la imagen de la imagen degradada entrante, cree una imagen en blanco para guardar el resultado de la operación y obtenga el número total de píxeles de la imagen degradada.

Mat OTSU = Mat::zeros(Size(img.cols, img.rows), img.type());
int N = img.rows * img.cols;

② Obtenga la probabilidad correspondiente a cada píxel de la matriz de números de píxeles:

double pro[256] = { 0 };            // 定义概率数组
	for (int i = 0; i < 256; i++) {
		pro[i] = arr[i] / N;            // 得到每个像素值的概率
	}

Tenga en cuenta que la matriz de probabilidad creada es de tipo punto flotante. Si se define como un número entero, la probabilidad de todos los píxeles será cero.

③Cree las cantidades de proceso calculadas por OTSU a su vez, incluyendo la probabilidad y la media de las dos categorías, así como el umbral T y la varianza v, etc.; primero calcule la media y la probabilidad de las dos categorías, de acuerdo con la fórmula v = w0 * w1 * pow(u1 - u0 , 2); puede obtener la varianza de los píxeles atravesados ​​esta vez como valor de umbral.

double delta = 0, w0 = 0, w1 = 0, u0 = 0, u1 = 0, v = 0;           // 变量初始化为0
int T = 0, thresh = 0;
while (T <= 255) {
		for (int i = 0; i <= T; i++) {
			w0 += pro[i];                       // C0的概率
		}
		w1 = 1 - w0;                            // C1的概率
		for (int i = 0; i <= 255; i++) {
			if (i <= T) {
				u0 += pro[i] * i;                     // C0的平均值
			}
			else {
				u1 += pro[i] * i;                     // C1的平均值
			}
			v = w0 * w1 * pow(u1 - u0, 2);
			if (v > delta) {
				delta = v;
				thresh = T;
			}
			T += 1;
		}
	}

En el proceso de atravesar el bucle, es necesario juzgar la varianza. Si la varianza calculada esta vez es mayor que la delta de varianza máxima anterior, entonces actualice la delta y también use thresh para registrar el valor de píxel correspondiente al máximo histórico varianza como el umbral de segmentación T. De esta manera, el resultado del recorrido puede obtener el umbral T que queremos, lo que puede maximizar la diferencia entre las dos categorías.

④ Al atravesar la imagen en blanco con la misma dimensión que la imagen degradada, binarice cada píxel. Si el valor del píxel atravesado es mayor o igual a T, el valor se asigna a 255, de lo contrario es 0.

for (int row = 0; row < img.rows; row++) {
		for (int col = 0; col < img.cols; col++) {
			if (img.at<uchar>(row, col) > thresh) {    //遍历判断
				OTSU.at<uchar>(row, col) = 255;
			}
		}
	}

⑤Muestre la imagen de OTSU y obtenga el resultado de la segmentación del umbral:

imshow("OTSU阈值分割", OTSU);
waitKey(0);
destroyAllWindows();

4a9f558cd76f4273a9d39f8cce16d4a7.png
4. Resumen del código

#include<opencv2/opencv.hpp>
#include<iostream>
#include<math.h>

using namespace cv;
using namespace std;

void Sobel(Mat img);
void OTSU(Mat& img, int arr[]);

int main() {
	Mat Gray = imread("C:\\Users\\Czhannb\\Desktop\\gray.png", IMREAD_GRAYSCALE);
	if (Gray.empty()) {
		cout << "读取图片错误!" << endl;
	}
	else {
		imshow("未动工之前:", Gray);
	}
	Sobel(Gray);
	return 0;
}

void OTSU(Mat& img, int arr[]) {   // 需要输入待处理的图像 和 直方图像素个数数组
	int N = img.rows * img.cols;    // 统计输入图像的像素个数N
	double pro[256] = { 0 };            // 定义概率数组
	for (int i = 0; i < 256; i++) {
		pro[i] = arr[i] / N;            // 得到每个像素值的概率
	}
	double delta = 0, w0 = 0, w1 = 0, u0 = 0, u1 = 0, v = 0;           // 变量初始化为0
	int T = 0, thresh = 0;
while (T <= 255) {
		for (int i = 0; i <= T; i++) {
			w0 += pro[i];                       // C0的概率
		}
		w1 = 1 - w0;                            // C1的概率
		for (int i = 0; i <= 255; i++) {
			if (i <= T) {
				u0 += pro[i] * i;                     // C0的平均值
			}
			else {
				u1 += pro[i] * i;                     // C1的平均值
			}
			v = w0 * w1 * pow(u1 - u0, 2);
			if (v > delta) {
				delta = v;
				thresh = T;
			}
			T += 1;
		}
	}
	Mat OTSU = Mat::zeros(Size(img.cols, img.rows), img.type());
	for (int row = 0; row < img.rows; row++) {
		for (int col = 0; col < img.cols; col++) {
			if (img.at<uchar>(row, col) > int(thresh)) {
				OTSU.at<uchar>(row, col) = 255;
			}
		}
	}
	imshow("OTSU阈值分割", OTSU);
	waitKey(0);
	destroyAllWindows();
}

void Sobel(Mat img) {                   // 基于Prewitt算子的阈值分割
	Mat Sobel_City = Mat::zeros(Size(img.cols, img.rows), img.type());  //用于计算城市距离的空白图像
	Mat Sobel_Ojld = Mat::zeros(Size(img.cols, img.rows), img.type());  //用于计算欧几里得距离的空白图像
	for (int row = 1; row < img.rows - 1; row++) {
		for (int col = 1; col < img.cols - 1; col++) {                 // 由于像素之间的加减可能会小于零,因此记得加上绝对值函数fabs()
			Sobel_City.at<uchar>(row, col) = saturate_cast<uchar>(fabs(img.at<uchar>(row - 1, col + 1) - img.at<uchar>(row - 1, col - 1) + 2*img.at<uchar>(row, col + 1) - 2*img.at<uchar>(row, col - 1) + img.at<uchar>(row + 1, col + 1) - img.at<uchar>(row + 1, col - 1)) + fabs(img.at<uchar>(row + 1, col - 1) - img.at<uchar>(row - 1, col - 1) + 2*img.at<uchar>(row + 1, col) - 2*img.at<uchar>(row - 1, col) + img.at<uchar>(row + 1, col + 1) - img.at<uchar>(row - 1, col + 1)));
		}
	}
	for (int row = 1; row < img.rows - 1; row++) {
		for (int col = 1; col < img.cols - 1; col++) {
			Sobel_Ojld.at<uchar>(row, col) = saturate_cast<uchar>(sqrt(pow(img.at<uchar>(row - 1, col + 1) - img.at<uchar>(row - 1, col - 1) + 2*img.at<uchar>(row, col + 1) - 2*img.at<uchar>(row, col - 1) + img.at<uchar>(row + 1, col + 1) - img.at<uchar>(row + 1, col - 1), 2) + pow(img.at<uchar>(row + 1, col - 1) - img.at<uchar>(row - 1, col - 1) + 2*img.at<uchar>(row + 1, col) - 2*img.at<uchar>(row - 1, col) + img.at<uchar>(row + 1, col + 1) - img.at<uchar>(row - 1, col + 1), 2)));
		}
	}
	imshow("Sobel图像(街区距离)", Sobel_City);
	imshow("Sobel图像(欧几里得距离)", Sobel_Ojld);
	int pixel_num01[256] = { 0 };            //数组记得初始化为0,即未统计数量之前各个像素的个数都是0
	for (int row = 0; row < img.rows; row++) {
		for (int col = 0; col < img.cols; col++) {
			pixel_num01[Sobel_Ojld.at<uchar>(row, col)] += 1;   //遍历到的像素值作为索引,次数+1
		}
	}
	int times = 0;         //定义遍历数组的变量,从0开始,依次输出0到255每个像素值的数目
	while (times <= 255) {
		cout << "像素值" << times << "的数目为:  " << pixel_num01[times] << endl;    // 遍历输出
		times++;                             // 不要忘了自增
	}
	//得到10作为分割阈值
	for (int row = 0; row < img.rows; row++) {
		for (int col = 0; col < img.cols; col++) {       //遍历所有像素点进行判断
			if (Sobel_Ojld.at<uchar>(row, col) < 70) {
				Sobel_Ojld.at<uchar>(row, col) = 0;     // 小于阈值赋值为0,否则赋值255
			}
			else {
				Sobel_Ojld.at<uchar>(row, col) = 255;
			}
		}
	}
	imshow("欧几里得阈值分割", Sobel_Ojld);
	int pixel_num02[256] = { 0 };            //数组记得初始化为0,即未统计数量之前各个像素的个数都是0
	for (int row = 0; row < img.rows; row++) {
		for (int col = 0; col < img.cols; col++) {
			pixel_num01[Sobel_City.at<uchar>(row, col)] += 1;   //遍历到的像素值作为索引,次数+1
		}
	}
	int time = 0;         //定义遍历数组的变量,从0开始,依次输出0到255每个像素值的数目
	while (times <= 255) {
		cout << "像素值" << time << "的数目为:  " << pixel_num02[time] << endl;    // 遍历输出
		time++;                             // 不要忘了自增
	}


	OTSU(Sobel_Ojld, pixel_num01);     // OTSU阈值分割
	OTSU(Sobel_City, pixel_num02);

}

 

 

 

 

Supongo que te gusta

Origin blog.csdn.net/m0_64007201/article/details/128144957
Recomendado
Clasificación