OpenCV图像处理——遍历图像像素的几种方式比对

前言

在 OpenCV 中,Mat 对象是图像的核心数据结构,包含了图像的元数据和像素数据。了解 Mat 对象的数据组织形式以及像素数据的存储方式对于实现高效的像素级别图像操作非常重要。

Mat 对象的组成:

  1. 元数据头部:

    • 包含图像的基本信息,如行数(rows)、列数(cols)、通道数(channels)、数据类型(data type)等。
    • 存储了图像的一些属性和结构信息。
  2. 像素数据块部分:

    • 存储了图像的像素值。
    • 数据存储的方式取决于图像的数据类型和通道数。

像素数据的存储方式:

  1. 连续存储:

    • 对于单通道图像,像素值是按行存储的,每一行的像素连续排列。
    • 对于多通道图像,每个像素的所有通道值连续存储,然后下一个像素的所有通道值排列在后面。
  2. 行优先存储(Row-Major Order):

    • 对于多通道图像,行优先存储意味着每个通道的所有像素值排列完后,再处理下一个通道的所有像素值。
  3. 列优先存储(Column-Major Order):

    • 对于多通道图像,列优先存储意味着每个像素的所有通道值排列完后,再处理下一个像素的所有通道值。

高效遍历方法的选择:

在考虑像素遍历方法的效率时,可以利用图像的连续存储性质,以及行优先或列优先存储的方式来提高访问速度。使用 .ptr.data 方法可以直接访问图像数据,而使用 MatIterator_ 则提供了一种更抽象的迭代器方式。

选择合适的遍历方法还要考虑到图像的大小、数据类型、通道数等因素。在实践中,经过性能测试后再选择最适合场景的方法是一个不错的做法,以确保在大图像处理中能够获得较好的性能。

遍历图像

  1. 使用嵌套循环遍历每个像素:
//数组遍历 -- 进行反色处理
void read_pixe_array(cv::Mat srcImg, cv::Mat& dstImg)
{
    dstImg = Mat::zeros(srcImg.size(), srcImg.type());

    if (srcImg.channels() == 1)  //单通道
    {
        for (int i = 0; i < srcImg.rows; i++)
        {
            for (int j = 0; j < srcImg.cols; j++)
            {
                dstImg.at<uchar>(i, j) = 255 - srcImg.at<uchar>(i, j);
            }
        }
    }
    else
    {
        //3通道
        for (int i = 0; i < srcImg.rows; i++)
        {
            for (int j = 0; j < srcImg.cols; j++)
            {
                dstImg.at<Vec3b>(i, j)[0] = 255 - srcImg.at<Vec3b>(i, j)[0]; //B通道
                dstImg.at<Vec3b>(i, j)[1] = 255 - srcImg.at<Vec3b>(i, j)[1]; //G通道
                dstImg.at<Vec3b>(i, j)[2] = 255 - srcImg.at<Vec3b>(i, j)[2]; //R通道
            }
        }
    }
}

  1. 使用指针遍历图像像素:

    这种方法使用指针来访问图像像素,相对于嵌套循环,可能会更快一些。

//指针遍历 -- 所有像素加50,图片整体变亮
void read_pixe_ptr(cv::Mat srcImg, cv::Mat& dstImg)
{
    dstImg = Mat::zeros(srcImg.size(), srcImg.type());

    if (srcImg.channels() == 1)  //单通道
    {
        for (int i = 0; i < srcImg.rows; i++)
        {
            uchar* src = srcImg.ptr<uchar>(i);
            uchar* dst = dstImg.ptr<uchar>(i);
            for (int j = 0; j < srcImg.cols; j++)
            {
                dst[j] = saturate_cast<uchar>(src[j] + 50);
            }
        }
    }
    else
    {
        //3通道
        for (int i = 0; i < srcImg.rows; i++)
        {
            uchar* src = srcImg.ptr<uchar>(i);
            uchar* dst = dstImg.ptr<uchar>(i);
            for (int j = 0; j < srcImg.cols * 3; j++)
            {
                dst[j] = saturate_cast<uchar>(src[j] + 50);
            }
        }
    }
}
  1. 使用C指针遍历图像像素:
