快速对图像的像素进行操作 opencv 实战

来源:互联网 发布:北京奥运会姚明数据 编辑:程序博客网 时间:2024/05/24 03:21

OpenCV 如何对图像的像素进行操作

对图像的像素进行操作,我们可以实现空间增强,反色,大部分图像特效系列都是基于像素操作等目的。先来看一下内存空间中图像矩阵,也就是Mat的矩阵数值部分是怎么存储的:

如果图像是一幅灰度图像,他就像这样,从左到右,从上到下,依次是矩阵的每一行每一列,这时候矩阵M(i,j)的值自然就是当前点的灰度值了。

而对于一幅彩色图像,由于它的像素分量channel并不是一个,所以每一列又分为了几个channel。拿常见的RGB图像来说,就像这样:

从这张图上,就可以比较清楚地看出来在内存中矩阵是如何存储多channel图像的了。这里要注意的是在RGB模型中,每一个子列依次为BGR,也就是正好是颠倒的,第一个分量是蓝色,第二个是绿色,第三个是红色。

清楚了图像在内存中的存储方式,我们也就可以来进行像素值的操作了。在这里,我们举这样一个例子。我们对一幅灰度图像的灰度值进行变换:

小于100的灰度值被统一映射为0;100到200之间的灰度值被映射为100;大于200的灰度值被映射为200.

