我有一台24x7全天候拍照的数码相机,它通常取决于天气返回的图像,色彩偏差。一般是蓝色。
我一直试图找到一些我可以从c#调用的源代码或库来减少图像的偏色。
Photoshop的功能在我测试的图像上运行良好,即:
这很好用,但我不知道它在做什么。
我不擅长数学,所以我正在寻找可以使用的现有代码或库的想法。
我一直在网上搜索但没有找到任何有用的东西 - 会喜欢一些帮助。
答案 0 :(得分:4)
自动调整白平衡不能总是很好用,因为你的算法输入数据信息较少(没有真正的测光,只有矩阵记录的像素值,其中一些可能是限幅强>)。因此,当相机的设置非常错误时(例如在您的照片上),这可以提供帮助,但它无法使WB正确。你最好买一个像样的相机(甚至还有便宜的相机,仍然可以拍出好照片)
顺便说一句,如果你想发明一个轮子,我们的想法是缩放颜色通道,使它们的平均水平相等。您可以在此处尝试不同的“平均值”定义,也可以尝试从测量中排除带有限幅值的像素。但再次这样做并不好玩,因为在@ mickro的回答中有很好的方法可以做到这一点。答案 1 :(得分:2)
我猜最好的解决方案是使用ImageMagick来获取.Net implementation。
你也应该找到正确的效果。通过尝试匹配和中和效果当然很好。随机这个autocolour's script可能有所帮助。
希望有所帮助。祝你好运。答案 2 :(得分:2)
这看起来像是在室内设置白平衡(期待微红光)但是获得日光(蓝色)。 GIMP有一个色温滑块,可以改变图片的投射。您是在谈论将来阻止这种情况,还是批量处理一堆现有图像。即使是简单的相机(但可能不是手机)也能控制白平衡,以备未来拍摄。
这看起来像是插入计算机的网络摄像头?因此它可能是一个移动目标,这意味着每次拍摄时都会重新评估WB,并且您可能无法对每个图像应用相同的校正。
Here是一个imagemagick脚本,可以批量处理一堆图像的色温。我认为使用温度的方法会优于正常化水平的方法,因为如果你拍摄天空或海洋,应该是蓝色的呢?你只是想确保它是正确的蓝色。
编辑:对于特定的C#代码,您可以check here。第一组色彩平衡图像中的左下角示例与您的源图像非常相似。 paint.net
的源代码中还有白平衡功能答案 3 :(得分:1)
您可以使用OpenCV开发适合您需求的算法。在研究找到问题的解决方案时,我意识到“色彩平衡问题可以用很多不同的方式解决。”
我选择向您展示如何编写一个非常简单的算法,该算法不会完全重新创建您使用photoshop获得的“完美”图片,但比原始图片更好。然后,您可以在google上的openCV中搜索这些主题,并尝试不同的方法。为了对此进行编码,我使用了新的OpenCV NuGet包,您可以获得here。只需在输出目录(debug文件夹)中添加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;
}
我把它放在一个表单中,但你也可以在控制台应用程序中使用它。
结果如下
希望它有所帮助!
答案 4 :(得分: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);
}
}
}