Ask Your Question

Revision history [back]

click to hide/show revision 1
initial version

Brightness and contrast is linear operator with parameter alpha and beta

D(x,y) = alpha * I(x,y) + beta

In OpenCV you can do this with convertTo.

The question here is how to calculate alpha and beta automatically ?

Looking at histogram, alpha operates as color range amplifier, beta operates as range shift.

Automatic brightness and contrast optimization calculates alpha and beta so that the output range is 0..255.

input range = max(I) - min(I)
wanted output range = 255;
alpha = output range / input range =  255 / ( max(A) - min(A) )

You can calculate beta so that min(D) = 0;

min(D) = alpha * min(A) + beta
beta = -min(A) * alpha

Histogram Wings Cut

To maximize the result of it's useful to cut out some color with few pixels.

This is done cutting left right and wings of histogram where color frequency is less than a value (typically 1%). Calculating cumulative distribution from the histogram you can easly find where to cut.

however here is the code :)

/**
\brief Automatic brightness and contrast optimization with wings cut.
\param [in]wingsCutPercent cut wings of histogram at given percent
*/
void BrightnessAndContrast(const cv::Mat &src, Mat &dst, float wingsCutPercent)
{

    CV_Assert(wingsCutPercent>=0);
    CV_Assert((src.type() == CV_8UC3) || (src.type() == CV_8UC1));

    int histSize = 256;
    float alpha, beta;
    double minGray = 0, maxGray = 0;

    //to calculate grayscale histogram
    Mat gray;
    if (src.type() == CV_8UC3) cv::cvtColor(src, gray, CV_BGR2GRAY);
    else if (src.type() == CV_8UC1) gray = src;

    if (wingsCutPercent == 0)
    {
        // keep full available range
        cv::minMaxLoc(gray, &minGray, &maxGray);
    }
    else
    {
        Mat hist; //the grayscale histogram

        float range[] = { 0, 256 };
        const float* histRange = { range };
        bool uniform = true;
        bool accumulate = false;
        calcHist(&gray, 1, 0, Mat(), hist, 1, &histSize, &histRange, uniform, accumulate);

        // calculate cumulative distribution from the histogram
        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();
        wingsCutPercent = wingsCutPercent / 2;  // left and right wings
        wingsCutPercent = wingsCutPercent * max / 100.0;
        for (int i = 1; i < histSize / 2; i++)
        {
            if (accumulator[i] < wingsCutPercent)
                minGray = i+1;
            if (accumulator[histSize - 1 - i] >= (max - wingsCutPercent))
                maxGray = i + 1;
        }
        maxGray = histSize - 1 - 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);

    return;
}

Brightness and contrast is linear operator with parameter alpha and beta

D(x,y) O(x,y) = alpha * I(x,y) + beta

In OpenCV you can do this with convertTo.

The question here is how to calculate alpha and beta automatically ?

Looking at histogram, alpha operates as color range amplifier, beta operates as range shift.

Automatic brightness and contrast optimization calculates alpha and beta so that the output range is 0..255.

input range = max(I) - min(I)
wanted output range = 255;
alpha = output range / input range =  255 / ( max(A) max(I) - min(A) min(I) )

You can calculate beta so that min(D) min(O) = 0;

min(D) min(O) = alpha * min(A) min(I) + beta
beta = -min(A) -min(I) * alpha

Histogram Wings Cut

To maximize the result of it's useful to cut out some color with few pixels.

This is done cutting left right and wings of histogram where color frequency is less than a value (typically 1%). Calculating cumulative distribution from the histogram you can easly find where to cut.

may be sample chart helps to understand image description

however here is the code :)

