您的位置:首页 > 运维架构

opencv 学习笔记--图像的基本操作(一)

2017-01-07 15:30 736 查看

基础概念:

一副尺寸为M*N的图像可以用一个M*N的矩阵来表示。
一般来说,灰度图用2维矩阵来表示,彩色(多通道)图像用3维矩阵(M*N*3)表示。对于图像显示来说,在大部分设备中都是用无符号8位整数(类型为CV_8U)表示像素亮度。



l(ij) 表示第i行j列的像素值,如果是多通道图像,比如RGB图像,则每个像素用三个字节表示。在OpenCV中,RGB图像的通道顺序为BGR.



Mat类
早期OpenCV中,使用lpllmage和CvMat数据结构来表示图像,这两个数据类型都是C语言结构,需要手动管理内存,包括如何内存的申请和释放。为了使开发者专注于算法设计,新版的OpenCV中引入了Mat类。
Mat类的优点是能够自动管理内存,代码结构更加简介。
class CV_EXPORTS Mat

{

public:

//一系列函数

...

/* flag 参数中包含许多关于矩阵的信息,如:

-Mat 的标识

-数据是否连续

-深度

-通道数目

*/

int flags; 
//矩阵的维数,取值应该大于或等于 2

int dims;

//矩阵的行数和列数,如果矩阵超过 2 维,这两个变量的值都为-1

int rows, cols;

//指向数据的指针

uchar* data;

//指向引用计数的指针

//如果数据是由用户分配的,则为 NULL

int* refcount;

23//其他成员变量和成员函数

...

};

Mat M(3,2, CV_8UC3, Scalar(0,0,255));

cout << "M = " << endl << " " << M << endl;

创建一个行数(高度)为3, 列数(宽度)为2的图像,图像元素是8位无符号整数类型,且有3个通道,根据OpenCV中默认的颜色顺序为BGR, 因此这是一个全红色的图像,同时直接输出M实例中的所有像素。
代码输出结果:



常用的构造函数有:

Mat::Mat()

无参数构造方法;

Mat::Mat(int rows, int cols, int type)

创建行数为 rows,列数为 col,类型为 type 的图像;

Mat::Mat(Size size, int type)

创建大小为 size,类型为 type 的图像;

Mat::Mat(int rows, int cols, int type, const Scalar& s)

24创建行数为 rows,列数为 col,类型为 type 的图像,并将所有元素初始

化为值 s;

Mat::Mat(Size size, int type, const Scalar& s)

创建大小为 size,类型为 type 的图像,并将所有元素初始化为值 s;

Mat::Mat(const Mat& m)

将 m 赋值给新创建的对象,此处不会对图像数据进行复制, m 和新对象

共用图像数据;
4000

Mat::Mat(int rows, int cols, int type, void* data, size_t step=AUTO_STEP)

创建行数为 rows,列数为 col,类型为 type 的图像,此构造函数不创建

图像数据所需内存,而是直接使用 data 所指内存,图像的行步长由 step

指定。

Mat::Mat(Size size, int type, void* data, size_t step=AUTO_STEP)

创建大小为 size,类型为 type 的图像,此构造函数不创建图像数据所需

内存,而是直接使用 data 所指内存,图像的行步长由 step 指定。

Mat::Mat(const Mat& m, const Range& rowRange, const Range& colRange)

创建的新图像为 m 的一部分,具体的范围由 rowRange 和 colRange 指

定,此构造函数也不进行图像数据的复制操作,新图像与 m 共用图像数

据;

Mat::Mat(const Mat& m, const Rect& roi)

创建的新图像为 m 的一部分,具体的范围 roi 指定,此构造函数也不进

行图像数据的复制操作,新图像与 m 共用图像数据。

这些构造函数中,很多都涉及到类型 type。 type 可以是 CV_8UC1, CV_16SC1, ...,

CV_64FC4 等。里面的 8U 表示 8 位无符号整数,16S 表示 16 位有符号整数,64F

