opencv学习(四)之像素遍历三种方式

本文详细介绍了OpenCV中遍历图像像素的三种方法:C操作符[](指针方式)、迭代器iterator和at()函数,并通过计时函数比较了它们的效率。at()函数虽然可读性好但速度慢,迭代器提供了一种安全的遍历方式,而指针操作速度快但需谨慎避免越界。代码示例展示了如何使用这些方法遍历和修改像素值。

在上一篇文章中介绍了图像颜色空间缩减、查找表等内容。在对图像像素进行遍历时共有三种方法:
(1). C操作符[] (指针方式访问)
(2). 迭代器iterator
(3). 动态地址计算
这三种像素遍历方式在速度上有所不同,上一篇文章介绍过用C操作符[]是最快的访问方式。下面会通过对同一幅图像进行处理来直观的比较三种访问方式的速度差异。首先介绍一下opencv中提供的计时函数

1.计时函数

opencv中提供两个简便的计时函数getTickCount()和getTickFrequency(),这两个函数配合使用即可计算出程序耗时,这两个函数有点类似于C++中的clock()函数和CLK_TCK(C\C++中计时、延时函数
getTickCount():返回CPU自某时间开始的时钟周期
getTickFrequency():获取CPU时钟频率
其用法如下所示:

double start,stop,duration;
start = static_cast<double>(getTickCount());    //记录当前时刻CPU时钟周期
/*
...
代码片段
...
*/
stop = static_cast<double>(getTickCount());     //记录程序运行结束CPU时钟周期
duration = ((double)(stop - start))/getTickFrequency(); //计算时间,以秒为单位

//上述代码也可精简
double timeConsume,start;
start = static_cast<double>(getTickCount());    //记录当前时刻CPU时钟周期
/*
...
代码片段
...
*/
timeConsume = ((double)getTickCount() - start) / getTickFrequency();

2.常用像素存储结构

在介绍像素遍历之前需要先对opencv中像素的存储方式有一定的了解,不然直接看代码的适合可能不知所云,比较费劲。
有一些opencv中常见的数据结构使用”<<”操作符进行输出如:

#include <iostream>
#include <opencv2/core.hpp>

using namespace std;
using namespace cv;

int main()
{
    //2D Point
    Point2f P(5, 1);
    cout << endl << "Point (2D) = " << P << endl << endl;

    //3D Point
    Point3f P3f(2, 5, 7);
    cout << "Point (3D) = " << P3f << endl << endl;

    //std::vector via cv::Mat
    vector<float> v;
    v.push_back((float)CV_PI);      //将pi的值以float类型存入v,push_back是C++中容器的一种操作方式
    v.push_back(2);
    v.push_back(3.01f);
    cout << "vector of floats via Mat = " << endl << Mat(v) << endl << endl;

    //std::vector of points
    vector<Point2f> vPoints(20);
    for(size_t i = 0; i < vPoints.size(); ++i)
    vPoints[i] = Point2f((float)(i * 5), (float)(i % 7));
    cout << "vector of 2D points = " << endl << vPoints << endl << endl;


    return 0;
}

这里写图片描述

opencv中有模板类Vec,可以表示一个向量。opencv中使用Vec类预定义了一些小向量,可以用于矩阵元素的表达。

typedef Vec<uchar, 2> Vec2b;
typedef Vec<uchar, 3> Vec3b;
typedef Vec<uchar, 4> Vec4b;

typedef Vec<short, 2> Vec2s;
typedef Vec<short, 3> Vec3s;
typedef Vec<short, 4> Vec4s;

typedef Vec<int, 2> Vec2i;
typedef Vec<int, 3> Vec3i;
typedef Vec<int, 4> Vec4i;

typedef Vec<float, 2> Vec2f;
typedef Vec<float, 3> Vec3f;
typedef Vec<float, 4> Vec4f;
typedef Vec<float, 6> Vec6f;

typedef Vec<double, 2> Vec2d;
typedef Vec<double, 3> Vec3d;
typedef Vec<double, 4> Vec4d;
typedef Vec<double, 6> Vec6d;

例如8U类型的RGB彩色图像也可以使用Vec3b,3通道float类型的矩阵可以使用Vec3f.对于Vec对象可以使用[]符号如数组一样对其元素进行读写操作。如:

Vec3b color;            //用color变量描述一种RGB颜色
color[0] = 255;         //B分量
color[1] = 0;           //G分量
color[2] = 0;           //R分量

3.像素值的读写

在对图像进行处理时,需要读取某个像素值或者设置某个像素值;在更多的时候需要对图像中所有像素进行遍历。在前面介绍用三种方法可以对图像像素进行遍历。下面逐一介绍。

3.1 at()函数

可以用函数at()来实现对矩阵中某个像素值进行读取或进行赋值操作,但是前提要知道该像素行列位置。其用法如下:

uchar value = img.at<uchar>(i,j);       //读出第i行第j列像素值
img.at<uchar>(i,j) = 128;               //将第i行第j列像素值设置为128

如果要对图像进行遍历,可以参照下面的代码:

#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>

using namespace std;
using namespace cv;

int main()
{
    double timeConsume = static_cast<double>(getTickCount());
    Mat grayImage(400, 600, CV_8UC1);       //创建一个大小为600x800的单通道灰度图
    Mat colorImage(400, 600, CV_8UC3);      //创建一个大小为600x800的三通道彩色图

    //遍历所有像素并设置像素值
    for(int i = 0; i < grayImage.rows; ++i)         //遍历行
        for(int j = 0; j < grayImage.cols; ++j)     //遍历列
            grayImage.at<uchar>(i, j) = (i + j) % 255;

    //遍历所有像素并设置像素值

    for(int i = 0; i < colorImage.rows; ++i)         //遍历行
        for(int j = 0; j < colorImage.cols; ++j)     //遍历列
        {
            Vec3b pixel;            //定义三通道像素值变量
            pixel[0] = i % 255;     //Blue
            pixel[1] = j % 255;     //Green
            pixel[2] = 0;           //Red

            colorImage.at<Vec3b>(i, j) = pixel;
        }

    //显示简历图像的结果
    namedWindow("grayImage", WINDOW_AUTOSIZE);
    imshow("grayImage", grayImage);

    namedWindow("colorImage", WINDOW_AUTOSIZE);
    imshow("colorImage", colorImage);

    timeConsume = ((double)getTickCount() - timeConsume) / getTickFrequency();
    cout << "程序耗时: " << timeConsume << endl;

    waitKey(0);

    return 0;
}

运行结果如下:
这里写图片描述
程序中带有计时函数计算程序耗时,程序耗时根据电脑配置不同有所差异。
如果需要对图像像素进行遍历,不推荐使用at()函数,因为使用这个函数其效率不高,但是其可读性较好。

3.2 使用迭代器iterator

相信学过C++的同学一定对STL库有印象,其中就包含iterator的使用。迭代器可以很方便的遍历所有元素。Mat类支持迭代器的方式对矩阵元素进行遍历。由于使用迭代器就不需要再使用行列数进行操作。示例如下:

#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>

using namespace std;
using namespace cv;

int main()
{
    double timeConsume = static_cast<double>(getTickCount());
    Mat grayImage(400, 600, CV_8UC1);       //创建一个大小为600x800的单通道灰度图
    Mat colorImage(400, 600, CV_8UC3);      //创建一个大小为600x800的三通道彩色图

    //遍历所有像素并设置像素值
    MatIterator_<uchar> grayit, grayend;
    for(grayit = grayImage.begin<uchar>(), grayend = grayImage.end<uchar>(); grayit != grayend; ++grayit)
        *grayit = rand() % 255;

    //遍历所有像素并设置像素值
    MatIterator_<Vec3b> colorit, colorend;
    for(colorit = colorImage.begin<Vec3b>(), colorend = colorImage.end<Vec3b>(); colorit != colorend; ++colorit)
    {
        (*colorit)[0] = rand() % 255;       //Blue
        (*colorit)[1] = rand() % 255;       //Green
        (*colorit)[2] = rand() % 255;       //Red
    }


    //显示简历图像的结果
    namedWindow("grayImage", WINDOW_AUTOSIZE);
    imshow("grayImage", grayImage);

    namedWindow("colorImage", WINDOW_AUTOSIZE);
    imshow("colorImage", colorImage);

    timeConsume = ((double)getTickCount() - timeConsume) / getTickFrequency();
    cout << "程序耗时: " << timeConsume << endl;

    waitKey(0);

    return 0;
}

运行结果如下:
这里写图片描述

使用迭代器进行像素遍历被认为是一种更安全的方式。使用迭代器只需要求出矩阵的开头和矩阵末尾,接下来使用for循环进行迭代,直到”it == end”.
代码中”colorit = colorImage.begin(), colorend = colorImage.end();”是求出矩阵起始位置和结束位置。

3.3 使用指针进行访问

其实在前面已经提到用指针对数据进行访问,虽然使用指针速度最快,但是指针操作不进行类型以及越界检查,所有在程序写好编译没问题,但是运行就有可能出错。而使用at()和iterator()进行遍历时,虽然速度不如指针,但是相对两件比较容易实现,可读性也比较好。示例代码如下:

#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>

using namespace std;
using namespace cv;

int main()
{
    double timeConsume = static_cast<double>(getTickCount());
    Mat grayImage(400, 600, CV_8UC1);       //创建一个大小为600x800的单通道灰度图
    Mat colorImage(400, 600, CV_8UC3);      //创建一个大小为600x800的三通道彩色图

    //遍历所有像素并设置像素值
    for(int i = 0; i < grayImage.rows; ++i)
    {
        uchar* p = grayImage.ptr<uchar>(i);     //获取第i行第一个像素的指针
        for(int j = 0; j < grayImage.cols; ++j)
            p[j] = (i + j) % 255;               //对每个i行的所有像素进行赋值操作
    }

    //遍历所有像素并设置像素值

    for(int i = 0; i < colorImage.rows; ++i)
    {
        Vec3b* p = colorImage.ptr<Vec3b>(i);
        for(int j = 0; j < colorImage.cols; ++j)
        {
            p[j][0] = i % 255;      //Blue
            p[j][1] = j % 255;      //Gree
            p[j][2] = 0;            //Red
        }
    }

    //显示简历图像的结果
    namedWindow("grayImage", WINDOW_AUTOSIZE);
    imshow("grayImage", grayImage);

    namedWindow("colorImage", WINDOW_AUTOSIZE);
    imshow("colorImage", colorImage);

    timeConsume = ((double)getTickCount() - timeConsume) / getTickFrequency();
    cout << "程序耗时: " << timeConsume << endl;

    waitKey(0);

    return 0;
}

运行结果如下:
这里写图片描述

下一篇对三种像素遍历方式进行一个综合比较!!!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值