Adadev

Algoritmo Balanço de Branco com Gdal em C#

6 de Junho de 2017

O balanço de branco é um algoritmo que ajusta a intensidade de cores em uma imagem. É usado para melhorar fotografias com tons esbranquiçados, alaranjados, azulados ou esverdeados. Neste artigo, descrevo um algoritmo de balanço de branco baseado na ideia do editor de imagens GIMP. É um algoritmo simples e rápido.

Pseudo código do balanço de branco

Considerando uma imagem RGB em bytes, ou seja, cada pixel varia de 0 a 255, o algoritmo é aplicado para cada banda separadamente. O código abaixo deve ser chamado para cada banda em forma de vetor. Para isso, é só passar os valores da matriz em sequência, linha a linha.

WhiteBalanceBand(bandArray) {
	// fazer uma cópia do vetor
	sortedBandArray = CopyOf(bandArray)
	// ordenar vetor
	sortedBandArray = Sort(sortedBandArray)
	
	// porcentagem (configurável) para o balanço
	percentForBalance = 0.6;

	// calcula o percentil
	perc05 = Percentile(sortedBandArray, percentForBalance)
	perc95 = Percentile(sortedBandArray, 100 - percentForBalance)

	bandBalancedArray[bandArray.Length]

	for(i = 0; i < bandArray.length; i++) {
		// calcular valor balanceado
		valueBalanced = (bandArray[i] - perc05) * 255.0 / (perc95 - perc05);
		// limitar entre 0 e 255
		bandBalancedArray[i] = LimitToByte(valueBalanced);
	}

	return bandBalancedArray;
}

Para o cálculo do percentil:

Percentile(array,  percentile) {
	nArray = array.Length
	nPercent = (nArray + 1) * percentile / 100
	if(nPercent == 1) {
		return array[0];
	} else if(nPercent == nArray) {
		return array[nArray - 1]
	} else {
		intNPercent = (int)nPercent
		d = nPercent - intNPercent
		return array[intNPercent - 1] + d * (array[intNPercent] - array[intNPercent - 1])
	}
}

Para limitar os valores entre 0 e 255:

LimitToByte(value) {
	if(value < 0) {
		return 0
	} else if(value > 255) {
		return 255
	} 
	
	return value
}

As imagens abaixo mostram o resultado da aplicação desse algoritmo. À esquerda, a imagem original sem filtros e à direita, a imagem após a aplicação do balanço de branco usando percentil igual a 0.6:

Imagem original sem filtro Imagem com balanço de branco

Para implementar o balanço de branco com o Gdal, primeiro vamos às configurações:

Importar o Gdal no Visual Studio

Primeiramente, importamos o Gdal para o projeto: no Visual Studio, clique com o botão direito no projeto, vá em "Manage NuGet packages", digite "Gdal" no campo de busca, selecione o Gdal (1.11.1 é a última versão disponível até o momento em que este artigo foi escrito) e instale. Selecione e instale também o Gdal.Native (sem ele o projeto compila mas não executa corretamente). A classe "GdalConfiguration.cs" será criada automaticamente. Ela será usada antes de iniciar o método para conversão.

Para importá-lo em sua classe use:

using OSGeo.GDAL;

Configurar o Gdal

Antes de usar as classes do Gdal, precisamos configurá-lo. Para isso, basta chamar o método ConfigureGdal da classe GdalConfiguration.cs. Você pode chamá-lo, por exemplo, no construtor de sua classe.

public WhiteBalance() {
    GdalConfiguration.ConfigureGdal();
}

Balanço de branco com Gdal em C#

O código abaixo mostra a implementação completa do balanço de branco no Gdal, em C#:

new WhiteBalance().AplyWhiteBalance(@"C:\development\input-image.tif", @"C:\development\output-image.tif");
using OSGeo.GDAL;
using System;

namespace Adadev.GdalModule {

    public class WhiteBalance {

        private double percentForBalance = 0.6;

        public WhiteBalance() {
            GdalConfiguration.ConfigureGdal();  
        }

        public WhiteBalance(double percentForBalance) {
            this.percentForBalance = percentForBalance;
            GdalConfiguration.ConfigureGdal();
        }