/**
\brief Automatic brightness and contrast optimization with wings cut.
\param [in]wingsCutPercent cut wings of histogram at given percent
*/
void BrightnessAndContrast(const cv::Mat &src, Mat &dst, float wingsCutPercent)
{

    CV_Assert(wingsCutPercent>=0);
    CV_Assert((src.type() == CV_8UC3) || (src.type() == CV_8UC1));

    int histSize = 256;
    float alpha, beta;
    double minGray = 0, maxGray = 0;

    //to calculate grayscale histogram
    Mat gray;
    if (src.type() == CV_8UC3) cv::cvtColor(src, gray, CV_BGR2GRAY);
    else if (src.type() == CV_8UC1) gray = src;

    if (wingsCutPercent == 0)
    {
        // keep full available range
        cv::minMaxLoc(gray, &minGray, &maxGray);
    }
    else
    {
        Mat hist; //the grayscale histogram

        float range[] = { 0, 256 };
        const float* histRange = { range };
        bool uniform = true;
        bool accumulate = false;
        calcHist(&gray, 1, 0, Mat(), hist, 1, &histSize, &histRange, uniform, accumulate);

        // calculate cumulative distribution from the histogram
        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();
        wingsCutPercent = wingsCutPercent / 2;  // left and right wings
        wingsCutPercent = wingsCutPercent * max / 100.0;
        for (int i = 1; i < histSize / 2; i++)
        {
            if (accumulator[i] < wingsCutPercent)
                minGray = i+1;
            if (accumulator[histSize - 1 - i] >= (max - wingsCutPercent))
                maxGray = i + 1;
        }
        maxGray = histSize - 1 - 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);

    return;
}

Brightness and contrast is linear operator with parameter alpha and beta

O(x,y) = alpha * I(x,y) + beta

In OpenCV you can do this with convertTo.

The question here is how to calculate alpha and beta automatically ?

Looking at histogram, alpha operates as color range amplifier, beta operates as range shift.

Automatic brightness and contrast optimization calculates alpha and beta so that the output range is 0..255.

input range = max(I) - min(I)
wanted output range = 255;
alpha = output range / input range =  255 / ( max(I) - min(I) )

You can calculate beta so that min(O) = 0;

min(O) = alpha * min(I) + beta
beta = -min(I) * alpha

Histogram Wings Cut

To maximize the result of it's useful to cut out some color with few pixels.

This is done cutting left right and wings of histogram where color frequency is less than a value (typically 1%). Calculating cumulative distribution from the histogram you can easly find where to cut.

may be sample chart helps to understand image descriptionunderstand: image description

however here is the code :)

