opencv——图像遍历以及像素操作
摘要
我们在图像处理时经常会用到遍历图像像素点的方式,在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表示输出图像
如何使用该函数?
- 首先我们建立一个mat型用于查表
- 然后我们调用函数 (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——图像遍历以及像素操作的更多相关文章
- Opencv中图像的遍历与像素操作
Opencv中图像的遍历与像素操作 OpenCV中表示图像的数据结构是cv::Mat,Mat对象本质上是一个由数值组成的矩阵.矩阵的每一个元素代表一个像素,对于灰度图像,像素是由8位无符号数来表示(0 ...
- OpenCV基础篇之像素操作对照度调节
程序及分析 /* * FileName : contrast.cpp * Author : xiahouzuoxin @163.com * Version : v1.0 * Date : Tue 29 ...
- Opencv图像与矩阵的操作
#include "stdafx.h" #include <cv.h> #include <cxcore.h> #include <highgui.h ...
- opencv中对图像的像素操作
1.对灰度图像的像素操作: #include<iostream> #include<opencv2/opencv.hpp> using namespace std; using ...
- 访问图像中的像素[OpenCV 笔记16]
再更一发好久没更过的OpenCV,不过其实写到这个部分对计算机视觉算法有所了解的应该可以做到用什么查什么了,所以后面可能会更的慢一点吧,既然开了新坑,还是机器学习更有研究价值吧... 图像在内存中的存 ...
- opencv中Mat类型数据操作与遍历
Mat作为opencv中一种数据类型常常用来存储图像,相对与以前的IplImgae类型来说,Mat类型省去了人工的对内存的分配与释放,转而自动分配释放.Mat Class主要包括两部个数据部分:一个是 ...
- 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 ...
- OpenCV计算机视觉学习(2)——图像算术运算 & 掩膜mask操作(数值计算,图像融合,边界填充)
在OpenCV中我们经常会遇到一个名字:Mask(掩膜).很多函数都使用到它,那么这个Mask到底是什么呢,下面我们从图像基本运算开始,一步一步学习掩膜. 1,图像算术运算 图像的算术运算有很多种,比 ...
- HTML5 canvas图像绘制方法与像素操作属性和方法
图像绘制方法 drawImage() 向画布上绘制图像.画布或视频 像素操作属性和方法 width 返回 ImageData ...
随机推荐
- ES6学习笔记(3)- 对象的功能性扩展
一.什么是对象字面量 对象字面量就是创建对象(Object)的一种简单容易理解的方式,再通俗点就是所谓的键值对的集合.举个简单的例子: let book = { name: 'JavaScript', ...
- windbg安装pykd记录
https://githomelab.ru/pykd/pykd 1.安装python (坑,分x86和x64,对应windbg版本) 2.安装pykd:'pip install pykd' 3.安 ...
- 攻防世界 reverse 进阶 1-4
1.dmd-50 suctf-2016 md5后比较,在线解md5得到: md5(md5($pass)),所以将grape再进行MD5 b781cbb29054db12f88f08c6e161c199 ...
- JavaScript中函数防抖、节流
码文不易,转载请带上本文链接,感谢~ https://www.cnblogs.com/echoyya/p/14565642.html 目录 码文不易,转载请带上本文链接,感谢~ https://www ...
- 图解双链表(Java实现)
原创公众号:bigsai 文章已收录在 全网都在关注的数据结构与算法学习仓库 前言 前面有很详细的讲过线性表(顺序表和链表),当时讲的链表以但链表为主,但实际上在实际应用中双链表的应用多一些就比如Li ...
- Python | random 模块:Python 中如何生成随机数和随机抽样?
random 是平时开发过程中常用的一个模块,该模块实现了各种分布的伪随机数生成器,以及和随机数相关的各种实用函数.基本函数 random() 在区间 [0.0, 1.0) 内均匀生成随机浮点数,是模 ...
- 简单了解Git
目录 Git命令 如何将一个新建的文件添加到Git仓库 版本控制 本地的项目丢到Gitee上 代码修改以及推送步骤 分支管理 Git命令 1.git init创建git本地仓库 2.ls 查看 ...
- ES 分页方案
ES 中,存在三种常见的分页方案: FROM, SIZE Search-After Scroll 下面将依次比较三种方案之间的 trede-off,并给出相应建议的应用场景. 常见分页,FROM, S ...
- Sql Server存储过程和游标的配合操作
本段代码主要为了记录存储过程以及游标的使用,防止以后自己忘记 知识点:1.存储过程书写 2.游标书写 3.游标循环更新记录 create proc saletargetas declare @ower ...
- Dapr | 云原生的抽象与实现
引言 Dapr 是微软主导的云原生开源项目,2019年10月首次发布,到今年2月正式发布 V1.0 版本.在不到一年半的时间内,github star 数达到了 1.2 万,超过同期的 kuberne ...