5

I need to implement in C++ algorithm for adjusting image levels that works similar to Levels function in Photoshop or GIMP. I.e. inputs are: color RGB image to be adjusted adjust, while point, black point, midtone point, output from/to values. But I didn't find yet any info on how to perform this adjustment. Probably someone recommend me algorithm description or materials to study.

To the moment I've came up with following code myself, but it doesn't give expected result, similar to what I can see, for example in the GIMP, image becomes too lightened. Below is my current fragment of the code:

const int normalBlackPoint = 0;
const int normalMidtonePoint = 127;
const int normalWhitePoint = 255;
const double normalLowRange = normalMidtonePoint - normalBlackPoint + 1;
const double normalHighRange = normalWhitePoint - normalMidtonePoint;

int blackPoint = 53;
int midtonePoint = 110;
int whitePoint = 168;
int outputFrom = 0;
int outputTo = 255;

double outputRange = outputTo - outputFrom + 1;
double lowRange = midtonePoint - blackPoint + 1;
double highRange = whitePoint - midtonePoint;
double fullRange = whitePoint - blackPoint + 1;
double lowPart = lowRange / fullRange; 
double highPart = highRange / fullRange; 

int dim(256);
cv::Mat lut(1, &dim, CV_8U);
for(int i = 0; i < 256; ++i)
{
    double p = i > normalMidtonePoint
        ? (static_cast<double>(i - normalMidtonePoint) / normalHighRange) * highRange * highPart + lowPart
        : (static_cast<double>(i + 1) / normalLowRange) * lowRange * lowPart;
    int v = static_cast<int>(outputRange * p ) + outputFrom - 1;
    if(v < 0) v = 0;
    else if(v > 255) v = 255;
    lut.at<uchar>(i) = v;
}


....

    cv::Mat sourceImage = cv::imread(inputFileName, CV_LOAD_IMAGE_COLOR);
    if(!sourceImage.data)
    {
        std::cerr << "Error: couldn't load image " << inputFileName << "." << std::endl;
        continue;
    }


#if 0       
    const int forwardConversion = CV_BGR2YUV;
    const int reverseConversion = CV_YUV2BGR;
#else
    const int forwardConversion = CV_BGR2Lab;
    const int reverseConversion = CV_Lab2BGR;
#endif

    cv::Mat convertedImage;
    cv::cvtColor(sourceImage, convertedImage, forwardConversion);

    // Extract the L channel
    std::vector<cv::Mat> convertedPlanes(3);
    cv::split(convertedImage, convertedPlanes);

    cv::LUT(convertedPlanes[0], lut, convertedPlanes[0]);

    //dst.copyTo(convertedPlanes[0]);
    cv::merge(convertedPlanes, convertedImage);

    cv::Mat resImage;
    cv::cvtColor(convertedImage, resImage, reverseConversion);
    cv::imwrite(outputFileName, resImage);
ivan.ukr
  • 2,853
  • 1
  • 23
  • 41
  • 1
    Please show us what you have tried so far. First, you should familiarize yourself with *how* images are organised (RGB channels) and what basic operations you can do on them. Stack Overflow is not a code writing service, but people are willing to help you if you at least try to solve the problem at your own. Please read [How to create a Minimal, Complete, and Verifiable example](http://stackoverflow.com/help/mcve) and [How do I ask a good question?](http://stackoverflow.com/help/how-to-ask). Then, update and *improve your question*. – Martin Nyolt Sep 15 '16 at 11:37
  • I am familiar with image representations, loading images with OpenCV, manipulating image channels and so on. It is not a question here. The question is image transformation algorithm. I am not asking to write any code for me. Instead, I am asking to help me to find information about the algorithm. Having that, I am able to write code myself. – ivan.ukr Sep 15 '16 at 11:43
  • 1
    I have updated the post with my present code. Idea behind the code is simple, it calculates lookup table and applies it to image. But image gets overlighted instead what I get with same input values with GIMP on the same image. So I am trying to understand what I am doing wrong, and probably the whole my current implementation based on the wrong idea of how to calculate lookup table, so that's why I am asking about correct algorithm, not the code (i.e. its implementation). – ivan.ukr Sep 15 '16 at 11:53

2 Answers2

13

Pseudocode for Photoshop's Levels Adjustment

First, calculate the gamma correction value to use for the midtone adjustment (if desired). The following roughly simulates Photoshop's technique, which applies gamma 9.99-1.00 for midtone values 0-128, and 1.00-0.01 for 128-255.

Apply gamma correction:

Gamma = 1
MidtoneNormal = Midtones / 255
If Midtones < 128 Then
    MidtoneNormal = MidtoneNormal * 2
    Gamma = 1 + ( 9 * ( 1 - MidtoneNormal ) )
    Gamma = Min( Gamma, 9.99 )
Else If Midtones > 128 Then
    MidtoneNormal = ( MidtoneNormal * 2 ) - 1
    Gamma = 1 - MidtoneNormal
    Gamma = Max( Gamma, 0.01 )
End If
GammaCorrection = 1 / Gamma

Then, for each channel value R, G, B (0-255) for each pixel, do the following in order.

Apply the input levels:

ChannelValue = 255 * ( ( ChannelValue - ShadowValue ) / 
    ( HighlightValue - ShadowValue ) )

Apply the midtones:

If Midtones <> 128 Then
    ChannelValue = 255 * ( Pow( ( ChannelValue / 255 ), GammaCorrection ) )
End If

Apply the output levels:

ChannelValue = ( ChannelValue / 255 ) *
    ( OutHighlightValue - OutShadowValue ) + OutShadowValue

Where:

  • All channel and adjustment parameter values are integers, 0-255 inclusive
  • Shadow/Midtone/HighlightValue are the input adjustment values (defaults 0, 128, 255)
  • OutShadow/HighlightValue are the output adjustment values (defaults 0, 255)
  • You should optimize things and make sure values are kept in bounds (like 0-255 for each channel)

For a more accurate simulation of Photoshop, you can use a non-linear interpolation curve if Midtones < 128. Photoshop also chops off the darkest and lightest 0.1% of the values by default.

Community
  • 1
  • 1
Beejor
  • 8,606
  • 1
  • 41
  • 31
2

Ignoring the midtone/Gamma, the Levels function is a simple linear scaling.

All input values are first linearly scaled so that all values less or equal to the "black point" are set to 0, and all values greater than or equal white point are set to 255. Then all values are linearly scaled from 0/255 to the output range.

For the mid-point—it depends what you actually mean by that. In GIMP, there is a Gamma value. The Gamma value is a simple exponent of the input values (after restricting to the black/white points). For Gamma == 1, the values are not changed. For gamma < 1, the values are darkened.

Martin Nyolt
  • 4,463
  • 3
  • 28
  • 36