对图像的像素进行访问,可以实现空间增强,反色,大部分图像特效系列都是基于像素操作的。图像容器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. 【POJ1179】Polygon 区间DP

    这道题是典型的环形石子归并模型,破环成链后时间复杂度为\(O(n^3)\) 不过,因为题目中所给的数字可能是负数,仅仅记录区间内合并之后的最大值并不满足动态规划的最优子结构性质.因此,还需要额外记录下 ...

  2. 多线程状态下调用SimpleDateFormat.format()抛出 ArrayIndexOutOfBoundsException 异常

    本来想在类的顶部设置一个 静态的SimpleDateFormat常量 public final static DateFormat dateFormatGMT = new SimpleDateForm ...

  3. Linux:从文件中搜索关键字并显示行数(cat,grep函数)

    假如有test1.txt的格式如下图所示: 有test2.txt的内容如下: 现需将test2.txt含有的关键字的行搜索出来并显示行数 则可以用到命令: cat test1.txt | grep - ...

  4. 7.14 Git 工具 - 凭证存储

    凭证存储 如果你使用的是 SSH 方式连接远端,并且设置了一个没有口令的密钥,这样就可以在不输入用户名和密码的情况下安全地传输数据. 然而,这对 HTTP 协议来说是不可能的 —— 每一个连接都是需要 ...

  5. 何谓SQL Server参数嗅探

    大家听到"嗅探"这个词应该会觉得跟黑客肯定有关系吧,使用工具嗅探一下参数,然后截获,脱裤o(∩_∩)o . 事实上,我觉得大家太敏感了,其实这篇文章跟数据库安全没有什么关系,实际上 ...

  6. 修改tomcat启动窗口的名称

    场景:我们在启动tomcat的时候,一般会出现tomcat窗口,默认窗口名字是tomcat,如果我们想知道这个tomcat的端口号,我们需要查看配置文件,很麻烦有木有.如果我们可以直接把端口号设置在窗 ...

  7. javascript 函数的4种调用模式

    1. 函数模式 // this 指向 window 全局对象 2. 方法模式 // this 指向调用这个方法的对象 3. 构造函数模式 // this 指向 new 新创建出来的实例 4. 上下文模 ...

  8. 转--Python语言特性

    1 Python的函数参数传递 看两个例子: a = 1 def fun(a): a = 2 fun(a) print a # 1 a = [] def fun(a): a.append(1) fun ...

  9. DOM-Document对象

     一. 整体介绍   这里介绍DOM对象中的Document对象. 何为Document对象?每个载入浏览器的HTML文档都会成为Document对象,Document对象可以帮助我们对所有的HTML ...

  10. 基于Redis主从复制读写分离架构的Session共享(Windows Server)

    搭建主从复制 1.安装软件 下载Redis-x64-3.2.100.zip:https://github.com/MicrosoftArchive/redis/releases 第一步:将Redis拷 ...