1. 程式人生 > >Unity 3D : 區域性 Gamma 校正 ( 自動曝光 )

Unity 3D : 區域性 Gamma 校正 ( 自動曝光 )

前言 :

這是一種改善曝光不足與過曝的演算法。

拿我之前找小姊姊外拍的圖做測試,可以看見原圖左上的地方曝光都不足了,但是經由演算法校正後,曝光就正常。

本範例是參考 Local Color Correction Using Non-Linear Masking 的論文

基本思路是 :

  1. 製作 Mask : 將原圖取反,然後做高斯模糊
  2. 以 Mask 的值來當作 Gamma 引數
  3. 出圖

原 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