前言 :
簡易動態閥值白平衡,這個範例我沒有做切成區塊來做,亮度是取最大值而不是前 10% 亮度,不過效果還行,湊合看吧
這個範例比較特別的是,最後有做 Y 增益調整,防止過曝,且亮度值與原圖相同 ( 調整後的 Y 與原圖 Y 相同 )
注意 : 輸入的圖片需要減過黑電平才能做白平衡哦 ! 不然不論如何調整都會偏色 !
運行前 :
運行後 :
C # :
using UnityEngine;
using UnityEngine.UI;
public class NewBehaviourScript : MonoBehaviour
{
public Texture2D inputTexture;
public RawImage outputImg;
void Start()
{
Texture2D t = new Texture2D(inputTexture.width, inputTexture.height, TextureFormat.RGB24, false);
float[] H = new float[256];
//------------------------------------------------------------------------------
// 計算 MB, MR
float Mb = 0, Mr = 0; // Mb, Mr
float Db = 0, Dr = 0;
for (int y = 0; y < inputTexture.height; y++)
{
for (int x = 0; x < inputTexture.width; x++)
{
Color c = inputTexture.GetPixel(x, y);
float[] YCbCr = ColorToYCbCr(c);
float Y = YCbCr[0];
float Cb = YCbCr[1];
float Cr = YCbCr[2];
Mb += Cb;
Mr += Cr;
}
}
Mb = Mb / (inputTexture.height * inputTexture.width);
Mr = Mr / (inputTexture.height * inputTexture.width);
//------------------------------------------------------------------------------
// 計算 Db, Dr
for (int y = 0; y < inputTexture.height; y++)
{
for (int x = 0; x < inputTexture.width; x++)
{
Color c = inputTexture.GetPixel(x, y);
float[] YCbCr = ColorToYCbCr(c);
float Y = YCbCr[0];
float Cb = YCbCr[1];
float Cr = YCbCr[2];
Db += Mathf.Abs(Cb - Mb);
Dr += Mathf.Abs(Cr - Mr);
}
}
Db /= inputTexture.height * inputTexture.width;
Dr /= inputTexture.height * inputTexture.width;
//------------------------------------------------------------------------------
// 判斷白點 並 計算白點 RGB 平均值
float AvgR = 0, AvgG = 0, AvgB = 0;
float AvgCount = 0;
float Ymax = 0, Ymax2 = 0; //Ymax: 未調整增益,Ymax2: 調整過增益的
for (int y = 0; y < inputTexture.height; y++)
{
for (int x = 0; x < inputTexture.width; x++)
{
Color c = inputTexture.GetPixel(x, y);
float[] YCbCr = ColorToYCbCr(c);
float Y = YCbCr[0];
float Cb = YCbCr[1];
float Cr = YCbCr[2];
// 符合條件為白點,並計算 RGB 平均值
if (Mathf.Abs(Cb - (Mb + Db * Mathf.Sign(Mb))) < 1.5f * Db)
{
if (Mathf.Abs(Cr - (Mr + Dr * Mathf.Sign(Mr))) < 1.5f * Dr)
{
Color cc = YCbCrToColor(YCbCr);
AvgR += cc.r;
AvgG += cc.g;
AvgB += cc.b;
AvgCount++;
}
}
if (Y > Ymax) Ymax = Y;
}
}
AvgR /= AvgCount;
AvgG /= AvgCount;
AvgB /= AvgCount;
print("Ymax : " + Ymax);
//------------------------------------------------------------------------------
// Ymax 增益調整 ( 防止過曝 )
float _Rgain = Ymax / AvgR;
float _Ggain = Ymax / AvgG;
float _Bgain = Ymax / AvgB;
for (int y = 0; y < inputTexture.height; y++)
{
for (int x = 0; x < inputTexture.width; x++)
{
Color c = inputTexture.GetPixel(x, y);
c = new Color(c.r * _Rgain, c.g * _Ggain, c.b * _Bgain);
float Y = ColorToYCbCr(c)[0];
if (Y > Ymax2) Ymax2 = Y;
}
}
float dY = Ymax / Ymax2;
Ymax2 = Ymax * dY;
print("Ymax2 : " + Ymax2);
//------------------------------------------------------------------------------
// 個通道增益套用至原圖中,並輸出顯示
float Rgain = Ymax2 / AvgR;
float Ggain = Ymax2 / AvgG;
float Bgain = Ymax2 / AvgB;
for (int y = 0; y < inputTexture.height; y++)
{
for (int x = 0; x < inputTexture.width; x++)
{
Color c = inputTexture.GetPixel(x, y);
c = new Color(c.r * Rgain, c.g * Ggain, c.b * Bgain);
t.SetPixel(x, y, c);
}
}
t.Apply();
outputImg.texture = t;
print("R-Gain : " + Rgain);
print("G-Gain : " + Ggain);
print("B-Gain : " + Bgain);
}
float[] ColorToYCbCr(Color color)
{
float[] yuv = new float[3];
yuv[0] = 0.299f * color.r + 0.587f * color.g + 0.114f * color.b;
yuv[1] = 0.564f * (color.b - yuv[0]);
yuv[2] = 0.713f * (color.r - yuv[0]);
return yuv;
}
// [Y: 0~1, Cb: -1~1, Cr: -1~1]
Color YCbCrToColor(float[] yuv)
{
float r = yuv[0] + 1.402f * yuv[2];
float g = yuv[0] - 0.344f * yuv[1] - 0.714f * yuv[2];
float b = yuv[0] + 1.772f * yuv[1];
return new Color(r, g, b);
}
}
參考 :
論文 : https://files.cnblogs.com/files/Imageshop/ANovelAutomaticWhiteBalanceMethodforDigital.pdf
原理 : https://www.cnblogs.com/Imageshop/archive/2013/04/20/3032062.html