想說就說
繁忙的大三學期開始了,這一年將會有很多個課程project要做,包括物聯網的,安卓的,Web網頁開發,傳感器實驗,雲計算,數據庫,人工智能等等,趁國慶這個假期,我想在CSDN博客開始我的整理,一來是對這些project開發過程的記錄,二來是以後找工作面試可以回頭看看,複習複習,(甚至可以跟HR裝逼說想了解更多,就去看我的博客吧hh),三來也是覺得不要荒廢了這個博客嘛。
好了,廢話不多說。這個系列的博客記錄的是我做物聯網導論實驗課project的過程,其實這些都是我們平常的作業,估計最後的project成品就是這樣一步步累積起來的,暫時我也不太清楚最後能實現什麼,但是走一步就記錄一步吧。
二維碼的二值化
第一次作業是做二維碼的二值化,或者說是圖像的二值化。爲什麼是二維碼呢?因爲我們後邊要做二維碼的識別(但我覺得不會整個過程都讓我們實現),其關鍵的第一步就是二維碼的二值化,直觀來講,就是將二維碼圖案本身與背景分割開來,並將圖像的像素是二維碼黑色部分置爲0,其餘部分置爲1(從顏色上講,也就是白色),具體操作步驟如下:
- 將圖像轉化爲灰度圖
- 對灰度圖進行二值化
其中難點就在於二值化了。上課時,TA提了一種算法,稱爲大津算法(OTSU),其思想是:找到一個閾值,將0~255的像素分成黑白兩部分,並使每個點的原灰度值到此閾值的距離的方差最小。
原理很簡單,我就大概說一下計算過程。
設前景像素(二維碼黑色部分)所佔比例爲
設那個閾值爲
其中,
具體計算的時候呢,可以先求出灰度值圖像的直方圖向量,如果用
大津法Matlab代碼實現
由於我們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報了這個喜訊之後,他又給了我兩張圖做測試,我將其結果都貼在這裏,分別對比了以上兩種算法:
原圖,大津法,高斯法分別對應左、中、右。
從以上結果來看,這種亮度不均的數據,大津法可謂無能爲力,高斯法表現得更好一些。但是這兩張圖還是蠻刁鑽的,一個是高強度的曝光,一個是誇張得不得了的遮擋,這對於本來就是用於檢測邊緣的高斯法也構成了不小的挑戰,可以看到二值化結果雖說大部分還原出來了,但是在光暗交接處還是顯得有點乏力。
對於強光,我覺得可能可以先用一些去強光的算法,對於光暗交接的陰影,我現在還不太清楚解決思路。但我有種感覺,以上兩種情況其實可以交給後面的步驟再來處理,如果我們已經將二維碼矯正,將其各個區域都找了出來,我們可以精確到在每個格子裏統計黑像素的個數,從而來判斷整個格子是不是都是黑的。當然,二維碼本身的糾錯能力,也許也可以解決這些問題。
總而言之,這一次的實驗我就到此爲止了,還算是差強人意。