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;
}
2 | No.2 Revision |
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
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;
}
3 | No.3 Revision |
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 understand:
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;
}
4 | No.4 Revision |
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:
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:
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;
}
5 | No.5 Revision |
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
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;
}
6 | No.6 Revision |
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 :)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;
}