简单高效的自适应阈值二值化 C语言的实现

简单高效的自适应阈值二值化 C语言的实现

关于自适应阈值二值化OpenCV中的源码,只从中移植出了部分内容,剩余内容凭借自己的理解进行的补充。本文中的二值化与OpenCV中所介绍的二值化方法最大的一个不同就是,对于模板的核大小,OpenCV中不管核大小为多大,其运行速度都是几乎一样,但是本文中的致命缺陷就是随着核大小的增大,运行速度也会变慢。我唯一能想到弥补这一缺陷的办法就是,核大小根据整个图片的大小自动适合,这样也基本能保证跟OpenCV源码的效率一致。这样做还有个合理的点就是,用一个很大的核去处理一张很小的图片实际上是没有必要的,核只要适合就好。

当然,如果读者有兴趣,有能力解决本文中的缺陷,楼主绝对支持,也欢迎相互学习与沟通。

其中boxFilter函数为未做归一化的均值滤波函数,_Gray为输入BMP灰度图图片的数据段部分,_Width为图片的宽,_Height为图片的高,_CoreSize为滤波核大小。

而函数adaptiveThreshold之中是调用的boxFilter。

void boxFilter( unsigned char* _Gray, const int _Width, const int _Height, int _CoreSize )
{
	unsigned char* grayBorder = NULL;
	int* firstWidthBlock = NULL;
	int widthBorder = _Width + _CoreSize - 1;
	int heightBorder = _Height + _CoreSize - 1;
	int halfCore = (_CoreSize-1)>>1;
	int i = 0, j = 0, l = 0, n = 0;
	int coreCounts = 0;
	int stepWidthGray = (_Width + 3) & ~3;
	int stepWidthBorder = (widthBorder + 3) & ~3;


	firstWidthBlock = ( int *)malloc( _Width * sizeof(int) );
	memset(firstWidthBlock, 0, _Width * sizeof(int));
	grayBorder = ( unsigned char *)malloc( stepWidthBorder*heightBorder );
	memset( grayBorder, 0, stepWidthBorder*heightBorder );//全图置0

	for ( i = 0; i < _Height; i ++ )//灰度图嵌入边框图中间
		for ( j = 0; j < _Width; j ++ )
		{
			grayBorder[ ( i + halfCore )*stepWidthBorder + j + halfCore ] = _Gray[ i*stepWidthGray + j ];
		}

	for ( i = 0; i < _CoreSize; i ++ )
		for ( j = 0; j < _CoreSize; j ++ ) firstWidthBlock[0] += grayBorder[ i*stepWidthBorder + j ];
	
	for ( i = 1; i < _Width; i++ )
	{
		firstWidthBlock[i] += firstWidthBlock[i-1];
		for ( l = 0; l < _CoreSize; l ++ )
		{ 
			
			firstWidthBlock[i] += grayBorder[ l*stepWidthBorder + i+_CoreSize-1 ] 
			                     - grayBorder[ l*stepWidthBorder + i-1 ];
		}
	}

	for ( i = halfCore+1; i < heightBorder-halfCore; i ++ )
		for ( j = halfCore; j < widthBorder-halfCore; j ++ )//全图均值滤波
		{
			coreCounts = 0;
			for ( n = j-halfCore; n < j+halfCore; n ++ ) 
				firstWidthBlock[j-halfCore] += grayBorder[ (i+halfCore)*stepWidthBorder + n ]
											- grayBorder[ (i-halfCore)*stepWidthBorder + n ];
			coreCounts = firstWidthBlock[j-halfCore];
			_Gray[ (i-halfCore)*stepWidthGray + j-halfCore ] = ( unsigned char)(coreCounts/(_CoreSize*_CoreSize));
		}
}

void adaptiveThreshold( const unsigned char* _Gray, unsigned char** _Binary,
					    const int _Width, const int _Height,
					    int _Block, int _Idelta, int _MaxValue )
{
	int i = 0, j = 0;
	unsigned char _Tab[768] = {0};
	unsigned char* _GrayFilter = NULL;
	unsigned char* ptrBinary = NULL;
	int nRowBytes = (_Width + 3) & ~3;


	*_Binary = ( unsigned char *)malloc( nRowBytes*_Height );
	memset( *_Binary, 0, nRowBytes*_Height );
	ptrBinary = *_Binary;
	_GrayFilter = ( unsigned char *)malloc( nRowBytes*_Height );
	memcpy( _GrayFilter, _Gray, nRowBytes*_Height );
	boxFilter( _GrayFilter, _Width, _Height, _Block );
	for ( i = 0; i < 768; i ++ )//防止数据溢出的三倍tab
		_Tab[i] = ( unsigned char)( i-255 > -_Idelta ? _MaxValue : 0 );
	
	for ( i = 0; i < _Height; i ++ )//自适应阈值二值化核心代码
		for ( j = 0; j < _Width; j ++ )
			ptrBinary[ i*nRowBytes + j] = _Tab[ _Gray[ i*nRowBytes + j] 
												- _GrayFilter[ i*nRowBytes + j]
												+ 255];
}