//迭C指针方式访问 -- 对图像取反
void read_pixe_c(cv::Mat srcImg, cv::Mat& dstImg)
{
    dstImg = srcImg.clone();

    if (srcImg.channels() == 1)  //单通道
    {
        for (int rows = 0; rows < dstImg.rows; rows++)
        {
            uchar* pixel = dstImg.data + rows * dstImg.step;
            (*pixel) = 255 - (*pixel);
        }
    }
    else
    {
        for (int rows = 0; rows < dstImg.rows; rows++)
        {
            uchar* pixel = dstImg.data + rows * dstImg.step;
            for (int cols = 0; cols < dstImg.cols; cols++)
            {
                pixel[0] = 255 - pixel[0];
                pixel[1] = 255 - pixel[1];
                pixel[2] = 255 - pixel[2];
                pixel += 3;
            }
        }
    }
}
  1. 使用迭代器遍历图像像素:

    C++中的迭代器提供了一种更现代的方式来访问容器元素,包括图像像素。

/迭代器访问 -- 所有像素减50,图片整体变暗
void read_pixe_it(cv::Mat srcImg, cv::Mat& dstImg)
{
    dstImg = srcImg.clone();

    if (srcImg.channels() == 1)  //单通道
    {
        MatIterator_<uchar>it = dstImg.begin<uchar>();
        MatIterator_<uchar>itEnd = dstImg.end<uchar>();
        for (; it != itEnd; it++)
        {
            (*it) = saturate_cast<uchar>((*it) - 50);
        }
    }
    else
    {
        //3通道
        MatIterator_<Vec3b>it = dstImg.begin<Vec3b>();
        MatIterator_<Vec3b>itEnd = dstImg.end<Vec3b>();
        for (; it != itEnd; it++)
        {
            (*it)[0] = saturate_cast<uchar>((*it)[0] - 50);
            (*it)[1] = saturate_cast<uchar>((*it)[1] - 50);
            (*it)[2] = saturate_cast<uchar>((*it)[2] - 50);
        }
    }
}

测试速度

测试的图像大小是100m左右,下载地址:https://www.nodeseek.com/post-10739-1,测试方法是,调用10次的方式,下载是测试代码:

void main()
{
    cv::Mat cv_src = cv::imread("13.png");

    for (int i = 0; i < 10; ++i)
    {

        std::cout << "遍历第: " << i << "次" << std::endl;
        cv::Mat cv_dst;

        double start_1 = getTickCount();

        read_pixe_array(cv_src, cv_dst);

        int64 end_1 = cv::getTickCount();
        // 计算运行时间(以秒为单位)
        double totalTime_1 = (end_1 - start_1) / cv::getTickFrequency();

        std::cout << "数组遍历运行时间: " << totalTime_1 << " seconds" << std::endl;

        double start_2 = getTickCount();

        read_pixe_ptr(cv_src, cv_dst);

        int64 end_2 = cv::getTickCount();
        // 计算运行时间(以秒为单位)
        double totalTime_2 = (end_2 - start_2) / cv::getTickFrequency();

        std::cout << "指针遍历运行时间: " << totalTime_2 << " seconds" << std::endl;

        double start_3 = getTickCount();

        read_pixe_it(cv_src, cv_dst);

        int64 end_3 = cv::getTickCount();
        // 计算运行时间(以秒为单位)
        double totalTime_3 = (end_3 - start_3) / cv::getTickFrequency();

        std::cout << "迭代器遍历运行时间: " << totalTime_3 << " seconds" << std::endl;


        //cv::waitKey();


        double start_4 = getTickCount();
        read_pixe_c(cv_src, cv_dst);

        int64 end_4 = cv::getTickCount();
        // 计算运行时间(以秒为单位)
        double totalTime_4 = (end_4 - start_4) / cv::getTickFrequency();

        std::cout << "C指针遍历运行时间: " << totalTime_4 << " seconds" << std::endl;

        std::cout << std::endl;
    }
}

测试结果,可以看出,使用C指针的方式遍历图像的方式是最快的:
在这里插入图片描述

  • 7
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

知来者逆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值