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.
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:
Para implementar o balanço de branco com o Gdal, primeiro vamos às configurações:
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;
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();
}
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.
Ler e Escrever Imagens com o Gdal em C#
Converter JSON para shapefile usando Gdal em C#