Niblack快速实现与分块大津

Niblack

Niblack是局部二值化算法里思路简单,同时效果稳定的算法了。中心思想是T = m + s*k,T为以像素点为中心的一个w * w区域内所有像素点灰度值的平均值,s为矫正系数,k为w * w个像素点灰度的标准差
这里省略后面运算标准差的计算(K66算不过来),单用平均值作为单个像素的阈值,效果也很不错。

同时。,为了减少代码运算量,运用积分图法计算全局像素的灰度和,从而大大减少代码的重复运算量,K66终于不掉帧了(积分图法自行百度,百度讲得很清楚)

uint32 temp[70][186];
uint16 T[70][186];

int max(int a,int b)
{
	return a >= b ? a : b;
}

int min(int a,int b)
{
	return a <= b ? a : b;
}

void Niblack()
{
		uint8_t *p_image = &image[30][1];
		uint8_t *p_Pixels = &P_Pixel[0][0];
		uint16	*p_T = &T[0][0];
		uint8_t  w =  60;
		int m = 70;
		int n = 186;
		int j = 0;
		float sum = 0;
		float average = 0;
//创建积分图
		temp[0][0] = image[30][1];

		for (j = 1;j < n;j++)
				 temp[0][j] = image[30][1 + j] + temp[0][j - 1];

		for (i = 1;i < m;i++)
				 temp[i][0] = image[30 + i][1] + temp[i - 1][0];

		for (i = 1;i < m;i++)
		{
				for (j = 1;j < n;j++)
				{
						temp[i][j] = image[30 + i][1 + j] + temp[i][j - 1] + temp[i - 1][j] - temp[i - 1][j - 1];
				}
		}
		//开始计算阈值
		for (i = 0;i < m;i++)
		{
			 for (j = 0;j < n;j++)
			 {
						int a1 = max(i - w - 1,0);
						int a2 = min(i + w - 1,m - 1);
						int b1 = max(j - w - 1,0);
						int b2 = min(j + w - 1,n - 1);
						sum = temp[a1][b1] - temp[a1][b2] - temp[a2][b1] + temp[a2][b2];
						average = sum /((a2 - a1)*(b2 - b1));
						*p_T = (uint16)average;
						p_T++;
				}
		}
		
		p_T = &T[0][0];
		for (i =  0;i<m;i++)   
		{
				for (j = 0;j<n;j++)  
				{	
						if (*p_image > (uint8_t)((*p_T) * 0.95f))
						{
								*p_Pixels = white;
						}
						else
						{
								*p_Pixels = black;
						}
						p_Pixels++;
						p_image++;
						p_T++;
				}
				p_image+=2;
		}
}

最后二值化时,增加一个小于1的系数,能对平滑局域产生的为噪点很好的抑制,对大津无法处理的环内黑线有良好的处理效果

I = imread('图像路径');
I = rgb2gray(I);
figure(1);
imshow(I);
w =  60;
[m,n] = size(I);
T = zeros(m,n);
temp = zeros(m,n);
back = zeros(m,n);
%创建积分图
temp(1,1) = I(1,1);

for j = 2:n
     temp(1,j) = double(I(1,j)) + temp(1,j - 1);
end

for i = 2:m
     temp(i,1) = double(I(i,1)) + temp(i - 1,1);
end

for i = 2:m
    for j = 2:n
        temp(i,j) = double(I(i,j)) + temp(i,j - 1) + temp(i - 1,j) - temp(i - 1,j - 1);
    end
end

for i = 1:m
    for j = 1:n
        a1 = max(i - w,1);
        a2 = min(i + w,m);
        b1 = max(j - w,1);
        b2 = min(j + w,n);
        sum = temp(a1,b1) - temp(a1,b2) - temp(a2,b1) + temp(a2,b2);
        average = double(sum) /((a2 - a1)*(b2 - b1));
%         s = 0;
%         for k = a1:a2
%             for l = b1:b2
%                 s = s + (uint32(I(k,l)) - average)*(uint32(I(k,l)) - average);
%             end
%         end
%         s= sqrt(double(s)/((a2 - a1)*(b2 - b1)));
        T(i,j) = average + 0;
    end
end

for i =  1:m   
    for j = 1:n   
        if I(i,j) > T(i,j)*0.95
            back(i,j) = uint8(255);
        else    
            back(i,j) = uint8(0);
        end
    end
end
figure(2);
imshow(back);

