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:
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:
EDIT2: Add support for BGRA images too
EDIT3: Correct error in "restore alpha channel from source "
however here is the 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 ...
(more)
Some easy techniques for auto contrast: