OpenCV笔记(2)图像基本操作
一副尺寸为 M × N 的图像可以用一个 的图像可以用一个 M × N 的矩 阵来表示,的矩 阵来表示,阵元素的值表示这个位置上像亮度,一般来说越大该点亮。
一般来说,灰度图用 2维矩阵表示,彩色(多通道)图像用 3维矩阵( M × N × 3)表示。对于图像显来说,目前大部分设备都是用无符号 )表示。对于图像显来说,目前大部分设备都是用无符号 8 位整 数(类型为 CV_8U )
图像数据在计算机内存中的储顺序为以最左上点(也可能是下点)开始
I00 | I01 | … | I0(N-1) |
---|---|---|---|
I10 | I11 | … | I1(N-1) |
… | … | … | … |
I(M-1)0 | I(M-1)1 | … | I(M-1)(N-1) |
Iij 表示第 i行 j列的像素值。如果是多通道图,比RGB图像,则每个图像,则每个像素用三个字节表示。在OpenCV中,RGB图像的通道顺序为BGR。
B00 | G00 | R00 | B01 | G01 | R01 | … |
---|---|---|---|---|---|---|
B10 | G10 | R10 | B11 | G11 | R11 | … |
… | … | … | … | … | … | … |
1.OpenCV命名空间
OpenCV中的C++类和函数都是定义在命名空间cv
之内的,有两种方法可以访问。(1)在代码开头的适当位置,加上usingnamespace cv
;这句。
(2)在使用OpenCV类和函数时,都加入cv::
命名空间。
2.Mat类
cv::Mat类是用于保存图像以及其他矩阵数据的数据结构,由两个数据部分组成:矩阵头(包含矩阵尺寸,存储方法,存储地址等信息)和一个指向存储所有像素值的矩阵(根据所选存储方法的不同矩阵可以是不同的维数)的指针。矩阵头的尺寸是常数值,但矩阵本身的尺寸会依图像的不同而不同,通常比矩阵头的尺寸大数个数量级。
Mat基本操作——矩阵
//二维三通道矩阵建立 (定义尺寸、指定存储元素的数据类型以及每个矩阵点的通道数)
Mat M(2, 2, CV_8UC3, Scalar(0, 0, 255)); //使用构造函数创建矩阵
/*
CV_8UC3 表示使用8位的 unsigned char 型,每个像素由三个元素组成三通道,初始化为(0,0,255)
*/
cout << "M = " << endl << " " << M << endl << endl; //格式化输出
Mat重新定义了“<<”这个操作符,可以用来方便地输出所有像素值,而不需要使用for循环逐个像素输出。
//三维
int sz[3] = { 3,3,3 };
Mat L(3, sz, CV_8UC(1), Scalar::all(0));
/*
超过两维的矩阵:指定维数,然后传递一个指向一个数组的指针,这个数组包含每个维度的尺寸;其余的相同
*/
对于单通道图像,元素类型通常是8U(即8 unsigned char),也有可能是16S(16 short)或者32F(32 float)等等,但是基本都是C中的基本数据类型。
对于多通道图像,如RGB彩色图像,就需要用三个通道来表示。如果仍将图像视为二维矩阵,那么矩阵元素就不再是基本数据类型了,OpenCV中用Vec类预定义了一些向量,可以用来表示矩阵元素,如
typedef Vec<uchar, 2> Vec2b;
typedef Vec<uchar, 3> Vec3b;
typedef Vec<uchar, 4> Vec4b;
例如8U类型的RGB彩色图像可以用Vec3b,对Vec对象读取的时候也可以像数组一样用[]
Vec3b color; // 用 color变量描述一种RGB 颜色
color[0]=255; //B 分量
color[1]= 0; //G分量
color[2]= 0; //R分量
Mat基本操作——图像
当在程序中传递图像并创建拷贝时,思路是让每个 Mat 对象有自己的信息头,但共享同一个矩阵。这通过让矩阵指针指向同一地址而实现。而拷贝构造函数则只拷贝信息头和矩阵指针,而不拷贝矩阵。
//拷贝构造函数和赋值函数 只拷贝信息头和矩阵指针
Mat A, C; // 创建信息头
A=imread("test.jpg", CV_LOAD_IMAGE_COLOR); // 读入图像
Mat B(A); // 使用拷贝构造函数
C = A; // 赋值
//创建一个感兴趣区域(ROI),只需要创建包含边界信息的信息头
Mat D(A, Rect(10, 10, 100, 100)); // 选取A中一个矩形区域,即只访问其矩形区域的信息头,只是创建信息头
Mat E = A( Range::all(), Range(1, 3)); // 创建访问边界的信息头
//拷贝矩阵本身(不只是信息头和矩阵指针)
Mat F = A.clone();//复制图像,包括数据
Mat G;
A.copyTo(G);
//测试
namedWindow( "a", CV_WINDOW_AUTOSIZE );
namedWindow( "c", CV_WINDOW_AUTOSIZE );
imshow( "a", D);
imshow( "c", E );
使用Mat要注意:
1.OpenCV函数中输出图像的内存分配是自动完成的(如果不特别指定的话)
2.使用OpenCV的C++接口时不需要考虑内存释放问题
3.赋值运算符和拷贝构造函数只拷贝信息头,仍然共用同一个矩阵
4.如果要复制矩阵数据,可以使用clone和copyTo函数
3.选取局部区域
1.单行或者单列选择
Mat line = A.row(i);//取出A矩阵的第i行
2.用Range选择多行或者多列
Mat B = A(Range::all(),Range(1,3));//提取1-3列
Mat C = B(Range(5,9),Range::all());//提取5-9行
//等价于C = A(Range(5,9),Range(1,3))
3.感兴趣区域(Region of Interest)
可使用构造函数或者括号运算符
对于ROI区域的定义也可以使用矩形对象或者Range对象
Mat ROI(img,Rect(10,10,100,100));
//Mat ROI = img(Rect(10,10,100,100));
Mat ROI(img,Range(10,100),Range(10,100);
//Mat ROI = img(Range(10,100),Range(10,100));
4.读写像素值
1.at()
函数
读取矩阵中的某个像素或对某个像素赋值
uchar value = img.at<uchar>(i,j);//读出第i行第j列像素值
img.at<uchar>(i,j)=128;//将第i行第j列像素值设置为128
遍历图像
Mat greyim(600, 800, CV_8UC1);
Mat colorim(600, 800, CV_8UC3);
// 遍历所有像素,并设置值
for (int i = 0; i < greyim.rows; ++i)
for (int j = 0; j < greyim.cols; ++j)
greyim.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[0] = i%255;
pixel[1] = j % 255; //Green pixel[1] = j%255;
pixel[2] = 0; //Re dpixel[2] = 0;
colorim.at<Vec3b>(i, j) = pixel;
}
// 显示结果
imshow("greyim", greyim);
imshow("colorim", colorim);
2.通过指针访问像素
for( int i = 0; i< greyim.rows; ++i)
{
// 获取第i行首像素指针
uchar * p = greyim.ptr<uchar>(i);
// 对第i行的每个像素 (byte) 操作
for( int j = 0; j< greyim.cols; ++)
p[j] = (i+j)%255;
}
3.把W·H的一幅图像看成是一个1·(W·H)的一个一维数组,利用isContinuous()
这个函数判断图像内的像素是否填充满
int nc;
if(greyim.isContinuous())//判断是否被所有的像素填满
{
nc = greyim.rows*greyim.cols*greyim.channels();
}
uchar* data_2 = greyim.ptr<uchar>(0);//提取第一个像素点指针
for(int i=0;i<nc;i++)//遍历所有的元素
{
data_2[i] = 255;
}
4.使用迭代器(iterator)
MatIterator_<Vec3b> it = img.begin<Vec3b>();
MatIterator_<Vec3b> itend = img.end<Vec3b>();
for (; it!=itend; it++)
{
(*it)[0] = 255;
}
Tips:
1.使用迭代器也会是速度变慢,但迭代器的使用可以减少程序错误的发生几率
2.at()操作要比指针的操作慢很多,有大量数据时,用指针
3.扫描连续图像的做法用看作数组的那个方法也可以提高速度
上一篇: 基于maven的旅游网站设计之BaseSeverlet抽取
下一篇: 一个简单的java定时任务