左大津,右Niblack(还有两个参数可以调参,一个是w,还有一个是最后阈值时T乘的小于1的数)
在这里插入图片描述
在k66上运算大津一次大约为2.3ms,Niblack。。算了吧。

尝试改进大津

由于Niblack的运算时序还是使k66爆炸(同时TC264方面虽然是双核,但传图处理再赛道识别是一套连续的操作,实际上还是单核在处理,然后264单个核比不过K66。。。),于是转换一下思路,能不能把大津改了?就试了一下,效果还挺好
首先中心思路是赛道图像中间比较亮(一般大津不出问题)两边边缘部分较暗,在赛道部分灰度比中间部分小10左右(有时更多),于是大津边缘糊的便是由于全局阈值,有部分边缘赛道被划入到背景中,同理赛道的反光是由于蓝布上光线太亮,部分区域灰度值过大,被划入目标(赛道)中。基于此,分块大津将赛道图像分成三份(左,中,右),分别处理三份光线条件不同的图像(同时大津的时序也并没有增加多少,仅是多判断了两次类间方差),用三个阈值处理一份图像,可以比较有效抑制模糊和反光问题。
(左边赛道糊的部分减少,右上方反光部分得到抑制)
在这里插入图片描述
可惜分块阈值的先天缺陷是一旦某快阈值出现问题,将出现图像上的分块
(这里是由于右边分块时白色赛道太少,于是。。大津方差算出来阈值误差较大。)
在这里插入图片描述
为了解决这部分阈值错误的bug,便利用大津算出来的部分图像信息,灰度和以及背景像素点占比来判断这种错误阈值问题,思路是统计学。。。。灰度和与像素点比例之间存在一定的关系,即比例越大,灰度和会越小,而反常情况时,灰度和与比例之间会有很大的误差(与正常情况相比)
(图中右块灰度和17左右时,比例正常应在0.9左右)
然后这种bug一般发生在灰度和很小或者很大时(相对于正常目标和背景像素点比较平均时),因为这时候目标和背景像素点极有可能某个部分占有很大比例,使得大津的二值化变成一种强制化的操作,类似于鸡蛋里挑骨头。于是判断条件的一个比较好的思路便是先判断灰度和过大或过小,再进行比例和阈值差(与中间图像)的判断
在这里插入图片描述
判出错误情况后,再将中间部分的阈值(中间一般情况下没有差错,除了坡道)减去一个常数后, 再进行阈值分割,便可以较好得处理赛道图像

float bin_float[256];     //灰度比例直方图
int size = 70 * 186;
float u = 0;                //全图平均灰度
float w0 = 0;
float u0 = 0;               //前景灰度
byte Bin_Array[256];
int i;
float gray_hh = 0;//前景灰度和
float var = 0;//方差
float maxvar = 0;//最大方差
float maxgray = 0;//最大灰度占比
float maxbin = 0;

struct size_point
{
		int x0;
		int y0;
		int x1;
		int y1;
};

struct size_point ostu_point[3]={
{0,0,40,69},
{41,0,135,69},
{136,0,185,69},
};

