对图像的像素进行访问,可以实现空间增强,反色,大部分图像特效系列都是基于像素操作的。图像容器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. 从C,C++,JAVA和C#来看String库的发展(二)---JAVA和C#篇

    http://www.cnblogs.com/wenjiang/p/3272859.html 终于要进入面向对象的世界了,虽然C++也是面向对象,但是它的面向对象程度并不高,因为考虑到要兼容C语言的移 ...

  2. java -server 和 -client 的不同,及 java -server 时抛错原因

    在dos窗口运行:java -server Error: missing `server' JVM at `C:\Program Files\Java\jre7\bin\server\jvm.dll' ...

  3. C语言实现KMP模式匹配算法

    next: /*! * Description: * author scictor <scictor@gmail.com> * date 2018/7/4 */ #include < ...

  4. xargs与exec详解

    一.场景 这个命令是错误的 1 find ./ -perm +700 |ls -l 这样才是正确的 1 find ./ -perm +700 |xargs ls -l  二.用法 1 2 3 4 5 ...

  5. Django 日志配置按日期滚动

    记录下Django关于日期的配置,以及如何根据日期滚动切割日志的问题. 配置的源码在githun上 https://github.com/blackmatrix7/django-examples/tr ...

  6. scrapy xpath、正则表达式、css选择器

    scrapy xpath XPath即为XML路径语言,它是一种用来确定XML(标准通用标记语言的子集)文档中某部分位置的语言.XPath基于XML的树状结构,提供在数据结构树中找寻节点的能力. 学习 ...

  7. Scala进阶之路-并发编程模型Akka入门篇

    Scala进阶之路-并发编程模型Akka入门篇 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.Akka Actor介绍 1>.Akka介绍 写并发程序很难.程序员不得不处 ...

  8. 约束Constraints--主键约束、外键约束、唯一约束、检查约束、默认约束、NOT NULL约束、列约束与表约束、创建约束、删除约束

    约束   Including Constraints 以下内容转自:https://www.cnblogs.com/wcl2017/p/7043939.html和http://blog.csdn.ne ...

  9. .Net MVC个人笔记

    关于转向的问题,目前知道的是Response.Redirect 和 location.href 我现在有两个controller,Home和Test <h2>this is Home< ...

  10. java 中如何声明线程安全的集合 set, map 和list【转】

    线程安全的集合 引用自 http://blog.sina.com.cn/s/blog_508938e10102v1ig.html //make thread-safe list List MyStrL ...