去年年末一直忙写论文也没时间跟新这篇均值滤波的文章,其实去年代码已经写好,因为实习结束代码没有及时备份,于是那份与滤波窗口大小无关的均值滤波函数丢失,最近有时间于是又重新在Qt上用C++写了一份,现在吧代码贴出来:
#include <QCoreApplication>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main()
{   
    double duration;

    Mat src = imread("lena.jpg", 0);
    Mat dst(src.rows, src.cols, CV_8UC1, Scalar(0)), dst2;
    int coreSize = 31;
    int coreSizeHalf = (coreSize-1)/2;
    int coreSizeSquare = coreSize * coreSize;
    int imgRows = src.rows;
    int imgCols = src.cols;


duration = static_cast<double>(getTickCount());

    float temp = 0;
    Mat buffer(1, imgCols+coreSize-1, CV_32SC1, Scalar(0));
    float *bufferPtr = buffer.ptr<float>(0);
    Mat borderSrc(imgRows+coreSize-1, imgCols+coreSize-1, CV_8UC1, Scalar(128));

    for ( int i = 0; i < imgRows; i ++ )//边界填充
    {
        uchar *imgRowPtr = src.ptr<uchar>(i);
        uchar *borderRowPtr = borderSrc.ptr<uchar>(i+coreSizeHalf);
        for ( int j = 0; j < imgCols; j ++ )
        {
            borderRowPtr[j+coreSizeHalf] = imgRowPtr[j];
        }
    }

    for ( int i = 0; i < coreSize; i ++ )//buffer初始化循环还是与coreSize相关
                                         //暂时没有解决办法
    {
        uchar *borderSrcPtr = borderSrc.ptr<uchar>(i);
        for ( int j = 0; j < buffer.cols; j ++ )
        {
            bufferPtr[j] += borderSrcPtr[j];
        }
    }

    uchar *dstPtrFirstRow = dst.ptr<uchar>(0);//第一行的均值滤波
    temp = 0;
    for ( int n = 0; n < coreSize; n ++ )
    {
        temp += bufferPtr[n];
    }
    dstPtrFirstRow[0] = temp/coreSizeSquare;//第一行第一个像素的均值滤波
    for ( int i = 1; i < imgCols; i ++ )//第一个像素之后的均值滤波
    {
        temp += bufferPtr[i+coreSize-1] - bufferPtr[i-1];//使用向量法加速使循环与coreSize无关
        dstPtrFirstRow[i] = temp/coreSizeSquare;
    }

    for ( int i = 1; i < imgRows; i ++ )//第一行之后的均值滤波
    {
        uchar *dstPtr = dst.ptr<uchar>(i);
        for ( int r = 0; r < buffer.cols; r ++ )//buffer数据更新
        {
            uchar *borderSrcPtrLast = borderSrc.ptr<uchar>(i-1);
            uchar *borderSrcPtrFirst = borderSrc.ptr<uchar>(i+coreSize-1);
            //使用向量发更新buffer的每个元素
            bufferPtr[r] = bufferPtr[r] + borderSrcPtrFirst[r] - borderSrcPtrLast[r];
        }

        temp = 0;
        for ( int n = 0; n < coreSize; n ++ )
        {
            temp += bufferPtr[n];
        }
        dstPtr[0] = temp/coreSizeSquare;//第一行之后每行的第一个元素滤波

        for ( int j = 1; j < imgCols; j ++ )//第一个元素之后的滤波
        {
            temp += bufferPtr[j+coreSize-1] - bufferPtr[j-1];//向量法加速使循环与coreSize无关
            dstPtr[j] = temp/coreSizeSquare;
        }
    }

duration = static_cast<double>(getTickCount()) - duration;
    duration /= getTickFrequency();
    cout << duration << endl;

    namedWindow("dst", 0);
    imshow("dst", dst);

    blur(src,dst2,Size(31,31),Point(-1,-1));
    namedWindow("dst2", 0);
    imshow("dst2", dst2);

    waitKey(0);

    return 0;
}

dst是本文的与窗口大小无关的均值滤波函数结果,dst2是opencv自带的均值滤波结果:



仔细观察还是有一点不同的,本文的均值滤波颜色灰度更加淡,当然还是opencv自带的函数会比较好,在时间上,还是比opencv自带的均值滤波时间要慢一些,大家可以自行验证运行速度,因为计算机配置不同于是不贴出运行时间。对于本文算法运行时间比opencv自带的算法效率低,笔者分析有以下原因:
1、边界扩充函数占整体滤波算法的1/7~1/6的时间,边界扩充函数没有做优化,有兴趣的的朋友可以试试优化,有时间我也会去完成这个工作。
2、buffer 第一次初始化的时候还是与 coreSize 有关,暂时也没有解决方法。
3、关于图像像素引用并没有采用最高效的像素引用方法,时间有限,有时间我也会去完成这个工作。

以下是与窗口无关的均值滤波算法原理,也是笔者毕业论文中的一部分内容:





具体算法实施过程中要注意加速原理,不论是行运算,和列运算都可以加速,再就是涉及 uchar 和 float 的数据转换,一定要整体运算,整体赋值,否则结果会出问题。


大津法(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字节。在实际使用中需要根据具体情况进行修改。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值