void Ostu(int x,int y)//大津
{
	int j, k;
	uint8_t (*p_image)[188] = &image[17];
	for (k = 0; k < 3; k++)
	{
		maxvar = 0;
		w0 = 0;
		u = 0;
		gray_hh = 0;
		var = 0;
		Thresholds[k] = 0;
		for (i = 0; i < 256; i++)
		{ bin_float[i] = 0; }
		for (i = ostu_point[k].y0; i <= ostu_point[k].y1; i++)
		{
				for (j = ostu_point[k].x0; j <= ostu_point[k].x1; j++)
				{
						++bin_float[*(*(p_image + i) + j)];
				}
		}
		size = (ostu_point[k].y1 - ostu_point[k].y0 + 1) * (ostu_point[k].x1 - ostu_point[k].x0 + 1);
		for (i = 0; i < 256; i++)
		{
				bin_float[i] = bin_float[i] / size;
				u += i * bin_float[i];
		}
		//创建比例灰度直方图
		for (i = 0; i < 256; i++)
		{
				w0 += bin_float[i];
				gray_hh += i * bin_float[i];             //灰度和
				u0 = gray_hh / w0;
				var = (u0 - u) * (u0 - u) * w0 / (1 - w0);
				if (var > maxvar)
				{
						maxgray = gray_hh;
						maxbin = w0;
						maxvar = var;
						Thresholds[k] = (byte)i;
				}
		}
		if (k == 0)
		{
				if (gray_hh > 15 && gray_hh <= 33)
				{
						if (maxbin < 0.9f)
						{
								Thresholds[k] = (byte)(Thresholds[1] - 3);
						}
				}
				else if (gray_hh > 41 && gray_hh <= 47)
				{
						if (maxbin < 0.64f || maxbin > 0.76f)
						{
								Thresholds[k] = (byte)(Thresholds[1] - 3);
						}
				}
				else if (gray_hh > 50 && gray_hh <= 60)
				{
						if (maxbin < 0.42f || maxbin > 0.58f)
						{
								Thresholds[k] = (byte)(Thresholds[1] - 3);
						}
				}
				//SetText("右边var:" + maxvar + " 阈值:" + Thresholds[k] + " 比例:" + maxbin + " 灰度和" + gray_hh);
		}
		else if (k == 1)
		{
				if(gray_hh > 69 && gray_hh < 80)
				{
						if (maxbin > 0.15f)
						{
								Thresholds[k] = (byte)(Thresholds[0] + 3);
						}
				}
				//SetText("中间var:" + maxvar + " 阈值:" + Thresholds[k] + " 比例:" + maxbin + " 灰度和" + gray_hh);
		}
		else if (k == 2)
		{
				if (maxbin < 0.85f && gray_hh < 28)
				{
						Thresholds[k] = (byte)(Thresholds[1] - 3);
				}
				else if(gray_hh > 69 && gray_hh < 79)
				{
						if (maxbin < 0.5f || maxbin > 0.15f)
						{
								Thresholds[k] = (byte)(Thresholds[1] - 3);
						}
				}
				//SetText("左边var:" + maxvar + " 阈值:" + Thresholds[k] + " 比例:" + maxbin + " 灰度和" + gray_hh);
		}
		for (i = 0; i < Thresholds[k]; i++)
		{
				Bin_Array[i] = black;
		}
		for (i = Thresholds[k]; i < 256; i++)
		{
				Bin_Array[i] = white;
		}
		for (i = ostu_point[k].y0; i <= ostu_point[k].y1; i++)
		{
				for (j = ostu_point[k].x0; j <= ostu_point[k].x1; j++)
				{
						P_Pixel[i][j] = Bin_Array[*(*(p_image + i) + j)];
				}
		}
	}
}

之后还有一些无法处理的问题,之后再想想有什么方法解决吧,或许还可以有更好的办法改大津。

无法处理的情况:坡道部分开到天上去了,上面灯光太亮白色的天灰板都变黑了。。
处理不好的情况:当整幅图灰度直方图是比较标准的双峰时,强行分块会产生反效果
在这里插入图片描述

抽一半改进大津

最近发现,对于186*70的灰度图,遍历所有的点和只用一半的点(隔行取点)算出来的大津几乎没有差别(最多差2),基于此,那我们便可以用多出来的时序做点其他的事啦。
由于在分块大津中,我们很难找到一个标准值去修正强行二值化时错误的阈值,所以我们先算一遍一半大津,再分三块进行一半大津,在一半整体大津的基础上,与算出来的三块大津值进行一个互补,这样做虽然效果不会有单用三块大津值明显,但是稳定性拉满,只要大津不出错,修正后的大津阈值也不会出错;而且有了标准值后,出现强行二值化情况也可以很好得到避免(即与标准值相差一定值以上时)

if (My_Abs(Thresholds - Thresholds[k]) >= 45)//相差太大直接用标准值
{
    Thresholds[k] = Thresholds;
}
else
{
    Thresholds[k] = (uint8_t)(Thresholds[k] + 0.5f * (Thresholds - Thresholds[k]));//这里是取了平均,可以通过调整占比适应不同的光照条件
}

这应该是最后的一版改大津,快比赛了(混队友的,我很久没调过车了,孙哥nb),我也没有什么想法,代码写得很烂,很多都没法在k66上使用,也没办法很好解决问题,等比赛结束再写感想吧。

参考:改进Niblack算法及其在不均匀光照条件下的应用_贾坤昊
https://kns.cnki.net/KCMS/detail/detail.aspx?dbcode=CJFQ&dbname=CJFDLAST2019&filename=RJDK201904020&v=MTMwMDRSN3FmWXVSbkZ5L2tXcnpLTnlmUFpiRzRIOWpNcTQ5SFpJUjhlWDFMdXhZUzdEaDFUM3FUcldNMUZyQ1U=

  • 7
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值