1、什么是LUT
Look Up Table(颜色查找表)、俗称调色板,一行多列的矩阵/数组;可以通过图像像素值或者一组数据值查找表(LUT)中对应的值进行重映射(当像素值x = 180 , 去查找表中第180个位置的值将原x替换)
2、LUT的作用
1、伪彩色增强
设计一种合理的颜色表,将灰度(黑白)图像,通过颜色表进行重映射为彩色图像,使得其具有更丰富明显的视觉效果
2、灰度增强
设计一种合理的颜色表,将灰度(黑白)图像,通过颜色表进行重映射为一种新的灰度图像,使得其具有更丰富明显的视觉效果
3、图像增强
设计一种合理的颜色表,将图像,通过颜色表进行重映射为一种新的图像,使得其具有更丰富明显的视觉效果
4、数据增强
设计一种合理的表,将一组数据,通过表进行重映射为一组新的数据,使得其具有更丰富明显的表达效果
3、OpenCV中的LUT函数
1、函数原型
void LUT(InputArray src, InputArray lut, OutputArray dst);
/**
* @param {InputArray} src 输入图像
* @param {InputArray} lut 查找表的地址,对于多通道图像的查找,它可以有一个通道,也可以与原始图* 像有相同的通道;
* @param {InputArray} dst 输出图像
*/
2、代码实现
实现的大概思路如下,以RGB->RGB为例
void LUT(Mat src, InputArray lut, Mat & dst)
{
Vec3b * p = lut.ptr<Vec3b>(0);
//指针遍历
for(int i = 0 ; i < src.rows ; i++)
{
Vec3b *cur = src.ptr< Vec3b >(i); // 取当前行的首地址
Vec3b *out = dst.ptr< Vec3b >(i); // 取当前行的首地址
for(int j = 0 ; j < in.cols ; j++)
{
out[j][0] = p[cur[j][0]][0];
out[j][1] = p[cur[j][1]][1];
out[j][2] = p[cur[j][2]][2];
}
}
3、原理图
SRC代表原图中的某一行,OUT代表LUT映射后的输出图
从上图可以看出,输入图x位置处的BGR值为:SRC[x][0]、 SRC[x][1]、SRC[x][2];对应输出图x位置处的BGR值为:OUT[x][0]、 OUT[x][1]、OUT[x][2];以原图BGR值为下标去查找LUT对应位置的像素值进行替换,如公式所示
4、OpenCV-LUT优缺点
1、优点
避免对每一个像素都进行一次计算。预先计算每个像素值的对应的匹配值,然后直接对原图中的像素值在LUT中进行查找,即可快速得到匹配值,减少大量不必要的计算。
2、缺点
1、颜色表长度为256,仅适用于8bit的图像
2、对与灰度图的伪彩色增强不可直接使用LUT函数,可以将其转换为RGB图像,此时视觉效果依然是灰度图,只是3个通道的颜色值与原单通道颜色值一致。例:234->[234,234,234],再使用LUT函数
5、构建自己的颜色表
首先,为了方便对比我们构建一个常规的16bit灰度映射表Gray分别从0->65535。
- C++代码实现
void OpenCVStudy::Build_COLOR_table(Mat & MyLUT)
{
Mat GRAY_LUT(5000 , 65536 , CV_16UC1); // 为了方便观察输出,使用5000行数据,映射时需要改成1
MyLUT = GRAY_LUT;
for(int j = 0 ; j < MyLUT.rows ; j ++)
{
ushort * GRAY = MyLUT.ptr<ushort>(j);
for(int i = 0 ; i < MyLUT.cols ; i ++)
{
GRAY[i] = i;
}
}
imwrite("../images/16bit_LUT.tif" , MyLUT);
}
- 输出
1、JET算法
该算法为OpenCV中 cv::applyColorMap()包含的一种颜色映射算法,映射方式如下表所示
像素值 | B | G | R |
---|---|---|---|
0-31 | 128+4*value | 0 | 0 |
32 | 255 | 0 | 0 |
33-95 | 255 | 4+4*value | 0 |
96 | 254 | 255 | 2 |
97-158 | 250-4*value | 255 | 6+4*value |
159 | 1 | 255 | 254 |
160-223 | 0 | 252-4*value | 255 |
224-255 | 0 | 0 | 252-4*value |
需要注意的是,其中value从0开始,当像素值为33-95之间时,value从0-62。
- C++代码实现:
void OpenCVStudy::Build_COLOR_table(Mat & MyLUT)
{
MyLUT = Mat(20 , 256 , CV_8UC3); // 映射时需要将20行改为1行
for(int i = 0 ; i < MyLUT.rows ; i++)
{
//获取行首地址
uchar * p = MyLUT.ptr<uchar>(i);
// *p++ // B
// *p++ // G
// *p++ // R
for(int value = 0 ; value <= 31 ; value++)
{
*p++ = 128 + (4 * value);
*p++ = 0;
*p++ = 0; // B G R
}
// 32
{
*p++ = 255;
*p++ = 0;
*p++ = 0;
}
//32 + 63 = 95
for(int value = 0 ; value < 63 ; value++)
{
*p++ = 255;
*p++ = 4 + (4 * value);
*p++ = 0;
}
//96
{
*p++ = 254;
*p++ = 255;
*p++ = 2;
}
//96 + 62 = 158
for(int value = 0 ; value < 62 ; value++)
{
*p++ = 250 - (4 * value);
*p++ = 255;
*p++ = 6 + (4 * value);
}
//159
{
*p++ = 1;
*p++ = 255;
*p++ = 254;
}
//159 + 64 = 223
for(int value = 0 ; value < 64 ; value++)
{
*p++ = 0;
*p++ = 252 - (4 * value);
*p++ = 255;
}
//223 + 32 = 255
for(int value = 0 ; value < 32 ; value++)
{
*p++ = 0;
*p++ = 0;
*p++ = 252 - (4 * value);
}
}
}
- 输出结果
- 使用OpenCV自带的LUT函数
Mat out;
cv::LUT(SRC , MyLUT , out); // 映射
- 对比图
然而,OpenCV自带的LUT函数只支持8bit的图像,下面以LUT映射思想,构建并实现16bit图像的LUT映射
2、16bit对数变换算法LUT应用
- 对数变换算法:
dst 代表输出图像素,src代表输入图像素,LUT表的输入像素从0-65535,为了保证数据在0-65535之间,乘以系数C
- C++代码实现
Mat LOG_LUT(5000 , 65536 , CV_16UC1); // 映射时需要将行数改为1
for(int j = 0 ; j < LOG_LUT.rows ; j ++)
{
ushort * LOG = LOG_LUT.ptr<ushort>(j);
double c = (65535 / log(65535 + 1));
for(int i = 0 ; i < LOG_LUT.cols ; i ++)
{
LOG[i] = saturate_cast<ushort>(c * log(1.0 + i));
SRC[i] = i;
}
}
imwrite("../images/LOG_LUT.tif" , LOG_LUT);
- 输出:
我们可以从构建的LUT表直观的看到灰度变换算法的特点---图像整体亮度提高,我们在本章开始实现了常规的0-65535表, 如果以常规的表作为输入,将得到与LUT表相同的效果。
- 接下来,将通过LUT映射思想,实现16bit图像的映射
void 16bitLUT(Mat & in)
{
cvtColor(in , in ,COLOR_BGR2GRAY);
in.convertTo(in , CV_16UC1);
normalize(in, in, 0, 65535, NORM_MINMAX);
Mat LOG_OUT(in.rows , in.cols , CV_16UC1);
ushort * lut = LOG_LUT.ptr<ushort>(0);
//指针遍历
for(int i = 0 ; i < in.rows ; i++)
{
ushort *cur = in.ptr<ushort>(i); // 取当前行的首地址
ushort * OUT = LOG_OUT.ptr<ushort>(i);
for(int j = 0 ; j < in.cols ; j++)
{
*OUT++ = lut[*cur];
cur++; // 偏移到下一个像素值
}
}
imwrite("../images/LOG-OUT.tif" , LOG_OUT);
}
- 输出结果:
抛开算法效果,重点在于思想!!!
3、单通道8bit JET算法伪彩色增强
该方法输入图像为单通道8bit图像,有两种方法:
1、映射时可以将其转换为BGR图像通过5.1方法即可实现
2、LUT表查询时,可以将输出图BGR数据分别使用一个通道的数据,本质上与1一致
- C++代码实现
Mat in = imread("../images/test.heic" , 0);
in.convertTo(in , CV_8UC1);
normalize(in, in, 0, 255, NORM_MINMAX);
Mat MyLUT ;
image.Build_COLOR_table(MyLUT);
cout << "ch" << in.channels() << endl;
Mat out(in.rows , in.cols , MyLUT.type());
Vec3b * p = MyLUT.ptr<Vec3b>(0);
//指针遍历
for(int i = 0 ; i < out.rows ; i++)
{
uchar *cur = in.ptr<uchar>(i); // 取当前行的首地址
Vec3b *o = out.ptr<Vec3b>(i); // 取当前行的首地址
for(int j = 0 ; j < out.cols ; j++)
{
o[j][0] = p[cur[j]][0];
o[j][1] = p[cur[j]][1];
o[j][2] = p[cur[j]][2];
}
}
imwrite("../images/OUT-8.jpg" , in);
imwrite("../images/JET-8.jpg" , out);
- 输出对比图
强调:重在思想!!!!!
6、总结
1、LUT函数提供了一种颜色以及颜色空间与另一种颜色及颜色空间的映射方法,虽然OpenCV本身提供的函数对非8bit图像不友好,但是我们可以利用其思想自己动手实现
2、通过构建LUT,我们可以直观的看到我们算法的特点(例:色谱颜色过渡自然程度、灰度增强灰度值偏亮、暗等等)
- 请各位大佬批评指正!!