摘要

我们在图像处理时经常会用到遍历图像像素点的方式,在OpenCV中一般有四种图像遍历的方式,在这里我们通过像素变换的点操作来实现对图像亮度和对比度的调整。

数据格式千万不要搞错:
uchar对应的是CV_8U,char对应的是CV_8S,int对应的是CV_32S,float对应的是CV_32F,double对应的是CV_64F。


补充: 图像变换可以看成

  • 像素变换——点操作
  • 邻域变换——区域操作(卷积,特征提取,梯度计算等)

对于点操作:

q(i,j)=αf(i,j)+β

其中f(i,j)是输入点像素值,q(i,j)是输出点像素值。


 1,数组遍历-- at<typename>(i,j)

说明:就是把图像看成二维矩阵,at(i,j)索引坐标位置,单通道直接得到坐标位置对应的像素值,三通道就这个位置代表了像素值的一维数组;

Mat类提供了一个at的方法用于取得图像上的点,它是一个模板函数,可以取到任何类型的图像上的点。这里选用参数α=1.5,β=0.5来提高图像亮度。

int main(int argc, char** argv)
{    Mat src;src = imread("D:/opencv练习图片/薛之谦.jpg");imshow("Image", src);//创建一个和原图一致的空白图像Mat dst = Mat::zeros(src.size(), src.type());for (int i = 0; i < src.rows; i++){for (int j = 0; j < src.cols; j++){if (src.channels() == 1) //单通道遍历{dst.at<uchar>(i, j) = src.at<uchar>(i, j) + 100; }else if (src.channels() ==3)//三通道遍历{//通过数组遍历获取图像每个点float b = src.at<Vec3b>(i, j)[0];float g = src.at<Vec3b>(i, j)[1];float r = src.at<Vec3b>(i, j)[2];//进行点操作后赋值给空白图像dstfloat alpha = 1.5;float beta = 0.5;dst.at<Vec3b>(i, j)[0] = saturate_cast<uchar>(b*alpha + beta);dst.at<Vec3b>(i, j)[1] = saturate_cast<uchar>(g*alpha + beta);dst.at<Vec3b>(i, j)[2] = saturate_cast<uchar>(r*alpha + beta);}}}imshow("点操作", dst);waitKey(0);return 0;
} 

saturate_cast<uchar>是溢出保护,在进行像素的乘法后很容易造成像素点的值超出0-255的范围,因此使用saturate_cast<uchar>确保像素值始终在0-255的范围内。

2,指针遍历法

OpenCV中cv::Mat类提供了成员函数ptr得到图像任意行的首地址。ptr函数是一个模板函数,如:src.ptr<uchar>(i) 

说明:ptr指针尤其固定格式,就是先把图像看成(src.rows,1)的图像,ptr获取每个位置的地址,地址位置隐藏了列的数据,由于列表名就是列表的地址,所以ptr获取的地址就是此行中列这样一维数据的列表名称。这样通过下标就可以获取像素值

int main(int argc, char** argv)
{    Mat src;src = imread("D:/opencv练习图片/薛之谦.jpg");imshow("Image", src);//创建一个和原图一致的空白图像Mat dst = Mat::zeros(src.size(), src.type());int width ;//判断图像是否连续if (src.isContinuous() && dst.isContinuous()){// 将3通道转换为1通道    width = src.cols * src.channels();}for (int i = 0; i < src.rows; i++){// 获取第i行的首地址const uchar* src_rows = src.ptr<uchar>(i);uchar* dst_ptr = dst.ptr<uchar>(i);//像素点操作处理for (int j = 0; j < width; j++){dst_ptr[j] = saturate_cast<uchar>(src_rows[j] *1.5 + 0.5);dst_ptr[j + 1] = saturate_cast<uchar>(src_rows[j + 1] *1.5 + 0.5);dst_ptr[j + 2] = saturate_cast<uchar>(src_rows[j + 2] *1.5 + 0.5);}}imshow("点操作", dst);waitKey(0);return 0;
}

程序中将三通道的数据转换为1通道,是建立在每一行数据元素之间在内存里是连续存储的。但在opencv中由于的存储机制问题,行与行之间可能有空白单元,因此Mat提供了一个检测图像是否连续的函数isContinuous(),当图像连通时,我们就可以把图像完全展开,看成是一行。


