本文为系列文章的第2篇,主要讲解对图像的像素的操作方法。

2.1存取像素值

为了存取矩阵元素,需要指定元素所在的行和列,程序会返回相应的元素。单通道图像返回单个数值,多通道图像,返回的则是一组向量(Vector)。

我们通过分析一段代码来学习这一节的知识点:

void salt(cv::Mat &image,int n){
for(int k = ;k<n;k++){
int i = rand()%image.cols;
int j = rand()%image.rows;
  if(image.channel() == 1)//灰度图
  image.at<cv::Vec3b>(j,i)=255;
if(image.channels() == ){//彩色
image.at<cv::Vec3b>(j,i)[] = ;//设置为白色 Vec3b为一个由三个8位数组成的向量,是一种数据类型,即由三个unsigned char 组成的向量(三元素向量类型)
image.at<cv::Vec3b>(j,i)[] = ;//彩色图像每个像素由三部分组成:红,绿,蓝通道
image.at<cv::Vec3b>(j.i)[] = ;
}
}
}

这里创建了一个函数,他的第一个参数书一张输入图片,该函数会修改此函数,为了达到这个目的,我们需要传使用的参数传递方式。函数的第二个参数是我们想要替换成白的像素点个数。

Mat有很多函数可以获取其初始化的图像的的属性,公有成员变量cols和rows给出了图像的宽和高。成员函数at(int x,int y)可以用来存放图像元素,但需要在编译期知道图像的数据类型,因为cv::Mat可以存放任意数据类型的元素。所以调用at时需要使用以下方式:

image.at<uchar>(j,i) = ;

调用时需要先打开一个图像;

cv::Mat  image = cv::imread("liufeng.jpg");
salt(image,);
cv::namedWindow("Image");
cv::imshow("image",image);

我们再来看一个实际应用的函数:

void colorReduce(cv::Mat &image, int div = ){
int nl = image.rows;//行数
int nc = image.cols*image.channels();//每行的像素个数
for(int j=;j<nl;j++){
uchar* data = image.ptr<uchar>(j);//cv::Mat提供ptr函数可以得到图像任意行的首地址 等效的指针运算:*data++=*data/div*div + div2
for(int i=;i<nc;i++){//对每行的元素进行数据处理
data[i]= data[i]/div*div+div/;//公式
}
}
}

解释一下原理:在一个彩色图像中,图像数据缓冲区中的前三个自己对应图像左上角像素的三个通道值,接下来的三个字节对应第一行的第二个元素,以此类推。opencv默认使用BGR的通道顺序,第一个通道通常是蓝色。一个WxH大小的图像需要WxHx3个uchar构成的内存块。但是,一般会为了提高效率而在每行的末尾添加一些额外像素,这是处理的的特性决定的,如果行的长度是4或8的倍数,芯片就会更加高效的处理图像,因为他们本身的处理器的位数就是4的倍数(8,16,32,64)。

关于公式的解释:注意到data[i]是整数(假设原来是120),x/div得到的是商(1),余数被舍弃,再×div得到的是64,再加上div/2就是64+32=98。推广点我们可以想到64~127之间的数经过上述运算得到的都是98,其他区间的数可以依此类推。这样就起到了压缩色彩空间的作用。

但是上面这个方法的缺点为有两个for循环,效率较低,我们可以改进一下:

void colorReduce(cv::Mat & image,int div =){
int nl = image.rows;
int nc= iamge.cols*image.channels();//每行的像素个数
if(image.isContinuous()){//,没有额外的填补像素
nc = nc*nl;
nl = ;}
}
for(int j=;j<nl;j++){//对于连续图像,本循环只执行一次
uchar*data = image.ptr<uchar>(j);
for(int i=;i<nc;i++){
data[i]=data[i]/div*div+div/;}
}

isContinuous函数可以用来判断这幅图像是否进行了填补,为真则没有进行填补。在没有进行填补时我们就将图像视为长度为WxH的一维数组。

reshape函数可以用来转换数组的维数。

if(image.isContinuous){
image.reshape(,//通道数 reshape不需要内存拷贝或者重新分配就能改变矩阵的维度。连个参数分别为新的通道数和新的行数
image.cols*image.rows);//行数
}
int nl = image.rows;
int nc = image.cols*image.channels;

2.2使用迭代器遍历图像

迭代器是一种特殊的类,他专门用来遍历集合中的各个元素,同时隐藏了在给定的集合上元素迭代的具体实现方式。标准模板库(STL)为每个容器类提供了迭代器。下划线意味着cv::Mat Iterator_是一个模板类。这样做的原因是因为通过迭代器来存取图像的元素,就必须在编译期知道图像元素的数据类型。

