Reputation: 319
I have a digital camera that takes photos 24x7 and it often depending on the weather returns images with a nasty color cast. Generally blue.
I have been trying to find some source code or library that I can call from c# to reduce the color cast of the images.
Photoshop has a feature that works quite well on the images I tested which is:
This works well, but I do not know what it is doing.
I am not good at the maths so was looking for ideas on existing code or libraries that I could just use.
I have been searching the web but not found anything useful - would love some help.
Upvotes: 6
Views: 3345
Reputation: 852
Create a histogram, auto-generate corrected levels (max, min and gamma), apply the levels to the image. Assuming you have somehow gathered your pixel data into an array of type Color...
public static Color[] AutoLevel(Color[] input) {
var histogram = new Histogram();
foreach(var _ in input) histogram.Add(_);
var levels = histogram.GetAutoLevels();
var ret = new Color[input.Length];
for(int _ = 0; _ < input.Length; _++) {
ret[_] = levels.Apply(input[_]).ToColor();
}
return ret;
}
... and here's the class...
public class Histogram {
private long[,] _values = new long[3, 256];
public void AddColor(Color color) {
AddColor(color.R, color.G, color.B);
}
public void AddColor(RGB color) {
AddColor(color.R, color.G, color.B);
}
public void AddColor(byte r, byte g, byte b) {
_values[0, b]++;
_values[1, g]++;
_values[2, b]++;
}
public long this[int channel, int index] {
get { return _values[channel, index]; }
}
public long GetMaxValue() {
var ret = long.MinValue;
foreach(var _ in _values) if(_ > ret) ret = _;
return ret;
}
public RGB GetMeanColor() {
var total = new long[3];
var count = new long[3];
var value = new byte[3];
for(var _ = 0; _ < 3; _++) {
for(var __ = 0; __ < 256; __++) {
total[_] += (_values[_, __] * __);
count[_] += _values[_, __];
}
value[_] = (byte)Math.Round((double)total[_] / count[_]);
}
return new RGB(value[2], value[1], value[0]);
}
public RGB GetPercentileColor(double percentile) {
var ret = new RGB();
for(var _ = 0; _ < 3; _++) {
var total = 0L;
for(var __ = 0; __ < 256; __++) total += _values[_, __];
var cutoff = (total * percentile);
var count = 0L;
for(var __ = 0; __ < 256; __++) {
count += _values[_, __];
if(count > cutoff) {
ret[_] = (byte)__;
break;
}
}
}
return ret;
}
public Levels GetAutoLevels() {
var low = GetPercentileColor(0.005);
var middle = GetMeanColor();
var high = GetPercentileColor(0.995);
return Levels.GetAdjusted(low, middle, high);
}
public class Levels {
private RGB _inputLow = new RGB(0, 0, 0);
private RGB _inputHigh = new RGB(255, 255, 255);
private RGB _outputLow = new RGB(0, 0, 0);
private RGB _outputHigh = new RGB(255, 255, 255);
private double[] _gamma = { 1, 1, 1 };
public RGB InputLow {
get { return _inputLow; }
set {
for(var _ = 0; _ < 3; _++) {
if(value[_] == 255) value[_] = 254;
if(_inputHigh[_] <= value[_]) _inputHigh[_] = (byte)(value[_] + 1);
}
_inputLow = value;
}
}
public RGB InputHigh {
get { return _inputHigh; }
set {
for(var _ = 0; _ < 3; _++) {
if(value[_] == 0) value[_] = 1;
if(_inputLow[_] >= value[_]) _inputLow[_] = (byte)(value[_] - 1);
}
_inputHigh = value;
}
}
public RGB OutputLow {
get { return _outputLow; }
set {
for(var _ = 0; _ < 3; _++) {
if(value[_] == 255) value[_] = 254;
if(_outputHigh[_] <= value[_]) _outputHigh[_] = (byte)(value[_] + 1);
}
_outputLow = value;
}
}
public RGB OutputHigh {
get { return _outputHigh; }
set {
for(var _ = 0; _ < 3; _++) {
if(value[_] == 0) value[_] = 1;
if(_outputLow[_] >= value[_]) _outputLow[_] = (byte)(value[_] - 1);
}
_outputHigh = value;
}
}
public double GetGamma(int channel) {
return _gamma[channel];
}
public void SetGamma(int channel, double value) {
_gamma[channel] = SetRange(value, 0.1, 10);
}
public RGB Apply(int r, int g, int b) {
var ret = new RGB();
var input = new double[] { b, g, r };
for(var _ = 0; _ < 3; _++) {
var value_ = (input[_] - _inputLow[_]);
if(value_ < 0) {
ret[_] = _outputLow[_];
} else if((_inputLow[_] + value_) >= _inputHigh[_]) {
ret[_] = _outputHigh[_];
} else {
ret[_] = (byte)SetRange((_outputLow[_] + ((_outputHigh[_] - _outputLow[_]) * Math.Pow((value_ / (_inputHigh[_] - _inputLow[_])), _gamma[_]))), 0, 255);
}
}
return ret;
}
internal static Levels GetAdjusted(RGB low, RGB middle, RGB high) {
var ret = new Levels();
for(var _ = 0; _ < 3; _++) {
if((low[_] < middle[_]) && (middle[_] < high[_])) {
ret._gamma[_] = SetRange(Math.Log(0.5, ((double)(middle[_] - low[_]) / (high[_] - low[_]))), 0.1, 10);
} else {
ret._gamma[_] = 1;
}
}
ret._inputLow = low;
ret._inputHigh = high;
return ret;
}
}
private static double SetRange(double value, double min, double max) {
if(value < min) value = min;
if(value > max) value = max;
return value;
}
public struct RGB {
public byte B;
public byte G;
public byte R;
public RGB(byte r, byte g, byte b) {
B = b;
G = g;
R = r;
}
public byte this[int channel] {
get {
switch(channel) {
case 0: return B;
case 1: return G;
case 2: return R;
default: throw new ArgumentOutOfRangeException();
}
}
set {
switch(channel) {
case 0: B = value; break;
case 1: G = value; break;
case 2: R = value; break;
default: throw new ArgumentOutOfRangeException();
}
}
}
public Color ToColor() {
return Color.FromArgb(R, G, B);
}
}
}
Upvotes: 0
Reputation: 4340
You can use OpenCV to develop an algorithm that will fit your needs. When researching to find a solution for your problem, I realized that the problem of "color balance could be solved with a lot of different way.
I choose to show you how to code a very simple algorithm that will not completely re-create the "perfect" picture that you get with photoshop but something better than the original. You can then search for these topic in openCV on google and try different approach. To code this, I have use the new OpenCV NuGet package, that you can get here. Just add the binary from openCV in your output directory (debug folder) and you are up and running!
Then here is the code:
public Form1()
{
InitializeComponent();
NamedWindow windowsOriginal = new NamedWindow("Original");
NamedWindow windowsModified = new NamedWindow("Modified");
IplImage img = OpenCV.Net.CV.LoadImage(@"D:\hZpWG.jpg", LoadImageFlags.Color);
IplImage imgDest = equalizeIntensity(img);
windowsOriginal.ShowImage(img);
windowsModified.ShowImage(imgDest);
}
IplImage equalizeIntensity(IplImage inputImage)
{
if(inputImage.Channels >= 3)
{
IplImage ycrcb = new IplImage(inputImage.Size, inputImage.Depth, inputImage.Channels);
OpenCV.Net.CV.CvtColor(inputImage, ycrcb, ColorConversion.Bgr2YCrCb);
IplImage Y = new IplImage(ycrcb.Size, IplDepth.U8, 1);
IplImage Cr = new IplImage(ycrcb.Size, IplDepth.U8, 1);
IplImage Cb = new IplImage(ycrcb.Size, IplDepth.U8, 1);
OpenCV.Net.CV.Split(ycrcb, Y, Cr, Cb, null);
OpenCV.Net.CV.EqualizeHist(Y, Y);
IplImage result = new IplImage(inputImage.Size, IplDepth.U8, inputImage.Channels);
OpenCV.Net.CV.Merge(Y, Cr, Cb, null, ycrcb);
OpenCV.Net.CV.CvtColor(ycrcb, result, ColorConversion.YCrCb2Bgr);
return result;
}
return null;
}
I put it in a form but you can use it in a console application too.
Here is the result
Hope it helps!
Upvotes: 1
Reputation: 12316
This looks like the White Balance is set for indoors (expecting reddish light) but getting daylight (blue). GIMP has a color temperature slider that will change the cast of the pictures. Are you talking about preventing this in the future, or batch processing a bunch of existing images. Even simple cameras (but maybe not mobile phones) have controls on white balance, for future shots.
This looks like a web cam plugged into your computer? So it is probably a moving target, meaning that the WB is being re-evaluated each time it takes a pic, and you may not be able to apply the same correction to each image.
Here is an imagemagick script which can batch process color temperature on a bunch of images. I think an approach which uses the temperature would be better than one that is just normalizing levels, because what if you are shooting the sky or ocean, and it should be blue? You just want to make sure it is the right blue.
Edit: For specific C# code, you might check here. The lower-left example in the first set of color balanced images look very much like your source image. There is also white balance function in the source code of paint.net
Upvotes: 2
Reputation: 8128
Tuning White Balance automatically cannot always work nice, because your algorithm will have less information as input data (no real light metering, only pixel values recorded by matrix, some of which may be clipped). So this can help when camera's setting is ridiculously wrong (like on your picture), but it cannot make WB right. You'd better buy a decent camera (there are even cheap ones that still can do good pictures)
BTW, if you want to invent a wheel, the idea is to scale color channels to make their average level equal. You can try different definitions of "average" here, and you can also try to exclude pixels with clipped values from measurement. But there is no fun in doing it again, as there are good ways to do this mentioned in @mickro's answer.
Upvotes: 4
Reputation: 891
I guess best solution is playing with ImageMagick which gets .Net implementation.
To start there is this topic from stackoverflow
Also you should find the correct effect. Certainly good by trying around match and neutralize effects. Randomly this autocolour's script may help.
hope that help. Good luck.
Upvotes: 2