OpenCV从入门到放弃(五):像素!
来源:互联网 发布:touch.js 编辑:程序博客网 时间:2024/05/17 03:03
一.概念
1.图像本质上面是由数值组成的矩阵。矩阵中的一个元素对应一个像素。
2.对于灰度图像(黑白图像),像素是8位无符号数(CV_8U)。0表示黑色,255表示白色。对于彩色图像,是用三原色数据合成彩色。3个8位(CV_8UC3)的数值组成矩阵的一个元素。而且顺序是BGR
3.一般来说8位的通道够用了。但是有些特殊的需要16位。
4.经验之谈:矩阵可以有很多种类型,但是大部分操作可以使用任何类型的矩阵来完成。但是还是有一些操作必须使用特性的类型或者特定的通道数量。有时候留个心积累那些图用什么矩阵来处理。
下面废话不多说,先来一个启发性的例子(可以暂时不用知道其中全部的细节,稍后会讲到这些东西)
代码:
结果:
在原来的图片上面生成了很多很多的点。
上面这段代码的原理很简单:就是随机生成坐标值,然后把这些坐标处的灰度值改为255(白色).
大概有这些就已经算是一个访问像素的完整过程了.下面就详细讲一些访问像素的细节.
二.访问像素的三种方法
1.at方法(cv::Mat::at(…..)).
at方法顾名思义,就是在某个位置.其实用at可以直接访问到某个位置的像素.在OpenCV中,at方法为一个模板方法且有很多的变种,下面只讲最基本常用的两种方法.(分别是传入坐标和传入点的方法)
at方法是一个模板函数,在官方文档中抽取最简单的写法:
at <类型> (行,列) [通道(如果有通道的话)]
at<类型>(行,列)就能够访问到一幅图片中的一个像素了,每个像素的chanel用[]来提取.是不是很简单.
因为这是模板方法,选择类型成了重要的一步,而且类型的选择是与图片元素的类型要对应起来,at方法不负责转化类型.下面给出一个详细的类型对应表.(要是现在不知道什么是图像元素的类型.那么点击下面的链接转到之前的core组件,有详细的类型介绍.)
http://blog.csdn.net/xierhacker/article/details/52457907
首先,OpenCV中有一个基本的向量模板类,一些基本的”N个元素”向量可以由这个模板类来定义,简单地可以写为cv::Vec
uchar类型(分别为2元素,3元素,4元素):typedef Vec<uchar, 2> cv::Vec2b typedef Vec<uchar, 3> cv::Vec3b typedef Vec<uchar, 4> cv::Vec4bShort类型typedef Vec<short, 2> cv::Vec2s typedef Vec<ushort, 2> cv::Vec2wtypedef Vec<short, 3> cv::Vec3s typedef Vec<ushort, 3> cv::Vec3wtypedef Vec<short, 4> cv::Vec4s typedef Vec<ushort, 4> cv::Vec4wInt类型(同上):typedef Vec<int, 2> cv::Vec2i typedef Vec<int, 3> cv::Vec3itypedef Vec<int, 4> cv::Vec4i typedef Vec<int, 6> cv::Vec6itypedef Vec<int, 8> cv::Vec8ifloat类型:typedef Vec<float, 2> cv::Vec2f typedef Vec<float, 3> cv::Vec3f typedef Vec<float, 4> cv::Vec4f typedef Vec<float, 6> cv::Vec6fdouble类型:typedef Vec<double, 2> cv::Vec2d typedef Vec<double, 3> cv::Vec3dtypedef Vec<double, 4> cv::Vec4d typedef Vec<double, 6> cv::Vec6d
由此,可以得到一个常用的访问像素的时候模板中放类型的表:
像素类型(模板传入关键字):
CV_8U(uchar) CV_8UC1 (uchar) CV_8UC2 (Vec2b) CV_8UC3 (Vec3b) CV_8UC4(Vec4b) CV_8S(char) CV_8SC1 (1通道) CV_8SC2 (2通道) CV_8SC3 (3通道) CV_8SC4 (4通道) CV_16U (ushort)CV_16UC1 (ushort) CV_16UC2 (Vec2w) CV_16UC3 (Vec3w) CV_16UC4 (Vec4w) CV_16S (short)CV_16SC1(short) CV_16SC2(Vec2s) CV_16SC3(Vec3s) CV_16SC4(Vec4s) CV_32S (int)CV_32SC1(int) CV_32SC2(Vec2i) CV_32SC3(Vec3i) CV_32SC4(Vec4i) CV_32F (float)CV_32FC1(float) CV_32FC2(Vec2f) CV_32FC3(Vec3f) CV_32FC4(Vec4f) CV_64F(double) CV_64FC1(double) CV_64FC2(Vec2d) CV_64FC3(Vec3d) CV_64FC4(Vec4d)
现在再来看上面的那个加入白色噪点的例子,是不是豁然开朗,知道是怎么用的了.
一个改变某点像素来画线的例子:
代码:
#include <iostream>#include <cmath>#include <opencv2/core.hpp>#include <opencv2/highgui.hpp>int main(){ cv::Mat pic1(300, 300, CV_8UC3, cv::Scalar(255, 0, 0)); cv::Mat pic2(10, 3, CV_32F, 20.3); //访问像素 //pic1上面画出一条直线 for (int i = 0; i <300; i++) { int j = i; //访问像素改变颜色CV_8UC3对应的就是Vec3b. pic1.at<cv::Vec3b>(i, j)[0] = 0; pic1.at<cv::Vec3b>(i, j)[2] = 255; } cv::imshow("test", pic1); cv::waitKey(0); return 0;}
结果:
代码的意义很容易懂,就是建立一个80*80的图片,初始化为蓝色,然后根据一个直线方程把某点的颜色改为红色,那么最终就得到了一条红色的直线.
2.指针(cv::Mat::ptr(….))
http://docs.opencv.org/3.1.0/d3/d63/classcv_1_1Mat.html#a5a9ffc908ac90604f36a8b6a1038747d
用指针访问的话,OpcnCV提供了一个方法,cv::Mat::ptr()。下面是这个方法的几种常用的定义。
定义一:
_Tp* cv::Mat::ptr (int i0 = 0) 返回mat的某行的一个地址,地址的类型与你之前在mat中选择的类型有关(模板函数),因此,要非常注意选择正确的返回以及模板参数的类型. i0代表0轴,或者通俗一点理解就是矩阵的一行.(索引是从0开始,要小心)
定义二:
_Tp* cv::Mat::ptr ( int i0,int i1 ) 返回mat某个位置元素的地址,还是老话,地址的类型与你之前在mat中选择的类型有关(模板函数),因此,要非常注意选择正确的返回以及模板参数的类型. i0代表0轴,或者通俗一点理解就是矩阵的一行.(索引是从0开始,要小心) I1代表1轴,或者通俗一点理解就是矩阵的一列,(索引是从0开始,要小心)
例一(只有一个通道):
代码:
#include <iostream>#include <cmath>#include <opencv2/core.hpp>#include <opencv2/highgui.hpp>int main(){ cv::Mat pic2(10, 3, CV_32F, 20.3); //每行元素数量 int numOfRow = pic2.cols; //访问像素 for (int row = 0; row < pic2.rows; row++) { //获得该行的地址 float *data = pic2.ptr<float>(row); //访问该行元素 for (int col = 0; col < numOfRow; col++) { std::cout << data[col] << " "; } std::cout << std::endl; } cv::imshow("test", pic2); cv::waitKey(0); return 0;}
结果:
这里的Mat中的数据类型选择的是CV_32F,是float的单通道类型.选择这种类型就是要展示接下来的指针的模板中应该选择的参数.
例二(多个通道):
代码:
#include <iostream>#include <cmath>#include <opencv2/core.hpp>#include <opencv2/highgui.hpp>int main(){ cv::Mat pic1(10, 3, CV_8UC3, cv::Scalar(255, 0, 0));// cv::Mat pic2(10, 3, CV_32F, 20.3); //每行元素数量 int numOfRow = pic1.cols; //访问像素 for (int row = 0; row < pic1.rows; row++) { //获得该行的地址 cv::Vec3b *data = pic1.ptr<cv::Vec3b>(row); //访问该行元素 for (int col = 0; col < numOfRow; col++) { data[col][0] = 0; data[col][2] = 255; } //std::cout << std::endl; } std::cout << cv::format(pic1, cv::Formatter::FMT_PYTHON) << std::endl; cv::imshow("test", pic1); cv::waitKey(0); return 0;}
结果:
例三(两个索引):
代码:
#include <iostream>#include <cmath>#include <opencv2/core.hpp>#include <opencv2/highgui.hpp>int main(){ cv::Mat pic1(10, 3, CV_8UC3, cv::Scalar(255, 0, 0));// cv::Mat pic2(10, 3, CV_32F, 20.3); //每行元素数量 int numOfRow = pic1.cols; //返回1行3列的地址(索引从0开始) cv::Vec3b *pixel = pic1.ptr<cv::Vec3b>(0, 2); //0通道改为128 pixel[0] = 128; //输出 std::cout << *pixel << std::endl; cv::imshow("test", pic1); cv::waitKey(0); return 0;}
结果:
也就是说,可以同时使用两个索引来得到一个具体位置的地址.
三.感兴趣区域(Region Of Interest,ROI)
有时候我们并不想在一整张图片上面做文章..我们只想选择某一个小区域上面完成一些操作.
OpenCV能够让我们仅仅选择一些子区域,并且把这个子区域当做普通的图像来操作.这就引出了感兴趣区域(ROI)这个话题.
使用ROI通常可以减少处理时间,增加精度,因此是一个必须要掌握的”技能”.
定义ROI区域方式:
方式一:使用矩形(Rect)类
Mat ROI;ROI=image(Rect_ (_Tp _x, _Tp _y, _Tp _width, _Tp _height)); x,y这两个参数就是矩形区域左上角的坐标 width,height这两个参数就是矩形局域的宽和高
(Rect类不熟悉的话:转到之前的core组件:http://blog.csdn.net/xierhacker/article/details/52457907)
方式二:手动指定感兴趣的行和列的范围
Mat ROI;ROI=image(Rect_ (range(row_start,row_end),range(col_strat,col_end))); range(row_start,row_end):行的开始和结束 range(col_strat,col_end):列的开始和结束
说了这么多,就要讲具体怎么用了,下面图像运算的第一个实例图像叠加就使用了ROI的概念,可以看下实例是怎么用的.
四.简单图像运算
首先,标题是简单图像运算,是因为接下来的例子都是很简单很基础的.但是也是很综合的,综合使用了之前接触到的一些编程以及理论知识.
这部分有必要消化,因为这些实例中包含了一些很基本的概念,这些概念会在这些例子中很形象的展示出来.比死记硬背一些理论好多了.
Ⅰ图像叠加
图像叠加是一个很基本的例子,通过这个例子可以联系ROI的使用.
我们这里有两个图片,一个是主图片如下
一个是很小的logo如下.
任务就是要将logo叠加到主图片上面去.不啰嗦了,上代码
代码:
#include <iostream>#include <cmath>#include <opencv2/core.hpp>#include <opencv2/highgui.hpp>int main(){ //读主图片 cv::Mat image = cv::imread("1.jpg"); //读logo cv::Mat logo = cv::imread("logo.png"); //在主图片上面定义"感兴趣"区域 cv::Mat ROI = image(cv::Rect(200, 200, logo.cols, logo.rows)); //logo复制到感兴趣区域 logo.copyTo(ROI); cv::imshow("test", image); cv::waitKey(0); return 0;}
效果:
上面的代码很简单,所以这里不给出解释,看注释完完全全能够懂.
还有内容,稍后更新……
- OpenCV从入门到放弃(五):像素!
- OpenCV从入门到放弃:摸鱼笔记(二)Mat类常用变量和像素访问
- opencv环境配置 从入门到放弃.....
- Python从入门到放弃(五):类细讲
- Python从入门到放弃(五):类细讲
- JavaScript从入门到放弃(五)
- OpenCV从入门到放弃(二):架构和上手
- OpenCV从入门到放弃(三):Core组件细讲
- OpenCV从入门到放弃(四):HighGui组件细讲
- OpenCV从入门到放弃(七):直方图那些事儿
- 《Opencv从入门到放弃》-- 1th 显示图像
- 《OpenCV从入门到放弃》 -- 2th 图像二值化
- OpenCV从入门到放弃:摸鱼笔记(一)
- NDK开发 从入门到放弃(五:JNI抛异常)
- Unity3D 从入门到放弃(五)----射箭游戏
- 微信小程序从入门到放弃(五)
- UnityShader从入门到放弃(五)漫反射—逐片元光照
- Freemarker 从入门到放弃
- 121. VLD 的使用
- c++编程4
- JavaScript趣题:回文质数
- Android Studio进度条回滚
- 关于Swift中遵守UIPickerViewDataSource协议的问题
- OpenCV从入门到放弃(五):像素!
- 转:python学习(异常和错误)(BeginMan)
- iOS应用架构谈 view层的组织和调用方案
- rtl8188eu无线网卡驱动移植
- 如何优化你的布局层级结构之RelativeLayout和LinearLayout及FrameLayout性能分析
- 接口篇(5.2)-01. 修改接口IP地址 ❀ 飞塔 (Fortinet) 防火墙
- 如何调用数据库的存储过程
- VC++入门经典学习笔记--结构和类
- Mybatis初学使用方法总结