在unity中做圖片二值化的一些方法。C#

公司搞了一個項目。給阿里閒魚做的。要求把一張圖片用很多口紅拼圖顯示出來。然後就只能夠擼一發二值化算法了

1.圖片二值化主要分兩種一種叫做全局二值化,一種叫做局部二值化

全局二值化 是指用整張圖片所有像素的灰度值算出一個全局的閾值,然後在用所有像素的灰度值和這個閾值比較,如果大於這個閾值就是黑小於這個就是白。這個做法做出來的圖片相對來說比較豐滿。但是對於那些圖片灰度分佈不均勻的圖片效果就很不理想了

局部二值化 是指把圖片分成很多小塊對每一塊的圖片做閾值比較。這樣的方法可以適應更多類型的圖片。

在全局二值化中大津法可以說是標杆,而在局部二值化中Sauvola算法可以算是標杆。
廢話不說上代碼C#代碼。這是王宇大神折騰出來的大津法

int FinalValue;
float finalValueFloat;  
void Otsu(Texture2D texTemp)
    {
        int width = texTemp.width;
        int height = texTemp.height;
        float[] nHistogram = new float[256];//灰度直方圖  
        float[] dVariance = new float[256];//類間方差
        int N = width * height;//總像素數
        for (int i = 0; i < 256; i++)
        {
            nHistogram[i] = 0.0f;
            dVariance[i] = 0.0f;
        }
        Color pc = texTemp.GetPixel(0, height - 1);
        Debug.Log(texTemp.width + "  " + texTemp.height);
        string msg = "";
        float g = 0;
        for (int i = 0; i < width; i++)
        {
            for (int j = 0; j < height; j++)
            {
                g = texTemp.GetPixel(i, j).grayscale;
                int temp = (int)Math.Round(g * 255);
                nHistogram[temp]++;//建立直方圖
            }
        }
        float Pa = 0.0f;      //背景出現概率  
        float Pb = 0.0f;      //目標出現概率  
        float Wa = 0.0f;      //背景平均灰度值  
        float Wb = 0.0f;      //目標平均灰度值  
        float W0 = 0.0f;      //全局平均灰度值  
        float dData1 = 0.0f;
        float dData2 = 0.0f;
        //計算全局平均灰度 
        for (int i = 0; i < 256; i++)
        {
            nHistogram[i] /= N;
            W0 += i * nHistogram[i];
        }
        scale = -0.008f * W0 + 2.5f;
        //對每個灰度值計算類間方差  
        for (int i = 0; i < 256; i++)
        {
            Pa += nHistogram[i];
            Pb = 1 - Pa;
            dData1 += i * nHistogram[i];
            dData2 = W0 - dData1;
            Wa = dData1 / Pa;
            Wb = dData2 / Pb;
            dVariance[i] = (Pa * Pb * Mathf.Pow((Wb - Wa), 2));
        }
        //遍歷每個方差,求取類間最大方差所對應的灰度值 
        float temp2 = 0f;
        for (int i = 0; i < 256; i++)
        {
            if (dVariance[i] > temp2)
            {
                temp2 = dVariance[i];
                FinalValue = i;
                finalValueFloat = FinalValue / 255f;
            }
        }
    }

finalValueFloat 既是全局得出來的閾值。可以根據這個閾值和圖像的灰度值做比較,大於這個閾值爲黑,小於爲白

局部的計算稍微複雜一些。
1.首先要把灰度值從第一個像素全部磊加到最後一個,Sauvola還的把灰度平方後累加。
2.定好每個小塊的寬高
3.循環每個像素點,以像素點爲中心,寬高就是2步驟的寬高取一個方塊。然後獲得這個方塊的所有灰度值,和所有灰度方差值
通過這兩個可以計算出這個方塊的閾值 
其中m(x,y)爲當前方塊平均灰度,s(x,y)爲當前灰度標準方差
R爲標準方差的動態範圍,如果輸入圖像爲8位的灰度圖像則 R=128.我用的是256.因爲我覺得我的圖像質量比較高
K是使用者自定義的一個修正參數,k的取值範圍一般來說是0<k<1.可以用來調節二值化畫出來的大小的流量,
上代碼

grayImage數組爲我把一個位圖的所有灰度全部取出來的(方法如下) trueTex爲Unity中的Texture2D
float[] grayImagenew float[trueTex.width * trueTex.height];
       int tw = trueTex.width;
       int th = trueTex.height;
       for (int i = 0; i < tw; i++)
       {
           for (int j = 0; j < th; j++)
           {
               gryarr[j * tw + i] = trueTex.GetPixel(i, j).grayscale;
           }
       }  

