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 ...
随机推荐
- python基础之流程控制(2)
今天将是基础篇的最后一篇,咱们来补上最后一个内容,流程控制for循环 For 循环 一.为什么有for循环? for循环能做的事情,while循环全都可以实现,但是在某些情境下,for循环相对于whi ...
- BUAAOO第二单元代码分析
第一次作业 设计思路与感想 第一次作业是要求有捎带的电梯实现, 第一次作业是花费的时间比较长的一次,花费了很多的时间去思考架构的问题.起初是想要搞三个线程的:输入线程,调度器线程和电梯线程,想要搞一个 ...
- JavaWeb 补充(Json)
HTML DOM alert() 方法 定义和用法 alert() 方法用于显示带有一条指定消息和一个 OK 按钮的警告框. 参数 描述 message 要在 window 上弹出的对话框中显示的纯文 ...
- Horovod in Docker
https://horovod.readthedocs.io/en/stable/docker.html Step1 构建镜像 GPU $ mkdir horovod-docker-gpu $ wge ...
- 11. Grub 介绍
Grub 全称:Grand Unified Bootloader grub引导也分为两个阶段stage1阶段和stage2阶段(有些较新的grub又定义了stage1.5阶段). 一般配置文件:/bo ...
- 西门子PLC开发笔记(一):PLC介绍,西门子S1200系列接线、编程、下载和仿真
前言 西门西PLC.台达触摸屏.法兰克机床等等多年以前玩得比较多,改造机床.维修机床.给机床编程等等,没事还能扯个零件啥的,之前也没总结过,有时间就重新整理下. 本章后面以西门1200实物为例, ...
- JavaScript设计模式(一):单例模式
单例模式的定义与特点 单例(Singleton)模式的定义:指一个类只有一个实例,且该类能自行创建这个实例的一种模式.例如,Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗 ...
- Spring Cloud & Alibaba 实战 | 第十二篇: 微服务整合Sentinel的流控、熔断降级,赋能拥有降级功能的Feign新技能熔断,实现熔断降级双剑合璧(JMeter模拟测试)
目录 一. Sentinel概念 1. 什么是Sentinel? 2. Sentinel功能特性 3. Sentinel VS Hystrix 二. Docker部署Sentinel Dashboar ...
- 『动善时』JMeter基础 — 2、JMeter的安装和启动
1.安装Java环境 由于JMeter是纯Java的桌面应用程序,因此它的运行环境需要Java环境,即需要安装JDK或JRE.(也就是安装JDK环境) 步骤简要说明: 下载并安装JDK 配置环境变量 ...
- 软件篇-05-融合ORB_SLAM2和IMU闭环控制SLAM底盘运动轨迹
前面我们已经得到了当前底盘在世界坐标系中的位姿,这个位姿是通过融合ORB_SLAM2位姿和IMU积分得到的,在当前位姿已知的case下,给SLAM小车设置一个goal,我这里是通过上位机设置,然后 ...