Автоматическое улучшение отсканированных изображений
Я разрабатываю рутину для автоматического улучшения сканированных 35-мм слайдов. Я ищу хороший алгоритм для увеличения контраста и удаления цветового оттенка. Алгоритм должен быть полностью автоматическим, так как тысячи изображений будут обрабатываться. Это несколько образцов изображений прямо со сканера, только обрезанные и уменьшенные для веб-страниц:
![A_Cropped]()
![B_Cropped]()
Я использую библиотеку AForge.NET и пробовал фильтры HistogramEqualization
и ContrastStretch
. HistogramEqualization
хорош для максимизации локального контраста, но не дает приятных результатов в целом. ContrastStretch
лучше, но поскольку он растягивает гистограмму каждой цветовой полосы по отдельности, она иногда производит сильный цвет:
![A_Stretched]()
Чтобы уменьшить смещение цвета, я сам создал фильтр 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);
![A_UniformStretched]()
Изображение все еще довольно голубое, поэтому я создал фильтр 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
, как ожидалось:
![A_UniformStretchedCorrected]()
Теперь, дойдя до настоящей проблемы... Я полагаю, что исправление среднего цвета изображения до серого немного чересчур и сделает некоторые изображения довольно скучными по внешнему виду, например, второй образец (первое изображение равномерно растянуто, следующий - тот же самый цвет изображения):
![B_UniformStretched]()
![B_UniformStretchedCorrected]()
Одним из способов ручной коррекции цвета в программе редактирования фотографий является выбор цвета известного нейтрального цвета (белый/серый/черный) и настройка остальной части изображения на него. Но поскольку эта процедура должна быть полностью автоматической, это не вариант.
Я думаю, я мог бы добавить настройку силы в мой фильтр ColorCorrection
, так что сила 0,5 будет перемещать средние значения на половину расстояния до средней яркости. Но, с другой стороны, некоторые изображения могут делать лучше всего без какой-либо коррекции цвета.
Любые идеи для лучшего алгоритма? Или какой-либо метод для определения того, имеет ли изображение цветное изображение или просто имеет много цвета, например, второй образец?
Ответы
Ответ 1
- переведен в hsv
- V-слой корректируется путем масштабирования значений от (min, max) до диапазона (0,255)
- собрано обратно в rgb
- исправление R, G, B уровней результата по той же идее, что и V-слой на втором шаге
нет кода aforge.net, потому что он обрабатывается кодом прототипа php, но afaik не существует никакой проблемы, чтобы сделать это с aforge.net.
результаты:
![enter image description here]()
![enter image description here]()
Ответ 2
Чтобы избежать изменения цвета вашего изображения при растяжении констраста, сначала преобразуйте его в цветовое пространство HSV/HSL. Затем накладывайте регулярное растяжение в L-канале или V-канале, но не chagen H или S.
Ответ 3
Преобразуйте 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
// [email protected]
// 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;
}
Оригинальный код:
Ответ 4
Мне нужно было сделать то же самое над большой библиотекой видео эскизов. Мне нужно решение, которое было бы консервативным, так что мне не нужно было проверять, чтобы эскизы были полностью уничтожены. Здесь беспорядочное, взломанное решение, которое я использовал.
Сначала я использовал этот класс для вычисления распределения цветов в изображении. Я сначала сделал это в цветовом пространстве 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 [email protected]
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 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