OpenCV-------Mat和IplImage联系与区别

Mat与Iplimage访问像素方法总结

在opencv的编程中,遍历访问图像元素是经常遇到的操作,掌握其方法非常重要,无论是Mat类的像素访问,还是IplImage结构体的访问的方法,都必须扎实掌握,毕竟,图像处理本质上就是对像素的各种操作,访问元素就是各种图像处理算法的第一步。

首先先看看图像的是怎么存储的。

单通道图像

多通道图像

Mat访问图像元素方法汇总

1.用指针访问元素

在大多数图像处理任务中, 执行计算时你都需要对图像的所有像素进行扫描。 当需要访问的像素数量非常庞大, 你必须采用高效的方式来执行这个任务来提高效率。 如果你需要高效扫描大图片的数据,那么请使用指针方式。

#include<opencv2\opencv.hpp>   
#include<opencv2\highgui\highgui.hpp>

using namespace std;
using namespace cv;

int main(int argc, char** argv)
{
    Mat img = imread("lena.jpg", 1); 
    if (img.empty())
    {
        cout << "fail to read image" << endl;
        return -1;
    }
    Mat img1 = img.clone();
    int div = 64;

    /* 方法1:用指针访问 */
    //多通道访问法1
    int rows = img1.rows;
    int cols = img1.cols; 
    for (int i = 0; i < rows; i++)
    {
        //uchar* p = img1.ptr<uchar>(i);  //获取第i行的首地址
        for (int j = 0; j < cols; j++)
        {
            //在这里操作具体元素
            uchar *p = img1.ptr<uchar>(i, j);
            p[0] = p[0] / div*div + div / 2;
            p[1] = p[1] / div*div + div / 2;
            p[2] = p[2] / div*div + div / 2;
        }
    }

    imshow("lean", img1);


    //多通道访问法2
    Mat img3 = img.clone();
    int channels = img3.channels(); //获取通道数
    int rows3 = img3.rows;
    int cols3 = img3.cols* channels; //注意,是列数*通道数
    for (int i = 0; i < rows3; i++)
    {
        uchar* p = img3.ptr<uchar>(i);  //获取第i行的首地址
        for (int j = 0; j < cols3; j++)
        {
            //在这里操作具体元素
            p[j] = p[j] / div*div + div / 2;
            p[j+1] = p[j+1] / div*div + div / 2;
            p[j+2] = p[j+2] / div*div + div / 2;
        }
    }

    imshow("lean3", img3);

    //单通道图像
    Mat img2 = img.clone();
    cvtColor(img2, img2, COLOR_BGR2GRAY);
    for (int i = 0; i < img2.rows; i++)
    {
        uchar* p = img2.ptr<uchar>(i);  //获取第i行的首地址
        for (int j = 0; j < img2.cols; j++)
        {
            //在这里操作具体元素
            p[j] = p[j] / div*div + div / 2;
        }
    }

    imshow("lean2", img2);
    waitKey(0);
    return 0;
}

2.用迭代器访问元素

在面向对象编程时, 我们通常用迭代器对数据集合进行循环遍历。 标准模板库(STL) 对每个集合类都定义了对应的迭代器类, OpenCV也提供了cv::Mat的迭代器类, 并且与C++ STL中的标准迭代器兼容。

#include<opencv2\opencv.hpp>   
#include<opencv2\highgui\highgui.hpp>

using namespace std;
using namespace cv;

