31

スキャンした 35 mm スライドを自動的に強調するためのルーチンを開発しています。コントラストを上げて色かぶりを取り除くための優れたアルゴリズムを探しています。何千もの画像を処理する必要があるため、アルゴリズムは完全に自動化する必要があります。これらは、スキャナーから直接抽出したサンプル画像の 2 つで、Web 用にトリミングしてサイズを縮小しただけです。

A_CroppedB_トリミング

私は AForge.NET ライブラリを使用しており、フィルターHistogramEqualizationContrastStretchフィルターの両方を試しました。HistogramEqualization局所的なコントラストを最大化するのには適していますが、全体的に満足のいく結果にはなりません。ContrastStretchの方がはるかに優れていますが、各カラー バンドのヒストグラムが個別に引き伸ばされるため、色かぶりが強くなることがあります。

A_ストレッチ

カラー シフトを減らすために、およびクラスUniformContrastStretchを使用して自分でフィルターを作成しました。これにより、すべてのカラー バンドに同じ範囲が使用され、コントラストが低下しますが、色が維持されます。ImageStatisticsLevelsLinear

    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

写真編集プログラムで手動で色補正を実行する 1 つの方法は、既知のニュートラル カラー (白/灰色/黒) の色をサンプリングし、画像の残りの部分をそれに合わせて調整することです。しかし、このルーチンは完全に自動化する必要があるため、それはオプションではありません。

ColorCorrectionフィルターに強度設定を追加して、強度 0.5 で平均値を平均輝度までの距離の半分に移動できると思います。しかし一方で、一部の画像は、色補正をまったく行わなくても最適に機能する場合があります。

より良いアルゴリズムのアイデアはありますか? または、2 番目のサンプルのように、画像に色かぶりがあるのか​​、それとも色がたくさんあるのかを検出する方法はありますか?

4

5 に答える 5

2
  • hsvに変換
  • V レイヤーは、(最小、最大) 範囲から (0,255) 範囲の値をスケーリングすることによって修正されます
  • RGBに組み立て直します
  • 2番目のステップでVレイヤーと同じアイデアで結果のR、G、Bレイヤーを修正する

aforge.net のコードはありません。これは、php プロトタイプ コードで処理されているためです。結果は次のとおりです。

ここに画像の説明を入力 ここに画像の説明を入力

于 2013-03-26T10:11:05.247 に答える
2

これを使用して 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;
}

元のコード:

于 2013-03-30T17:05:44.440 に答える
1

コントラストを引き伸ばすときに画像の色が変化しないようにするには、まず画像を HSV/HSL 色空間に変換します。次に、L または V チャネルに通常のコンストラスト ストレッチングを適用しますが、H または S チャネルは変更しません。

于 2013-02-25T14:22:51.690 に答える
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
于 2013-04-12T17:49:34.753 に答える