创建迭代器

cv::Mat  Iterator_<cv::Vec3b> it;

另一种方法是使用定义在Mat_内部的迭代器类型:

cv::Mat_<cv::Vec3b>::iterator  it;

使用第二种方法可以通过常规的begin和end这两个迭代器方法来遍历所有像素:

void colorReduce(cv::Mat &image,int div = ){
cv::Mat_<cv::Vec3b>::iterator it =image.begin<cv::Vec3b>();//得到起始位置的迭代器
cv::Mat_<cv::Vec3b>::iterator itend =image.begin<cv::Vec3b>();//终止位置迭代器
for(;it!= itend;++it){//处理每个像素
(*it)[] = (*it)[]/div[]*div+div/;
(*it)[] = (*it)[]/div[]*div+div/;
(*it)[] = (*it)[]/div[]*div+div/;
}
}

因为我们处理的是彩色图像,所以迭代器返回的是一个向量cv::Vec3b。每个颜色分量可以通过操作符[]得到。

需要的话,对迭代器也可以采取代数运算,如需要把矩阵的第二行作为起始点时可以利用image.begin<cv::Vec3b>()+image.rows来初始化迭代器,使用运算符“*”来读或写元素,读操作:element = *it;写操作:*it = element.

2.3遍历图像与领域操作

在图像处理中,通过当期位置的相邻像素计算新的像素值是很常见的操作(对比等等),当相邻包含图像的前几行和下几行是,你就需要同时扫描图像的若干行。

const  uchar* previous = image.ptr<const uchar>(j-);
const uchar* current = image.ptr<const uchar>(j);
const uchar* next = image.ptr<const uchar>(j+);
uchar* output = result.ptr<uchar>(j);//输出行

2.4简单的图像运算

图像是矩阵,矩阵可以代数运算,那么图像当然也可以诺。所有的二元算术函数工作方式都是一样的,他接受两个输入变量和一个输出变量,在一些情况下,还需要指定权重作为运算中的标量因子。每种函数都有几个不同的形式:

//c[i]=a[i]+b[i];
cv::add(imageA,imageB,resultC);
//c[i]=a[i]+b[i];
cv::add(imageA.cv::Scalar(k),resultC);
//c[i]=k1*a[i]+k2*b[i]+k3;
cv::addWeighted(imageA,k1,imageB,k2,k3,resultC);
//c[i]=l*a[i]+b[i]
cv::sacleAdd(imageA,k,inageB,resultC);

数学运算时常常可能会发生结果超出范围(0~255)的情况发生,这时候我们就可以用cv::saturate_cast来保证结果在范围内。参与运算的图像必须相同的大小和类型,输出图像如果格式不符,那么他会重新分配。

是不是上面的运算特别的复杂,没事,opencv为你做了简化,你可以直接的进行代数运算:

result= 0.7*image1+0.9*image2;

2.5定义感兴趣区域

由于操作运算需要图片具有相同的尺寸,所以我们需要在运算之前定义感兴趣区域(ROI)。需要注意的是ROI和他的父元素指向同一块内存缓冲区。

cv::Mat imageROI;
imageROI = image(cv::Rect(,,liufeng.cols,liufeng,rows));
cv::addWeighted(imageaROI,1.0,logo,0.3,.,imageROI);

两张图片合成后可能会出现不协调的情况出现,所以可以将插入出的像素设置为待插入图像的像素值效果会好一点。

imageROI =image(cv::Rect(,,liufeng.cols,liufeng.rows));//定义ROI
cv::Mat mask= cv::imread("liufeng.bmp",);//加载掩模
liufeng.copyTo(imageROI,mask);//通过掩模拷贝ROI

利用cv:: rect定义ROI,指定矩形的左上角坐标(前两个参数)和矩形的长宽(后两个参数)就可以定义一个矩形区域了。

另一种方法是使用cv::range来指定感兴趣的行或列的范围:

cv::Mat imageROT= image(cv::Range(,+liufeng.rows),cv::range(,+liufeng.cols));

如果想创建原始图像特定行或列的ROI,则可以

cv::Mat imageROI = image.rowRange(start,end);
cv::Mat imageROI = image.colRange(start,end);

opencv基础到进阶(2)的更多相关文章

  1. opencv基础到进阶(1)

    Opencv是一个用户基础非常多的视觉开发库,可以用来实现人脸识别等功能,由于涉及到大量的调用与计算,所以对硬件的条件要求很高,并且还需要时时刻刻注意内存溢出这个问题,怎么样?很刺激吧. 从这篇文章开 ...

  2. jQuery基础---Ajax进阶

    原文:jQuery基础---Ajax进阶 内容提纲: 1.加载请求 2.错误处理 3.请求全局事件 4.JSON 和 JSONP 5.jqXHR 对象 发文不易,转载请注明出处! 在 Ajax 基础一 ...

  3. python基础——面向对象进阶下

    python基础--面向对象进阶下 1 __setitem__,__getitem,__delitem__ 把对象操作属性模拟成字典的格式 想对比__getattr__(), __setattr__( ...

  4. python基础——面向对象进阶

    python基础--面向对象进阶 1.isinstance(obj,cls)和issubclass(sub,super) isinstance(obj,cls)检查是否obj是否是类 cls 的对象 ...

  5. Python基础与进阶

    1 Python基础与进阶 欢迎来到Python世界 搭建编程环境 变量 | 字符串 | 注释 | 错误消除 他只用一张图,就把Python中的列表拿下了! 使用 If 语句进行条件测试 使用字典更准 ...

  6. 什么是图像 -- opencv基础

    opencv基础篇--到底什么是图像 什么是图像?英语中有两个单词来形容图像,一个是picture,一个是image.这两者虽然是形容同一个东西,但却又有着区别.picture代表实而有物的真实图像: ...

  7. OpenCV基础篇之读取显示图片

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

  8. Dart编程语言从基础到进阶1

    Dart编程语言从基础到进阶Dart的语言的发展史以及Dart能做什么未来发展怎么样等等问题我们在这里是不讨论的.我相信既然选择了来学习它,那你内心基本已经认可了它,所以我们废话不多说直接进入主题. ...

  9. Git基础及进阶-系统总结

    Git基础及进阶-系统总结 by 小强 2019-07-01 考虑到入职后不仅需要熟练掌握git的基本使用,在企业实际操作中还涉及一些进阶指令.作为一个程序员,熟练使用工具是一项基本技能,也是程序员的 ...

随机推荐

  1. C++ fstream 详解

    最近在写哈夫曼压缩,遇到了一个比较让人头疼的问题,那就是对文件的读写操作,尤其是以二进制的形式来读写,无奈C++Primer第五版上写的并不详细,很多让人困惑的地方没有涉及或者没有讲清楚.于是这几天我 ...

  2. iterable

    iterable 阅读: 148111 遍历Array可以采用下标循环,遍历Map和Set就无法使用下标.为了统一集合类型,ES6标准引入了新的iterable类型,Array.Map和Set都属于i ...

  3. 你绝对想不到R文件找不到(cannot resolve symbol R)的原因

    你绝对想不到R文件找不到(cannot resolve symbol R)的原因 最近在项目开发中 Android Studio 的 R 文件突然找不到了.IDE 中出现了以下提示 cannot re ...

  4. FarPoint.Win.Spread 自定义表头

    最近C/S项目中用到FarPoint.Win.Spread,想在表头加个全选的checkbox,实现效果如图:   列的设置大家都清楚,直接可视化视图中设置该列CellType为CheckBox类型即 ...

  5. aos.js超赞页面滚动元素动画jQuery动画库

    插件描述:aos.js 是一款效果超赞的页面滚动元素动画jQuery动画库插件.该动画库可以在页面滚动时提供28种不同的元素动画效果,以及多种easing效果.在页面往回滚动时,元素会恢复到原来的状态 ...

  6. JS的内置对象以及JQuery中的部分内容

     [js中的数组]              1  数组的概念:可以再内存中连续存储的多个有序元素的结构                元素的顺序:称为下标,通过下标查找对应元素.           ...

  7. IT职场经纬 |阿里web前端面试考题,你能答出来几个?

    有很多小伙伴们特别关心面试Web前端开发工程师时,面试官都会问哪些问题.今天小卓把收集来的"阿里Web前端开发面试题"整理贴出来分享给大家伙看看,赶紧收藏起来做准备吧~~ 一.CS ...

  8. rsync远程同步

    一.概念 Rsync是一个开源的快速备份工具,可以在不同主机之间镜像同步整个目录树,支持增量备份,保持连接和权限,且采用优化的同步算法,传输前执行压缩,因此非常适用于异地备份,镜像服务器等应用.rsy ...

  9. C语言学习第七章

    今天开始学习指针,指针在C语言中具有很重要的地位,按照老师所说,学C学不好指针跟没学一样,可见指针在C语言中的重要地位.废话不多说,首先我们先要知道什么是指针. 指针:指针是一个变量,它存储另一个对象 ...

  10. Python魔法方法总结及注意事项

    1.何为魔法方法: Python中,一定要区分开函数和方法的含义: 1.函数:类外部定义的,跟类没有直接关系的:形式: def func(*argv): 2.方法:class内部定义的函数(对象的方法 ...