int main(int argc, char** argv)
{
    Mat img = imread("lena.jpg",1); //载入灰度图
    Mat img1 = img.clone();
    int div = 64;
    /* 方法2:用迭代器访问 */

    /******************多通道的可以这么写***************/
    Mat_<Vec3b>::iterator it = img1.begin<Vec3b>();  //获取起始迭代器
    Mat_<Vec3b>::iterator it_end = img1.end<Vec3b>();  //获取结束迭代器
    for (; it != it_end; it++)
    {
        //在这里分别访问每个通道的元素
        (*it)[0] = (*it)[0] / div*div + div / 2;
        (*it)[1] = (*it)[1] / div*div + div / 2;
        (*it)[1] = (*it)[1] / div*div + div / 2;
    }

    imshow("lean", img1);


    /******************单通道的可以这么写***************/
    Mat img2;
    cvtColor(img, img2, COLOR_RGB2GRAY); //转化为单通道灰度图

    Mat_<uchar>::iterator it2 = img2.begin<uchar>();  //获取起始迭代器
    Mat_<uchar>::iterator it_end2 = img2.end<uchar>();  //获取结束迭代器
    for (; it2 != it_end2; it2++)
    {
            //在这里分别访问每个通道的元素
            *it2 = *it2 / div*div + div / 2;
    }
    imshow("lena2", img2);

    waitKey(0);
    return 0;
}
}

若要从图像的第二行开始,程序该怎么修改? 我们可以用

image.begin<cv::Vec3b>()+image.cols

初始化cv::Mat迭代器。 获得集合结束位置的方法也类似, 只是改用end方法。 但是, 用end方法得到的迭代器已经超出了集合范围, 因此必须在结束位置停止迭代过程。 结束的迭代器也能使用数学计算, 例如, 如果你想在最后一行前就结束迭代, 可使用

image.end<cv::Vec3b>()-image.cols

3.动态地址+at()访问元素

#include<opencv2\opencv.hpp>   
#include<opencv2\highgui\highgui.hpp>

using namespace std;
using namespace cv;

int main(int argc, char** argv)
{
    Mat img = imread("lena.jpg",1); 
    Mat img1 = img.clone();
    int div = 64;
    /* 方法3:用at访问 */

    /****************访问多通道元素*********************/
    int rows = img1.rows;
    int cols = img1.cols;
    for (int i = 0; i < rows; i++)
    {
        for (int j = 0; j < cols; j++)
        {
            //在这里访问每个通道的元素,注意,成员函数at(int y,int x)的参数
            img1.at<Vec3b>(i,j)[0] = img1.at<Vec3b>(i, j)[0] / div*div + div / 2;
            img1.at<Vec3b>(i, j)[1] = img1.at<Vec3b>(i, j)[1] / div*div + div / 2;
            img1.at<Vec3b>(i, j)[2] = img1.at<Vec3b>(i, j)[2] / div*div + div / 2;

        }
    }

    imshow("lena", img1);

    /****************访问单通道元素*********************/
    Mat img2;
    cvtColor(img, img2, COLOR_RGB2GRAY);

    for (int i = 0; i < rows; i++)
    {
        for (int j = 0; j < cols; j++)
        {
            //在这里访问每个通道的元素,注意,成员函数at(int y,int x)的参数
            img2.at<uchar>(i, j) = img2.at<uchar>(i, j) / div*div + div / 2;
        }
    }

    imshow("lena2", img2);

    waitKey(0);
    return 0;
}

IplImage访问元素方法汇总

1.使用cvGet2D()函数访问

#include<opencv2\opencv.hpp>   
#include<opencv2\highgui\highgui.hpp>

using namespace std;
using namespace cv;

int main(int argc, char** argv)
{
    /*访问单通道元素*/
    IplImage* img = cvCreateImage(cvSize(640, 480), IPL_DEPTH_8U, 1); //单通道图像
    CvScalar s;
    double tmp;
    for (int i = 0; i < img->height; i++)
    {
        for (int j = 0; j < img->width; j++)
        {
            //可以在这里访问元素
            tmp = cvGet2D(img, i, j).val[0];
            cvSet2D(img, i, j, 255);  //第三个参数是要设置的值
        }
    }
    cvShowImage("img", img);


    /*访问多通道元素*/
    IplImage* img2 = cvCreateImage(cvSize(640, 480), IPL_DEPTH_32F, 3);
    double tmpb, tmpg, tmpr;
    for (int i = 0; i < img->height; i++)
    {
        for (int j = 0; j < img->width; j++)
        {
            tmpb = cvGet2D(img, i, j).val[0];
            tmpg = cvGet2D(img, i, j).val[1];
            tmpr = cvGet2D(img, i, j).val[2];

            cvSet2D(img2, i, j, CvScalar(255,255,255));  //第三个参数是要设置的值,三个通道一起设置
        }
    }
    cvShowImage("img2", img2);

    waitKey(0);
    return 0;
}

