Unity 3D : 區域性 Gamma 校正 ( 自動曝光 )
阿新 • • 發佈:2019-02-08
前言 :
這是一種改善曝光不足與過曝的演算法。
拿我之前找小姊姊外拍的圖做測試,可以看見原圖左上的地方曝光都不足了,但是經由演算法校正後,曝光就正常。
本範例是參考 Local Color Correction Using Non-Linear Masking 的論文
基本思路是 :
- 製作 Mask : 將原圖取反,然後做高斯模糊
- 以 Mask 的值來當作 Gamma 引數
- 出圖
原 Gamma 公式 :
區域性 Gamma 公式 :
執行結果 :
原圖輸入 :
輸出 Mask 與 結果
程式碼 ( RGB 三通道實現 ) :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class GammaMasking : MonoBehaviour
{
public Texture2D inputTexture;
public RawImage maskImg;
public RawImage outputImg;
void Start()
{
// ---------------------------------------------------------------------------------
// Mask 製作第一步:先將影象取反
Texture2D invertTexture = new Texture2D(inputTexture.width, inputTexture.height);
for (int y = 0; y < inputTexture.height; y++)
for (int x = 0; x < inputTexture.width; x++)
invertTexture.SetPixel(x, y, Color.white - inputTexture.GetPixel(x, y));
invertTexture.Apply();
// ---------------------------------------------------------------------------------
// Mask 製作第二步:高斯模糊
float[,,] colorArray = VisionLibrary.Texture2D_To_ColorArray(invertTexture);
VisionLibrary.SetColorArray(colorArray);
Texture2D mask = new Texture2D(inputTexture.width, inputTexture.height);
for (int y = 0; y < inputTexture.height; y++)
{
for (int x = 0; x < inputTexture.width; x++)
{
float[] c = VisionLibrary.Blur_Pixel_Gaussian_RGB(x, y, 16, 1.4f);
mask.SetPixel(x, y, new Color(c[0], c[1], c[2]));
}
}
mask.Apply();
maskImg.texture = mask;
// Mask 製作完成
// ---------------------------------------------------------------------------------
// 套用至 Gamma
Texture2D outputTexture = new Texture2D(inputTexture.width, inputTexture.height);
for (int y = 0; y < inputTexture.height; y++)
{
for (int x = 0; x < inputTexture.width; x++)
{
Color maskColor = mask.GetPixel(x, y);
float gammaR = Mathf.Pow(2, (0.5f - maskColor.r) / 0.5f);
float gammaG = Mathf.Pow(2, (0.5f - maskColor.g) / 0.5f);
float gammaB = Mathf.Pow(2, (0.5f - maskColor.b) / 0.5f);
Color color = inputTexture.GetPixel(x, y);
float r = Mathf.Pow(color.r, gammaR);
float g = Mathf.Pow(color.g, gammaG);
float b = Mathf.Pow(color.b, gammaB);
outputTexture.SetPixel(x, y, new Color(r, g, b));
}
}
outputTexture.Apply();
outputImg.texture = outputTexture;
}
}
優化方法 :
把第一步取反刪掉,然後用 1- maskColor 取代,如下 :
float gammaR = Mathf.Pow(2, (0.5f - (1 - maskColor.r)) / 0.5f);
程式碼 ( Y 實現 ) :
只做 YUV 的 Y
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class GammaMasking : MonoBehaviour
{
public Texture2D inputTexture;
public RawImage maskImg;
public RawImage outputImg;
void Start()
{
// ---------------------------------------------------------------------------------
// RGB Mask 製作:高斯模糊
float[,,] colorArray = VisionLibrary.Texture2D_To_ColorArray(inputTexture);
VisionLibrary.SetColorArray(colorArray);
Texture2D mask = new Texture2D(inputTexture.width, inputTexture.height);
for (int y = 0; y < inputTexture.height; y++)
{
for (int x = 0; x < inputTexture.width; x++)
{
float[] c = VisionLibrary.Blur_Pixel_Gaussian_RGB(x, y, 16, 1.4f);
mask.SetPixel(x, y, new Color(c[0], c[1], c[2]));
}
}
mask.Apply();
maskImg.texture = mask;
// ---------------------------------------------------------------------------------
// 將 RGB Mask 轉 Lab Mask
//float[,] lab_mask = new float[inputTexture.width, inputTexture.height];
float[,] yuv_mask = new float[inputTexture.width, inputTexture.height];
for (int y = 0; y < inputTexture.height; y++)
{
for (int x = 0; x < inputTexture.width; x++)
{
Color color = mask.GetPixel(x, y);
// float[] lab = ColorSpace.CIE_RGB_To_Lab(new float[] { color.r, color.g, color.b });
// lab_mask[x, y] = lab[0] / 100f; // 只放 L
float[] yuv = ColorToYUV(color);
yuv_mask[x, y] = yuv[0];
}
}
// ---------------------------------------------------------------------------------
// 套用至 Gamma
Texture2D outputTexture = new Texture2D(inputTexture.width, inputTexture.height);
for (int y = 0; y < inputTexture.height; y++)
{
for (int x = 0; x < inputTexture.width; x++)
{
Color maskColor = mask.GetPixel(x, y);
float gamma = Mathf.Pow(2, (0.5f - (1 - yuv_mask[x, y])) / 0.5f);
Color color = inputTexture.GetPixel(x, y);
float[] yuv = ColorToYCbCr(color);
float newY = Mathf.Pow(yuv[0], gamma);
yuv[0] = newY;
Color newColor = YCbCrToColor(yuv);
outputTexture.SetPixel(x, y, newColor);
}
}
outputTexture.Apply();
outputImg.texture = outputTexture;
}
float[] ColorToYUV(Color color)
{
float[] yuv = new float[3];
yuv[0] = 0.299f * color.r + 0.587f * color.g + 0.114f * color.b;
yuv[1] = -0.169f * color.r - 0.331f * color.g + 0.5f * color.b;
yuv[2] = 0.5f * color.r - 0.419f * color.g - 0.081f * color.b;
return yuv;
}
Color YUVToColor(float[] yuv)
{
float r = yuv[0] + 1.13983f * yuv[2];
float g = yuv[0] - 0.39465f * yuv[1] - 0.7169f * yuv[2];
float b = yuv[0] + 2.03211f * yuv[1];
return new Color(r, g, b);
}
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;
}
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);
}
}
飽和度校正 :
由於單純改 Y 再轉回 RGB 會有飽和度丟失,所以區要依照Y的調整幅度增加飽和度。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class GammaMasking : MonoBehaviour
{
public Texture2D inputTexture;
public RawImage maskImg;
public RawImage outputImg;
void Start()
{
// ---------------------------------------------------------------------------------
// RGB Mask 製作:高斯模糊
float[,,] colorArray = VisionLibrary.Texture2D_To_ColorArray(inputTexture);
VisionLibrary.SetColorArray(colorArray);
Texture2D mask = new Texture2D(inputTexture.width, inputTexture.height);
for (int y = 0; y < inputTexture.height; y++)
{
for (int x = 0; x < inputTexture.width; x++)
{
float[] c = VisionLibrary.Blur_Pixel_Gaussian_RGB(x, y, 16, 1.4f);
mask.SetPixel(x, y, new Color(c[0], c[1], c[2]));
}
}
mask.Apply();
maskImg.texture = mask;
// ---------------------------------------------------------------------------------
// 將 RGB Mask 轉 Lab Mask
//float[,] lab_mask = new float[inputTexture.width, inputTexture.height];
float[,] yuv_mask = new float[inputTexture.width, inputTexture.height];
for (int y = 0; y < inputTexture.height; y++)
{
for (int x = 0; x < inputTexture.width; x++)
{
Color color = mask.GetPixel(x, y);
// float[] lab = ColorSpace.CIE_RGB_To_Lab(new float[] { color.r, color.g, color.b });
// lab_mask[x, y] = lab[0] / 100f; // 只放 L
float[] yuv = ColorToYUV(color);
yuv_mask[x, y] = yuv[0];
}
}
// ---------------------------------------------------------------------------------
// 套用至 Gamma
Texture2D outputTexture = new Texture2D(inputTexture.width, inputTexture.height);
for (int y = 0; y < inputTexture.height; y++)
{
for (int x = 0; x < inputTexture.width; x++)
{
Color maskColor = mask.GetPixel(x, y);
float gamma = Mathf.Pow(2, (0.5f - (1 - yuv_mask[x, y])) / 0.5f);
Color color = inputTexture.GetPixel(x, y);
float[] yuv = ColorToYCbCr(color);
float oldY = yuv[0];
float newY = Mathf.Pow(oldY, gamma);
Color color2 = YCbCrToColor(yuv);
float r = ((newY / oldY) * (color2.r + oldY) + color2.r - oldY) / 2;
float g = ((newY / oldY) * (color2.g + oldY) + color2.g - oldY) / 2;
float b = ((newY / oldY) * (color2.b + oldY) + color2.b - oldY) / 2;
Color color3 = new Color(r, g, b);
outputTexture.SetPixel(x, y, color3);
}
}
outputTexture.Apply();
outputImg.texture = outputTexture;
}
float[] ColorToYUV(Color color)
{
float[] yuv = new float[3];
yuv[0] = 0.299f * color.r + 0.587f * color.g + 0.114f * color.b;
yuv[1] = -0.169f * color.r - 0.331f * color.g + 0.5f * color.b;
yuv[2] = 0.5f * color.r - 0.419f * color.g - 0.081f * color.b;
return yuv;
}
Color YUVToColor(float[] yuv)
{
float r = yuv[0] + 1.13983f * yuv[2];
float g = yuv[0] - 0.39465f * yuv[1] - 0.7169f * yuv[2];
float b = yuv[0] + 2.03211f * yuv[1];
return new Color(r, g, b);
}
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;
}
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);
}
}
參考 :
Local Color Correction Using Non-Linear Masking