针对at和ptr有很多人容易理解at,却理解不了ptr,下面讲一个用at生成ptr模式的解析例子:

说明:这是为了对比at和ptr而增加的,主要是获取at(i,0)位置处的地址,将其看成数值名称,通过下标索引像素值,和ptr原理一样,只是获取地址的方式不一样(数组名)

1️⃣遍历灰度图像像素方法:(采用at方法,使用ptr模式)

    for (int i = 0; i < src.rows; i++){//将灰度图片看成(src.rows,1)维度的二维矩阵,获取(i,0)数据的地址uchar* src_rows_ptr = &(src.at<uchar>(i, 0));uchar* dst_rows_ptr = &(dst.at<uchar>(i, 0));for (int j = 0; j < src.cols; j++){//将(i,0)数据的地址下的内容看成是一维数组,(i,0)数据的地址是一维数组的名字dst_rows_ptr[j] = src_rows_ptr[j] + 100;}}

2️⃣遍历彩色图像像素方法:(采用at方法,使用ptr模式)

for (int i = 0; i < src.rows; i++){//将彩色图片看成(src.rows,1)维度的二维矩阵,获取(i,0)数据的地址Vec3b* src_rows_ptr = &(src.at<Vec3b>(i, 0));Vec3b* dst1_rows_ptr = &(dst1.at<Vec3b>(i, 0));for (int j = 0; j < src.cols; j++){//将(i,0)数据的地址下的内容看成是二维数组,(i,0)数据的地址是二维数组的名字dst1_rows_ptr[j][0] = src_rows_ptr[j](0) + 100;dst1_rows_ptr[j][1] = src_rows_ptr[j](1) + 100;dst1_rows_ptr[j][2] = src_rows_ptr[j](2) + 100;}}

综上所述:使用ptr指针效率非常高,大家普遍使用的是at和ptr方法;使用的时候,一定要规范格式;其中:at<类型>(i,j)ptr<类型>(i)


3、迭代器遍历

迭代器是专门用于遍历数据集合的一种非常重要的特殊的类,用其遍历隐藏了在给定集合上元素迭代的具体实现方式。迭代器方法是一种更安全的用来遍历图像的方式,首先获取到数据图像的矩阵起始,再通过递增迭代实现移动数据指针。

1、迭代器Matlterator_   Matlterator_是Mat数据操作的迭代器,:begin()表示指向Mat数据的起始迭代器,:end()表示指向Mat数据的终止迭代器。

2、迭代器Mat_              OpenCV定义了一个Mat的模板子类为Mat_,它重载了operator()让我们可以更方便的取图像上的点。

int main(int argc, char** argv)
{        Mat src;src = imread("D:/opencv练习图片/薛之谦.jpg");imshow("Image", src);// 初始化图像迭代器 Mat_<Vec3b>::iterator it = src.begin<Vec3b>();Mat_<Vec3b>::iterator itend = src.end<Vec3b>();while (it != itend){//像素点操作(*it)[0] = saturate_cast<uchar>((*it)[0]*1.5+0.5);(*it)[1] = saturate_cast<uchar>((*it)[1] * 1.5 + 0.5);(*it)[2] = saturate_cast<uchar>((*it)[2] * 1.5 + 0.5);it++;}imshow("点操作", src);waitKey(0);return 0;
}

经测试,得到与数组遍历一样的效果。

4、核心函数LUT

LUT(LOOK -UP-TABLE)查找表。简言之:在一幅图像中,假如我们想将图像某一灰度值换成其他灰度值,用LUT就很好用。这样可以起到突出图像的有用信息,增强图像的光对比度的作用对某图像中的像素值进行替换。。

在图像处理中,对于一个给定的值,将其替换成其他的值是一个很常见的操作,OpenCV 提供里一个函数直接实现该操作LUT函数

函数 API

void LUT(InputArray src, InputArray lut, OutputArray dst);
//src表示的是输入图像(可以是单通道也可是3通道)
//lut表示查找表(查找表也可以是单通道,也可以是3通道;
//...如果输入图像为单通道,那查找表必须为单通道;
//...若输入图像为3通道,查找表可以为单通道,也可以为3通道;
//...若为单通道则表示对图像3个通道都应用这个表,若为3通道则分别应用 )
//dst表示输出图像

如何使用该函数?

  1. 首先我们建立一个mat型用于查表
  2. 然后我们调用函数 (I 是输入 J 是输出):LUT(I, lookUpTable, J);

LUT函数的作用:

(1)改变图像中像素灰度值

通过构建查找表,图片0-100灰度的像素灰度就变成0,101-200的变成100,201-255的就变成255。

int main(int argc, char** argv)
{        Mat src,dst1,dst3;src = imread("D:/opencv练习图片/薛之谦.jpg");imshow("Image", src);//查找表,数组的下标对应图片里面的灰度值//例如lutData[20]=0;表示灰度为20的像素其对应的值0.    uchar lutData[256];for (int i = 0; i < 256; i++){if (i <= 100)lutData[i] = 0;if (i > 100 && i <= 200)lutData[i] = 100;if (i > 200)lutData[i] = 255;}Mat lut(1, 256, CV_8UC1, lutData);LUT(src, lut, dst1);    imshow("LUC", dst1);    waitKey(0);return 0;
} 

(2)颜色空间缩减

如果矩阵元素存储的是单通道像素,使用uchar (无符号字符,即0到255之间取值的数)那么像素可有256个不同值。但若是三通道图像,这种存储格式的颜色数就是256*256*256个(有一千六百多万种)。用如此之多的颜色可能会对我们的算法性能造成严重影响。其实有时候,仅用这些颜色的一小部分,就足以达到同样效果。

这种情况下,常用的一种方法是 颜色空间缩减 。其做法是:将现有颜色空间值除以某个输入值,以获得较少的颜色数。例如,颜色值0-9的取为0,10-19的取为10,以此类推。就把256个不同值划分为26个,大大减少运算时间。

uchar 类型的值除以 int 值,结果仍是 char 。因为结果是char类型的,所以求出来小数也要向下取整。利用这一点,刚才提到在 uchar 定义域中进行的颜色缩减运算就可以表达为下列形式:

这样的话,简单的颜色空间缩减算法就可由下面两步组成:

一、遍历图像矩阵的每一个像素

二、对像素应用上述公式。

下面将图像压缩级设置为20(即0-19变为0,20-39变为20…)

int main(int argc, char** argv)
{        Mat src,dst;src = imread("D:/opencv练习图片/薛之谦.jpg");imshow("Image", src);uchar table[256];Mat lut(1, 256, CV_8U);//创建查找表int divideWith = 20; //压缩级 20灰度为1级for (int i = 0; i < 256; ++i){table[i] = divideWith * (i / divideWith);//颜色缩减运算}uchar *p = lut.data;for (int i = 0; i < 256; ++i){p[i] = table[i];//这样就实现了利用查找表table的方法来替换源图像中的数据,//这对图像就不是加减乘除这种计算了,而全部是直接去查询表中找对应的值然后再替换。}    LUT(src, lut, dst);imshow("LUT", dst);        waitKey(0);return 0;
}


效率探讨

一般图像规模比较大的话,图像的遍历是一项相当耗时的工作,因此为提高效率,以下几点值得我们注意:

  • 对于可提前计算的变量应避免写在循环体内;如
int cols=img.cols*img.channels();
for(int i=0;i<cols;i++)   //而不是
// for(int i=p;i<img.cols*img.channels();i++)

  • 在以上四种图像遍历方法中,从效率来看使用 OpenCV 内置函数LUT可以获得最快的速度,这是因为OpenCV库可以通过英特尔线程架构启用多线程。其次,指针遍历最快,迭代器遍历次之,at方法遍历最慢。一般情况下,我们只有在对任意位置的像素进行读写时才考虑at方法。

 最后顺便提一下图像的邻域操作

opencv——图像遍历以及像素操作相关推荐

  1. 【OpenCV 4开发详解】两图像间的像素操作

    本文首发于"小白学视觉"微信公众号,欢迎关注公众号 本文作者为小白,版权归人民邮电出版社发行所有,禁止转载,侵权必究! 经过几个月的努力,小白终于完成了市面上第一本OpenCV 4 ...

  2. opencv图像遍历方法速度对比

    原文:http://blog.csdn.net/sanwandoujiang/article/details/22087973 //#include "stdafx.h" #inc ...

  3. opencv图像操作:读取,裁剪,保存,缩放,遍历和读取文件夹图片

    opencv.cpp文件 #include <iostream> #include "opencv.hpp" #include "opencv_test.h& ...

  4. opencv像素基本操作及图像遍历at

    在实现一个小的程序时需要自行将像素点值写入,但是忘记灰度图像如何写入了,找到一篇介绍较好的文章,转过来 转自:http://blog.csdn.net/xuhang0910/article/detai ...

  5. android都图片mat_计算机视觉 OpenCV Android | Mat像素操作(图像像素的读写、均值方差、算术、逻辑等运算、权重叠加、归一化等操作)...

    本文目录 1. 像素读写 2. 图像通道与均值方差计算 3. 算术操作与调整图像的亮度和对比度 4. 基于权重的图像叠加 5. Mat的其他各种像素操作 1. 像素读写 Mat作为图像容器,其数据部分 ...

  6. opencv高效遍历图像

    初次接触OpenCV的开发者,必须过的第一道坎就是学会如何遍历访问Mat对象中每个像素,实现像素级别的图像操作,这个是最级别的编程技能,但是不同的像素遍历方法效率有云泥之别,相差特别大,甚至可能成为算 ...

  7. 遍历opencv中的mat像素的几种方法和概念

    今天在看矩形滤波的时候忽然脑子短路,把一些概念全弄混了,现总结一下,以便下次再混的时候可以参考确认下,自己的理解,有错的地方还请指正. 首先,在Opencv2中基本上都是用的Mat来表示图像了,C++ ...

  8. OpenCV Mat数据类型像素操作

    转自:http://blog.csdn.net/skeeee/article/details/13297457 OpenCV图像像素操作及效率分析 在计算机视觉应用中,对于图像内容的读取分析是第一步, ...

  9. OpenCV——高效遍历图像(C++版本)

    OpenCV遍历图像像素是很常见的事情,比较下面的三种遍历方式哪个是最高效的. 前言 在OpenCV C++中Mat对象的内存管理由OpenCV框架自动负责内存分配与回收,基于智能指针实现内存管理.M ...

最新文章

  1. jQuery UI 之 LigerUI 快速入门
  2. 运维想吃透监控系统,就这一篇足够了
  3. 二叉树的最小高度,最大高度(深度)和宽度
  4. zzuli-1726:迷宫(语文功底题。。。)
  5. Java学习笔记53(网络编程:TCP协议案例)
  6. Android 5.1 API 22 所有sdk文件下载地址
  7. filebeat 解析日志 并发送到Elasticsearch
  8. 为什么我要构建这个脚手架
  9. 2021-2025年中国串级太阳能逆变器行业市场供需与战略研究报告
  10. 常用20个正则表达式
  11. Java:(游戏:豆机)
  12. 信息安全领域四大顶会
  13. 面试官问你什么是QPS?
  14. 记-----租房七大注意事项
  15. python句柄无效_作为Windows服务运行的Python:OSError:[WinError 6]句柄无效
  16. A. Neko Finds Grapes-奇偶的性质及运用-Codeforces Round #554 (Div. 2)
  17. 全球与中国婴儿零食市场深度研究分析报告
  18. linux 根目录变为只读了,如何在 Web 服务器文档根目录上设置只读文件权限 | Linux 中国...
  19. ean13解码 matlab,ean13: EAN13 Code Generator
  20. C++ 20 std::chrono 库使用 | std::chrono::year_month_day |std::chrono::hh_mm_ss 使用

热门文章

  1. SVG中path标签的简单使用
  2. 中兴校园招聘 他人面经整理
  3. 计算机如何设置桌面文件筐,电脑桌面图标有文件筐怎么?
  4. db2数据库的启动和关闭
  5. mysql日期格式化比较_MYSQL DATE_FORMAT() 函数时间大小比较
  6. word文档保存修订信息,打开时默认不显示标记信息
  7. 华为服务器 修复raid,服务器重做raid
  8. js 正则不少于六位
  9. 分享一款AE变化文字等间距脚本Monospacer
  10. 新手入驻eBay需要哪些条件?eBay防关联同样很重要