void Csauvola(float[] grayImage, int w, int h, float k, int windowSize)
    {
        int whalf = windowSize >> 1;
        //windowSize = 1;
        int i, j;
        int IMAGE_WIDTH = w;
        int IMAGE_HEIGHT = h;
        // create the integral image
        float[] integralImg = new float[IMAGE_WIDTH * IMAGE_HEIGHT];
        float[] integralImgSqrt = new float[IMAGE_WIDTH * IMAGE_HEIGHT];
        float sum = 0;
        float sqrtsum = 0;
        int index;
        StringBuilder sbs = new StringBuilder();
        int ids = 0;
        for (i = 0; i < IMAGE_HEIGHT; i++)
        {
            sum = 0;
            sqrtsum = 0;
            for (j = 0; j < IMAGE_WIDTH; j++)
            {
                //index = (IMAGE_HEIGHT - 1 - i) * IMAGE_WIDTH + j;
                index = i * IMAGE_WIDTH + j;
                sum += grayImage[index];
                sqrtsum += grayImage[index] * grayImage[index];
                ids += 1;
                sbs.AppendLine("g=" + grayImage[index] + "sum=" + sum + "sqrtsum=" + sqrtsum + "index=" + ids);
                if (i == 0)
                {
                    integralImg[index] = sum;
                    integralImgSqrt[index] = sqrtsum;
                }
                else
                {
                    integralImgSqrt[index] = integralImgSqrt[(i - 1) * IMAGE_WIDTH + j] + sqrtsum;
                    integralImg[index] = integralImg[(i - 1) * IMAGE_WIDTH + j] + sum;
                }
            }
        }
        int xmin, ymin, xmax, ymax;
        float mean, std, threshold;
        float diagsum, idiagsum, diff, sqdiagsum, sqidiagsum, sqdiff, area;
        Color[] biImage = new Color[IMAGE_WIDTH * IMAGE_HEIGHT];
        for (i = 0; i < IMAGE_WIDTH; i++)
        {
            for (j = 0; j < IMAGE_HEIGHT; j++)
            {
                xmin = Mathf.Max(0, i - whalf);
                ymin = Mathf.Max(0, j - whalf);
                xmax = Mathf.Min(IMAGE_WIDTH - 1, i + whalf);
                ymax = Mathf.Min(IMAGE_HEIGHT - 1, j + whalf);
                area = (xmax - xmin + 1) * (ymax - ymin + 1);
                if (area <= 0)
                {
                    biImage[i * IMAGE_WIDTH + j] = Color.clear;//255;
                    continue;
                }
                if (xmin == 0 && ymin == 0)
                {
                    diff = integralImg[ymax * IMAGE_WIDTH + xmax];
                    sqdiff = integralImgSqrt[ymax * IMAGE_WIDTH + xmax];
                }
                else if (xmin > 0 && ymin == 0)
                {
                    diff = integralImg[ymax * IMAGE_WIDTH + xmax] - integralImg[ymax * IMAGE_WIDTH + xmin - 1];
                    sqdiff = integralImgSqrt[ymax * IMAGE_WIDTH + xmax] - integralImgSqrt[ymax * IMAGE_WIDTH + xmin - 1];
                }
                else if (xmin == 0 && ymin > 0)
                {
                    diff = integralImg[ymax * IMAGE_WIDTH + xmax] - integralImg[(ymin - 1) * IMAGE_WIDTH + xmax];
                    sqdiff = integralImgSqrt[ymax * IMAGE_WIDTH + xmax] - integralImgSqrt[(ymin - 1) * IMAGE_WIDTH + xmax]; ;
                }
                else
                {
                    diagsum = integralImg[ymax * IMAGE_WIDTH + xmax] + integralImg[(ymin - 1) * IMAGE_WIDTH + xmin - 1];
                    idiagsum = integralImg[(ymin - 1) * IMAGE_WIDTH + xmax] + integralImg[ymax * IMAGE_WIDTH + xmin - 1];
                    diff = diagsum - idiagsum;
                    sqdiagsum = integralImgSqrt[ymax * IMAGE_WIDTH + xmax] + integralImgSqrt[(ymin - 1) * IMAGE_WIDTH + xmin - 1];
                    sqidiagsum = integralImgSqrt[(ymin - 1) * IMAGE_WIDTH + xmax] + integralImgSqrt[ymax * IMAGE_WIDTH + xmin - 1];
                    sqdiff = sqdiagsum - sqidiagsum;
                }
                //灰度和平均值
                mean = diff / area;
                //標準方差。
                std = Mathf.Sqrt((sqdiff - diff * diff / area) / (area - 1));
                //閾值
                threshold = mean * (1 + k * ((std / 255) - 1));
                //當前像素灰度
                float golys = grayImage[j * IMAGE_WIDTH + i];
                //用當前像素灰度和當前閾值做比較
                if (grayImage[j * IMAGE_WIDTH + i] < threshold)
                {
                    biImage[j * IMAGE_WIDTH + i] = Color.white * 0;//0;
                }
                else
                {
                    biImage[j * IMAGE_WIDTH + i] = Color.white;//255;
                }
                //索貝爾算出圖片邊緣
                int iw = IMAGE_WIDTH;
                float gx = 0;
                float gy = 0;
                int addline = 1;
                if (j - addline < 0 || j + addline >= IMAGE_HEIGHT || i - addline < 0 || i + addline >= iw)
                {
                    biImage[j * IMAGE_WIDTH + i] = Color.clear;
                    continue;
                }
                gx = grayImage[(j - addline) * iw + i + addline] + 2 * grayImage[(j) * iw + i + addline] + grayImage[(j + addline) * iw + i + addline] - (grayImage[(j - addline) * iw + i - addline] + 2 * grayImage[(j) * iw + i - addline] + grayImage[(j + 1) * iw + i - addline]);
                gy = grayImage[(j - addline) * iw + i - addline] + 2 * grayImage[(j - addline) * iw + i] + grayImage[(j - addline) * iw + i + addline] - (grayImage[(j + addline) * iw + i - addline] + 2 * grayImage[(j + addline) * iw + i] + grayImage[(j + addline) * iw + i + addline]);
                float g = Mathf.Sqrt(Mathf.Pow(gx, 2) + Mathf.Pow(gy, 2));
                if (golys < g)
                {
                    biImage[j * iw + i] = Color.clear;
                }
            }
        }
    }  

爲來讓圖片更加豐滿在代碼裏面用索貝爾算出圖片邊緣

當然還有很多其他的二值化算法可以參考其他鏈接
http://blog.csdn.net/tyf122/article/details/8738156  這個是列舉一些局部和全局二值化的目錄
http://www.lxway.com/498114011.htm   這個是Sauvola 算法,但是他下面還有很多相關文章很有價值
http://www.cnblogs.com/carekee/articles/3643394.html  13種全局二值化帶C#代碼。很叼 

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