1.自动色阶调整
算法原理:
(2)对每个通道,利用LowCut和HighCut,计算灰度最小值min和最大值max;
(3)对每个通道分别建立分段线性拉伸查找表,
f(g) = 0 g<=min
f(g) = 255 g>=max
f(g) = ((g-min)/(max-min)) * 255 min<g<max
(4)对每个通道利用相应的查找表进行分段线性拉伸,得到效果图。
2.自动对比度调整
算法原理:
(1)分别统计每个通道的灰度直方图;
(2)对每个通道,利用LowCut和HighCut,分别计算灰度最小值和最大值,取三个通道最小值中最小者作为最小值min,取三个通道最大值中最大者为最大值max;
(3)对每个通道建立统一的分段线性拉伸查找表,
f(g) = 0 g<=min
f(g) = 255 g>=max
f(g) = ((g-min)/(max-min)) * 255 min<g<max
(4)对每个通道利用统一的查找表进行分段线性拉伸,得到效果图。
3.区别
两者的区别主要是取最大值最小值的不同,自动色阶每个通道取的是当前通道的最大值最小值,而自动对比度取的是三个通道之最。
根据这个区别,不难推测出它们在效果上的一个区别:自动对比度不会改变RGB之间的大小顺序,而自动色阶有可能改变RGB之间的大小顺序。
参考博客:
http://www.cnblogs.com/Imageshop/archive/2011/11/13/2247614.html
博客中提到这两个算法主要是参考PS中的功能。做图像处理的,不会PS,确实有点说不过去,有时间我还是要学习一下PS。
然后针对其中的一些功能,用opencv实现一下,肯定能有很多收获。(待做事件)
4.OpenCV实现
(1)自动色阶
/*
*函数功能:自动色阶调整(仿照PS功能)
*输入参数:src 输入彩色图像
*输出参数:dst 输出调整色阶之后的彩色图像
*返回值:void
*算法步骤:
(1)分别统计每个通道的灰度直方图;
(2)对每个通道,利用LowCut和HighCut,计算灰度最小值min和最大值max;
(3)对每个通道分别建立分段线性拉伸查找表,
f(g) = 0 g<=min
f(g) = 255 g>=max
f(g) = ((g-min)/(max-min)) * 255 min<g<max
(4)对每个通道利用相应的查找表进行分段线性拉伸,得到效果图。
http://www.cnblogs.com/Imageshop/archive/2011/11/13/2247614.html
*/
void AutoLevelsAdjust(cv::Mat &src, cv::Mat &dst)
{
CV_Assert(!src.empty() && src.channels() == 3);
//统计灰度直方图
int BHist[256] = { 0 }; //B分离
int GHist[256] = { 0 }; //G分量
int RHist[256] = { 0 }; //R分量
cv::MatIterator_<Vec3b> its, ends;
for (its = src.begin<Vec3b>(), ends = src.end<Vec3b>(); its != ends; its++)
{
BHist[(*its)[0]]++;
GHist[(*its)[1]]++;
RHist[(*its)[2]]++;
}
//设置LowCut和HighCut
float LowCut = 0.5;
float HighCut = 0.5;
//根据LowCut和HighCut查找每个通道最大值最小值
int BMax = 0, BMin = 0;
int GMax = 0, GMin = 0;
int RMax = 0, RMin = 0;
int TotalPixels = src.cols * src.rows;
float LowTh = LowCut * 0.01 * TotalPixels;
float HighTh = HighCut * 0.01 * TotalPixels;
//B通道查找最小最大值
int sumTempB = 0;
for (int i = 0; i < 256; i++)
{
sumTempB += BHist[i];
if (sumTempB >= LowTh)
{
BMin = i;
break;
}
}
sumTempB = 0;
for (int i = 255; i >= 0; i--)
{
sumTempB += BHist[i];
if (sumTempB >= HighTh)
{
BMax = i;
break;
}
}
//G通道查找最小最大值
int sumTempG = 0;
for (int i = 0; i < 256; i++)
{
sumTempG += GHist[i];
if (sumTempG >= LowTh)
{
GMin = i;
break;
}
}
sumTempG = 0;
for (int i = 255; i >= 0; i--)
{
sumTempG += GHist[i];
if (sumTempG >= HighTh)
{
GMax = i;
break;
}
}
//R通道查找最小最大值
int sumTempR = 0;
for (int i = 0; i < 256; i++)
{
sumTempR += RHist[i];
if (sumTempR >= LowTh)
{
RMin = i;
break;
}
}
sumTempR = 0;
for (int i = 255; i >= 0; i--)
{
sumTempR += RHist[i];
if (sumTempR >= HighTh)
{
RMax = i;
break;
}
}
//对每个通道建立分段线性查找表
//B分量查找表
int BTable[256] = { 0 };
for (int i = 0; i < 256; i++)
{
if (i <= BMin)
BTable[i] = 0;
else if (i > BMin && i < BMax)
BTable[i] = cvRound((float)(i - BMin) / (BMax - BMin) * 255);
else
BTable[i] = 255;
}
//G分量查找表
int GTable[256] = { 0 };
for (int i = 0; i < 256; i++)
{
if (i <= GMin)
GTable[i] = 0;
else if (i > GMin && i < GMax)
GTable[i] = cvRound((float)(i - GMin) / (GMax - GMin) * 255);
else
GTable[i] = 255;
}
//R分量查找表
int RTable[256] = { 0 };
for (int i = 0; i < 256; i++)
{
if (i <= RMin)
RTable[i] = 0;
else if (i > RMin && i < RMax)
RTable[i] = cvRound((float)(i - RMin) / (RMax - RMin) * 255);
else
RTable[i] = 255;
}
//对每个通道用相应的查找表进行分段线性拉伸
cv::Mat dst_ = src.clone();
cv::MatIterator_<Vec3b> itd, endd;
for (itd = dst_.begin<Vec3b>(), endd = dst_.end<Vec3b>(); itd != endd; itd++)
{
(*itd)[0] = BTable[(*itd)[0]];
(*itd)[1] = GTable[(*itd)[1]];
(*itd)[2] = RTable[(*itd)[2]];
}
dst = dst_;
}
(2)自动对比度
/*
*函数功能:自动对比度调整(仿照PS功能)
*输入参数:src 输入彩色图像
*输出参数:dst 输出调整色阶之后的彩色图像
*返回值:void
*算法步骤:
(1)分别统计每个通道的灰度直方图;
(2)对每个通道,利用LowCut和HighCut,分别计算灰度最小值和最大值,取三个通道最小值中
最小者作为最小值min,取三个通道最大值中最大者为最大值max;
(3)对每个通道建立统一的分段线性拉伸查找表,
f(g) = 0 g<=min
f(g) = 255 g>=max
f(g) = ((g-min)/(max-min)) * 255 min<g<max
(4)对每个通道利用统一的查找表进行分段线性拉伸,得到效果图。
http://www.cnblogs.com/Imageshop/archive/2011/11/13/2247614.html
*/
void AutoContrastAdjust(cv::Mat &src, cv::Mat &dst)
{
CV_Assert(!src.empty() && src.channels() == 3);
//统计灰度直方图
int BHist[256] = { 0 };
int GHist[256] = { 0 };
int RHist[256] = { 0 };
cv::MatIterator_<Vec3b> its, ends;
for (its = src.begin<Vec3b>(), ends = src.end<Vec3b>(); its != ends; its++)
{
BHist[(*its)[0]]++;
GHist[(*its)[1]]++;
RHist[(*its)[2]]++;
}
//设置LowCut和HighCut
float LowCut = 0.5;
float HighCut = 0.5;
//根据LowCut和HighCut查找每个通道最大值最小值
int BMax = 0, BMin = 0;
int GMax = 0, GMin = 0;
int RMax = 0, RMin = 0;
int TotalPixels = src.cols * src.rows;
float LowTh = LowCut * 0.01 * TotalPixels;
float HighTh = HighCut * 0.01 * TotalPixels;
//B通道查找最小最大值
int sumTempB = 0;
for (int i = 0; i < 256; i++)
{
sumTempB += BHist[i];
if (sumTempB >= LowTh)
{
BMin = i;
break;
}
}
sumTempB = 0;
for (int i = 255; i >= 0; i--)
{
sumTempB += BHist[i];
if (sumTempB >= HighTh)
{
BMax = i;
break;
}
}
//G通道查找最小最大值
int sumTempG = 0;
for (int i = 0; i < 256; i++)
{
sumTempG += GHist[i];
if (sumTempG >= LowTh)
{
GMin = i;
break;
}
}
sumTempG = 0;
for (int i = 255; i >= 0; i--)
{
sumTempG += GHist[i];
if (sumTempG >= HighTh)
{
GMax = i;
break;
}
}
//R通道查找最小最大值
int sumTempR = 0;
for (int i = 0; i < 256; i++)
{
sumTempR += RHist[i];
if (sumTempR >= LowTh)
{
RMin = i;
break;
}
}
sumTempR = 0;
for (int i = 255; i >= 0; i--)
{
sumTempR += RHist[i];
if (sumTempR >= HighTh)
{
RMax = i;
break;
}
}
//获取最大值,最小值,与自动色阶的不同之处主要在此,取的是三通道之最
int Max = std::max(std::max(BMax, GMax), RMax);
int Min = std::min(std::min(BMin, GMin), RMin);
//建立统一的分段线性查找表
int Table[256] = { 0 };
for (int i = 0; i < 256; i++)
{
if (i <= Min)
Table[i] = 0;
else if (i > Min && i < Max)
Table[i] = (int)((float)(i - Min) / (Max - Min) * 255);
else
Table[i] = 255;
}
//对每个通道用统一的查找表进行分段线性拉伸
cv::Mat dst_ = src.clone();
cv::MatIterator_<Vec3b> itd, endd;
for (itd = dst_.begin<Vec3b>(), endd = dst_.end<Vec3b>(); itd != endd; itd++)
{
(*itd)[0] = Table[(*itd)[0]];
(*itd)[1] = Table[(*itd)[1]];
(*itd)[2] = Table[(*itd)[2]];
}
dst = dst_;
}
(3)效果
原图
自动色阶效果图
自动对比度效果图
单从这幅图的测试效果来看,没法说自动色阶和自动对比度哪个效果更好。但是两者之间确实存在区别,为了对比一下两者的不同,利用ImageWatch观察一下内部像素值的变化
原图,位置(0000,0416)处的像素值 B |G|R = 213|231|160, 大小关系 G>B>R
自动色阶,位置(0000,0416)处的像素值 B |G|R = 255|254|157, 大小关系 B>G>R,改变了B和G的大小关系
自动对比度,位置(0000,0416)处的像素值 B |G|R = 226|248|160, 大小关系 G>B>R,RGB大小关系不变
小结:如果在图像处理的过程中比较关心RGB之间的大小关系,就不能用自动色阶,而应该用自动对比度。如果不关心RGB的大小关系,就根据实际测试对后续的影响来判断使用哪种处理。
5.扩展
在查询相关资料的时候,看到很多资料提到了彩色图像的直方图均衡化。于是,决定也了解一下彩色图像直方图均衡化的效果,并且与自动对比度进行一下比对。
参考博客:
https://blog.csdn.net/frank_xu_0818/article/details/39232157
(1)RGB空间下分通道直方图均衡化
/*
*函数功能:对彩色图像进行直方图均衡化
*输入参数:src 输入彩色图像
*输出参数:dst 输出均衡化之后的图像
*返回值:void
*备注:先通道分离,然后对每一通道进行直方图均衡化,最后融合
*/
void ColorEqualizeHist(cv::Mat &src, cv::Mat &dst)
{
CV_Assert(!src.empty() && src.channels() == 3);
//通道分离
cv::Mat channels[3];
cv::split(src, channels);
//每个通道进行直方图均衡化
cv::Mat equalizeHistImg[3];
for (int i = 0; i < 3; i++)
cv::equalizeHist(channels[i], equalizeHistImg[i]);
//通道融合
cv::Mat dst_;
cv::merge(equalizeHistImg, 3, dst_);
dst = dst_;
}
(2)转到YCbCr空间下单独对Y通道直方图均衡化
/*
*函数功能:对彩色图像进行直方图均衡化
*输入参数:src 输入彩色图像
*输出参数:dst 输出均衡化之后的图像
*返回值:void
*备注:先转到YCbCr颜色空间,然后通道分离,只对灰度通道进行直方图均衡化,
进行通道融合,最后转回到BGR空间
*/
void EqualizeHistByYCbCr(cv::Mat &src, cv::Mat &dst)
{
CV_Assert(!src.empty() && src.channels() == 3);
//BGR颜色空间转换到YCbCr颜色空间
cv::Mat srcYCbCr;
cv::cvtColor(src, srcYCbCr, CV_BGR2YCrCb);
//对YCbCr进行通道分离
cv::Mat channels[3];
cv::split(srcYCbCr, channels);
//对亮度通道进行直方图均衡化
cv::equalizeHist(channels[0], channels[0]);
//图像融合
cv::Mat dst_;
cv::merge(channels, 3, dst_);
//将YCbCr颜色空间转换回BGR颜色空间
cv::cvtColor(dst_, dst_, CV_YCrCb2BGR);
dst = dst_;
}
(3)效果
RGB直方图均衡化
YCbCr直方图均衡化
小结:RGB分通道均衡化再融合,颜色容易失真,实用性很低。转换为YCbCr再进行对亮度通道均衡化,融合后转回RGB,效果不够稳定。参考博客中的lena图我自己也测了,效果确实不错,但是我测gakki还有别的一幅图,效果不好。
根据我测得一些图,可以发现自动对比度与直方图均衡化相比,自动对比度效果更好,更稳定。