Automatic enhancement of scanned images

做~自己de王妃 提交于 2019-12-02 17:58:12
  • translated to hsv
  • V-layer is corrected by scaling values from (min,max) range to (0,255) range
  • assembled back to rgb
  • correcting R,G,B layers of result by same idea as the V-layer on second step

there is no aforge.net code, because it processed by php prototype code, but afaik there is no any problem to do such with aforge.net. results are:

In order to avoid changing the color of your image when stretching the constrast, convert it first to HSV/HSL color space. Then, apply regular constrast stretching in the L or V channel but do not chagen H or S channels.

Shreyas Kapur

Convert your RGB to HSL using this:

    System.Drawing.Color color = System.Drawing.Color.FromArgb(red, green, blue);
    float hue = color.GetHue();
    float saturation = color.GetSaturation();
    float lightness = color.GetBrightness();

Adjust your Saturation and Lightness accordingly

Convert HSL back to RGB by:

/// <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;
}

Original Code:

I needed to do the same thing over a big library of video thumbnails. I wanted a solution that would be conservative, so that I didn't have to spot check for thumbnails getting completely trashed. Here's the messy, hacked-together solution I used.

I first used this class to calculate the distribution of colors in an image. I first did one in HSV-colorspace, but found a grayscale-based one was way faster and almost as good:

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

I then created this class to use the histogram to figure out how to correct an image:

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

I ran it like so:

origThumbs = `find thumbs | grep jpg`.split("\n")
origThumbs.each do |origThumb|
  newThumb = origThumb.gsub(/thumb/, "newthumb")
  imgCorrector = ImgCorrector.new(origThumb)
  imgCorrector.writeCorrected(newThumb)
end

You can try auto brightness and contrast from this link : 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;
}

Or apply Auto Color Balance from this link : 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);
}

Or apply CLAHE to BGR image

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!