[課程Project-物聯網導論] 二維碼的二值化 (1)

想說就說


繁忙的大三學期開始了,這一年將會有很多個課程project要做,包括物聯網的,安卓的,Web網頁開發,傳感器實驗,雲計算,數據庫,人工智能等等,趁國慶這個假期,我想在CSDN博客開始我的整理,一來是對這些project開發過程的記錄,二來是以後找工作面試可以回頭看看,複習複習,(甚至可以跟HR裝逼說想了解更多,就去看我的博客吧hh),三來也是覺得不要荒廢了這個博客嘛。

好了,廢話不多說。這個系列的博客記錄的是我做物聯網導論實驗課project的過程,其實這些都是我們平常的作業,估計最後的project成品就是這樣一步步累積起來的,暫時我也不太清楚最後能實現什麼,但是走一步就記錄一步吧。

二維碼的二值化


第一次作業是做二維碼的二值化,或者說是圖像的二值化。爲什麼是二維碼呢?因爲我們後邊要做二維碼的識別(但我覺得不會整個過程都讓我們實現),其關鍵的第一步就是二維碼的二值化,直觀來講,就是將二維碼圖案本身與背景分割開來,並將圖像的像素是二維碼黑色部分置爲0,其餘部分置爲1(從顏色上講,也就是白色),具體操作步驟如下:

  • 將圖像轉化爲灰度圖
  • 對灰度圖進行二值化

其中難點就在於二值化了。上課時,TA提了一種算法,稱爲大津算法(OTSU),其思想是:找到一個閾值,將0~255的像素分成黑白兩部分,並使每個點的原灰度值到此閾值的距離的方差最小

原理很簡單,我就大概說一下計算過程。

設前景像素(二維碼黑色部分)所佔比例爲w0 ,期望爲u0 ,背景像素所佔比例爲w1 ,期望爲u1 ,那麼有整個圖像灰度值的期望u=w0u0+w1u1 .

設那個閾值爲t (某灰度值),則目標函數爲:

t=argmax g(t)=argmax w0(u0u)2+w1(u1u)2

其中,g(t) 爲圖像各個點的灰度到該閾值距離的方差。由於w1=1w0u=w0u0+w1u1g(t) 可以簡化爲:

g(t)=w0(uu0)1w0

具體計算的時候呢,可以先求出灰度值圖像的直方圖向量,如果用p(x) 表示0~255個灰度值在圖像中出現的頻率,那麼w0=p(x<=t)u0=1w0x<=txp(x)w1u1 就也依次類推。

大津法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在它上面還是發揮得挺好的。但是如果作用於下面這一張進階圖的話,效果就不太好了:

OTSU2-1

OTSU2-2

原因是圖像的亮度不均勻,有部分二維碼的圖像被照得很亮,有的卻處於暗處,大津法找到的閾值是全局的,一劃分下來,就白不是白,黑不是黑了。

改進的局部算法


針對於上述情況,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源碼裏扒出來的。

該算法作用與上面那個圖的結果如下所示:

G2

跟TA報了這個喜訊之後,他又給了我兩張圖做測試,我將其結果都貼在這裏,分別對比了以上兩種算法:

test1

這裏寫圖片描述

原圖,大津法,高斯法分別對應左、中、右。

從以上結果來看,這種亮度不均的數據,大津法可謂無能爲力,高斯法表現得更好一些。但是這兩張圖還是蠻刁鑽的,一個是高強度的曝光,一個是誇張得不得了的遮擋,這對於本來就是用於檢測邊緣的高斯法也構成了不小的挑戰,可以看到二值化結果雖說大部分還原出來了,但是在光暗交接處還是顯得有點乏力。

對於強光,我覺得可能可以先用一些去強光的算法,對於光暗交接的陰影,我現在還不太清楚解決思路。但我有種感覺,以上兩種情況其實可以交給後面的步驟再來處理,如果我們已經將二維碼矯正,將其各個區域都找了出來,我們可以精確到在每個格子裏統計黑像素的個數,從而來判斷整個格子是不是都是黑的。當然,二維碼本身的糾錯能力,也許也可以解決這些問題。

總而言之,這一次的實驗我就到此爲止了,還算是差強人意。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章