从图像中消除色偏

6
我有一台数码相机,可以24小时拍照,但它经常因天气原因返回带有恶劣色调的图像。通常是蓝色。

enter image description here

我一直在寻找一些源代码或库,以便从c#中调用来减少图像的色偏。

Photoshop有一个功能,在我测试的图像上效果很好,即:

  1. 打开图像
  2. 选择 图像 -> 调整 -> 匹配颜色
  3. 勾选 Neutralize 复选框

这个功能很好用,但我不知道它在做什么。

enter image description here

我不擅长数学,所以正在寻找现有代码或库的想法,以便我可以直接使用。

我一直在搜索网络,但没有找到任何有用的东西——希望能得到一些帮助。


从帖子中看来,ImageMagick似乎是.NET社区调整图像的唯一方法。真遗憾,因为我本来想看到一个本地的c#解决方案来解决这个问题。正如我所说,这是一个“数码相机”(不是网络摄像头),全天候运行-因此不是在一天结束时处理一堆图像,而是实时处理。老实说,我本来期望有一些源代码解决方案,而不是所有答案都涉及ImageMagik,它是一个很好的产品,但并不是真正的源代码解决方案。 - Chris
明白。我添加了一些源代码的链接,但老实说我还没有测试过它。不过网站上的示例看起来与您原始的图像非常相似。 - beroe
5个回答

4
调整白平衡自动化并不总是能够达到良好的效果,因为你的算法只能利用少量输入数据(没有真正的光度计,只有矩阵记录的像素值,其中一些可能会被截断)。因此,这可以帮助当相机的设置非常错误(如您所拍的照片),但它不能使白平衡正确。您最好购买一台体面的相机(甚至还有便宜的相机也可以拍出好照片)。
顺便提一下,如果想要发明轮子,那么将颜色通道按比例缩放以使它们的平均水平相等是个好主意。在这里,您可以尝试不同的“平均”定义,并且还可以尝试从测量中排除具有截断值的像素。但是,由于@mickro回答中提到了很好的方法,所以再做一遍并没有什么乐趣。

2
我猜最好的解决方案是使用ImageMagick,它得到了.Net实现
首先,有这个stackoverflow上的话题可以帮助你开始。
此外,你应该找到正确的效果。一定要尝试一下匹配和中和效果。随机地,这个autocolour的脚本也可能会有所帮助。
希望这能有所帮助。祝你好运。

2
这看起来像白平衡设置为室内(期望红色光)但实际上是在阳光下(蓝色)。GIMP有一个色温滑块,可以改变图片的色调。你是在谈论未来如何防止这种情况,还是批量处理现有图像?即使是简单的相机(但可能不包括手机),也有白平衡控制,以备未来拍摄。
这看起来像是插入计算机的网络摄像头?因此它可能是一个移动目标,这意味着每次拍照时都会重新评估WB,并且您可能无法对每个图像应用相同的校正。 这里是一个imagemagick脚本,可以批量处理一堆图像的色温。我认为使用温度的方法比仅规范化级别的方法更好,因为如果您拍摄天空或海洋,而它应该是蓝色的,那怎么办?您只需要确保它是正确的蓝色。
编辑:有关特定C#代码,您可以在此处检查。第一组颜色平衡图像中左下角的示例非常类似于您的源图像。还有一个白平衡功能在paint.net的源代码中。

1
您可以使用OpenCV来开发符合您需求的算法。在寻找解决方案时,我意识到“色彩平衡”的问题可以用许多不同的方式来解决。
我选择向您展示如何编写一个非常简单的算法,它不会完全重新创建您使用 Photoshop 得到的“完美”图片,但比原始图片更好。然后,您可以在 Google 上搜索这些主题,并尝试不同的方法。为了编写此代码,我使用了新的 OpenCV NuGet 包,您可以在此处获取。只需将 openCV 的二进制文件添加到输出目录 (debug 文件夹) 中即可运行!
下面是代码:
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;
}

我把它放在一个表单中,但你也可以在控制台应用程序中使用它。
这是结果。 原始图片 输入图像描述 希望能有所帮助!

0
创建一个直方图,自动生成校正级别(最大值、最小值和伽马值),将这些级别应用到图像上。假设您已经以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;
}

...这里是类的代码...

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);
        }
    }
}

结果:
结果


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接