摘要

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


补充: 图像变换可以看成

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

对于点操作:

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

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


 1,数组遍历-- at<typename>(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++)
{
//通过数组遍历获取图像每个点
float b = src.at<Vec3b>(i, j)[0];
float g = src.at<Vec3b>(i, j)[1];
float r = src.at<Vec3b>(i, j)[2];
//进行点操作后赋值给空白图像dst
float 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) 

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(),当图像连通时,我们就可以把图像完全展开,看成是一行。

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

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方法。

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

很多时候,我们对图像处理时,要考虑它的邻域,比如3*3是我们常用的,这在图像滤波、去噪中最为常见,下面我们介绍如果在一次图像遍历过程中进行邻域的运算。

下面我们进行一个简单的滤波操作,滤波算子为[0 –1 0;-1 5 –1;0 –1 0]。它可以让图像变得尖锐,而边缘更加突出。核心公式即:sharp(i.j)=5*image(i,j)-image(i-1,j)-image(i+1,j)-image(i,j-1)-image(i,j+1)。

int main(int argc, char** argv)
{
Mat src,dst;
src = imread("D:/opencv练习图片/薛之谦.jpg");
imshow("Image", src);
ImgFilter2d(src, dst);
imshow("filter", dst);
waitKey(0);
return 0;
}
//构建滤波函数
void ImgFilter2d(const Mat &image, Mat& result)
{
result.create(image.size(), image.type());
int nr = image.rows;
int nc = image.cols*image.channels();
for (int i = 1; i < nr - 1; i++)
{
//用指针遍历获取当前行,上一行,下一行
const uchar* up_line = image.ptr<uchar>(i - 1);//指向上一行
const uchar* mid_line = image.ptr<uchar>(i);//当前行
const uchar* down_line = image.ptr<uchar>(i + 1);//下一行
uchar* cur_line = result.ptr<uchar>(i);//创建结果图像指针
for (int j = 1; j < nc - 1; j++)
{
//核心公式
cur_line[j] = saturate_cast<uchar>(5 * mid_line[j] - mid_line[j - 1] - mid_line[j + 1] -up_line[j] - down_line[j]);
}
}
// 把图像边缘像素设置为0
result.row(0).setTo(Scalar(0));
result.row(result.rows - 1).setTo(Scalar(0));
result.col(0).setTo(Scalar(0));
result.col(result.cols - 1).setTo(Scalar(0));
}

 

