我正在开发一种自动增强扫描35毫米幻灯片的程序。我正在寻找一种增强对比度和消除色偏的好算法。该算法必须是完全自动的,因为将有数千个图像要处理。这些是直接来自扫描仪的几个样本图像,仅针对网页进行裁剪和缩小尺寸:
我正在使用AForge.NET库并尝试了HistogramEqualization
和ContrastStretch
过滤器。 HistogramEqualization
有助于最大化局部对比度,但总体上不会产生令人满意的结果。 ContrastStretch
更好,但由于它会单独拉伸每个色带的直方图,因此有时会产生强烈的偏色:
为了减少颜色偏移,我使用UniformContrastStretch
和ImageStatistics
类自行创建了LevelsLinear
过滤器。这对所有色带使用相同的范围,以较低的对比度为代价保留色彩。
ImageStatistics stats = new ImageStatistics(image);
int min = Math.Min(Math.Min(stats.Red.Min, stats.Green.Min), stats.Blue.Min);
int max = Math.Max(Math.Max(stats.Red.Max, stats.Green.Max), stats.Blue.Max);
LevelsLinear levelsLinear = new LevelsLinear();
levelsLinear.Input = new IntRange(min, max);
Bitmap stretched = levelsLinear.Apply(image);
虽然图像仍然很蓝,所以我创建了一个ColorCorrection
滤镜,首先计算图像的平均亮度。然后为每个颜色通道计算伽马校正值,使得每个颜色通道的平均值将等于平均亮度。均匀对比度拉伸图像具有平均值R=70 G=64 B=93
,平均亮度为(70 + 64 + 93) / 3 = 76
。伽玛值计算为R=1.09 G=1.18 B=0.80
,得到的非常中性的图像的平均值为R=76 G=76 B=76
,如预期的那样:
现在,解决真正的问题... 我认为将图像的平均颜色校正为灰色有点过于剧烈,会使一些图像在外观上显得相当沉闷,就像第二个样本一样(第一张图像是均匀拉伸的,接下来是相同的图像颜色校正):
在照片编辑程序中手动执行色彩校正的一种方法是对已知中性色(白色/灰色/黑色)的颜色进行采样,并将图像的其余部分调整为该颜色。但由于这个例程必须是完全自动的,所以这不是一个选择。
我想我可以为我的ColorCorrection
滤镜添加强度设置,这样0.5的强度会将平均值的一半移动到平均亮度。但另一方面,有些图像在没有任何色彩校正的情况下可能会做得最好。
任何有关更好算法的想法?或者某种方法来检测图像是否有偏色或者只是有很多颜色,比如第二个样本?
答案 0 :(得分:2)
没有aforge.net代码,因为它是由php原型代码处理的,但afaik使用aforge.net没有任何问题。 结果是:
答案 1 :(得分:1)
为了避免在拉伸对比度时更改图像的颜色,请先将其转换为HSV / HSL色彩空间。然后,在L或V通道中应用常规的约束拉伸,但不要使用H或S通道。
答案 2 :(得分:1)
使用以下方法将RGB转换为HSL:
System.Drawing.Color color = System.Drawing.Color.FromArgb(red, green, blue);
float hue = color.GetHue();
float saturation = color.GetSaturation();
float lightness = color.GetBrightness();
相应地调整饱和度和亮度
通过以下方式将HSL转换回RGB:
/// <summary>
/// Convert HSV to RGB
/// h is from 0-360
/// s,v values are 0-1
/// r,g,b values are 0-255
/// Based upon http://ilab.usc.edu/wiki/index.php/HSV_And_H2SV_Color_Space#HSV_Transformation_C_.2F_C.2B.2B_Code_2
/// </summary>
void HsvToRgb(double h, double S, double V, out int r, out int g, out int b)
{
// ######################################################################
// T. Nathan Mundhenk
// mundhenk@usc.edu
// C/C++ Macro HSV to RGB
double H = h;
while (H < 0) { H += 360; };
while (H >= 360) { H -= 360; };
double R, G, B;
if (V <= 0)
{ R = G = B = 0; }
else if (S <= 0)
{
R = G = B = V;
}
else
{
double hf = H / 60.0;
int i = (int)Math.Floor(hf);
double f = hf - i;
double pv = V * (1 - S);
double qv = V * (1 - S * f);
double tv = V * (1 - S * (1 - f));
switch (i)
{
// Red is the dominant color
case 0:
R = V;
G = tv;
B = pv;
break;
// Green is the dominant color
case 1:
R = qv;
G = V;
B = pv;
break;
case 2:
R = pv;
G = V;
B = tv;
break;
// Blue is the dominant color
case 3:
R = pv;
G = qv;
B = V;
break;
case 4:
R = tv;
G = pv;
B = V;
break;
// Red is the dominant color
case 5:
R = V;
G = pv;
B = qv;
break;
// Just in case we overshoot on our math by a little, we put these here. Since its a switch it won't slow us down at all to put these here.
case 6:
R = V;
G = tv;
B = pv;
break;
case -1:
R = V;
G = pv;
B = qv;
break;
// The color is not defined, we should throw an error.
default:
//LFATAL("i Value error in Pixel conversion, Value is %d", i);
R = G = B = V; // Just pretend its black/white
break;
}
}
r = Clamp((int)(R * 255.0));
g = Clamp((int)(G * 255.0));
b = Clamp((int)(B * 255.0));
}
/// <summary>
/// Clamp a value to 0-255
/// </summary>
int Clamp(int i)
{
if (i < 0) return 0;
if (i > 255) return 255;
return i;
}
原始代码:
答案 3 :(得分:1)
我需要在一个大型视频缩略图库中做同样的事情。我想要一个保守的解决方案,这样我就不必检查缩略图是否完全被破坏了。这是我使用的混乱,黑客攻击的解决方案。
我首先使用这个类来计算图像中颜色的分布。我第一次在HSV色彩空间中做了一个,但发现基于灰度的一个更快,几乎同样好:
class GrayHistogram
def initialize(filename)
@hist = hist(filename)
@percentile = {}
end
def percentile(x)
return @percentile[x] if @percentile[x]
bin = @hist.find{ |h| h[:count] > x }
c = bin[:color]
return @percentile[x] ||= c/256.0
end
def midpoint
(percentile(0.25) + percentile(0.75)) / 2.0
end
def spread
percentile(0.75) - percentile(0.25)
end
private
def hist(imgFilename)
histFilename = "/tmp/gray_hist.txt"
safesystem("convert #{imgFilename} -depth 8 -resize 50% -colorspace GRAY /tmp/out.png")
safesystem("convert /tmp/out.png -define histogram:unique-colors=true " +
" -format \"%c\" histogram:info:- > #{histFilename}")
f = File.open(histFilename)
lines = f.readlines[0..-2] # the last line is always blank
hist = lines.map { |line| { :count => /([0-9]*):/.match(line)[1].to_i, :color => /,([0-9]*),/.match(line)[1].to_i } }
f.close
tot = 0
cumhist = hist.map do |h|
tot += h[:count]
{:count=>tot, :color=>h[:color]}
end
tot = tot.to_f
cumhist.each { |h| h[:count] = h[:count] / tot }
safesystem("rm /tmp/out.png #{histFilename}")
return cumhist
end
end
然后我创建了这个类来使用直方图来弄清楚如何校正图像:
def safesystem(str)
out = `#{str}`
if $? != 0
puts "shell command failed:"
puts "\tcmd: #{str}"
puts "\treturn code: #{$?}"
puts "\toutput: #{out}"
raise
end
end
def generateHist(thumb, hist)
safesystem("convert #{thumb} histogram:hist.jpg && mv hist.jpg #{hist}")
end
class ImgCorrector
def initialize(filename)
@filename = filename
@grayHist = GrayHistogram.new(filename)
end
def flawClass
if !@flawClass
gapLeft = (@grayHist.percentile(0.10) > 0.13) || (@grayHist.percentile(0.25) > 0.30)
gapRight = (@grayHist.percentile(0.75) < 0.60) || (@grayHist.percentile(0.90) < 0.80)
return (@flawClass="low" ) if (!gapLeft && gapRight)
return (@flawClass="high" ) if ( gapLeft && !gapRight)
return (@flawClass="narrow") if ( gapLeft && gapRight)
return (@flawClass="fine" )
end
return @flawClass
end
def percentileSummary
[ @grayHist.percentile(0.10),
@grayHist.percentile(0.25),
@grayHist.percentile(0.75),
@grayHist.percentile(0.90) ].map{ |x| (((x*100.0*10.0).round)/10.0).to_s }.join(', ') +
"<br />" +
"spread: " + @grayHist.spread.to_s
end
def writeCorrected(filenameOut)
if flawClass=="fine"
safesystem("cp #{@filename} #{filenameOut}")
return
end
# spread out the histogram, centered at the midpoint
midpt = 100.0*@grayHist.midpoint
# map the histogram's spread to a sigmoidal concept (linearly)
minSpread = 0.10
maxSpread = 0.60
minS = 1.0
maxS = case flawClass
when "low" then 5.0
when "high" then 5.0
when "narrow" then 6.0
end
s = ((1.0 - [[(@grayHist.spread - minSpread)/(maxSpread-minSpread), 0.0].max, 1.0].min) * (maxS - minS)) + minS
#puts "s: #{s}"
safesystem("convert #{@filename} -sigmoidal-contrast #{s},#{midpt}% #{filenameOut}")
end
end
我是这样跑的:
origThumbs = `find thumbs | grep jpg`.split("\n")
origThumbs.each do |origThumb|
newThumb = origThumb.gsub(/thumb/, "newthumb")
imgCorrector = ImgCorrector.new(origThumb)
imgCorrector.writeCorrected(newThumb)
end
答案 4 :(得分:0)
您可以从以下链接尝试自动设置亮度和对比度:http://answers.opencv.org/question/75510/how-to-make-auto-adjustmentsbrightness-and-contrast-for-image-android-opencv-image-correction/
void Utils::BrightnessAndContrastAuto(const cv::Mat &src, cv::Mat &dst, float clipHistPercent)
{
CV_Assert(clipHistPercent >= 0);
CV_Assert((src.type() == CV_8UC1) || (src.type() == CV_8UC3) || (src.type() == CV_8UC4));
int histSize = 256;
float alpha, beta;
double minGray = 0, maxGray = 0;
//to calculate grayscale histogram
cv::Mat gray;
if (src.type() == CV_8UC1) gray = src;
else if (src.type() == CV_8UC3) cvtColor(src, gray, CV_BGR2GRAY);
else if (src.type() == CV_8UC4) cvtColor(src, gray, CV_BGRA2GRAY);
if (clipHistPercent == 0)
{
// keep full available range
cv::minMaxLoc(gray, &minGray, &maxGray);
}
else
{
cv::Mat hist; //the grayscale histogram
float range[] = { 0, 256 };
const float* histRange = { range };
bool uniform = true;
bool accumulate = false;
calcHist(&gray, 1, 0, cv::Mat(), hist, 1, &histSize, &histRange, uniform, accumulate);
// calculate cumulative distribution from the histogram
std::vector<float> accumulator(histSize);
accumulator[0] = hist.at<float>(0);
for (int i = 1; i < histSize; i++)
{
accumulator[i] = accumulator[i - 1] + hist.at<float>(i);
}
// locate points that cuts at required value
float max = accumulator.back();
clipHistPercent *= (max / 100.0); //make percent as absolute
clipHistPercent /= 2.0; // left and right wings
// locate left cut
minGray = 0;
while (accumulator[minGray] < clipHistPercent)
minGray++;
// locate right cut
maxGray = histSize - 1;
while (accumulator[maxGray] >= (max - clipHistPercent))
maxGray--;
}
// current range
float inputRange = maxGray - minGray;
alpha = (histSize - 1) / inputRange; // alpha expands current range to histsize range
beta = -minGray * alpha; // beta shifts current range so that minGray will go to 0
// Apply brightness and contrast normalization
// convertTo operates with saurate_cast
src.convertTo(dst, -1, alpha, beta);
// restore alpha channel from source
if (dst.type() == CV_8UC4)
{
int from_to[] = { 3, 3 };
cv::mixChannels(&src, 4, &dst, 1, from_to, 1);
}
return;
}
或通过以下链接应用自动色彩平衡:http://www.morethantechnical.com/2015/01/14/simplest-color-balance-with-opencv-wcode/
void Utils::SimplestCB(Mat& in, Mat& out, float percent) {
assert(in.channels() == 3);
assert(percent > 0 && percent < 100);
float half_percent = percent / 200.0f;
vector<Mat> tmpsplit; split(in, tmpsplit);
for (int i = 0; i < 3; i++) {
//find the low and high precentile values (based on the input percentile)
Mat flat; tmpsplit[i].reshape(1, 1).copyTo(flat);
cv::sort(flat, flat, CV_SORT_EVERY_ROW + CV_SORT_ASCENDING);
int lowval = flat.at<uchar>(cvFloor(((float)flat.cols) * half_percent));
int highval = flat.at<uchar>(cvCeil(((float)flat.cols) * (1.0 - half_percent)));
cout << lowval << " " << highval << endl;
//saturate below the low percentile and above the high percentile
tmpsplit[i].setTo(lowval, tmpsplit[i] < lowval);
tmpsplit[i].setTo(highval, tmpsplit[i] > highval);
//scale the channel
normalize(tmpsplit[i], tmpsplit[i], 0, 255, NORM_MINMAX);
}
merge(tmpsplit, out);
}
或将CLAHE应用于BGR图片