2.指针方式直接访问

追求高效率地访问元素请使用该方法。

#include<opencv2\opencv.hpp>   
#include<opencv2\highgui\highgui.hpp>

using namespace std;
using namespace cv;

int main(int argc, char** argv)
{
    /*访问多通道元素*/
    IplImage* img = cvCreateImage(cvSize(640, 480), IPL_DEPTH_8U, 3);
    uchar* data = (uchar *)img->imageData;
    int step = img->widthStep / sizeof(uchar);
    int channels = img->nChannels;
    uchar b, g, r;
    for (int i = 0; i < img->height; i++)
    {
        for (int j = 0; j < img->width; j++)
        {
            //获得元素的值
            b = data[i*step + j*channels + 0];
            g = data[i*step + j*channels + 1];
            r = data[i*step + j*channels + 2];

            //修改元素的值
            data[i*step + j*channels + 0] = 255;
        }
    }

    cvShowImage("img", img);


    /*访问单通道元素*/
    IplImage* img2 = cvCreateImage(cvSize(640, 480), IPL_DEPTH_8U, 1);
    uchar* data2 = (uchar *)img2->imageData;
    int step2 = img2->widthStep / sizeof(uchar);
    uchar v;
    for (int i = 0; i < img2->height; i++)
    {
        for (int j = 0; j < img2->width; j++)
        {
            //获得元素的值
            v = data2[i*step2 + j];

            //修改元素的值
            data2[i*step2 + j] = 255;
        }
    }

    cvShowImage("img2", img2);



    waitKey(0);
    return 0;
}

两者之间的转换

在OpenCV 2.0中使用:

IplImage * ipl1, *ipl2;
    // ...
const cv::Mat m = cv::Mat(ipl,false); 
cv::Mat  m2 = ipl2; 

在OpenCV 3.0中:

IplImage * ipl = ...;
cv::Mat m = cv::cvarrToMat(ipl); 

两者之间的区别

IplImage:

在OpenCV中IplImage是表示一个图像的结构体,也是从OpenCV1.0到目前最为重要的一个结构;

在之前的图像表示用IplImage,而且之前的OpenCV是用C语言编写的,提供的接口也是C语言接口;

Mat:

Mat是后来OpenCV封装的一个C++类,用来表示一个图像,和IplImage表示基本一致,但是Mat还添加了一些图像函数;

在OpenCV中, IplImage 与 Mat是可以相互转换的;

IplImage 转 Mat:

 // extern  IplImage * plpliamge; //假设 IplImage 已经创建;
 cv::Mat * pmatImage = new cv:Mat( IplImage, 0  ): //第二个参数表示不进行像素数据copy;

Mat 转 IplImage:


//extern cv:Mat  matImage; //假设已经创建cv:Mat;

IplImage limage = IplImage ( matImage );//不进行数据copy;

通常情况对于图像的读取,IplImage 通过 cvLoadImage, cv:Mat通过 cv::imread;

对于内存图像数据创建稍有不同:

IplImage ,通过cvCreateImage, 创建后复制像素到创建的内存,或者cvCreateImageHeader和cvSetImageData创建;

cv::Mat,直接可以通过构造函数Mat(int _rows, int _cols, int _type, void* _data, size_t _step=AUTO_STEP); 直接创建;

示例:

cv::Mat * pMat = new cv::Mat( 288, 352,  CV_8UC3, imagebufdata );
IplImage IplImagetmp = IplImage(*pMat);

注意:是前两个参数是图像的height和width,不是width和height;

通过上面的描述可以看出,创建内存数据图像,直接通过 cv::Mat类比较简单,然后可以通过Mat获取IplImage,通过cvCreateImage等函数创建内存图像,比较麻烦,而且创建后,还要通过cvReleaseImage等函数释放内存,所以这里建议用cv::Mat创建;

阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页