Подтвердить что ты не робот

Автоматическое улучшение отсканированных изображений

Я разрабатываю рутину для автоматического улучшения сканированных 35-мм слайдов. Я ищу хороший алгоритм для увеличения контраста и удаления цветового оттенка. Алгоритм должен быть полностью автоматическим, так как тысячи изображений будут обрабатываться. Это несколько образцов изображений прямо со сканера, только обрезанные и уменьшенные для веб-страниц:

A_CroppedB_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_UniformStretchedB_UniformStretchedCorrected

Одним из способов ручной коррекции цвета в программе редактирования фотографий является выбор цвета известного нейтрального цвета (белый/серый/черный) и настройка остальной части изображения на него. Но поскольку эта процедура должна быть полностью автоматической, это не вариант.

Я думаю, я мог бы добавить настройку силы в мой фильтр ColorCorrection, так что сила 0,5 будет перемещать средние значения на половину расстояния до средней яркости. Но, с другой стороны, некоторые изображения могут делать лучше всего без какой-либо коррекции цвета.

Любые идеи для лучшего алгоритма? Или какой-либо метод для определения того, имеет ли изображение цветное изображение или просто имеет много цвета, например, второй образец?

4b9b3361

Ответ 1

  • переведен в hsv
  • V-слой корректируется путем масштабирования значений от (min, max) до диапазона (0,255)
  • собрано обратно в rgb
  • исправление R, G, B уровней результата по той же идее, что и V-слой на втором шаге

нет кода aforge.net, потому что он обрабатывается кодом прототипа php, но afaik не существует никакой проблемы, чтобы сделать это с aforge.net. результаты:

enter image description hereenter 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