原文链接:https://blog.csdn.net/maozefa/article/details/4778934
图像亮度调整分为非线性和线性两种方法。
非线性图像亮度是将图像像素的R、G、B分别加上或减去某个值,其优点是代码简单,亮度调整速度快;缺点是图像信息损失较大,调整过的 图像显得平淡,无层次感。
线性图像亮度一般是将图像像素的RGB转换为HSL(HSV)等颜色空间,对L(V)部分进行增减调整后,再转换为RGB颜色空间,优点是调整过图像层次感很强;缺点是代码较复杂,调整速度慢,而且当图像亮度增减量较大时有很大的失真。
针对上面两种方法的优缺点,本人参照Photoshop的对比度、饱和度调整原理(可参见本人的有关文章),对图像亮度调整方法进行了改进,经测试,效果还不错,主要有不失真调整范围宽、有较好的层次感、尽可能减少图像信息损失量等;同时,在代码处理上,采用了灰度表查找法,先按制造了一个256个元素大小的线性亮度/对比度查找表,然后对图像数据逐像素按R、G、B分量值在查找表中取得调整后的数据,因此处理速度同《Delphi图像处理 -- 亮度/对比度调整》中的非线性亮度/对比度是基本相同的。
原理用公式表示为:
如果亮度增减量value范围为 -1 -- +1,当value > 0时:
rgb = RGB + RGB * (1 / (1 - value) - 1)
当value < 0时:
rgb = RGB + RGB * value
PS所用的正是线性方法,在PS6中也没有改变。原文代码非C++,且所用方法为HASH查表加速方法,比较难懂,我用C++实现了相同的方法没有进行HASH查表法,速度上较差。
其中色相饱和度和明度原本只以为是简单的HSV模型进行的应用,但是实际用简单加减,色相和饱和度大致相同。明度方面有较大差异,明度明显范围和PS6新版本方法不一致,在最大值处取不到白色,最小值处取不到黑色。
因此参考了原文链接:https://blog.csdn.net/matrix_space/article/details/72303250中关于明度的计算方法。
主要思想是PS的明度调整是采用Alpha合成方式,这里的value就是Alpha,公式前面部分RGB * (1 - value)的是图像部分,后面的255 * value部分则是一个白色遮照层,明度越大,遮照层的Alpha越大,图像就越谈,反之亦然。而明度的负调整则是以一个黑色遮照层来完成的。负100%就全黑了。只有遮照层Alpha=0,也就是明度值为0时,才是完完全全的图片显示。
明度调整,利用图层的合成
如果alpha大于0,相当于利用一个白色遮罩层合成
RGB = RGB * (1 - alpha) + 255 * alpha;
如果alpha小于0,相当于利用一个黑色遮罩层合成
RGB=RGB * (1+alpha) + 0 * alpha;
具体的代码实现如下范围阈值输入为
输入参数依次为:
i_light亮度(-150到+150)
contrast对比度(-50到100)
hue色相(-180到+180)
i_saturation饱和度(-100到+100)
输入图像与输出图像等大小三通道
代码基于QT界面
void ImageTools::AdjustImageColor(QImage *io_image, int i_light, int i_contrast,int i_hue, int i_saturation, int i_value,
int progress_start, int progress_end, ProgressCallBackPtr callProgress)
{
auto myClamp = [](int a, int min, int max)->int { return (a < min) ? min : (a > max) ? max : a; };//lambda表达式
int width = io_image->width();
int height = io_image->height();
int oldDisplayPos = progress_start;//界面显示进度
int progress_total = progress_end - progress_start;//进度总量
int total = width * height;//工作总量
int pos = 0;//当前工作进度
float c = i_contrast / 100.0f;
float b = i_light/255.f;
int nRed, nGreen, nBlue;
int result_color[3] = {0};
int h, s, v;
for (int x = 0; x < width; ++x) {
for (int y = 0; y < height; ++y) {
QColor var = io_image->pixelColor(x, y);
var.getHsv(&h, &s, &v);
h += i_hue;
if (h<0)
{
h = h + 360;
}
else if(h>360)
{
h = h - 360;
}
s += 2.55*i_saturation;
if (s<0)
{
s = 0;
}
else if(s>255)
{
s = 255;
}
var.setHsv(h, s, v);
var.getRgb(&nRed, &nGreen, &nBlue);
//明度新公式
int value= 2.55*i_value;
if (value>0)
{
nRed = (nRed * (255 - value) + 255 * value) / 255;
nGreen = (nGreen * (255 - value) + 255 * value) / 255;
nBlue = (nBlue * (255 - value) + 255 * value) / 255;
}
else
{
nRed = (nRed * (255 + value) + 0 * value) / 255;
nGreen = (nGreen * (255 + value) + 0 * value) / 255;
nBlue = (nBlue * (255+ value) + 0 * value) / 255;
}
int color[3] = {nRed, nGreen, nBlue};
for (int i = 0; i < 3; ++i) {
if (b>0.0)
{
color[i] = color[i] + color[i] * (1 / (1 - b) - 1)*0.85;
}
else
{
color[i] = color[i] + color[i] * b*0.65;
}
result_color[i] = color[i] +(color[i] -127.5) * c;
result_color[i] = myClamp(result_color[i], 0, 255);
}
io_image->setPixelColor(x, y, QColor(result_color[0], result_color[1], result_color[2]));
if (callProgress) {
//进度条设置
pos++;
int newDisplayPos = (double)pos / (double)total * progress_total + progress_start;
if (newDisplayPos > oldDisplayPos && newDisplayPos <= progress_end)
{
oldDisplayPos = newDisplayPos;
callProgress(oldDisplayPos);
}
}
}
}
}