表示 64 位浮点数(即 double 类型);C 后面的数表示通道数,例如 C1 表示一个
通道的图像,C4 表示 4 个通道的图像,以此类推。

如果你需要更多的通道数,需要用宏 CV_8UC(n),例如:

Mat M(3,2, CV_8UC(5));//创建行数为 3,列数为 2,通道数为 5 的图像。

----------------------------------------------------------------------------------------------------------------------------------------
 除了在构造函数中可以创建图像,也可以使用 Mat 类的 create()函数创建图

像。如果 create()函数指定的参数与图像之前的参数相同,则不进行实质的内存

申请操作; 如果参数不同, 则减少原始数据内存的索引, 并重新申请内存。使用

方法如下面例程所示:
Mat M(2,2, CV_8UC3);//构造函数创建图像
M.create(3,2, CV_8UC2);//释放内存重新创建图像

-----------------------------------------------------------------------------------------------------------------------------------------
矩阵的基本元素:
 对于单通道图像,其元素类型一般为 8U(即 8 位无符号整数),当然也可以

是 16S、32F 等;这些类型可以直接用 uchar、short、float 等 C/C++语言中的基本

数据类型表达。

如果多通道图像,如 RGB 彩色图像,需要用三个通道来表示。在这种情况下,如果依
然将图像视作一个二维矩阵,那么矩阵的元素不再是基本的数据类型。

26OpenCV 中有模板类 Vec,可以表示一个向量。OpenCV 中使用 Vec 类预定义了一

些小向量, 可以将之用于矩阵元素的表达。

typedef Vec<uchar, 2> Vec2b;

typedef Vec<uchar, 3> Vec3b;

typedef Vec<uchar, 4> Vec4b;

typedef Vec<short, 2> Vec2s;

typedef Vec<short, 3> Vec3s;

typedef Vec<short, 4> Vec4s;

typedef Vec<int, 2> Vec2i;

typedef Vec<int, 3> Vec3i;

typedef Vec<int, 4> Vec4i;

typedef Vec<float, 2> Vec2f;

typedef Vec<float, 3> Vec3f;

typedef Vec<float, 4> Vec4f;

typedef Vec<float, 6> Vec6f;

typedef Vec<double, 2> Vec2d;

typedef Vec<double, 3> Vec3d;

typedef Vec<double, 4> Vec4d;

typedef Vec<double, 6> Vec6d;

例如 8U 类型的 RGB 彩色图像可以使用 Vec3b,3 通道 float 类型的矩阵可以

使用 Vec3f。

对于 Vec 对象,可以使用[]符号如操作数组般读写其元素,如:

Vec3b color; //用 color 变量描述一种 RGB 颜色

color[0]=255; //B 分量

color[1]=0; //G 分量

color[2]=0; //R 分量
------------------------------------------------------------------------------------------------------------------------------------------
像素的访问:
函数 at()来实现读去矩阵中的某个像素,或者对某个像素进行赋值操作。下

面两行代码演示了 at()函数的使用方法。

uchar value = grayim.at<uchar>(i,j);//读出第 i 行第 j 列像素值

grayim.at<uchar>(i,j)=128; //将第 i 行第 j 列像素值设置为 128

如果要对图像进行遍历,可以参考下面的例程。这个例程创建了两个图像,

分别是单通道的 grayim 以及 3 个通道的 colorim,然后对两个图像的所有像素值

进行赋值,最后现实结果。

#include <iostream>

#include "opencv2/opencv.hpp"

using namespace std;

using namespace cv;

int main(int argc, char* argv[])

