自适应阈值二值化C语言代码实现

本文重点关注自适应阈值二值化代码实现

应用背景

光照不均匀的场景,大津法计算得到的阈值,用于二值化的效果并不理想。尝试自适应阈值二值化或许可以得到效果更好的二值化图像。

效果对比

从上至下分别是原图1、灰度图、大津法二值化结果、自适应阈值二值化结果2
Snipaste_2022-12-20_22-52-00
colorful2gray
binary_otsu
binary_adpative

可以看到大津法二值化和自适应二值化有各自的特点。大津法二值化比较准确的区分了前景和背景;自适应二值化保留了较多细节。

代码实现

自适应阈值二值化C语言实现:3

#include "stdlib.h"
#include "stdint.h"

typedef uint8_t gray_t;

/**
 * @brief:   获取积分图像
 *           
 * @param:   *gray_img  灰度图像数组首地址
 * @param:   *sum       保存积分图像数组的首地址
 * @param:   width      灰度图像宽度
 * @param:   height     灰度图像高度
 *           
 * @date:    2022-12-20 created by 吉平.「集」
 */
void integral(uint8_t *gray_img, int *sum, int width, int height)
{
	for (int y = 0; y < height; y++)
	{
		// 获取行指针
		gray_t *input_row_ptr = &gray_img[y * width];
		int *output_row_ptr = &sum[(y + 1) * width + 1]; // 积分图像比原始图像行和列都要多1

		// 计算积分图像
		for (int x = 0; x < width; x++)
		{
			output_row_ptr[x] = 0;															// 清零
			output_row_ptr[x] = output_row_ptr[x - 1] + input_row_ptr[x];					// 0 + s_{y,x-1} + a_{x,y}
			output_row_ptr[x] += output_row_ptr[x - width] - output_row_ptr[x - width - 1]; // 当前列和
		}
	}
}

/**
 * @brief:   自适应阈值二值化
 *           
 * @param:   *gray_img  灰度图像数组首地址
 * @param:   width      灰度图像宽度
 * @param:   height     灰度图像高度
 *           
 *           @note 二值化会直接作用在原灰度图像上
 *           
 * @date:    2022-12-20 created by 吉平.「集」
 */
void adaptive_threshold_binaryzation(uint8_t *gray_img, int width, int height)
{
	// 自适应阈值取n*n范围的像素计算局部最优阈值,这里计算合适的n的取值
	int S = (width > height ? width : height)/8;
	// T是一个可调参数,影响阈值(进而影响二值化效果)
	float T = 0.15;

	// 定义变量保存n*n框选的图像范围及像素数量
	int s2 = S/2;
	int x1, y1, x2, y2, count;

	// 申请内存空间用于保存积分图像,积分图像比原始图像多一行一列
	int *sum = NULL;
	sum = (int *)calloc((width + 1) * (height + 1), sizeof(int)); // 申请内存空间,并对申请到的空间做零初始化

	// 确认是否申请到了内存空间
	if(sum != NULL)
	{
		integral(gray_img, sum, width, height); // 计算和获取积分图像

		// 外层循环用于遍历图像的每一行
		for (int y = 0; y < height; y++)
		{
			// 计算y轴上的框选范围
			y1 = y - s2;
			y2 = y + s2;

			// 避免框选到图像外的像素点
			y1 = y1 < 0 ? 0 : y1;
			y2 = y2 > (height - 1) ? (height - 1) : y2;

			// 获取灰度图像和积分图像的行指针(可以理解为单行图像的首个像素的地址)
			gray_t *row_ptr = &gray_img[y * width];
			int *y1_ptr = &sum[y1 * width];
			int *y2_ptr = &sum[y2 * width];

			// 内层循环用于遍历图像的每一列
			for (int x = 0; x < width; x++)
			{
				// 计算x轴上的框选范围
				x1 = x - s2;
				x2 = x + s2;

				// 避免框选到图像外的像素点
				x1 = x1 < 0 ? 0 : x1;
				x2 = x2 > (width - 1) ? (width - 1) : x2;

				// 计算框选的像素点的个数
				count = (x2 - x1) * (y2 - y1);

				// 计算局部阈值
				int summation = y2_ptr[x2] + y1_ptr[x1] - y1_ptr[x2] - y2_ptr[x1]; // 利用积分图快速求区域和
				uint8_t threshold = summation / count;

				// 二值化
				row_ptr[x] = ((int)(row_ptr[x] * count) > (int)(summation * (1.f - T))) ? 255 : 0;
			}
		}

		free(sum); // 释放申请的内存空间
	}
	else
	{
		// do nothing
	}
}

参考链接

自适应阈值二值化算法

积分图像

Integral images in OpenCV

本文作者:吉平. 「集」,如有侵权,请联系我。

Jotting-Down_logo


  1. Two People On Mountain Cliff · Free Stock Photo ↩︎

  2. 转灰度和二值化用了 @Kyatto 制作的图形上位机 Mi-UpperMachine ↩︎

  3. 代码主要参考的是自适应阈值二值化算法 ↩︎

  • 1
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
大津法(Otsu's Method)是一种图像二值化算法,它是一种自适应阈值选择方法。以下是使用C语言实现大津法二值化代码: ```c #include <stdio.h> #include <stdlib.h> #include <math.h> int main() { int i, j, t, w, total; double u1, u2, w1, w2, var, max_var; unsigned char *img, *out; FILE *fp_in, *fp_out; // 打开输入图像文件 if ((fp_in = fopen("input.bmp", "rb")) == NULL) { printf("Cannot open file: input.bmp\n"); exit(1); } // 读取图像数据 fseek(fp_in, 54, SEEK_SET); img = (unsigned char*) malloc(512 * 512 * sizeof(unsigned char)); fread(img, sizeof(unsigned char), 512 * 512, fp_in); // 关闭输入图像文件 fclose(fp_in); // 分配输出图像内存 out = (unsigned char*) malloc(512 * 512 * sizeof(unsigned char)); // 计算灰度直方图 int hist[256] = {0}; for (i = 0; i < 512 * 512; i++) { hist[img[i]]++; } // 计算总像素数 total = 512 * 512; // 初始化最大方差 max_var = -1; // 寻找最佳阈值 for (t = 0; t < 256; t++) { // 计算背景和前景的像素数和像素值的总和 w1 = w2 = u1 = u2 = 0; for (i = 0; i < 256; i++) { if (i <= t) { w1 += hist[i]; u1 += i * hist[i]; } else { w2 += hist[i]; u2 += i * hist[i]; } } // 计算背景和前景的平均像素值 u1 /= w1; u2 /= w2; // 计算类间方差 var = w1 * w2 * pow(u1 - u2, 2); // 更新最大方差和最佳阈值 if (var > max_var) { max_var = var; w = t; } } // 二值化图像 for (i = 0; i < 512 * 512; i++) { out[i] = (img[i] <= w) ? 0 : 255; } // 打开输出图像文件 if ((fp_out = fopen("output.bmp", "wb")) == NULL) { printf("Cannot create file: output.bmp\n"); exit(1); } // 写入输出图像文件头 unsigned char header[54] = { 0x42, 0x4d, 0x36, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; fwrite(header, sizeof(unsigned char), 54, fp_out); // 写入输出图像数据 fwrite(out, sizeof(unsigned char), 512 * 512, fp_out); // 关闭输出图像文件 fclose(fp_out); // 释放内存 free(img); free(out); return 0; } ``` 注意:此代码仅适用于位深度为8位的BMP图像,且文件头为54字节。在实际使用中需要根据具体情况进行修改。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值