opencv(二)图像像素提取及操作

为了编写计算机视觉应用,我们必须会存取图像的内容,如修改或者创建图像,这些过程都需要对图像的基本元素进行操作,即所谓的像素。

1【存取像素值】

为了存取矩阵元素,我们需要在代码中指定元素所在的行和列,程序会返回相应的元素。如果是单通道的,返回值是单个数值;如果是多通道的,返回值是一组向量(Vector)。

我们先通过一个简单的例子演示如何直接存取像素值。在图像随机添加一些椒盐噪点,随机将一些像素设置为白色或者黑色。在传输过程中,如果部分像素值丢失,那么这种噪点就会出现。首先写一个简单的函数

void salt(cv::Mat&image, int n)
{
    for(int i = 0; i < n; i++)
    {
        int k = qrand() % image.cols;
        int j = qrand() % image.rows;
        if(image.channels() == 1)
        {
            image.at<uchar>(j, k) = 255;
        }
        else if(image.channels() == 3)
        {
            image.at<cv::Vec3b>(j, k)[0] = 255;
            image.at<cv::Vec3b>(j, k)[1] = 255;
            image.at<cv::Vec3b>(j, k)[2] = 255;
        }
    }

}

函数功能很简单,就不介绍了。

测试程序如下

    cv::Mat image = cv::imread("timg.jpg");
    cv::namedWindow("Test1");
    cv::imshow("Test1", image);


    salt(image, 30000);
    cv::namedWindow("Test2");
    cv::imshow("Test2", image);

效果如下所示


2【通过指针遍历图像】

通过一个颜色缩减函数来说明指针遍历图像的方法。主要函数如下

void colorReduce(const cv::Mat& image, cv::Mat& result, int div = 64)
{
    result.create(image.rows, image.cols, image.type());
    int nl = image.rows;
    int nc = image.cols * image.channels();
    if (image.isContinuous())
    {
        nc = nc * nl;
        nl = 1;
    }


    for(int i = 0; i < nl; i++)
    {
        const uchar* data_in = image.ptr<uchar>(i);
        uchar* data_out = result.ptr<uchar>(i);
        for (int j = 0; j < nc; j++)
        {
            data_out[j] = data_in[j]/div * div + div / 2;
        }
    }

}

运行效果如下所示


成员变量cols代表图像的列数(宽度),rows代表图像的行数(高度),step代表以字节为单位的图像的有效宽度。即使你的图像的元素类型不是uchar,step仍然代表着行的字节数。像素的大小可以油elemSize函数得到:对于一个三通道的short行矩阵(CV_16SC3),elemSize返回6。图像的通道数可以由channels方法得到:对于灰度图像来说为1,对于彩色图像来说为3。total函数返回矩阵的像素个数。一般出于效率考虑,每行会填补一些额外像素,这是因为,如果行的长度为4或者8的倍数,对于一些处理器来说,处理的更高效。如果需要根据新的尺寸和数据类型对一个矩阵进行重新分配,我们可以调用create成员函数。。而且,如果新指定的尺寸和数据类型与原有的一样,create函数会直接返回,不会对本矩阵做任何改动。图像分配内存的大小为total()*elemSize()。

3【使用迭代器遍历图像】

opencv为cv::Mat提供了与STL迭代器兼容的迭代器。可以通过cv::Mat Iteerator_的实例来得到。这是一个模板类。主要函数程序如下:

void colorIteratorReduce( cv::Mat& image, cv::Mat& result, int div = 64)
{
    result.create(image.rows, image.cols, image.type());
    cv::Mat_<cv::Vec3b>::iterator it = image.begin<cv::Vec3b>();
    cv::Mat_<cv::Vec3b>::iterator itend = image.end<cv::Vec3b>();
    cv::Mat_<cv::Vec3b>::iterator rit = result.begin<cv::Vec3b>();
    for(; it != itend; it++)
    {
        (*rit)[0] = (*it)[0]/div * div + div /2 ;
        (*rit)[1] = (*it)[1] / div * div + div / 2;
        (*rit)[2] = (*it)[2] / div * div + div / 2;
        rit++;
    }

}

运行效果和上一个效果一样。image.begin<cv::Vec3b>()来得到图像左上角位置的迭代器,也可以通过对迭代器进行代数运算,但是,end方法得到的迭代器其实已经超出了集合。迭代器之所以可以这样创建,是因为一个cv::Mat_引用在创建的时候就隐士声明了迭代器的类型,这样避免在使用begin和end方法的时候还要指定迭代器的类型。

二【遍历图像和邻域操作】

在图像处理中,通过当前位置的相邻像素计算新的像素位置是很常见的操作,当邻域包含图像的前几行和下几行是,就需要同时扫描图像的若干行。本例子对图像进行锐化,它是基于拉普拉斯算子(在后面章节会介绍)。本例子是减去前后左右的像素来锐化。主要代码如下

void sharpen(const cv::Mat& image, cv::Mat&result)
{
    result.create(image.rows, image.cols, image.type());
    int nl = image.rows - 1 ;
    int cl = (image.cols  - 1) * image.channels() ;
    std::cout<<"muyi"<<cl;


    for(int j = 1; j < nl ; j++)
    {
        const uchar* previous = image.ptr<const uchar>(j - 1);
        const uchar* current = image.ptr<const uchar>(j);
        const uchar* next = image.ptr<const uchar>( j + 1);
        uchar* outPut = result.ptr<uchar>(j);
        for(int i = 1; i < cl; i++)
        {
             *outPut++ = cv::saturate_cast<uchar>(5 * current[i] - current[i - 1] - current [ i + 1] - previous[i] - next[i]);
        }
    }
    result.row(0).setTo(cv::Scalar(0,0,0));
    result.row(nl).setTo(cv::Scalar(0,0,0));
    result.col(0).setTo(cv::Scalar(0,0,0));
    result.col(image.cols - 1).setTo(cv::Scalar(0,0,0));
}

运行效果如下


cv::saturate_cast被用来对计算结果进行截断。使用setTo函数来设置矩阵的值,这个函数会将矩阵的所有元素设为指定的值。cv::Scalar(a,b,c)来指定像素三个通道的目标值。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

木士易

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

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

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

打赏作者

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

抵扣说明:

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

余额充值