对图像的像素进行访问,可以实现空间增强,反色,大部分图像特效系列都是基于像素操作的。图像容器Mat是一个矩阵的形式,一般情况下是二维的。单通道灰度图一般存放的是<uchar>类型,其数据存放格式如下:

多通道的图像中,每列并列存放通道数量的子列,如RGB三通道彩色图:

注意通道的顺序为BGR。通常在内存足够大的情况下,图像的每一行是连续存放的,亦即在内存上图像的所有数据组成一个一维向量,这种情况下,在访问时将更快捷。可用成员函数isContinuous()来判断Mat图像在内存中是否为连续存储的。

清楚了图像在内存中的存储方式,下面对像素值操作:在只对深度为8bit字节型图像操作的前提下,导入一幅彩色图像,将其像素值变换为对255的补数。如原像素值为55,则变换为255-55=200。

使用一映射表来完成该转换:

     uchar mapTable[];//mapping table:mapTable[pixel_value_before]=255-pixel_value_before;
for (int i = ; i < ; i++)
mapTable[i] = - i;

数组mapTable中装入的即是原像素值变换后的像素值。

可用如下方法对像素值进行操作:

1、指针的方式

 //通过ptr和[]访问像素的值
void transformImageMethodOne(Mat& pSrcImg, const uchar* const table)
{
CV_Assert(pSrcImg.depth() != sizeof(uchar));//if the statement is false,then return a wrong message
int nchannels = pSrcImg.channels();
int nrows = pSrcImg.rows;//矩阵的行数
int ncols = pSrcImg.cols*nchannels;//矩阵的总列数=列数*nchannels
if (pSrcImg.isContinuous())//isContinuous()函数用于判断矩阵是否连续,若连续,相当于只需要遍历一个一维数组
{
cout << "only one row!" << endl;
ncols *= nrows;
nrows = ;//一维数组
}
//traverse pixel values
for (int i = ; i < nrows; i++)
{
uchar* ptr = pSrcImg.ptr<uchar>(i);//获取行地址
for (int j = ; j < ncols; j++)
ptr[j] = table[ptr[j]];//修改像素值
}
// return pSrcImg;
}

如代码所示,我们获取每一行开始处的指针,然后遍历至该行末尾。如果矩阵是连续存储的,则只需请求一次指针即可。

2、或者,也可以借助Mat的成员data。

data会从Mat中返回指向矩阵的首地址。通过遍历data来扫描整个图像。具体操作如下:

 //通过ptr和[]访问像素的值(使用到了Mat的成员data)
