欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

OpenCV笔记(2)图像基本操作

程序员文章站 2022-03-25 14:09:29
...

一副尺寸为 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循环逐个像素输出。
OpenCV笔记(2)图像基本操作

    //三维  
    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);

OpenCV笔记(2)图像基本操作

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.扫描连续图像的做法用看作数组的那个方法也可以提高速度

参考Mat - The Basic Image Container

相关标签: opencv