opencv——图像遍历以及像素操作的更多相关文章

  1. Opencv中图像的遍历与像素操作

    Opencv中图像的遍历与像素操作 OpenCV中表示图像的数据结构是cv::Mat,Mat对象本质上是一个由数值组成的矩阵.矩阵的每一个元素代表一个像素,对于灰度图像,像素是由8位无符号数来表示(0 ...

  2. OpenCV基础篇之像素操作对照度调节

    程序及分析 /* * FileName : contrast.cpp * Author : xiahouzuoxin @163.com * Version : v1.0 * Date : Tue 29 ...

  3. Opencv图像与矩阵的操作

    #include "stdafx.h" #include <cv.h> #include <cxcore.h> #include <highgui.h ...

  4. opencv中对图像的像素操作

    1.对灰度图像的像素操作: #include<iostream> #include<opencv2/opencv.hpp> using namespace std; using ...

  5. 访问图像中的像素[OpenCV 笔记16]

    再更一发好久没更过的OpenCV,不过其实写到这个部分对计算机视觉算法有所了解的应该可以做到用什么查什么了,所以后面可能会更的慢一点吧,既然开了新坑,还是机器学习更有研究价值吧... 图像在内存中的存 ...

  6. opencv中Mat类型数据操作与遍历

    Mat作为opencv中一种数据类型常常用来存储图像,相对与以前的IplImgae类型来说,Mat类型省去了人工的对内存的分配与释放,转而自动分配释放.Mat Class主要包括两部个数据部分:一个是 ...

  7. opencv —— src.at<Vec3b>(i, j)[0]、src.at<uchar>(i, j)、src.ptr<uchar>(i) 访问图像的单个像素

    动态地址访问像素:src.at<Vec3b>(i, j)[0].src.at<uchar>(i, j)  int b = src.at<Vec3b>(i, j)[0 ...

  8. OpenCV计算机视觉学习(2)——图像算术运算 & 掩膜mask操作(数值计算,图像融合,边界填充)

    在OpenCV中我们经常会遇到一个名字:Mask(掩膜).很多函数都使用到它,那么这个Mask到底是什么呢,下面我们从图像基本运算开始,一步一步学习掩膜. 1,图像算术运算 图像的算术运算有很多种,比 ...

  9. HTML5 canvas图像绘制方法与像素操作属性和方法

    图像绘制方法 drawImage()        向画布上绘制图像.画布或视频 像素操作属性和方法 width                                返回 ImageData ...

随机推荐

  1. 【linux】驱动-2-内核模块

    目录 前言 2. 内核模块 2.1 内核模块概念 2.1.1 内核 2.1.2 内核模块机制的引入 2.2 内核模块 2.2.1 内核模块参考例程 2.2.2 内核模块命令 2.2.3 系统自动加载模 ...

  2. springBoot高级:自动配置分析,事件监听,启动流程分析,监控,部署

    知识点梳理 课堂讲义 02-SpringBoot自动配置-@Conditional使用 Condition是Spring4.0后引入的条件化配置接口,通过实现Condition接口可以完成有条件的加载 ...

  3. 微信小程序 | app.json配置属性

    app.json 文件用来对微信小程序进行全局配置,决定页面文件的路径.窗口表现.设置网络超时时间.设置多 tab 等. widows: 用于设置小程序的状态栏.导航条.标题.窗口背景色. navig ...

  4. 2020 OO 第一单元总结 表达式求导

    title: BUAA-OO 第一单元总结 date: 2020-03-19 20:53:41 tags: OO categories: 学习 OO第一单元通过三次递进式的作业让我们实现表达式求导,在 ...

  5. 记一次 .NET游戏站程序的 CPU 爆高分析

    一:背景 1. 讲故事 上个月有个老朋友找到我,说他的站点晚高峰 CPU 会突然爆高,发了两份 dump 文件过来,如下图: 又是经典的 CPU 爆高问题,到目前为止,对这种我还是有一些经验可循的. ...

  6. 自动化kolla-ansible部署ubuntu20.04+openstack-victoria之vmware设置-02

    自动化kolla-ansible部署ubuntu20.04+openstack-victoria之vmware设置-02 欢迎加QQ群:1026880196  进行讨论 1. vmwae版本 2. 网 ...

  7. electron项目踩坑--A JavaScript error occurred in the main process:document is not defined

    前言 记录electron-vue项目开发中遇到的一个错误,运行时报错如图: 控制台报错如下: ReferenceError: document is not defined at Object.&l ...

  8. linux下Mysql 8.0.19 编译安装

    1 前言 linux下安装MySQL的方式有很多种,包括以仓库的方式安装(yum,apt,zypper),以包的方式安装(rpm,deb),以docker方式安装,从压缩包解压安装,从源码编译安装,这 ...

  9. Day05_19_方法回顾

    方法回顾 * 静态方法 和 非静态方法 1.静态方法属于类所有,类实例化前即可使用: 2.非静态方法可以访问类中的任何成员,静态方法只能访问类中的静态成员: 3.因为静态方法会在类加载的时候就进行初始 ...

  10. 序列化 pickle模块

    1. pickle 简介 2. pickle 核心函数 3. pickle 高级 -- 复杂对象 1. 持久化与 pickle 简介 1.1 什么是持久化? 持久化的基本思想很简单.假定有一个 Pyt ...