/**
/*
\brief Automatic brightness and contrast optimization with wings cut.
\param [in]wingsCutPercent [in]src Input image grayscale or BGR
\param [out]dst Destination image with 
\param wingsCutPercent cut wings of histogram at given percent
percent typical 1%
*/
void BrightnessAndContrast(const BrightnessAndContrastAuto(const cv::Mat &src, Mat cv::Mat &dst, float wingsCutPercent)
{

    CV_Assert(wingsCutPercent>=0);
CV_Assert(wingsCutPercent >= 0);
    CV_Assert((src.type() == CV_8UC3) || (src.type() == CV_8UC1));

    int histSize = 256;
    float alpha, beta;
    double minGray = 0, maxGray = 0;

    //to calculate grayscale histogram
    Mat cv::Mat gray;
    if (src.type() == CV_8UC3) cv::cvtColor(src, gray, CV_BGR2GRAY);
    else if (src.type() == CV_8UC1) gray = src;

    if (wingsCutPercent == 0)
    {
        // keep full available range
        cv::minMaxLoc(gray, &minGray, &maxGray);
    }
    else
    {
        Mat cv::Mat hist; //the grayscale histogram

        float range[] = { 0, 256 };
        const float* histRange = { range };
        bool uniform = true;
        bool accumulate = false;
        calcHist(&gray, 1, 0, Mat(), cv::Mat(), hist, 1, &histSize, &histRange, uniform, accumulate);

        // calculate cumulative distribution from the histogram
        vector<float> 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();
        wingsCutPercent = wingsCutPercent / 2;  // left and right wings
        wingsCutPercent = wingsCutPercent * max / 100.0;
        for (int i = 1; i < histSize / 2; i++)
        {
            if (accumulator[i] // locate left cut
        minGray = 0;
        while (accumulator[minGray] < wingsCutPercent)
                minGray = i+1;
            if (accumulator[histSize {
            minGray++;
        }
        // locate right
        maxGray = histSize - 1 - i] 1;
        while (accumulator[maxGray] >= (max - wingsCutPercent))
                maxGray = i + 1;
{
            maxGray--;
        }
        maxGray = histSize - 1 - maxGray;
    }

    // current range
    float inputRange = maxGray - minGray;

    alpha = (histSize-1) (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);

    return;
}

Brightness and contrast is linear operator with parameter alpha and beta

O(x,y) = alpha * I(x,y) + beta

In OpenCV you can do this with convertTo.

The question here is how to calculate alpha and beta automatically ?

Looking at histogram, alpha operates as color range amplifier, beta operates as range shift.

Automatic brightness and contrast optimization calculates alpha and beta so that the output range is 0..255.

input range = max(I) - min(I)
wanted output range = 255;
alpha = output range / input range =  255 / ( max(I) - min(I) )

You can calculate beta so that min(O) = 0;

min(O) = alpha * min(I) + beta
beta = -min(I) * alpha

Histogram Wings Cut

To maximize the result of it's useful to cut out some color with few pixels.

This is done cutting left right and wings of histogram where color frequency is less than a value (typically 1%). Calculating cumulative distribution from the histogram you can easly find where to cut.

may be sample chart helps to understand: image description

EDIT: Histogram Normalization vs Equalization

By the way BrightnessAndContrastAuto could be named normalizeHist because it works on BGR and gray images stretching the histogram to the full range without touching bins balance. If input image has range 0..255 BrightnessAndContrastAuto will do nothing.

Histogram equalization and CLAE works only on gray images and they change grays level balancing. look at the images below:

Normalization vs Equalization

however here is the code :)

/*
\brief Automatic brightness and contrast optimization with wings cut.
\param [in]src Input image grayscale or BGR
\param [out]dst Destination image with 
\param wingsCutPercent cut wings of histogram at given percent typical 1%
*/
void BrightnessAndContrastAuto(const cv::Mat &src, cv::Mat &dst, float wingsCutPercent)
{

    CV_Assert(wingsCutPercent >= 0);
    CV_Assert((src.type() == CV_8UC3) || (src.type() == CV_8UC1));

    int histSize = 256;
    float alpha, beta;
    double minGray = 0, maxGray = 0;

    //to calculate grayscale histogram
    cv::Mat gray;
    if (src.type() == CV_8UC3) cv::cvtColor(src, gray, CV_BGR2GRAY);
    else if (src.type() == CV_8UC1) gray = src;

    if (wingsCutPercent == 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();
        wingsCutPercent = wingsCutPercent / 2;  // left and right wings
        wingsCutPercent = wingsCutPercent * max / 100.0;
        // locate left cut
        minGray = 0;
        while (accumulator[minGray] < wingsCutPercent)
        {
            minGray++;
        }
        // locate right
        maxGray = histSize - 1;
        while (accumulator[maxGray] >= (max - wingsCutPercent))
        {
            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);

    return;
}

Brightness and contrast is linear operator with parameter alpha and beta

O(x,y) = alpha * I(x,y) + beta

In OpenCV you can do this with convertTo.

The question here is how to calculate alpha and beta automatically ?

Looking at histogram, alpha operates as color range amplifier, beta operates as range shift.

Automatic brightness and contrast optimization calculates alpha and beta so that the output range is 0..255.

input range = max(I) - min(I)
wanted output range = 255;
alpha = output range / input range =  255 / ( max(I) - min(I) )

You can calculate beta so that min(O) = 0;

min(O) = alpha * min(I) + beta
beta = -min(I) * alpha

Histogram Wings Cut (clipping)

To maximize the result of it's useful to cut out some color with few pixels.

This is done cutting left right and wings of histogram where color frequency is less than a value (typically 1%). Calculating cumulative distribution from the histogram you can easly find where to cut.

may be sample chart helps to understand: image descriptionimage description

EDIT: Histogram Normalization vs Equalization

By the way BrightnessAndContrastAuto could be named normalizeHist because it works on BGR and gray images stretching the histogram to the full range without touching bins balance. If input image has range 0..255 BrightnessAndContrastAuto will do nothing.

Histogram equalization and CLAE works only on gray images and they change grays level balancing. look at the images below:

Normalization vs EqualizationNormalization vs Equalization

EDIT2: Add support for BGRA images too

however here is the code :)

/*
/**
 *  \brief Automatic brightness and contrast optimization with wings cut.
optional histogram clipping
 *  \param [in]src Input image grayscale GRAY or BGR
BGR or BGRA
 *  \param [out]dst Destination image with 

 *  \param wingsCutPercent clipHistPercent cut wings of histogram at given percent typical 1%
tipical=>1, 0=>Disabled
 *  \note In case of BGRA image, we won't touch the transparency
*/
void BrightnessAndContrastAuto(const cv::Mat &src, cv::Mat &dst, float wingsCutPercent)
clipHistPercent=0)
{

    CV_Assert(wingsCutPercent CV_Assert(clipHistPercent >= 0);
    CV_Assert((src.type() == CV_8UC1) || (src.type() == CV_8UC3) || (src.type() == CV_8UC1));
CV_8UC4));

    int histSize = 256;
    float alpha, beta;
    double minGray = 0, maxGray = 0;

    //to calculate grayscale histogram
    cv::Mat gray;
    if (src.type() == CV_8UC3) cv::cvtColor(src, gray, CV_BGR2GRAY);
    else if (src.type() == CV_8UC1) gray = src;
     else if (wingsCutPercent (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(), 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();
        wingsCutPercent = wingsCutPercent clipHistPercent *= (max / 2;  100.0); //make percent as absolute
        clipHistPercent /= 2.0; // left and right wings
        wingsCutPercent = wingsCutPercent * max / 100.0;
        // locate left cut
        minGray = 0;
        while (accumulator[minGray] < wingsCutPercent)
        {
clipHistPercent)
            minGray++;
        }
         // locate right
right cut
        maxGray = histSize - 1;
        while (accumulator[maxGray] >= (max - wingsCutPercent))
        {
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,4, from_to, 1);
    }

    return;
}

Brightness and contrast is linear operator with parameter alpha and beta

O(x,y) = alpha * I(x,y) + beta

In OpenCV you can do this with convertTo.

The question here is how to calculate alpha and beta automatically ?

Looking at histogram, alpha operates as color range amplifier, beta operates as range shift.

Automatic brightness and contrast optimization calculates alpha and beta so that the output range is 0..255.

input range = max(I) - min(I)
wanted output range = 255;
alpha = output range / input range =  255 / ( max(I) - min(I) )

You can calculate beta so that min(O) = 0;

min(O) = alpha * min(I) + beta
beta = -min(I) * alpha

Histogram Wings Cut (clipping)

To maximize the result of it's useful to cut out some color with few pixels.

This is done cutting left right and wings of histogram where color frequency is less than a value (typically 1%). Calculating cumulative distribution from the histogram you can easly find where to cut.

may be sample chart helps to understand: image description

EDIT: Histogram Normalization vs Equalization

By the way BrightnessAndContrastAuto could be named normalizeHist because it works on BGR and gray images stretching the histogram to the full range without touching bins balance. If input image has range 0..255 BrightnessAndContrastAuto will do nothing.

Histogram equalization and CLAE works only on gray images and they change grays level balancing. look at the images below:

Normalization vs Equalization

EDIT2: Add support for BGRA images too

EDIT3: Correct error in "restore alpha channel from source "

however here is the code :)code:

/**
 *  \brief Automatic brightness and contrast optimization with optional histogram clipping
 *  \param [in]src Input image GRAY or BGR or BGRA
 *  \param [out]dst Destination image 
 *  \param clipHistPercent cut wings of histogram at given percent tipical=>1, 0=>Disabled
 *  \note In case of BGRA image, we won't touch the transparency
*/
void BrightnessAndContrastAuto(const cv::Mat &src, cv::Mat &dst, float clipHistPercent=0)
{

    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,4, &dst,1, from_to, 1);
    }
     return;
}