        public void AplyWhiteBalance(string imagePath, string outImagePath) {

            using(Dataset image = Gdal.Open(imagePath, Access.GA_ReadOnly)) {

                Band redBand = GetBand(image, ColorInterp.GCI_RedBand);
                Band greenBand = GetBand(image, ColorInterp.GCI_GreenBand);
                Band blueBand = GetBand(image, ColorInterp.GCI_BlueBand);

                if(redBand == null || greenBand == null || blueBand == null) {
                    throw new NullReferenceException("One or more bands are not available.");
                }

                int width = redBand.XSize;
                int height = redBand.YSize;

                using(Dataset outImage = Gdal.GetDriverByName("GTiff").Create(outImagePath, width, height, 3, DataType.GDT_Byte, null)) {

                    double[] geoTransformerData = new double[6];
                    image.GetGeoTransform(geoTransformerData);
                    outImage.SetGeoTransform(geoTransformerData);
                    outImage.SetProjection(image.GetProjection());

                    Band outRedBand = outImage.GetRasterBand(1);
                    Band outGreenBand = outImage.GetRasterBand(2);
                    Band outBlueBand = outImage.GetRasterBand(3);

                    int[] red = new int[width * height];
                    int[] green = new int[width * height];
                    int[] blue = new int[width * height];
                    redBand.ReadRaster(0, 0, width, height, red, width, height, 0, 0);
                    greenBand.ReadRaster(0, 0, width, height, green, width, height, 0, 0);
                    blueBand.ReadRaster(0, 0, width, height, blue, width, height, 0, 0);

                    int[] outRed = WhiteBalanceBand(red);
                    int[] outGreen = WhiteBalanceBand(green);
                    int[] outBlue = WhiteBalanceBand(blue);
                    outRedBand.WriteRaster(0, 0, width, height, outRed, width, height, 0, 0);
                    outGreenBand.WriteRaster(0, 0, width, height, outGreen, width, height, 0, 0);
                    outBlueBand.WriteRaster(0, 0, width, height, outBlue, width, height, 0, 0);

                    outImage.FlushCache();
                }
            }
        }

        public int[] WhiteBalanceBand(int[] band) {
             int[] sortedBand = new int[band.Length];
             Array.Copy(band, sortedBand, band.Length);
             Array.Sort(sortedBand);

             double perc05 = Percentile(sortedBand, percentForBalance);
             double perc95 = Percentile(sortedBand, 100.0 - percentForBalance);

             int[] bandBalanced = new int[band.Length];

             for(int i = 0; i < band.Length; i++) {

                 double valueBalanced = (band[i] - perc05) * 255.0 / (perc95 - perc05);
                 bandBalanced[i] = LimitToByte(valueBalanced);
             }

             return bandBalanced;
        }

        public double Percentile(int[] sequence, double percentile) {
            
            int nSequence = sequence.Length;
            double nPercent = (nSequence + 1) * percentile / 100d;
            if(nPercent == 1d) {
                return sequence[0];
            } else if(nPercent == nSequence) {
                return sequence[nSequence - 1];
            } else {
                int intNPercent = (int)nPercent;
                double d = nPercent - intNPercent;
                return sequence[intNPercent - 1] + d * (sequence[intNPercent] - sequence[intNPercent - 1]);
            }
        }

        private byte LimitToByte(double value) {
            byte newValue;

            if(value < 0) {
                newValue = 0;
            } else if(value > 255) {
                newValue = 255;
            } else {
                newValue = (byte)value;
            }
 
            return newValue;
        }
       /**
	* Retorna a banda para determinada cor (red, green, blue ou alfa)
	* O dataset deve ter 4 bandas
	* */
        public static Band GetBand(Dataset ImageDataSet, ColorInterp colorInterp) {
            if(colorInterp.Equals(ImageDataSet.GetRasterBand(1).GetRasterColorInterpretation())) {
                return ImageDataSet.GetRasterBand(1);
            } else if(colorInterp.Equals(ImageDataSet.GetRasterBand(2).GetRasterColorInterpretation())) {
                return ImageDataSet.GetRasterBand(2);
            } else if(colorInterp.Equals(ImageDataSet.GetRasterBand(3).GetRasterColorInterpretation())) {
                return ImageDataSet.GetRasterBand(3);
            } else {
                return ImageDataSet.GetRasterBand(4);
            }
        }
		
    }
}

Você pode baixar o código completo no nosso repositório no Github. Veja também mais detalhes sobre leitura e escrita de imagens com Gdal.

Conteúdo relacionado:

Ler e Escrever Imagens com o Gdal em C#

Converter JSON para shapefile usando Gdal em C#

Veja mais em:

Balanço de branco em Python

Percentil

Algoritmo do Percentil

[voltar ao início]