公司搞了一個項目。給阿里閒魚做的。要求把一張圖片用很多口紅拼圖顯示出來。然後就只能夠擼一發二值化算法了
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[] grayImage= new 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;
}
}
}
}
爲來讓圖片更加豐滿在代碼裏面用索貝爾算出圖片邊緣
當然還有很多其他的二值化算法可以參考其他鏈接