OpenCV - C++实战(03) — 像素的访问与扫描

目录

第3章  像素的访问与扫描

3.1 简介

3.2 访问像素值

3.3 用指针扫描图像

3.4 用迭代器扫描图像 

3.5 计算函数的运行时间

3.6 完整代码 

(1)代码1

(2)代码2


Github代码地址:GitHub - Qinong/OpenCV

第3章  像素的访问与扫描

3.1 简介

        图像本质上就是一个由数值组成的矩阵。因此,OpenCV 使用了 cv::Mat 结构来操作图像,矩阵中的每个元素表示一个像素。对灰度图像而言,像素是8位无符号数(数据类型为 unsigned char),0表示黑色,255 表示白色。对彩色图像而言,需要用三原色数据来重现不同的可见色。意味着彩色图像的每个像素都要对应三个数值,常用的主颜色通道是红色、绿色和蓝色,因此每三个8位数值组成短阵的一个元素,有些特殊的应用程序需要用16位通道(例如医学图像)。
 

3.2 访问像素值

        cv::Mat 类包含多种方法,可用来访问图像的各种属性,利用公共成员变量 cols 和rows
可得到图像的列数和行数;利用cv::Mat 的at (int y,int x)方法可以访问元素,其中x是列号,y是行号。在编译时必须明确方法返回值的类型,因为 cv::Mat 可以接受任何类型的元素,所以程序员需要指定返回值的预期类型。

        在调用at 方法时,你必须指定图像元素的类型,例如:

image.at<uchar>(j,i)=255;

        注意:必须保证指定的类型与矩阵内的类型是一致的,at 方法不会进行任何类型转换。

        彩色图像的每个像素对应三个部分:红色通道、绿色通道和蓝色通道,因此包含彩色图像的cv::Mat类会返回一个向量,向量中包含三个8位的数值。OpenCV 为这样的短向量定义了一种类型,即cv::Vec3b,向量包含三个无符号宇符( unsigned character) 类型的数据。因此,访问彩色像素中元素的方法如下所示:

image.at<cv::Vec3b>(j,i)[channel]= value;

        channel素引用来指明三个颜色的通道。OpenCV 存储通道数据的次序是蓝色、绿色和红色(因此蓝色是通道 0)。也可以直接使用短向量,方法如下所示:

image.at<cv::Vec3b>(j, i)=cv::Vec3b(255,255,255);

        还有类似的向量类型用来表示二元素向量和四元素向量(cv::Vec2b 和cv::Vec4b),此外还有针对其他元素类型的向量。

(1)对图像增加白噪声

// 对图像添加白噪声
void salt(cv::Mat image, int n) {

	// C++11随机数生成器
	std::default_random_engine generator;
	std::uniform_int_distribution<int> randomRow(0, image.rows - 1);
	std::uniform_int_distribution<int> randomCol(0, image.cols - 1);

	int i,j;
	for (int k=0; k<n; k++) {

		// 随机生成图形位置
		i= randomCol(generator);
		j= randomRow(generator);
 
		if (image.type() == CV_8UC1)    // 灰度图像
        { 
			image.at<uchar>(j,i)= 255; 

		} 
		else if (image.type() == CV_8UC3)    // 彩色图像
        { 
			image.at<cv::Vec3b>(j,i)[0]= 255; 
			image.at<cv::Vec3b>(j,i)[1]= 255; 
			image.at<cv::Vec3b>(j,i)[2]= 255; 
			// or
			// image.at<cv::Vec3b>(j, i) = cv::Vec3b(255, 255, 255);
		}
	}
}

  

3.3 用指针扫描图像

        为了简化指针运算的计算过程,cv::vat 类提供了一个方法,可以直接访问图像中一行的起始地址。这就是ptr方法,它是一个模板方法,返回第j行的地址:

void colorReduce(cv::Mat image) {
      int nl= image.rows;    // 图像的行数
      int nc= image.cols * image.channels();    // 图像每行的元素数量
      for (int j=0; j<nl; j++) {
          uchar* data= image.ptr<uchar>(j);   // 取第j行的地址
          for (int i=0; i<nc/2; i++)     // 处理每个像素
          {
            data[i]= 255
          }
      }
}

 

 

3.4 用迭代器扫描图像 

        在面向对象编程时,我们通常用迭代器对数据集合进行循环遍历。迭代器是一种类,专门用于遍历集合的每个元素,并能隐藏遍历过程的具体细节。信息隐藏原则的应用,使扫描集合的过程变得更加容易和安全。标淮模板库(Standard Template Library, STL)对每个集合类都定义了对应的迭代器类,OpenCV 也提供了ev::Mat 的迭代器类,并且与C++ STL 中的标准迭代器兼容。 

        要得到 cv::Mat 实例的迭代器,首先要创建一个cv::MatIterator_对象,下划线表示它是一个模板子类。因为图像迭代器是用来访问图像元素的,所以必须在编译时就明确返回值的类型。可以这样定义彩色图像的迭代器:

cv::MatIterator_<cv::Vec3b> it;

        也可以使用在Mat_模板类内部定义的iterator类型:

cv::Mat_<cv::Vec3b>::iterator it;

        然后就可以使用常规的选代器方法 begin 和 end 对像素进行循环遍历。