void transformImageMethodTwo(Mat& pSrcImg, const uchar* const table)
{
CV_Assert(pSrcImg.depth() != sizeof(uchar));//if the statement is false,then return a wrong message
int nchannels = pSrcImg.channels();
int nrows = pSrcImg.rows;
int ncols = pSrcImg.cols*nchannels;
//traver pixel values
uchar* ptr = pSrcImg.data;//指向矩阵的首地址
for (int i = ; i < ncols*nrows; i++)
*ptr++ = table[*ptr];//*ptr++是先取出*p的值,然后让p++ }

3、使用迭代器

使用迭代器的方法,仅仅需要获得图像矩阵的begin和end,然后从begin迭代至end,将操作符*添加至迭代指针前,即可访问当前指向的内容。

 //使用迭代器访问像素的值
void transformImageMethodThree(Mat& pSrcImg, const uchar* const table)
{
CV_Assert(pSrcImg.depth() != sizeof(uchar));//if the statement is false,then return a wrong message
const int nchannels = pSrcImg.channels();
switch (nchannels)
{
case :
{
MatIterator_<uchar> it, end;
for (it = pSrcImg.begin<uchar>(), end = pSrcImg.end<uchar>(); it != end; it++)
*it = table[*it];
break;
}
case :
{
MatIterator_<Vec3b> it, end;
for (it = pSrcImg.begin<Vec3b>(), end = pSrcImg.end<Vec3b>(); it != end; it++)
{
(*it)[] = table[(*it)[]];
(*it)[] = table[(*it)[]];
(*it)[] = table[(*it)[]];
}
break;
}
default:break;
} }

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

4、动态地址访问

这种方法在需要连续扫描所有点的应用场景时效率较低,因为它更适用于随机访问。这种方法最基本的用途是访问任意的某一行某一列:

 //使用动态地址访问像素的值
void transformImageMethodFour(Mat& pSrcImg, const uchar* const table)
{
CV_Assert(pSrcImg.depth() != sizeof(uchar));//if the statement is false,then return a wrong message
const int nchannels = pSrcImg.channels();
const int nrows = pSrcImg.rows;
const int ncols = pSrcImg.cols;
switch (nchannels)
{
case :
{
for (int i = ; i < nrows;i++)
for (int j = ; j < ncols; j++)
pSrcImg.at<uchar>(i, j) = table[pSrcImg.at<uchar>(i, j)];
break;
}
case :
{
Mat_<Vec3b> _pSrcImg = pSrcImg;
for (int i = ; i < nrows;i++)
for (int j = ; j < ncols; j++)
{
_pSrcImg(i, j)[] = table[_pSrcImg(i, j)[]];
_pSrcImg(i, j)[] = table[_pSrcImg(i, j)[]];
_pSrcImg(i, j)[] = table[_pSrcImg(i, j)[]];
}
// pSrcImg = _pSrcImg;
break;
}
default:break;
}
}

测试代码:

 /*
@author:CodingMengmeng
@theme:read the image pixel values by Mat
@time:2017-3-16 23:06:40
@blog:http://www.cnblogs.com/codingmengmeng/
*/
#include <cv.h>
#include <highgui.h>
using namespace std;
using namespace cv; //通过ptr和[]访问像素的值
void transformImageMethodOne(Mat& pSrcImg, const uchar* const table)
{
CV_Assert(pSrcImg.depth() != sizeof(uchar));//if the statement is false,then return a wrong message
int nchannels = pSrcImg.channels();
int nrows = pSrcImg.rows;//矩阵的行数
int ncols = pSrcImg.cols*nchannels;//矩阵的总列数=列数*nchannels
if (pSrcImg.isContinuous())//isContinuous()函数用于判断矩阵是否连续,若连续,相当于只需要遍历一个一维数组
{
cout << "only one row!" << endl;
ncols *= nrows;
nrows = ;//一维数组
}
//traverse pixel values
for (int i = ; i < nrows; i++)
{
uchar* ptr = pSrcImg.ptr<uchar>(i);//获取行地址
for (int j = ; j < ncols; j++)
ptr[j] = table[ptr[j]];//修改像素值
}
// return pSrcImg;
}
//通过ptr和[]访问像素的值(使用到了Mat的成员data)
void transformImageMethodTwo(Mat& pSrcImg, const uchar* const table)
{
CV_Assert(pSrcImg.depth() != sizeof(uchar));//if the statement is false,then return a wrong message
int nchannels = pSrcImg.channels();
int nrows = pSrcImg.rows;
int ncols = pSrcImg.cols*nchannels;
//traver pixel values
uchar* ptr = pSrcImg.data;//指向矩阵的首地址
for (int i = ; i < ncols*nrows; i++)
*ptr++ = table[*ptr];//*ptr++是先取出*p的值,然后让p++ }
//使用迭代器访问像素的值
void transformImageMethodThree(Mat& pSrcImg, const uchar* const table)
{
CV_Assert(pSrcImg.depth() != sizeof(uchar));//if the statement is false,then return a wrong message
const int nchannels = pSrcImg.channels();
switch (nchannels)
{
case :
{
MatIterator_<uchar> it, end;
for (it = pSrcImg.begin<uchar>(), end = pSrcImg.end<uchar>(); it != end; it++)
*it = table[*it];
break;
}
case :
{
MatIterator_<Vec3b> it, end;
for (it = pSrcImg.begin<Vec3b>(), end = pSrcImg.end<Vec3b>(); it != end; it++)
{
(*it)[] = table[(*it)[]];
(*it)[] = table[(*it)[]];
(*it)[] = table[(*it)[]];
}
break;
}
default:break;
} }
//使用动态地址访问像素的值
void transformImageMethodFour(Mat& pSrcImg, const uchar* const table)
{
CV_Assert(pSrcImg.depth() != sizeof(uchar));//if the statement is false,then return a wrong message
const int nchannels = pSrcImg.channels();
const int nrows = pSrcImg.rows;
const int ncols = pSrcImg.cols;
switch (nchannels)
{
case :
{
for (int i = ; i < nrows;i++)
for (int j = ; j < ncols; j++)
pSrcImg.at<uchar>(i, j) = table[pSrcImg.at<uchar>(i, j)];
break;
}
case :
{
Mat_<Vec3b> _pSrcImg = pSrcImg;
for (int i = ; i < nrows;i++)
for (int j = ; j < ncols; j++)
{
_pSrcImg(i, j)[] = table[_pSrcImg(i, j)[]];
_pSrcImg(i, j)[] = table[_pSrcImg(i, j)[]];
_pSrcImg(i, j)[] = table[_pSrcImg(i, j)[]];
}
// pSrcImg = _pSrcImg;
break;
}
default:break;
}
}
int main(void)
{
string imgName = "Route66.jpg";
Mat img = imread(imgName);
Mat imgCopy1 = img.clone();
Mat imgCopy2 = img.clone();
Mat imgCopy3 = img.clone();
Mat imgCopy4 = img.clone();
uchar mapTable[];//mapping table:mapTable[pixel_value_before]=255-pixel_value_before;
for (int i = ; i < ; i++)
mapTable[i] = - i;
imshow("SRCIMAGE", img);
transformImageMethodOne(imgCopy1, mapTable);
imshow("TRANSFORMIMAGE_USE_METHOD1", imgCopy1);
transformImageMethodTwo(imgCopy2, mapTable);
imshow("TRANSFORMIMAGE_USE_METHOD2", imgCopy2);
transformImageMethodThree(imgCopy3, mapTable);
imshow("TRANSFORMIMAGE_USE_METHOD3", imgCopy3);
transformImageMethodFour(imgCopy4, mapTable);
imshow("TRANSFORMIMAGE_USE_METHOD4", imgCopy4);
waitKey();
return ;
}

运行结果:

原图:

变换效果图:

以上。

opencv学习笔记(九)Mat 访问图像像素的值的更多相关文章

  1. 【opencv学习笔记七】访问图像中的像素与图像亮度对比度调整

    今天我们来看一下如何访问图像的像素,以及如何改变图像的亮度与对比度. 在之前我们先来看一下图像矩阵数据的排列方式.我们以一个简单的矩阵来说明: 对单通道图像排列如下: 对于双通道图像排列如下: 那么对 ...

  2. (转) OpenCV学习笔记大集锦 与 图像视觉博客资源2之MIT斯坦福CMU

          首页 视界智尚 算法技术 每日技术 来打我呀 注册     OpenCV学习笔记大集锦 整理了我所了解的有关OpenCV的学习笔记.原理分析.使用例程等相关的博文.排序不分先后,随机整理的 ...

  3. opencv学习笔记(八)IplImage* 访问图像像素的值

    opencv2.1版本之前使用IplImage*数据结构来表示图像,2.1之后的版本使用图像容器Mat来存储.IplImage结构体如下所示. typedef struct _IplImage { i ...

  4. [OpenCV学习笔记2][Mat数据类型和操作]

    [Mat数据类型和基本操作] ®.运行环境:Linux(RedHat+OpenCV3.0) 1.Mat的作用: Mat类用于表示一个多维的单通道或者多通道的稠密数组.能够用来保存实数或复数的向量.矩阵 ...

  5. OpenCV学习笔记(七) 图像金字塔 阈值 边界

    转自: OpenCV 教程 使用 图像金字塔 进行缩放 图像金字塔是视觉运用中广泛采用的一项技术.一个图像金字塔是一系列图像的集合 - 所有图像来源于同一张原始图像 - 通过梯次向下采样获得,直到达到 ...

  6. OpenCV学习笔记:MAT解析

    在2001年刚刚出现的时候,OpenCV基于 C 语言接口而建.为了在内存(memory)中存放图像,当时采用名为 IplImage 的C语言结构体,时至今日这仍出现在大多数的旧版教程和教学材料.但这 ...

  7. OpenCV学习笔记:如何扫描图像、利用查找表和计时

    目的 我们将探索以下问题的答案: 如何遍历图像中的每一个像素? OpenCV的矩阵值是如何存储的? 如何测试我们所实现算法的性能? 查找表是什么?为什么要用它? 测试用例 这里我们测试的,是一种简单的 ...

  8. OpenCV 学习笔记(13)图像转换成视频

    关键 1参数里的分辨率是图像本身的分辨率,而不是指定生成的视频分辨率.如果要修改分辨率,要么后期软件处理,要么读图的时候resize 2要正常退出,不要强制退出. 3生成的只能是avi格式. #inc ...

  9. OpenCV学习笔记(4)——图像上的算术运算

    学习图像上的算术运算,加法,减法,位运算等 1.图像加法 使用cv2.add()将两幅图像进行加法运算,也可以用numpy运算,直接img+img1.两幅图像的大小和类型必须一致,或者第二个图像可以是 ...

随机推荐

  1. MyEclipse中的Tomcat跑大项目时内存溢出:permgen space

    点击菜单栏的“Run”-"Run Configurations",在打开的窗口中点击“Arguments”选项卡. 在VM arguments中内容最下边(加上)输入:-Xms25 ...

  2. can't open file 'manage.py': [Errno 2] No such file or directory

    python Django创建数据库时can't open file 'manage.py': [Errno 2] No such file or directory 参考https://blog.c ...

  3. 关于checkbox全选与全不选的实现与遇到的问题

    HTML: <div class="outbox"> <label for="box">全选</label> <inp ...

  4. kubernetes下的Nginx加Tomcat三部曲之一:极速体验

    在生产环境中,常用到Nginx加Tomcat的部署方式,如下图: 从本章开始,我们来实战kubernetes下部署上述Nginx和Tomcat服务,并开发spring boot的web应用来验证环境, ...

  5. SQL Server 2012/2016/2017 新增函数

    /************************************************************** SQL Server 2012 新增的函数 ************** ...

  6. 比特币VS美元兑换查询网址

    sklearn实战-乳腺癌细胞数据挖掘(博客主亲自录制视频教程) https://study.163.com/course/introduction.htm?courseId=1005269003&a ...

  7. Python Machine Learning-Chapter3

    Chapter 3:A Tour of Machine Learning Classifiers Using Scikit-learn 3.1:Training a perceptron via sc ...

  8. google Guava包的ListenableFuture解析

     一. ListenableFuture是用来增强Future的功能的. 我们知道Future表示一个异步计算任务,当任务完成时可以得到计算结果.如果我们希望一旦计算完成就拿到结果展示给用户或者做另外 ...

  9. UVALive - 7639 G - Extreme XOR Sum(思维)

    题目链接 题意 给出一个序列,相邻两两异或,生成一个新序列,再相邻两两异或,直到只剩下一个元素,问最后结果为多少.m个查询,每次都有一个待查询区间. 分析 既然有多组查询,n只是1e4,那么可以考虑预 ...

  10. java中常用的包及作用

    1. java.awt:提供了绘图和图像类,主要用于编写GUI程序,包括按钮.标签等常用组件以及相应的事件类. 2. java.lang:java的语言包,是核心包,默认导入到用户程序,包中有obje ...