繁忙的大三学期开始了,这一年将会有很多个课程project要做,包括物联网的,安卓的,Web网页开发,传感器实验,云计算,数据库,人工智能等等,趁国庆这个假期,我想在CSDN博客开始我的整理,一来是对这些project开发过程的记录,二来是以后找工作面试可以回头看看,复习复习,(甚至可以跟HR装逼说想了解更多,就去看我的博客吧hh),三来也是觉得不要荒废了这个博客嘛。
好了,废话不多说。这个系列的博客记录的是我做物联网导论实验课project的过程,其实这些都是我们平常的作业,估计最后的project成品就是这样一步步累积起来的,暂时我也不太清楚最后能实现什么,但是走一步就记录一步吧。
第一次作业是做二维码的二值化,或者说是图像的二值化。为什么是二维码呢?因为我们后边要做二维码的识别(但我觉得不会整个过程都让我们实现),其关键的第一步就是二维码的二值化,直观来讲,就是将二维码图案本身与背景分割开来,并将图像的像素是二维码黑色部分置为0,其余部分置为1(从颜色上讲,也就是白色),具体操作步骤如下:
其中难点就在于二值化了。上课时,TA提了一种算法,称为大津算法(OTSU),其思想是:找到一个阈值,将0~255的像素分成黑白两部分,并使每个点的原灰度值到此阈值的距离的方差最小。
原理很简单,我就大概说一下计算过程。
设前景像素(二维码黑色部分)所占比例为 w0 ,期望为 u0 ,背景像素所占比例为 w1 ,期望为 u1 ,那么有整个图像灰度值的期望 u=w0∗u0+w1∗u1 .
设那个阈值为 t (某灰度值),则目标函数为:
其中, g(t) 为图像各个点的灰度到该阈值距离的方差。由于 w1=1−w0 , u=w0∗u0+w1∗u1 , g(t) 可以简化为:
具体计算的时候呢,可以先求出灰度值图像的直方图向量,如果用 p(x) 表示0~255个灰度值在图像中出现的频率,那么 w0=p(x<=t) , u0=1w0∑x<=tx∗p(x) , w1 、 u1 就也依次类推。
由于我们TA要求我们用matlab,而且不允许使用封装好的函数,因此一句话可以搞定的事情弄得如下这么多行:
function [dst] = OTSUthreshold(src)
if(size(src, 3) == 3)
src = rgb2gray(src);
end
histgram = zeros(1, 256);
imgHeight = size(src, 1);
imgWidth = size(src, 2);
imgSize = imgHeight * imgWidth;
% Get histogram vector
for i = 1 : imgHeight
for j = 1 : imgWidth
index = src(i, j)+1;
histgram(index) = histgram(index) + 1;
end
end
% Get frequency of indensity
avgForImg = 0.0;
for i = 1 : 256
histgram(i) = histgram(i) / imgSize;
avgForImg = avgForImg + (i-1) * histgram(i);
end
% Get the best threshold using OTUS algorithm
th = 0;
maxVariance = 0;
frontWeight = 0;
avgForFront = 0;
for i = 1 : 256
frontWeight = frontWeight + histgram(i);
avgForFront = avgForFront + (i-1) * histgram(i);
tmp = avgForFront / frontWeight - avgForImg;
variance = tmp * tmp * frontWeight * (1 - frontWeight);
if(variance > maxVariance)
maxVariance = variance;
th = i-1;
end
end
% Get the binary image using the best threshold
dst = zeros(imgHeight, imgWidth);
for i = 1 : imgHeight
for j = 1 : imgWidth
if(src(i, j) > th)
dst(i, j) = 255;
else
dst(i, j) = 0;
end
end
end
end
你会发现这份代码很C,没错,我就是从别人的C代码翻译过来的,出处是哪里倒是给忘了。它跑出来的结果如下所示:
这是TA给的一张基础测试图,OTSU在它上面还是发挥得挺好的。但是如果作用于下面这一张进阶图的话,效果就不太好了:
原因是图像的亮度不均匀,有部分二维码的图像被照得很亮,有的却处于暗处,大津法找到的阈值是全局的,一划分下来,就白不是白,黑不是黑了。
针对于上述情况,TA说要是能够解决好就能加分,我当然会好好研究一波啦。很容易想到的就是使用局部的算法,也即对于每一个像素点,在以其为中心的窗口里,计算一个阈值,这就是所谓的局部自适应阈值。我们可以使用局部的大津法进行二值化,这样效果肯定会好一些,但是我使用的是另外一种思路:我的阈值取自像素窗口中的高斯平均值。因为我想到,一般高斯滤波是用来检测边缘的,我其实可以将二维码的图案当作是没有内容的边框,当然我需要用到比较大的核。为了让某些结果更加明显,我可以再对高斯平均的值再减去一个常数。
下面是使用了高斯滤波的代码:
function [dst] = adaptiveThreshold(src, blocksize, delta)
if (nargin < 2)
blocksize = 13;
delta = 30;
elseif (nargin < 3)
delta = 30;
end
if(size(src, 3) == 3)
src = rgb2gray(src);
end
sigma = ((blocksize-1)*0.5-1)*0.3+0.8;
gaussianFilter = fspecial('gaussian', [blocksize, blocksize], sigma);
mid = imfilter(src, gaussianFilter, 'same');
imgHeight = size(src, 1);
imgWidth = size(src, 2);
dst = ones(imgHeight, imgWidth);
dst = (src > (mid-delta))*255;
end
需要说明的是,为了构造一个高斯滤波器,我需要计算参数sigma,这个公式是从opencv源码里扒出来的。
该算法作用与上面那个图的结果如下所示:
跟TA报了这个喜讯之后,他又给了我两张图做测试,我将其结果都贴在这里,分别对比了以上两种算法:
原图,大津法,高斯法分别对应左、中、右。
从以上结果来看,这种亮度不均的数据,大津法可谓无能为力,高斯法表现得更好一些。但是这两张图还是蛮刁钻的,一个是高强度的曝光,一个是夸张得不得了的遮挡,这对于本来就是用于检测边缘的高斯法也构成了不小的挑战,可以看到二值化结果虽说大部分还原出来了,但是在光暗交接处还是显得有点乏力。
对于强光,我觉得可能可以先用一些去强光的算法,对于光暗交接的阴影,我现在还不太清楚解决思路。但我有种感觉,以上两种情况其实可以交给后面的步骤再来处理,如果我们已经将二维码矫正,将其各个区域都找了出来,我们可以精确到在每个格子里统计黑像素的个数,从而来判断整个格子是不是都是黑的。当然,二维码本身的纠错能力,也许也可以解决这些问题。
总而言之,这一次的实验我就到此为止了,还算是差强人意。