// 使用cv::Mat_<cv::Vec3b>::iterator迭代器
void colorReduce2(cv::Mat image, int div=64) {
      cv::Mat_<cv::Vec3b>::iterator it= image.begin<cv::Vec3b>();
      cv::Mat_<cv::Vec3b>::iterator itend= image.end<cv::Vec3b>();
	  uchar div2 = div >> 1;     // div2 = div/2
      for ( ; it!= itend; ++it) {
        (*it)[0]= (*it)[0]/div*div + div2;
        (*it)[1]= (*it)[1]/div*div + div2;
        (*it)[2]= (*it)[2]/div*div + div2;
      }
}

// 使用cv::MatIterator_<cv::Vec3b>迭代器
void colorReduce3(cv::Mat image, int div=64) {
      cv::MatIterator_<cv::Vec3b> it= image.begin<cv::Vec3b>();
      cv::MatIterator_<cv::Vec3b> itend= image.end<cv::Vec3b>();
      const cv::Vec3b offset(div/2,div/2,div/2);
      for ( ; it!= itend; ++it) {
        *it= *it/div*div+offset;
      }
}

 

3.5 计算函数的运行时间

        OpenCV 有一个非常实用的丽数 cv::getTickcount()可以用来测算两数或代码段的运行时间。在代码开始和结束时记录时钟周期数,就可以计算代码的运行时间。若想得到以秒为单位的代码运行时间,可使用另一个方法 cv::getTickFrequenoy(),它返回每秒的时钟周期数。

        为了获得某个函数(或代码段)的运行时间,通常需使用如下的程序模板:

const int64 start = cv::getTickCount();
...... ; //调用函数
double duration = (cv::getTickcount()-start)/cv:: getTickFrequency();

3.6 完整代码 

(1)代码1

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <random>

// 对图像添加白噪声
void salt(cv::Mat image, int n) {

	// C++11随机数生成器
	std::default_random_engine generator;
	std::uniform_int_distribution<int> randomRow(0, image.rows - 1);
	std::uniform_int_distribution<int> randomCol(0, image.cols - 1);

	int i,j;
	for (int k=0; k<n; k++) {

		// 随机生成图形位置
		i= randomCol(generator);
		j= randomRow(generator);
 
		if (image.type() == CV_8UC1)    // 灰度图像
        { 
			image.at<uchar>(j,i)= 255; 

		} 
		else if (image.type() == CV_8UC3)    // 彩色图像
        { 
			image.at<cv::Vec3b>(j,i)[0]= 255; 
			image.at<cv::Vec3b>(j,i)[1]= 255; 
			image.at<cv::Vec3b>(j,i)[2]= 255; 
			// or
			// image.at<cv::Vec3b>(j, i) = cv::Vec3b(255, 255, 255);
		}
	}
}


int main()
{
	cv::Mat image1 = cv::imread("Ferrar_F8.png",1);
    cv::Mat image2 =  image1.clone();
    cv::namedWindow("Image1");
	cv::imshow("Image1",image1);
	
	salt(image2,6000);    // 调用噪声函数
	cv::namedWindow("Image2");
	cv::imshow("Image2",image2);
	cv::waitKey();
	return 0;
}


(2)代码2

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

// 更改指定位置的像素值
void colorReduce1(cv::Mat image) {
      int nl= image.rows;    // 图像的行数
      int nc= image.cols * image.channels();    // 图像每行的元素数量
      for (int j=0; j<nl; j++) {
          uchar* data= image.ptr<uchar>(j);   // 取第j行的地址
          for (int i=0; i<nc/2; i++)     // 处理每个像素
          {
            data[i]= 255; 
          }
      }
}


// 使用cv::Mat_<cv::Vec3b>::iterator迭代器
void colorReduce2(cv::Mat image, int div=64) {
      cv::Mat_<cv::Vec3b>::iterator it= image.begin<cv::Vec3b>();
      cv::Mat_<cv::Vec3b>::iterator itend= image.end<cv::Vec3b>();
	  uchar div2 = div >> 1; // div2 = div/2

      for ( ; it!= itend; ++it) {
        (*it)[0]= (*it)[0]/div*div + div2;
        (*it)[1]= (*it)[1]/div*div + div2;
        (*it)[2]= (*it)[2]/div*div + div2;
      }
}


// 使用cv::MatIterator_<cv::Vec3b>迭代器
void colorReduce3(cv::Mat image, int div=64) {
      cv::MatIterator_<cv::Vec3b> it= image.begin<cv::Vec3b>();
      cv::MatIterator_<cv::Vec3b> itend= image.end<cv::Vec3b>();
      const cv::Vec3b offset(div/2,div/2,div/2);
      
      for ( ; it!= itend; ++it) {
        *it= *it/div*div+offset;
      }
}


int main()
{
	cv::Mat image = cv::imread("Ferrar_F8.png");
    cv::Mat image1 =  image.clone();
    cv::Mat image2 =  image.clone();
    cv::Mat image3 =  image.clone();
    
    cv::namedWindow("Image",0);
	cv::imshow("Image", image);
	cv::waitKey();
    
    // 调用函数并记录处理图像的时间
	colorReduce1(image1);
	cv::namedWindow("Image1",0);
	cv::imshow("Image1", image1);
	cv::waitKey();
    
    // 调用函数并记录处理图像的时间
	colorReduce2(image2);
	cv::namedWindow("Image2",0);
	cv::imshow("Image2", image2);
	cv::waitKey();
    
    // 调用函数并记录处理图像的时间
	colorReduce3(image3);
	cv::namedWindow("Image3",0);
	cv::imshow("Image3", image3);
	cv::waitKey();
	return 0;
}

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

几度春风里

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

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

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

打赏作者

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

抵扣说明:

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

余额充值