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

2019-04-15 13:38发布

基础概念:

一副尺寸为M*N的图像可以用一个M*N的矩阵来表示。 一般来说,灰度图用2维矩阵来表示,彩 {MOD}(多通道)图像用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中默认的颜 {MOD}顺序为BGR, 因此这是一个全红 {MOD}的图像,同时直接输出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 和新对象
共用图像数据;
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 彩 {MOD}图像,需要用三个通道来表示。在这种情况下,如果依
然将图像视作一个二维矩阵,那么矩阵的元素不再是基本的数据类型。
26OpenCV 中有模板类 Vec,可以表示一个向量。OpenCV 中使用 Vec 类预定义了一
些小向量, 可以将之用于矩阵元素的表达。
typedef Vec Vec2b;
typedef Vec Vec3b;
typedef Vec Vec4b;
typedef Vec Vec2s;
typedef Vec Vec3s;
typedef Vec Vec4s;
typedef Vec Vec2i;
typedef Vec Vec3i;
typedef Vec Vec4i;
typedef Vec Vec2f;
typedef Vec Vec3f;
typedef Vec Vec4f;
typedef Vec Vec6f;
typedef Vec Vec2d;
typedef Vec Vec3d;
typedef Vec Vec4d;
typedef Vec Vec6d;
例如 8U 类型的 RGB 彩 {MOD}图像可以使用 Vec3b,3 通道 float 类型的矩阵可以
使用 Vec3f。
对于 Vec 对象,可以使用[]符号如操作数组般读写其元素,如:
Vec3b color; //用 color 变量描述一种 RGB 颜 {MOD}
color[0]=255; //B 分量
color[1]=0; //G 分量
color[2]=0; //R 分量
------------------------------------------------------------------------------------------------------------------------------------------ 像素的访问: 函数 at()来实现读去矩阵中的某个像素,或者对某个像素进行赋值操作。下
面两行代码演示了 at()函数的使用方法。
uchar value = grayim.at(i,j);//读出第 i 行第 j 列像素值
grayim.at(i,j)=128; //将第 i 行第 j 列像素值设置为 128
如果要对图像进行遍历,可以参考下面的例程。这个例程创建了两个图像,
分别是单通道的 grayim 以及 3 个通道的 colorim,然后对两个图像的所有像素值
进行赋值,最后现实结果。
#include
#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(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(i,j) = pixel;
}
//显示结果
imshow("grayim", grayim);
imshow("colorim", colorim);
28waitKey(0);
return 0;
}
需要注意的是,如果要遍历图像,并不推荐使用 at()函数。使用这个函数的
优点是代码的可读性高,但是效率并不是很高。
----------------------------------------------------------------------------------------------------------------------------------------- 如果你熟悉 C++的 STL 库,那一定了解迭代器(iterator)的使用。迭代器可
以方便地遍历所有元素。Mat 也增加了迭代器的支持,以便于矩阵元素的遍历。
下面的例程功能跟上一节的例程类似,但是由于使用了迭代器,而不是使用行数
和列数来遍历,所以这儿没有了 i 和 j 变量,图像的像素值设置为一个随机数。
#include
#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_ grayit, grayend;
29for(
grayit
=
grayim.begin(),
grayend =
colorend =
grayim.end(); grayit != grayend; ++grayit)
*grayit = rand()%255;
//遍历所有像素,并设置像素值
MatIterator_ colorit, colorend;
for(
colorit
=
colorim.begin(),
colorim.end(); 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
#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(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(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;
}