【问题标题】:Removing color cast from image从图像中去除偏色
【发布时间】:2013-09-07 06:09:24
【问题描述】:

我有一台可以 24x7 全天候拍摄照片的数码相机,它通常会根据天气返回带有令人讨厌的色偏的图像。一般是蓝色的。

我一直在尝试找到一些可以从 c# 调用的源代码或库,以减少图像的偏色。

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

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

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

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

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

【问题讨论】:

  • 从帖子看来,ImageMagick 是 .NET 社区调整图像的唯一方法。真的很遗憾,因为我希望看到这个问题的原生 c# 解决方案。正如我所说,它是一个 24/7 运行的“数码相机”(不是网络摄像头)——所以不是在一天结束时处理一堆图像,而是实时处理。老实说,我本来希望有一些源代码解决方案,而不是所有的答案都参考 ImageMagik,这是一个很好的产品,但不是真正的源代码解决方案。
  • 明白。我添加了一些源代码的链接,但老实说还没有测试过。不过,网站上的示例看起来就像您的原始图像。

标签: .net image colors


【解决方案1】:

我想最好的解决方案是使用ImageMagick 得到.Net implementation

首先是这个topic from stackoverflow

您还应该找到正确的效果。通过尝试 matchneutralize 效果当然很好。随机这个autocolour's script 可能会有所帮助。

希望有所帮助。祝你好运。

【讨论】:

    【解决方案2】:

    自动调整白平衡并不总是很好,因为您的算法将具有较少的信息作为输入数据(没有真正的测光,只有矩阵记录的像素值,其中一些可能是剪辑)。因此,当相机的设置非常错误时(如您的照片),这会有所帮助,但它不能使 WB 正确。你最好买个像样的相机(甚至便宜的也能拍出好照片)

    顺便说一句,如果你想发明一个轮子,想法是缩放颜色通道以使其平均水平相等。您可以在此处尝试不同的“平均值”定义,也可以尝试从测量中排除具有裁剪值的像素。但是再做一次没有乐趣,因为@mickro的回答中提到了这样做的好方法。

    【讨论】:

      【解决方案3】:

      这看起来像是为室内设置了白平衡(预期为红光),但得到的是日光(蓝色)。 GIMP 有一个色温滑块,可以改变图片的色调。您是在谈论将来防止这种情况,还是要批量处理一堆现有图像。即使是简单的相机(但可能不是手机)也可以控制白平衡,以备将来拍摄。

      这看起来像是插入您计算机的网络摄像头?所以它可能是一个移动的目标,这意味着每次拍照时都会重新评估 WB,您可能无法对每张图像应用相同的校正。

      Here 是一个 imagemagick 脚本,它可以在一堆图像上批量处理色温。我认为使用温度的方法会比仅仅标准化水平的方法更好,因为如果您拍摄天空或海洋,并且它应该是蓝色的怎么办?你只是想确保它是正确的蓝色。

      编辑:对于特定的 C# 代码,您可以check here。第一组色彩平衡图像中左下角的示例看起来非常像您的源图像。 paint.net的源码中还有白平衡功能

      【讨论】:

        【解决方案4】:

        您可以使用OpenCV 来开发适合您需求的算法。在研究为您的问题寻找解决方案时,我意识到“色彩平衡问题可以通过很多不同的方式来解决。

        我选择向您展示如何编写一个非常简单的算法,该算法不会完全重新创建您使用 Photoshop 获得的“完美”图片,但会比原始图片更好。然后,您可以在 google 上的 openCV 中搜索这些主题并尝试不同的方法。为了编码,我使用了新的 OpenCV NuGet 包,你可以得到here。只需将 openCV 中的二进制文件添加到您的输出目录(调试文件夹)中即可启动并运行!

        然后这里是代码:

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

        我把它放在一个表单中,但你也可以在控制台应用程序中使用它。

        这是结果

        希望对你有帮助!

        【讨论】:

          【解决方案5】:

          创建直方图,自动生成校正级别(最大值、最小值和伽马),将级别应用于图像。假设您以某种方式将像素数据收集到 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);
                  }
              }
          }
          

          结果:

          【讨论】:

            猜你喜欢
            • 2021-10-01
            • 2020-04-11
            • 2012-12-26
            • 2020-01-05
            • 2021-12-24
            • 2021-01-20
            • 1970-01-01
            • 2013-04-28
            • 2018-10-21
            相关资源
            最近更新 更多