{

Mat grayim(600, 800, CV_8UC1);

Mat colorim(600, 800, CV_8UC3);

//遍历所有像素,并设置像素值

for( int i = 0; i < grayim.rows; ++i)

for( int j = 0; j < grayim.cols; ++j )

grayim.at<uchar>(i,j) = (i+j)%255;

//遍历所有像素,并设置像素值

for( int i = 0; i < colorim.rows; ++i)

for( int j = 0; j < colorim.cols; ++j )

{

Vec3b pixel;

pixel[0] = i%255; //Blue

pixel[1] = j%255; //Green

pixel[2] = 0;

//Red

colorim.at<Vec3b>(i,j) = pixel;

}

//显示结果

imshow("grayim", grayim);

imshow("colorim", colorim);

28waitKey(0);

return 0;

}

需要注意的是,如果要遍历图像,并不推荐使用 at()函数。使用这个函数的

优点是代码的可读性高,但是效率并不是很高。

-----------------------------------------------------------------------------------------------------------------------------------------
如果你熟悉 C++的 STL 库,那一定了解迭代器(iterator)的使用。迭代器可

以方便地遍历所有元素。Mat 也增加了迭代器的支持,以便于矩阵元素的遍历。

下面的例程功能跟上一节的例程类似,但是由于使用了迭代器,而不是使用行数

和列数来遍历,所以这儿没有了 i 和 j 变量,图像的像素值设置为一个随机数。

#include <iostream>

#include "opencv2/opencv.hpp"

using namespace std;

using namespace cv;

int main(int argc, char* argv[])

{

Mat grayim(600, 800, CV_8UC1);

Mat colorim(600, 800, CV_8UC3);

//遍历所有像素,并设置像素值

MatIterator_<uchar> grayit, grayend;

29for(

grayit

=

grayim.begin<uchar>(),

grayend =

colorend =

grayim.end<uchar>(); grayit != grayend; ++grayit)

*grayit = rand()%255;

//遍历所有像素,并设置像素值

MatIterator_<Vec3b> colorit, colorend;

for(

colorit

=

colorim.begin<Vec3b>(),

colorim.end<Vec3b>(); colorit != colorend; ++colorit)

{

(*colorit)[0] = rand()%255; //Blue

(*colorit)[1] = rand()%255; //Green

(*colorit)[2] = rand()%255; //Red

}

//显示结果

imshow("grayim", grayim);

imshow("colorim", colorim);

waitKey(0);

return 0;

}

-----------------------------------------------------------------------------------------------------------------------------------------
使用指针访问图像的像素:
当程序规模较大,且逻辑复杂时,查找指针错误十分困难。对于不熟悉指针

的编程者来说,指针就如同噩梦。如果你对指针使用没有自信,则不建议直接通

过指针操作来访问像素。虽然 at()函数和迭代器也不能保证对像素访问进行充分

的检查,但是总是比指针操作要可靠一些。

如果你非常注重程序的运行速度,那么遍历像素时,建议使用指针。下面的

例程演示如何使用指针来遍历图像中的所有像素。此例程实现的操作跟第 3.5.1

节中的例程完全相同。例程代码如下:

#include <iostream>

#include "opencv2/opencv.hpp"

using namespace std;

using namespace cv;

int main(int argc, char* argv[])

{

Mat grayim(600, 800, CV_8UC1);

Mat colorim(600, 800, CV_8UC3);

//遍历所有像素,并设置像素值

for( int i = 0; i < grayim.rows; ++i)

{

//获取第 i 行首像素指针

uchar * p = grayim.ptr<uchar>(i);

//对第 i 行的每个像素(byte)操作

for( int j = 0; j < grayim.cols; ++j )

p[j] = (i+j)%255;

}

//遍历所有像素,并设置像素值

for( int i = 0; i < colorim.rows; ++i)

{

//获取第 i 行首像素指针

Vec3b * p = colorim.ptr<Vec3b>(i);

for( int j = 0; j < colorim.cols; ++j )

{

p[j][0] = i%255; //Blue

p[j][1] = j%255; //Green

p[j][2] = 0;

//Red

}

31}

//显示结果

imshow("grayim", grayim);

imshow("colorim", colorim);

waitKey(0);

return 0;

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: