1、查找表
之前我在OpenCV中图像遍历与像素操作中有提到过查找表遍历图像的API函数cv::LUT,鉴于其大巧于内的美感[笑],特意写一篇博客探讨一下。从数学上来看查找表是一个简单的一对一或多对一的函数,定义了如何将像素转换为新的值。从数据的组织关系上来看,查找表是一维或多维的数组,存储了不同输入值所对应的输出值。数据表在图像处理中主要用于像素的点运算,尤其是像素之间无位置相关性的操作中。比如我们在上面提到的博客中,求图像镜像变换的示例就很难运用查找表的方法来实现。而在颜色缩减、图片取反以及直方图均衡化等不涉及像素位置相关性的算法中我们都可以应用。当然,查找表的优势也很明显[大巧于内],只需读取、无需计算。
先上几个例子,直观感受一下。
1.颜色空间缩减:将现有颜色空间值除以某个输入值,以获得较少的颜色数。例如,颜色值0到9可取为新值0,10到19可取为10,以此类推。(示例来源于OpenCV官网)。
显然这是一个多对一的映射,I[new] = I[old]/10*10。很容易想到,只要遍历图像矩阵的每一个像素,对像素应用上述公式就可以完成任务。只是这里用到了除法和乘法运算,而这两种运算又特别费时。鉴于一幅图像只涉及256个像素,我们大可开一个长度为256的数组,让其下标代表旧像素值,数组值代表新的像素值,如lookup[256]={0,…,0,10,…,10,20,…,20,…,250,…,250}。这样我们遍历修改时不就可以通过像素值从表中查出要改变的像素值了么,而且这一过程只有赋值运算。
2.图像取反:反转图像的像素强度,使图像中的前景变为背景,背景变为前景。
显然这是一个一对一的映射,即像素值0变为255,1变为254…254变为1,255变为0。对应的查找表为lookup[256]={255,254,…,1,0}。
代码如下:
#include <iostream>
#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
using namespace std;
using namespace cv;
void Invert(Mat &img, const uchar* const lookup)
{
int rows=img.rows;
int cols=img.cols*img.channels();
for(int i=0; i<rows; i++)
{
uchar *p=img.ptr<uchar>(i);
for(int j=0; j<cols;j++)
p[j]=lookup[p[j]];
}
}
int main()
{
Mat src=imread("test.jpg"); //将任意一张名为test.jpg的图片放置于工程文件夹test中
if(!src.data)
{
cout<<"error! The image is not built!"<<endl;
return -1;
}
// 为了演示效果,将图片转换成灰度图片
Mat img1=src;
//cvtColor( src, img1, CV_RGB2GRAY );
imshow("First",img1);
//建立查找表
uchar lookup[256];
for(int i=0; i<256; i++)
lookup[i]=255-i;
//调用自定义图像取反函数
Invert(img1, lookup);
imshow("Second",img1);
waitKey();
return 0;
}
运行结果:
本来觉得上彩图难看,结果发现灰度图也难看的过分,那就这样吧。
2、LUT函数
上面我们程序中我们建立查找表之后,对图像的遍历是手动实现的,其实OpenCV中为我们提供了一个非常高效的API函数cv::LUT,可以应用查找表生成新图像。LUT是OpenCV的core mudle中的函数,其函数原型如下:
void LUT(InputArray src, InputArray lut, OutputArray dst)
Parameters:
第一个参数:原始图像的地址;
第二个参数:查找表的地址,对于多通道图像的查找,它可以有一个通道,也可以与原始图像有相同的通道;
第三个参数:输出图像的地址。
典型用法(借助图像取反示例说明)是:
Mat lookUpTable(1, 256, CV_8U);
uchar* p = lookUpTable.data;
for( int i = 0; i < 256; ++i)
p[i] = 255-i;
调用就很简单啦,LUT(src, lookUpTable,dst )
。
还是贴一下全部代码:
#include<iostream>
#include<opencv2/core/core.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<opencv2/highgui/highgui.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat src=imread("test.jpg"); //将任意一张名为test.jpg的图片放置于工程文件夹test中
if(!src.data)
{
cout<<"error! The image is not built!"<<endl;
return -1;
}
// 为了演示效果,将图片转换成灰度图片
Mat img1=src;
//cvtColor( src, img1, CV_RGB2GRAY );
imshow("First",img1);
//建立查找表
Mat lookUpTable(1, 256, CV_8U);
uchar *p = lookUpTable.data;
for(int i=0; i<256; i++)
p[i]=255-i;
//通过LUT函数实现图像取反
LUT(img1,lookUpTable,img1);
imshow("Second",img1);
waitKey();
return 0;
}
最后唠叨两句,虽然手动遍历可以达到同样效果,但尽量使用 OpenCV 内置函数。调用LUT 函数可以获得最快的速度,这是因为OpenCV库可以通过英特尔线程架构启用多线程。