主函数如下:

    int main()    {        string picName=“lena.jpg”;        Mat A=imread (picName,CV_LOAD_IMAGE_GRAYSCALE);    //读入灰度图像        uchar table[256];          //映射表,规定了变换前后灰度值的对应关系 table[gray_value_before]=gray_value_after        for (int i=0;i<256;i++)        {            table[i]=i/100*100;   //这里利用了C++的语言特性i/100只会留下整数部分        }        imshow(“变换前”,A);        Mat B=ChangeImg (A,table);    //变换函数        imshow (“变换后”,B);        waitKey ();        return 0;    }

首先,我们用指针方式对图像的像素点灰度值进行操作:

Mat ChangeImg(Mat &img,const uchar* table)
{
CV_Assert(img.depth ()!=sizeof(uchar)); //声明只对深度8bit的图像操作
int channels=img.channels (); //获取图像channel
int nrows=img.rows; //矩阵的行数
int ncols=img.cols*channels; //矩阵的总列数=列数*channel分量数
if (img.isContinuous ()) //判断矩阵是否连续,若连续,我们相当于只需要遍历一个一维数组
{
ncols*=nrows;
nrows=1; //一维数组
}
//遍历像素点灰度值
for (int i=0;i<nrows;i++)
{
uchar *p=img.ptr<uchar>(i); //获取行地址
for (int j=0;j<ncols;j++)
{
p[j]=table[p[j]]; //修改灰度值
}
}
return img;
}

这里,我们获取了每一行开始处的指针,然后遍历至该行末尾。如果矩阵是以连续方式存储的,我们只需请求一次指针、然后一路遍历下去就行。彩色图像的情况有必要加以注意:因为三个通道的原因,我们需要遍历的元素数目也是3倍。

或者,我们可以使用data。data会从Mat中返回指向矩阵第一行第一列的指针。注意如果该指针为NULL则表明对象里面无输入,所以这是一种简单的检查图像是否被成功读入的方法。当矩阵是连续存储时,我们就可以通过遍历 data 来扫描整个图像。例如,一个灰度图像,其操作如下:

uchar* p = img.data;for( unsigned int i =0; i < ncol*nrows; ++i)    *p++ = table[*p];

或者,更安全的方法,我们可以使用迭代器。在迭代法中,所需要做的仅仅是获得图像矩阵的begin和end,然后增加迭代直至从begin到end。将*操作符添加在迭代指针前,即可访问当前指向的内容。

    Mat& ScanImageAndReduceIterator(Mat& I, const uchar* const table)    {        // accept only char type matrices        CV_Assert(I.depth() != sizeof(uchar));        const int channels = I.channels();        switch(channels)        {        case 1:            {                MatIterator_<uchar> it, end;                for( it = I.begin<uchar>(), end = I.end<uchar>(); it != end; ++it)                    *it = table[*it];                break;            }        case 3:            {                MatIterator_<Vec3b> it, end;                for( it = I.begin<Vec3b>(), end = I.end<Vec3b>(); it != end; ++it)                {                    (*it)[0] = table[(*it)[0]];                    (*it)[1] = table[(*it)[1]];                    (*it)[2] = table[(*it)[2]];                }            }        }        return I;    }

注意,在这里对3通道的图像进行操作的时候,使用到了Vec3b。Vec3b作为一个对三元向量的数据结构,用在这里正好是能够表示RGB的三个分量。如果对于彩色图像,仍然用uchar的话,则只能获得3通道中的B分量。比如我们可以这样打印出图像的RGB三个分量:

for (int i=0;i<img.rows;i++)    {        const Vec3b* Mpoint=img.ptr <Vec3b>(i);        for (int j=0;j<img.cols;j++)        {            Vec3b intensity=*(Mpoint+j);            cout<<“R:”<<int(intensity[2])<<” G”<<int(intensity[1])<<” B”<<int(intensity[0])<<”    “;        }        cout<<endl;    }

也就是说比如原来src中值为1会映射为table[1]所对应的值再加上d。

所以上面的操作,我们其实只需要使用LUT函数就可以了。结合我们自己设计的table表,就能够实现对图像的操作。

int main(){    string picName=“lena.jpg”;    Mat A=imread (picName,CV_LOAD_IMAGE_GRAYSCALE);    //读入灰度图像    Mat lookUpLut(1,256,CV_8UC1);   //建立一个256个元素的映射表    imshow (“变换前”,A);    for (int i=0;i<256;i++)    {        lookUpLut.at<uchar>(i)=i/100*100;    }    Mat B;    LUT (A,lookUpLut,B);    imshow (“变换后”,B);    waitKey ();    return 0;}

下面的图就是效果啦~~~

补充:我们得出一些结论: 尽量使用 OpenCV 内置函数. 调用LUT 函数可以获得最快的速度. 这是因为OpenCV库可以通过英特尔线程架构启用多线程,下面的opencv矩阵操作均是优化的多线程并行处理,较高效
OpenCV – Operations on Arrays
对数组(矩阵)的一些操作
Function (函数名) Use (函数用处)
add 矩阵加法,A+B的更高级形式,支持mask
scaleAdd 矩阵加法,一个带有缩放因子dst(I) = scale * src1(I) + src2(I)
addWeighted 矩阵加法,两个带有缩放因子dst(I) = saturate(src1(I) * alpha + src2(I) * beta + gamma)
subtract 矩阵减法,A-B的更高级形式,支持mask
multiply 矩阵逐元素乘法,同Mat::mul()函数,与A*B区别,支持mask
gemm 一个广义的矩阵乘法操作
divide 矩阵逐元素除法,与A/B区别,支持mask
abs 对每个元素求绝对值
absdiff 两个矩阵的差的绝对值
exp 求每个矩阵元素 src(I) 的自然数 e 的 src(I) 次幂 dst[I] = esrc(I)
pow 求每个矩阵元素 src(I) 的 p 次幂 dst[I] = src(I)p
log 求每个矩阵元素的自然数底 dst[I] = log|src(I)| (if src != 0)
sqrt 求每个矩阵元素的平方根
min, max 求每个元素的最小值或最大值返回这个矩阵 dst(I) = min(src1(I), src2(I)), max同
minMaxLoc 定位矩阵中最小值、最大值的位置
compare 返回逐个元素比较结果的矩阵
bitwise_and, bitwise_not, bitwise_or, bitwise_xor 每个元素进行位运算,分别是和、非、或、异或
cvarrToMat 旧版数据CvMat,IplImage,CvMatND转换到新版数据Mat
extractImageCOI 从旧版数据中提取指定的通道矩阵给新版数据Mat
randu 以Uniform分布产生随机数填充矩阵,同 RNG::fill(mat, RNG::UNIFORM)
randn 以Normal分布产生随机数填充矩阵,同 RNG::fill(mat, RNG::NORMAL)
randShuffle 随机打乱一个一维向量的元素顺序
theRNG() 返回一个默认构造的RNG类的对象 theRNG()::fill(…)
reduce 矩阵缩成向量
repeat 矩阵拷贝的时候指定按x/y方向重复
split 多通道矩阵分解成多个单通道矩阵
merge 多个单通道矩阵合成一个多通道矩阵
mixChannels 矩阵间通道拷贝,如Rgba[]到Rgb[]和Alpha[]
sort, sortIdx 为矩阵的每行或每列元素排序
setIdentity 设置单元矩阵
completeSymm 矩阵上下三角拷贝
inRange 检查元素的取值范围是否在另两个矩阵的元素取值之间,返回验证矩阵
checkRange 检查矩阵的每个元素的取值是否在最小值与最大值之间,返回验证结果bool
sum 求矩阵的元素和
mean 求均值
meanStdDev 均值和标准差
countNonZero 统计非零值个数
cartToPolar, polarToCart 笛卡尔坐标与极坐标之间的转换
flip 矩阵翻转
transpose 矩阵转置,比较 Mat::t() AT
trace 矩阵的迹
determinant 行列式 |A|, det(A)
eigen 矩阵的特征值和特征向量
invert 矩阵的逆或者伪逆,比较 Mat::inv()
magnitude 向量长度计算 dst(I) = sqrt(x(I)2 + y(I)2)
Mahalanobis Mahalanobis距离计算
phase 相位计算,即两个向量之间的夹角
norm 求范数,1-范数、2-范数、无穷范数
normalize 标准化
mulTransposed 矩阵和它自己的转置相乘 AT * A, dst = scale(src – delta)T(src – delta)
convertScaleAbs 先缩放元素再取绝对值,最后转换格式为8bit型
calcCovarMatrix 计算协方差阵
solve 求解1个或多个线性系统或者求解最小平方问题(least-squares problem)
solveCubic 求解三次方程的根
solvePoly 求解多项式的实根和重根
dct, idct 正、逆离散余弦变换,idct同dct(src, dst, flags | DCT_INVERSE)
dft, idft 正、逆离散傅立叶变换, idft同dft(src, dst, flags | DTF_INVERSE)
LUT 查表变换
getOptimalDFTSize 返回一个优化过的DFT大小
mulSpecturms 两个傅立叶频谱间逐元素的乘法

0 0